diff --git a/composer.json b/composer.json index e9375295..dfa792d4 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "require": { "filp/whoops": "^1.1", - "potsky/pimp-my-log": "^1.7" + "potsky/pimp-my-log": "^1.7", + "robmorgan/phinx": "^0.4.6" } } diff --git a/composer.lock b/composer.lock index 401ef7ed..e10b7154 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "8230cf0ea7cd0dc12c54365f52c1b128", + "hash": "e3d6128e6ff6ffe5586c22906e3fe1de", "packages": [ { "name": "filp/whoops", @@ -102,6 +102,273 @@ "description": "Log viewer for your web server", "homepage": "http://pimpmylog.com", "time": "2015-05-27 07:09:41" + }, + { + "name": "robmorgan/phinx", + "version": "v0.4.6", + "source": { + "type": "git", + "url": "https://github.com/robmorgan/phinx.git", + "reference": "1351ca36dd2419d7de02afd1aaa415929112d7f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robmorgan/phinx/zipball/1351ca36dd2419d7de02afd1aaa415929112d7f1", + "reference": "1351ca36dd2419d7de02afd1aaa415929112d7f1", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "symfony/config": "~2.7", + "symfony/console": "~2.7", + "symfony/yaml": "~2.7" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "dev-phpcs-fixer" + }, + "bin": [ + "bin/phinx" + ], + "type": "library", + "autoload": { + "psr-4": { + "Phinx\\": "src/Phinx" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Morgan", + "email": "robbym@gmail.com", + "homepage": "http://robmorgan.id.au", + "role": "Lead Developer" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me", + "role": "Developer" + } + ], + "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", + "homepage": "https://phinx.org", + "keywords": [ + "database", + "database migrations", + "db", + "migrations", + "phinx" + ], + "time": "2015-09-11 15:44:41" + }, + { + "name": "symfony/config", + "version": "v2.7.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/9698fdf0a750d6887d5e7729d5cf099765b20e61", + "reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2015-09-21 15:02:29" + }, + { + "name": "symfony/console", + "version": "v2.7.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "06cb17c013a82f94a3d840682b49425cd00a2161" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/06cb17c013a82f94a3d840682b49425cd00a2161", + "reference": "06cb17c013a82f94a3d840682b49425cd00a2161", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/phpunit-bridge": "~2.7", + "symfony/process": "~2.1" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2015-09-25 08:32:23" + }, + { + "name": "symfony/filesystem", + "version": "v2.7.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab", + "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2015-09-09 17:42:36" + }, + { + "name": "symfony/yaml", + "version": "v2.7.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/31cb2ad0155c95b88ee55fe12bc7ff92232c1770", + "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2015-09-14 14:14:09" } ], "packages-dev": [], diff --git a/migration.php b/migration.php new file mode 100644 index 00000000..3e6658be --- /dev/null +++ b/migration.php @@ -0,0 +1,22 @@ + array( + "migrations" => "migrations" + ), + "environments" => array( + "default_migration_table" => "phinxlog", + "default_database" => "toutpratique", + "local" => array( + "adapter" => "mysql", + "host" => _DB_SERVER_, + "name" => _DB_NAME_, + "user" => _DB_USER_, + "pass" => _DB_PASSWD_, + "port" => 3306 + ) + ) +); \ No newline at end of file diff --git a/migrations/20151016095326_my_new_migration.php b/migrations/20151016095326_my_new_migration.php new file mode 100644 index 00000000..76f5a88e --- /dev/null +++ b/migrations/20151016095326_my_new_migration.php @@ -0,0 +1,46 @@ +table('users'); + $users->addColumn('username', 'string', array('limit' => 20)) + ->addColumn('password', 'string', array('limit' => 40)) + ->addColumn('password_salt', 'string', array('limit' => 40)) + ->addColumn('email', 'string', array('limit' => 100)) + ->addColumn('first_name', 'string', array('limit' => 30)) + ->addColumn('last_name', 'string', array('limit' => 30)) + ->addColumn('created', 'datetime') + ->addColumn('updated', 'datetime', array('null' => true)) + ->addIndex(array('username', 'email'), array('unique' => true)) + ->save(); + } +} diff --git a/migrations/20151016095903_customer_migration.php b/migrations/20151016095903_customer_migration.php new file mode 100644 index 00000000..6a40827c --- /dev/null +++ b/migrations/20151016095903_customer_migration.php @@ -0,0 +1,34 @@ +table('ps_customer'); + $table->addColumn('test', 'integer') + ->update(); + } +} diff --git a/migrations/20151016102101_customer2_migration.php b/migrations/20151016102101_customer2_migration.php new file mode 100644 index 00000000..2a6209af --- /dev/null +++ b/migrations/20151016102101_customer2_migration.php @@ -0,0 +1,34 @@ +table('ps_customer'); + $table->addColumn('testmigration', 'integer') + ->update(); + } +} diff --git a/migrations/20151016102502_customertest_migration.php b/migrations/20151016102502_customertest_migration.php new file mode 100644 index 00000000..5e46be3b --- /dev/null +++ b/migrations/20151016102502_customertest_migration.php @@ -0,0 +1,35 @@ +table('ps_customer'); + $table->addColumn('autochange', 'integer') + ->update(); + + } +} diff --git a/phinx.yml b/phinx.yml new file mode 100644 index 00000000..9c3a371b --- /dev/null +++ b/phinx.yml @@ -0,0 +1,32 @@ +paths: + migrations: %%PHINX_CONFIG_DIR%%/migrations + +environments: + default_migration_table: phinxlog + default_database: development + production: + adapter: mysql + host: 192.168.0.41 + name: toutpratiquefdsfds + user: root + pass: '' + port: 3306 + charset: utf8 + + development: + adapter: mysql + host: 192.168.0.41 + name: toutpratique_devfdsfds + user: root + pass: '' + port: 3306 + charset: utf8 + + testing: + adapter: mysql + host: localhost + name: testing_db + user: root + pass: '' + port: 3306 + charset: utf8 diff --git a/vendor/bin/phinx b/vendor/bin/phinx new file mode 100644 index 00000000..7bd88840 --- /dev/null +++ b/vendor/bin/phinx @@ -0,0 +1,7 @@ +#!/usr/bin/env sh +SRC_DIR="`pwd`" +cd "`dirname "$0"`" +cd "../robmorgan/phinx/bin" +BIN_TARGET="`pwd`/phinx" +cd "$SRC_DIR" +"$BIN_TARGET" "$@" diff --git a/vendor/bin/phinx.bat b/vendor/bin/phinx.bat new file mode 100644 index 00000000..ad500984 --- /dev/null +++ b/vendor/bin/phinx.bat @@ -0,0 +1,3 @@ +@ECHO OFF +SET BIN_TARGET=%~dp0/../robmorgan/phinx/bin/phinx +php "%BIN_TARGET%" %* diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index b265c64a..334d2f4e 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -6,4 +6,9 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'), + 'Phinx\\' => array($vendorDir . '/robmorgan/phinx/src/Phinx'), ); diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index cabc2271..e6f159cb 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -58,5 +58,323 @@ "whoops", "zf2" ] + }, + { + "name": "symfony/yaml", + "version": "v2.7.5", + "version_normalized": "2.7.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/31cb2ad0155c95b88ee55fe12bc7ff92232c1770", + "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "time": "2015-09-14 14:14:09", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/filesystem", + "version": "v2.7.5", + "version_normalized": "2.7.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab", + "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "time": "2015-09-09 17:42:36", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/config", + "version": "v2.7.5", + "version_normalized": "2.7.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/9698fdf0a750d6887d5e7729d5cf099765b20e61", + "reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "time": "2015-09-21 15:02:29", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/console", + "version": "v2.7.5", + "version_normalized": "2.7.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "06cb17c013a82f94a3d840682b49425cd00a2161" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/06cb17c013a82f94a3d840682b49425cd00a2161", + "reference": "06cb17c013a82f94a3d840682b49425cd00a2161", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/phpunit-bridge": "~2.7", + "symfony/process": "~2.1" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "time": "2015-09-25 08:32:23", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com" + }, + { + "name": "robmorgan/phinx", + "version": "v0.4.6", + "version_normalized": "0.4.6.0", + "source": { + "type": "git", + "url": "https://github.com/robmorgan/phinx.git", + "reference": "1351ca36dd2419d7de02afd1aaa415929112d7f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robmorgan/phinx/zipball/1351ca36dd2419d7de02afd1aaa415929112d7f1", + "reference": "1351ca36dd2419d7de02afd1aaa415929112d7f1", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "symfony/config": "~2.7", + "symfony/console": "~2.7", + "symfony/yaml": "~2.7" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "dev-phpcs-fixer" + }, + "time": "2015-09-11 15:44:41", + "bin": [ + "bin/phinx" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Phinx\\": "src/Phinx" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Morgan", + "email": "robbym@gmail.com", + "homepage": "http://robmorgan.id.au", + "role": "Lead Developer" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me", + "role": "Developer" + } + ], + "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", + "homepage": "https://phinx.org", + "keywords": [ + "database", + "database migrations", + "db", + "migrations", + "phinx" + ] + }, + { + "name": "potsky/pimp-my-log", + "version": "1.7.9", + "version_normalized": "1.7.9.0", + "source": { + "type": "git", + "url": "https://github.com/potsky/PimpMyLog.git", + "reference": "22f448c9aaf8115663536aaef410b5eb9152d950" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/potsky/PimpMyLog/zipball/22f448c9aaf8115663536aaef410b5eb9152d950", + "reference": "22f448c9aaf8115663536aaef410b5eb9152d950", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "time": "2015-05-27 07:09:41", + "type": "project", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0+" + ], + "authors": [ + { + "name": "Potsky", + "email": "potsky@me.com", + "homepage": "http://www.potsky.com", + "role": "Developer" + }, + { + "name": "Cassio Santos", + "homepage": "http://www.cassiosantos.com", + "role": "Translator (pt_BR)" + } + ], + "description": "Log viewer for your web server", + "homepage": "http://pimpmylog.com" } ] diff --git a/vendor/potsky/pimp-my-log/.gitignore b/vendor/potsky/pimp-my-log/.gitignore new file mode 100644 index 00000000..de7f6260 --- /dev/null +++ b/vendor/potsky/pimp-my-log/.gitignore @@ -0,0 +1,36 @@ +# Folder view configuration files +.DS_Store +Desktop.ini +._* +Thumbs.db +*.swp + +# Files that might appear on external disks +.Spotlight-V100 +.Trashes + +# Branch master old +/config.inc.php +/config.inc.php.tmp + +# Branch master +/inc/test.REMOVE_UPPERCASE.php +*.user.php +*.user.css +*.user.json +*.user.json.tmp +/config.user.d/* + +# Branch jekyll +/_site +/_build +/_tmp +/dist + +# Compiled Python files +*.pyc + +# Application specific files +/node_modules +/bower_components +/Gemfile.lock diff --git a/vendor/potsky/pimp-my-log/README.md b/vendor/potsky/pimp-my-log/README.md new file mode 100644 index 00000000..03bb477b --- /dev/null +++ b/vendor/potsky/pimp-my-log/README.md @@ -0,0 +1,12 @@ +Pimp my Log +=========== + +[![Latest Stable Version](https://poser.pugx.org/potsky/pimp-my-log/v/stable.svg)](https://packagist.org/packages/potsky/pimp-my-log) [![Build Status](https://travis-ci.org/potsky/PimpMyLog.svg)](https://travis-ci.org/potsky/PimpMyLog) + +All informations are available on [pimpmylog.com](http://pimpmylog.com). + +Please **do not open issues on GitHub**, this is for dev only. +Support, FAQ, knowledge base, ... are on [support.pimpmylog.com](http://support.pimpmylog.com). + +Please star this repository to support *Pimp My Log* ! + diff --git a/vendor/potsky/pimp-my-log/cfg/apache.config.php b/vendor/potsky/pimp-my-log/cfg/apache.config.php new file mode 100644 index 00000000..488003e9 --- /dev/null +++ b/vendor/potsky/pimp-my-log/cfg/apache.config.php @@ -0,0 +1,187 @@ + + __('Apache'), + 'desc' => __('Apache Hypertext Transfer Protocol Server'), + 'home' => __('http://httpd.apache.org'), + 'notes' => __('All versions 2.x are supported.'), + 'load' => ( stripos( $_SERVER["SERVER_SOFTWARE"] , 'Apache' ) !== false ) + ); +} + + +/* +You must escape anti-slash 4 times and escape $ in regex. +(Two for PHP and finally two for json) + */ + + +function apache_get_config( $type , $file , $software , $counter ) { + + $file_json_encoded = json_encode( $file ); + + ///////////////////////////////////////////////////////// + // Apache error files are not the same on 2.2 and 2.4 // + ///////////////////////////////////////////////////////// + if ( $type == 'error' ) { + + // Write a line of log and try to guess the format + $remain = 10; + $test = 0; + error_log( 'Pimp my Log has been successfully configured with Apache' ); + foreach ( LogParser::getLinesFromBottom( $file , 10 ) as $line ) { + $test = @preg_match('|^\[(.*) (.*) (.*) (.*):(.*):(.*)\.(.*) (.*)\] \[(.*):(.*)\] \[pid (.*)\] .*\[client (.*):(.*)\] (.*)(, referer: (.*))*$|U', $line ); + if ( $test === 1 ) { + break; + } + $remain--; + if ($remain<=0) { + break; + } + } + + ///////////////////// + // Error 2.4 style // + ///////////////////// + if ( $test === 1 ) { + return<< + array( + 'error.log', + 'error_log', + 'apache_error.log', + ), + 'access' => array( + 'access.log', + 'access_log', + 'apache.log', + 'apache_access.log', + ), +); diff --git a/vendor/potsky/pimp-my-log/cfg/config.example.php b/vendor/potsky/pimp-my-log/cfg/config.example.php new file mode 100644 index 00000000..faf0e252 --- /dev/null +++ b/vendor/potsky/pimp-my-log/cfg/config.example.php @@ -0,0 +1,139 @@ + + +{ + "globals": { + "_remove_me_to_set_AUTH_LOG_FILE_COUNT" : 100, + "_remove_me_to_set_AUTO_UPGRADE" : false, + "_remove_me_to_set_CHECK_UPGRADE" : true, + "_remove_me_to_set_EXPORT" : true, + "_remove_me_to_set_FILE_SELECTOR" : "bs", + "_remove_me_to_set_FOOTER" : "© Potsky<\/a> 2007-' . YEAR . ' - Pimp my Log<\/a>", + "_remove_me_to_set_FORGOTTEN_YOUR_PASSWORD_URL" : "http:\/\/support.pimpmylog.com\/kb\/misc\/forgotten-your-password", + "_remove_me_to_set_GEOIP_URL" : "http:\/\/www.geoiptool.com\/en\/?IP=%p", + "_remove_me_to_set_GOOGLE_ANALYTICS" : "UA-XXXXX-X", + "_remove_me_to_set_HELP_URL" : "http:\/\/pimpmylog.com", + "_remove_me_to_set_LOCALE" : "gb_GB", + "_remove_me_to_set_LOGS_MAX" : 50, + "_remove_me_to_set_LOGS_REFRESH" : 0, + "_remove_me_to_set_MAX_SEARCH_LOG_TIME" : 5, + "_remove_me_to_set_NAV_TITLE" : "", + "_remove_me_to_set_NOTIFICATION" : true, + "_remove_me_to_set_NOTIFICATION_TITLE" : "New logs [%f]", + "_remove_me_to_set_PIMPMYLOG_ISSUE_LINK" : "https:\/\/github.com\/potsky\/PimpMyLog\/issues\/", + "_remove_me_to_set_PIMPMYLOG_VERSION_URL" : "http:\/\/demo.pimpmylog.com\/version.js", + "_remove_me_to_set_PULL_TO_REFRESH" : true, + "_remove_me_to_set_SORT_LOG_FILES" : "default", + "_remove_me_to_set_TAG_DISPLAY_LOG_FILES_COUNT" : true, + "_remove_me_to_set_TAG_NOT_TAGGED_FILES_ON_TOP" : true, + "_remove_me_to_set_TAG_SORT_TAG" : "default | display-asc | display-insensitive | display-desc | display-insensitive-desc", + "_remove_me_to_set_TITLE" : "Pimp my Log", + "_remove_me_to_set_TITLE_FILE" : "Pimp my Log [%f]", + "_remove_me_to_set_UPGRADE_MANUALLY_URL" : "http:\/\/pimpmylog.com\/getting-started\/#update", + "_remove_me_to_set_USER_CONFIGURATION_DIR" : "config.user.d", + "_remove_me_to_set_USER_TIME_ZONE" : "Europe\/Paris" + }, + + "badges": { + "severity": { + "debug" : "success", + "info" : "success", + "notice" : "default", + "Notice" : "info", + "warn" : "warning", + "error" : "danger", + "crit" : "danger", + "alert" : "danger", + "emerg" : "danger", + "Notice" : "info", + "Fatal error" : "danger", + "Parse error" : "danger", + "Warning" : "warning" + }, + "http": { + "1" : "info", + "2" : "success", + "3" : "default", + "4" : "warning", + "5" : "danger" + } + }, + + "files": { + "apache1": { + "display" : "Apache Error #1", + "path" : "\/opt\/local\/apache2\/logs\/error_log", + "refresh" : 5, + "max" : 10, + "notify" : true, + "multiline" : "", + "format" : { + "regex" : "|^\\[(.*)\\] \\[(.*)\\] (\\[client (.*)\\] )*((?!\\[client ).*)(, referer: (.*))*$|U", + "export_title" : "Log", + "match" : { + "Date" : 1, + "IP" : 4, + "Log" : 5, + "Severity" : 2, + "Referer" : 7 + }, + "types": { + "Date" : "date:H:i:s", + "IP" : "ip:http", + "Log" : "pre", + "Severity" : "badge:severity", + "Referer" : "link" + }, + "exclude": { + "Log": ["\/PHP Stack trace:\/", "\/PHP *[0-9]*\\. \/"] + } + } + }, + "apache2": { + "display" : "Apache Access #2", + "path" : "\/opt\/local\/apache2\/logs\/access_log", + "refresh" : 0, + "max" : 10, + "notify" : false, + "multiline" : "", + "format" : { + "regex" : " |^(.*) (.*) (.*) \\[(.*)\\] \"(.*) (.*) (.*)\" ([0-9]*) (.*) \"(.*)\" \"(.*)\"( [0-9]*\/([0-9]*))*$|U", + "export_title" : "URL", + "match" : { + "Date" : 4, + "IP" : 1, + "CMD" : 5, + "URL" : 6, + "Code" : 8, + "Size" : 9, + "Referer" : 10, + "UA" : 11, + "User" : 3, + "\u03bcs" : 13 + }, + "types": { + "Date" : "date:H:i:s", + "IP" : "ip:geo", + "URL" : "txt", + "Code" : "badge:http", + "Size" : "numeral:0b", + "Referer" : "link", + "UA" : "ua:{os.name} {os.version} | {browser.name} {browser.version}\/100", + "\u03bcs" : "numeral:0,0" + }, + "exclude": { + "URL": ["\/favicon.ico\/", "\/\\.pml\\.php.*$\/"], + "CMD": ["\/OPTIONS\/"] + } + } + } + } +} diff --git a/vendor/potsky/pimp-my-log/cfg/iis.config.php b/vendor/potsky/pimp-my-log/cfg/iis.config.php new file mode 100644 index 00000000..bcf6f92d --- /dev/null +++ b/vendor/potsky/pimp-my-log/cfg/iis.config.php @@ -0,0 +1,247 @@ + + __('IIS'), + 'desc' => __('A flexible & easy-to-manage web server...'), + 'home' => __('http://www.iis.net'), + 'notes' => __('NCSA, IIS and W3C log formats are supported'), + 'load' => ( stripos( $_SERVER["SERVER_SOFTWARE"] , 'iis' ) !== false ) + ); +} + + +function iis_get_config( $type , $file , $software , $counter ) { + + $file_json_encoded = json_encode( $file ); + + // Type W3C + // + $ufields = array(); + $handle = @fopen( $file , "r" ); + if ( $handle ) { + while ( ! feof( $handle ) ) { + $buffer = fgets($handle); + if ( substr( $buffer , 0 , 9 ) == '#Fields: ' ) { + $ufields = explode( ' ' , substr( $buffer , 9 ) ); + if ( count( $ufields ) > 0 ) { + break; + } + } + } + fclose($handle); + } + if ( count( $ufields ) > 0 ) { + $regex = array(); + $types = array(); + $match = array( + 'Date' => false, + 'IP' => false, + 'Site' => false, + 'CMD' => false, + 'URL' => false, + 'QS' => false, + 'Code' => false, + 'Size' => false, + 'Referer' => false, + 'UA' => false, + 'User' => false, + 'ms' => false, + ); + $types = array( + 'Date' => "date:H:i:s", + 'Site' => 'txt', + 'CMD' => 'txt', + 'URL' => 'txt', + 'QS' => 'txt', + 'User' => 'txt', + 'IP' => "ip:geo", + 'UA' => "uaw3c:{os.name} {os.version} | {browser.name} {browser.version}/100", + 'Referer' => "link", + 'Code' => "badge:http", + 'Size' => "numeral:0b", + 'ms' => "numeral:0,0" + ); + $fields = array( + 'date' => 'Date', + 'time' => 'Date', + 's-sitename' => 'Site', +// 's-computername' => 0, +// 's-ip' => 0, + 'cs-method' => 'CMD', + 'cs-uri-stem' => 'URL', + 'cs-uri-query' => 'QS', +// 's-port' => 0, + 'cs-username' => 'User', + 'c-ip' => 'IP', +// 'cs-version' => 0, + 'cs(User-Agent)' => 'UA', +// 'cs(Cookie)' => 0, + 'cs(Referer)' => 'Referer', +// 'cs-host' => 0, + 'sc-status' => 'Code', +// 'sc-substatus' => 0, +// 'sc-win32-status' => 0, + 'sc-bytes' => 'Size', +// 'cs-bytes' => 0, + 'time-taken' => 'ms', + ); + + $position = 1; + + foreach ( $ufields as $field ) { + $regex[] = '(.*)'; + $field = trim($field); + if ( isset( $fields[ $field ] ) ) { + $thismatch = $fields[ $field ]; + + if ( array_key_exists( $thismatch , $match ) ) { + if ( $match[ $thismatch ] === false ) { + $match[ $thismatch ] = $position; + } else if ( is_array( $match[ $thismatch ] ) ) { + $match[ $thismatch ][] = $position; + $match[ $thismatch ][] = ' '; + } else { + $save = $match[ $thismatch ]; + $match[ $thismatch ] = array( $save , ' ' , $position , ' ' ); + } + } + } + $position++; + } + + $match = array_filter( $match ); + $regex = '|^' . implode( ' ' , $regex ) . '$|U'; + + $regex_json_encoded = json_encode( $regex ); + $match_json_encoded = json_encode( $match ); + $types_json_encoded = json_encode( $types ); + + return<< + array( + '*.log' + ), +); diff --git a/vendor/potsky/pimp-my-log/cfg/nginx.config.php b/vendor/potsky/pimp-my-log/cfg/nginx.config.php new file mode 100644 index 00000000..2fbe1ae6 --- /dev/null +++ b/vendor/potsky/pimp-my-log/cfg/nginx.config.php @@ -0,0 +1,118 @@ + + __('NGINX'), + 'desc' => __('The high performance reverse proxy, load balancer, edge cache, origin server'), + 'home' => __('http://nginx.com'), + 'notes' => __('Default log formats are supported'), + 'load' => ( stripos( $_SERVER["SERVER_SOFTWARE"] , 'nginx' ) !== false ) + ); +} + + +function nginx_get_config( $type , $file , $software , $counter ) { + + $file_json_encoded = json_encode( $file ); + + ///////////////////////////////////////////////////////// + // nginx error files are not the same on 2.2 and 2.4 // + ///////////////////////////////////////////////////////// + if ( $type == 'error' ) { + + return<< + array( + 'error.log', + 'error_log', + ), + 'access' => array( + 'access.log', + 'access_log', + ), +); diff --git a/vendor/potsky/pimp-my-log/cfg/php.config.php b/vendor/potsky/pimp-my-log/cfg/php.config.php new file mode 100644 index 00000000..ed5ab02c --- /dev/null +++ b/vendor/potsky/pimp-my-log/cfg/php.config.php @@ -0,0 +1,71 @@ + + __('PHP'), + 'desc' => __('PHP'), + 'home' => __('http://www.php.net/manual/errorfunc.configuration.php#ini.error-log' ), + 'notes' => __('PHP logs defined via the error_log ini parameter.' ) . ' ' . + sprintf( __('Pimp My Log has detected %s on your server.' ) , $path ), + 'load' => true, + ); + } else { + return array( + 'name' => __('PHP'), + 'desc' => __('PHP'), + 'home' => __('http://www.php.net/manual/errorfunc.configuration.php#ini.error-log'), + 'notes' => __('Pimp My Log has not detected any path in the ini parameter error_log.') . ' ' . + __('Activate this software only if you use ini_set(\'error_log\') directly in your scripts for example.'), + 'load' => false, + ); + } +} + + +function php_get_config( $type , $file , $software , $counter ) { + + $file_json_encoded = json_encode( $file ); + + return<< + array( + ) +); + +$path = ( SAFE_MODE === true ) ? '' : ini_get('error_log'); + +if ( $path !== '' ) { + $paths[] = dirname( $path ) . DIRECTORY_SEPARATOR ; + $files['error'][] = basename( $path ); +} diff --git a/vendor/potsky/pimp-my-log/cfg/pimpmylog.config.php b/vendor/potsky/pimp-my-log/cfg/pimpmylog.config.php new file mode 100644 index 00000000..0fac1a90 --- /dev/null +++ b/vendor/potsky/pimp-my-log/cfg/pimpmylog.config.php @@ -0,0 +1,73 @@ + + +{ + "globals": { + "_remove_me_to_set_AUTH_LOG_FILE_COUNT" : 100, + "_remove_me_to_set_AUTO_UPGRADE" : false, + "_remove_me_to_set_CHECK_UPGRADE" : true, + "_remove_me_to_set_EXPORT" : true, + "_remove_me_to_set_FILE_SELECTOR" : "bs", + "_remove_me_to_set_FOOTER" : "© Potsky<\/a> 2007-' . YEAR . ' - Pimp my Log<\/a>", + "_remove_me_to_set_FORGOTTEN_YOUR_PASSWORD_URL" : "http:\/\/support.pimpmylog.com\/kb\/misc\/forgotten-your-password", + "_remove_me_to_set_GEOIP_URL" : "http:\/\/www.geoiptool.com\/en\/?IP=%p", + "_remove_me_to_set_GOOGLE_ANALYTICS" : "UA-XXXXX-X", + "_remove_me_to_set_HELP_URL" : "http:\/\/pimpmylog.com", + "_remove_me_to_set_LOCALE" : "gb_GB", + "_remove_me_to_set_LOGS_MAX" : 50, + "_remove_me_to_set_LOGS_REFRESH" : 0, + "_remove_me_to_set_MAX_SEARCH_LOG_TIME" : 5, + "_remove_me_to_set_NAV_TITLE" : "", + "_remove_me_to_set_NOTIFICATION" : true, + "_remove_me_to_set_NOTIFICATION_TITLE" : "New logs [%f]", + "_remove_me_to_set_PIMPMYLOG_ISSUE_LINK" : "https:\/\/github.com\/potsky\/PimpMyLog\/issues\/", + "_remove_me_to_set_PIMPMYLOG_VERSION_URL" : "http:\/\/demo.pimpmylog.com\/version.js", + "_remove_me_to_set_PULL_TO_REFRESH" : true, + "_remove_me_to_set_SORT_LOG_FILES" : "default", + "_remove_me_to_set_TAG_DISPLAY_LOG_FILES_COUNT" : true, + "_remove_me_to_set_TAG_NOT_TAGGED_FILES_ON_TOP" : true, + "_remove_me_to_set_TAG_SORT_TAG" : "default | display-asc | display-insensitive | display-desc | display-insensitive-desc", + "_remove_me_to_set_TITLE" : "Pimp my Log", + "_remove_me_to_set_TITLE_FILE" : "Pimp my Log [%f]", + "_remove_me_to_set_UPGRADE_MANUALLY_URL" : "http:\/\/pimpmylog.com\/getting-started\/#update", + "_remove_me_to_set_USER_CONFIGURATION_DIR" : "config.user.d", + "_remove_me_to_set_USER_TIME_ZONE" : "Europe\/Paris" + }, + + "badges": { + "severity": { + "debug" : "success", + "info" : "success", + "notice" : "default", + "Notice" : "info", + "warn" : "warning", + "error" : "danger", + "crit" : "danger", + "alert" : "danger", + "emerg" : "danger", + "Notice" : "info", + "fatal error" : "danger", + "parse error" : "danger", + "Warning" : "warning" + }, + "http": { + "1" : "info", + "2" : "success", + "3" : "default", + "4" : "warning", + "5" : "danger" + } + }, + + "files": { +"FILES":"FILES" + } +} diff --git a/vendor/potsky/pimp-my-log/cfg/softwares.inc.php b/vendor/potsky/pimp-my-log/cfg/softwares.inc.php new file mode 100644 index 00000000..bd8f571f --- /dev/null +++ b/vendor/potsky/pimp-my-log/cfg/softwares.inc.php @@ -0,0 +1,48 @@ + + __('My Super Software'), + 'desc' => __('My Super Software build with love for you users which are installing Pimp my Log !'), + 'home' => __('http://www.example.com'), + 'note' => __('All versions 2.x are supported but 1.x too in fact.'), +); + +Just modify an existing software like this : +$softwares_all[ 'apache' ][ 'name' ] = 'Apache HTTPD'; + +You have to add these files too : +- my_software.config.user.php (which defines function my_software_get_config) +- my_software.paths.user.php +*/ +if ( file_exists( '../cfg/softwares.inc.user.php' ) ) { + include_once '../cfg/softwares.inc.user.php'; +} diff --git a/vendor/potsky/pimp-my-log/composer.json b/vendor/potsky/pimp-my-log/composer.json new file mode 100644 index 00000000..dbf3adc4 --- /dev/null +++ b/vendor/potsky/pimp-my-log/composer.json @@ -0,0 +1,29 @@ +{ + "name" : "potsky/pimp-my-log", + "description" : "Log viewer for your web server", + "version" : "1.7.9", + "license" : "GPL-3.0+", + "type" : "project", + "homepage" : "http://pimpmylog.com", + "support": { + "email" : "support@pimpmylog.com", + "issues" : "http://support.pimpmylog.com/discussion/new", + "source" : "https://github.com/potsky/PimpMyLog" + }, + "authors" : [ + { + "name" : "Potsky", + "email" : "potsky@me.com", + "homepage" : "http://www.potsky.com", + "role" : "Developer" + }, + { + "name" : "Cassio Santos", + "homepage" : "http://www.cassiosantos.com", + "role" : "Translator (pt_BR)" + } + ], + "require" : { + "php" : ">=5.2.0" + } +} diff --git a/vendor/potsky/pimp-my-log/css/config.inc.css b/vendor/potsky/pimp-my-log/css/config.inc.css new file mode 100644 index 00000000..de9988f5 --- /dev/null +++ b/vendor/potsky/pimp-my-log/css/config.inc.css @@ -0,0 +1,106 @@ +/*! pimpmylog - 1.7.9 - 10b502eaf17be208850be61febb044c2fdb86207*/ +/* + * pimpmylog + * http://pimpmylog.com + * + * Copyright (c) 2015 Potsky, contributors + * Licensed under the GPLv3 license. + */ +/* +This stylesheet is located at /css/config.inc.css +Your stylesheet will be loaded instead of this one if you create a file located at /css/config.inc.user.css + */ + +/* For logs table header */ +.logs th { + text-align : center; +} + +/* For all logs table cells */ +.logs td { + font-size : 0.8em; +} + +/* Specific to cell types */ +.pml-pre, +.pml-preformatted, +.pml-txt, +.pml-link { + font-size : 0.8em; + white-space : pre-wrap; + -ms-word-break : break-all; + word-break : break-all; + word-break : break-word; + -webkit-hyphens : auto; + -moz-hyphens : auto; + hyphens : auto; +} + +.pml-pre, .pml-prefake, .pml-preformatted { + font-family : 'ubuntu_monoregular', monospace, serif; +} + +.pml-numeral { + font-size : 0.8em; + text-align : right; + white-space : nowrap; +} + + +.pml-badge { + font-size : 0.9em; + text-align : center; +} + +/* Specific to column */ +.pml-CMD { + white-space :nowrap; +} + +.pml-Severity { + font-size : 1em; +} + +.pml-Code { + text-align : center; +} + +.pml-Referer { + min-width : 100px; +} + +.pml-UA { + font-size : 0.8em; +} + +.pml-URL { + font-size : 0.8em; + -ms-word-break : break-all; + word-break : break-all; + word-break : break-word; + -webkit-hyphens : auto; + -moz-hyphens : auto; + hyphens : auto; +} + +.pml-User { + font-size: 0.8em; +} + +/* New log color on the row */ +.newlog td:first-child { + background-position : 0 0; + background-image : url(../img/1x1pink_low.gif); + background-size : 1px 100%; + background-repeat : no-repeat; +} + +/* Marker colors when you click on a date field */ +td.pml-date { + cursor:pointer; +} +tr:nth-child(odd) td.marker { background-color : #efef00!important; } +tr:nth-child(even) td.marker { background-color : #ffff00!important; } +tr:nth-child(odd):hover td.marker { background-color : #e8e800!important; } +tr:nth-child(even):hover td.marker { background-color : #f8f800!important; } + diff --git a/vendor/potsky/pimp-my-log/css/hook-bg.png b/vendor/potsky/pimp-my-log/css/hook-bg.png new file mode 100644 index 00000000..8fa7ccd2 Binary files /dev/null and b/vendor/potsky/pimp-my-log/css/hook-bg.png differ diff --git a/vendor/potsky/pimp-my-log/css/hook-bgs.png b/vendor/potsky/pimp-my-log/css/hook-bgs.png new file mode 100644 index 00000000..9705e9cd Binary files /dev/null and b/vendor/potsky/pimp-my-log/css/hook-bgs.png differ diff --git a/vendor/potsky/pimp-my-log/css/hook-spinner.gif b/vendor/potsky/pimp-my-log/css/hook-spinner.gif new file mode 100644 index 00000000..3288d103 Binary files /dev/null and b/vendor/potsky/pimp-my-log/css/hook-spinner.gif differ diff --git a/vendor/potsky/pimp-my-log/css/hook-spinner.png b/vendor/potsky/pimp-my-log/css/hook-spinner.png new file mode 100644 index 00000000..09fbb94b Binary files /dev/null and b/vendor/potsky/pimp-my-log/css/hook-spinner.png differ diff --git a/vendor/potsky/pimp-my-log/css/hook-spinner@2x.png b/vendor/potsky/pimp-my-log/css/hook-spinner@2x.png new file mode 100644 index 00000000..1c215452 Binary files /dev/null and b/vendor/potsky/pimp-my-log/css/hook-spinner@2x.png differ diff --git a/vendor/potsky/pimp-my-log/css/pml.min.css b/vendor/potsky/pimp-my-log/css/pml.min.css new file mode 100644 index 00000000..6a15840d --- /dev/null +++ b/vendor/potsky/pimp-my-log/css/pml.min.css @@ -0,0 +1,39 @@ +@font-face{font-family:'ubuntu_monoregular';src:url("../fonts/ubuntumono-r-webfont.eot");src:url("../fonts/ubuntumono-r-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntumono-r-webfont.woff2") format("woff2"),url("../fonts/ubuntumono-r-webfont.woff") format("woff"),url("../fonts/ubuntumono-r-webfont.ttf") format("truetype"),url("../fonts/ubuntumono-r-webfont.svg#ubuntu_monoregular") format("svg");font-weight:normal;font-style:normal}@font-face{font-family:'ubuntulight';src:url("../fonts/ubuntu-l-webfont.eot");src:url("../fonts/ubuntu-l-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-l-webfont.woff2") format("woff2"),url("../fonts/ubuntu-l-webfont.woff") format("woff"),url("../fonts/ubuntu-l-webfont.ttf") format("truetype"),url("../fonts/ubuntu-l-webfont.svg#ubuntulight") format("svg");font-weight:normal;font-style:normal}/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff !important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url("../fonts/bootstrap/glyphicons-halflings-regular.eot");src:url("../fonts/bootstrap/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/bootstrap/glyphicons-halflings-regular.woff") format("woff"),url("../fonts/bootstrap/glyphicons-halflings-regular.ttf") format("truetype"),url("../fonts/bootstrap/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:"ubuntulight","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#6c5196;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h1 .small,h2 small,h2 .small,h3 small,h3 .small,h4 small,h4 .small,h5 small,h5 .small,h6 small,h6 .small,.h1 small,.h1 .small,.h2 small,.h2 .small,.h3 small,.h3 .small,.h4 small,.h4 .small,.h5 small,.h5 .small,.h6 small,.h6 .small{font-weight:normal;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,h1 .small,.h1 small,.h1 .small,h2 small,h2 .small,.h2 small,.h2 .small,h3 small,h3 .small,.h3 small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,h4 .small,.h4 small,.h4 .small,h5 small,h5 .small,.h5 small,.h5 .small,h6 small,h6 .small,.h6 small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width: 768px){.lead{font-size:21px}} +small,.small{font-size:85%}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#6c5196}a.text-primary:hover{color:#543f75}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff}.bg-primary{background-color:#6c5196}a.bg-primary:hover{background-color:#543f75}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ul ol,ol ul,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857}dt{font-weight:bold}dd{margin-left:0}.dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table}.dl-horizontal dd:after{clear:both}@media (min-width: 992px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}} +abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:14px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,.blockquote-reverse small:before,.blockquote-reverse .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,.blockquote-reverse small:after,.blockquote-reverse .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857}code,kbd,pre,samp{font-family:"ubuntu_monoregular",Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}@media (min-width: 768px){.container{width:750px}}@media (min-width: 992px){.container{width:970px}}@media (min-width: 1200px){.container{width:1170px}} +.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.row{margin-left:-15px;margin-right:-15px}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-1{width:8.33333%}.col-xs-2{width:16.66667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333%}.col-xs-5{width:41.66667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333%}.col-xs-8{width:66.66667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333%}.col-xs-11{width:91.66667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333%}.col-xs-pull-2{right:16.66667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333%}.col-xs-pull-5{right:41.66667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333%}.col-xs-pull-8{right:66.66667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333%}.col-xs-pull-11{right:91.66667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333%}.col-xs-push-2{left:16.66667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333%}.col-xs-push-5{left:41.66667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333%}.col-xs-push-8{left:66.66667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333%}.col-xs-push-11{left:91.66667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0%}.col-xs-offset-1{margin-left:8.33333%}.col-xs-offset-2{margin-left:16.66667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333%}.col-xs-offset-5{margin-left:41.66667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333%}.col-xs-offset-8{margin-left:66.66667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333%}.col-xs-offset-11{margin-left:91.66667%}.col-xs-offset-12{margin-left:100%}@media (min-width: 768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-1{width:8.33333%}.col-sm-2{width:16.66667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333%}.col-sm-5{width:41.66667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333%}.col-sm-8{width:66.66667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333%}.col-sm-11{width:91.66667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333%}.col-sm-pull-2{right:16.66667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333%}.col-sm-pull-5{right:41.66667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333%}.col-sm-pull-8{right:66.66667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333%}.col-sm-pull-11{right:91.66667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333%}.col-sm-push-2{left:16.66667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333%}.col-sm-push-5{left:41.66667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333%}.col-sm-push-8{left:66.66667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333%}.col-sm-push-11{left:91.66667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0%}.col-sm-offset-1{margin-left:8.33333%}.col-sm-offset-2{margin-left:16.66667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333%}.col-sm-offset-5{margin-left:41.66667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333%}.col-sm-offset-8{margin-left:66.66667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333%}.col-sm-offset-11{margin-left:91.66667%}.col-sm-offset-12{margin-left:100%}}@media (min-width: 992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-1{width:8.33333%}.col-md-2{width:16.66667%}.col-md-3{width:25%}.col-md-4{width:33.33333%}.col-md-5{width:41.66667%}.col-md-6{width:50%}.col-md-7{width:58.33333%}.col-md-8{width:66.66667%}.col-md-9{width:75%}.col-md-10{width:83.33333%}.col-md-11{width:91.66667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333%}.col-md-pull-2{right:16.66667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333%}.col-md-pull-5{right:41.66667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333%}.col-md-pull-8{right:66.66667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333%}.col-md-pull-11{right:91.66667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333%}.col-md-push-2{left:16.66667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333%}.col-md-push-5{left:41.66667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333%}.col-md-push-8{left:66.66667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333%}.col-md-push-11{left:91.66667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0%}.col-md-offset-1{margin-left:8.33333%}.col-md-offset-2{margin-left:16.66667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333%}.col-md-offset-5{margin-left:41.66667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333%}.col-md-offset-8{margin-left:66.66667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333%}.col-md-offset-11{margin-left:91.66667%}.col-md-offset-12{margin-left:100%}}@media (min-width: 1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-1{width:8.33333%}.col-lg-2{width:16.66667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333%}.col-lg-5{width:41.66667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333%}.col-lg-8{width:66.66667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333%}.col-lg-11{width:91.66667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333%}.col-lg-pull-2{right:16.66667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333%}.col-lg-pull-5{right:41.66667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333%}.col-lg-pull-8{right:66.66667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333%}.col-lg-pull-11{right:91.66667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333%}.col-lg-push-2{left:16.66667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333%}.col-lg-push-5{left:41.66667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333%}.col-lg-push-8{left:66.66667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333%}.col-lg-push-11{left:91.66667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0%}.col-lg-offset-1{margin-left:8.33333%}.col-lg-offset-2{margin-left:16.66667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333%}.col-lg-offset-5{margin-left:41.66667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333%}.col-lg-offset-8{margin-left:66.66667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333%}.col-lg-offset-11{margin-left:91.66667%}.col-lg-offset-12{margin-left:100%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>thead>tr>td,.table>tbody>tr>th,.table>tbody>tr>td,.table>tfoot>tr>th,.table>tfoot>tr>td{padding:8px;line-height:1.42857;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>th,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>thead>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>thead>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>thead>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>thead>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>thead>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}} +fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio: 0){input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{line-height:34px}input[type="date"].input-sm,.input-group-sm>input[type="date"].form-control,.input-group-sm>input[type="date"].input-group-addon,.input-group-sm>.input-group-btn>input[type="date"].btn,input[type="time"].input-sm,.input-group-sm>input[type="time"].form-control,.input-group-sm>input[type="time"].input-group-addon,.input-group-sm>.input-group-btn>input[type="time"].btn,input[type="datetime-local"].input-sm,.input-group-sm>input[type="datetime-local"].form-control,.input-group-sm>input[type="datetime-local"].input-group-addon,.input-group-sm>.input-group-btn>input[type="datetime-local"].btn,input[type="month"].input-sm,.input-group-sm>input[type="month"].form-control,.input-group-sm>input[type="month"].input-group-addon,.input-group-sm>.input-group-btn>input[type="month"].btn{line-height:30px}input[type="date"].input-lg,.input-group-lg>input[type="date"].form-control,.input-group-lg>input[type="date"].input-group-addon,.input-group-lg>.input-group-btn>input[type="date"].btn,input[type="time"].input-lg,.input-group-lg>input[type="time"].form-control,.input-group-lg>input[type="time"].input-group-addon,.input-group-lg>.input-group-btn>input[type="time"].btn,input[type="datetime-local"].input-lg,.input-group-lg>input[type="datetime-local"].form-control,.input-group-lg>input[type="datetime-local"].input-group-addon,.input-group-lg>.input-group-btn>input[type="datetime-local"].btn,input[type="month"].input-lg,.input-group-lg>input[type="month"].form-control,.input-group-lg>input[type="month"].input-group-addon,.input-group-lg>.input-group-btn>input[type="month"].btn{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.form-control-static.input-sm,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-left:0;padding-right:0}.input-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn,.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm,.input-group-sm>select.form-control,.input-group-sm>select.input-group-addon,.input-group-sm>.input-group-btn>select.btn,.form-group-sm .form-control{height:30px;line-height:30px}textarea.input-sm,.input-group-sm>textarea.form-control,.input-group-sm>textarea.input-group-addon,.input-group-sm>.input-group-btn>textarea.btn,.form-group-sm .form-control,select[multiple].input-sm,.input-group-sm>select[multiple].form-control,.input-group-sm>select[multiple].input-group-addon,.input-group-sm>.input-group-btn>select[multiple].btn,.form-group-sm .form-control{height:auto}.input-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn,.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg,.input-group-lg>select.form-control,.input-group-lg>select.input-group-addon,.input-group-lg>.input-group-btn>select.btn,.form-group-lg .form-control{height:46px;line-height:46px}textarea.input-lg,.input-group-lg>textarea.form-control,.input-group-lg>textarea.input-group-addon,.input-group-lg>.input-group-btn>textarea.btn,.form-group-lg .form-control,select[multiple].input-lg,.input-group-lg>select[multiple].form-control,.input-group-lg>select[multiple].input-group-addon,.input-group-lg>.input-group-btn>select[multiple].btn,.form-group-lg .form-control{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg>.form-control+.form-control-feedback,.input-group-lg>.input-group-addon+.form-control-feedback,.input-group-lg>.input-group-btn>.btn+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.input-group-sm>.form-control+.form-control-feedback,.input-group-sm>.input-group-addon+.form-control-feedback,.input-group-sm>.input-group-btn>.btn+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label ~ .form-control-feedback{top:25px}.has-feedback label.sr-only ~ .form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width: 768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}} +.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table}.form-horizontal .form-group:after{clear:both}@media (min-width: 768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width: 768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width: 768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}} +.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn.focus,.btn:active:focus,.btn:active.focus,.btn.active:focus,.btn.active.focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default.focus,.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{background-image:none}.btn-default.disabled,.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled.active,.btn-default[disabled],.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled].active,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#6c5196;border-color:#604885}.btn-primary:hover,.btn-primary:focus,.btn-primary.focus,.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#543f75;border-color:#43335e}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{background-image:none}.btn-primary.disabled,.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled.active,.btn-primary[disabled],.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary.active{background-color:#6c5196;border-color:#604885}.btn-primary .badge{color:#6c5196;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success.focus,.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{background-image:none}.btn-success.disabled,.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled.active,.btn-success[disabled],.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled].active,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info.focus,.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{background-image:none}.btn-info.disabled,.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled.active,.btn-info[disabled],.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled].active,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning.focus,.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{background-image:none}.btn-warning.disabled,.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled.active,.btn-warning[disabled],.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger.focus,.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled,.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled.active,.btn-danger[disabled],.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#6c5196;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#6c5196}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width: 992px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar:before,.btn-toolbar:after{content:" ";display:table}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle,.btn-group-lg.btn-group>.btn+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret,.btn-group-lg>.btn .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret,.dropup .btn-group-lg>.btn .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{content:" ";display:table}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav:before,.nav:after{content:" ";display:table}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#6c5196}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified,.nav-tabs.nav-justified{width:100%}.nav-justified>li,.nav-tabs.nav-justified>li{float:none}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){.nav-justified>li,.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{margin-bottom:0}} +.nav-tabs-justified,.nav-tabs.nav-justified{border-bottom:0}.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}} +.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{content:" ";display:table}.navbar:after{clear:both}@media (min-width: 992px){.navbar{border-radius:4px}} +.navbar-header:before,.navbar-header:after{content:" ";display:table}.navbar-header:after{clear:both}@media (min-width: 992px){.navbar-header{float:left}} +.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{content:" ";display:table}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media (min-width: 992px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;visibility:visible !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}} +.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}} +.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width: 992px){.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}} +.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width: 992px){.navbar-static-top{border-radius:0}} +.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width: 992px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}} +.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width: 992px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}} +.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width: 992px){.navbar-toggle{display:none}} +.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width: 991px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width: 992px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}} +.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@media (min-width: 768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width: 991px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width: 992px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}} +.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm,.btn-group-sm>.navbar-btn.btn{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs,.btn-group-xs>.navbar-btn.btn{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width: 992px){.navbar-text{float:left;margin-left:15px;margin-right:15px}} +@media (min-width: 992px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right ~ .navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width: 991px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:hover,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#33333f;border-color:#1c1c23}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a{color:#777}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#eee;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#eee;background-color:#1c1c23}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#23232b}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#1c1c23;color:#eee}@media (max-width: 991px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#1c1c23}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#1c1c23}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#eee;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#eee;background-color:#1c1c23}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#eee}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#eee}.navbar-inverse .btn-link[disabled]:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:hover,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857;text-decoration:none;color:#428bca;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>a:focus,.pagination>li>span:hover,.pagination>li>span:focus{color:#6c5196;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:hover,.pagination>.active>a:focus,.pagination>.active>span,.pagination>.active>span:hover,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#6c5196;border-color:#6c5196;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager:before,.pager:after{content:" ";display:table}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#6c5196}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#543f75}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width: 768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}} +.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border 0.2s ease-in-out;-o-transition:border 0.2s ease-in-out;transition:border 0.2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto;margin-left:auto;margin-right:auto}.thumbnail .caption{padding:9px;color:#333}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#6c5196;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;color:#555;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eee;color:#777;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#6c5196;border-color:#6c5196}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#d7cee5}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{content:" ";display:table}.panel-body:after{clear:both}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#6c5196}.panel-primary>.panel-heading{color:#fff;background-color:#6c5196;border-color:#6c5196}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#6c5196}.panel-primary>.panel-heading .badge{color:#6c5196;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#6c5196}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:0}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{content:" ";display:table}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width: 992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;visibility:visible;font-family:"ubuntulight","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:normal;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"ubuntulight","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:normal;line-height:1.42857;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto;line-height:1}@media all and (transform-3d), (-webkit-transform-3d){.carousel-inner>.item{transition:transform .6s ease-in-out;backface-visibility:hidden;perspective:1000}.carousel-inner>.item.next,.carousel-inner>.item.active.right{transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#fff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:transparent}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width: 768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important;visibility:hidden !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width: 767px){.visible-xs{display:block !important}table.visible-xs{display:table}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width: 767px){.visible-xs-block{display:block !important}} +@media (max-width: 767px){.visible-xs-inline{display:inline !important}} +@media (max-width: 767px){.visible-xs-inline-block{display:inline-block !important}} +@media (min-width: 768px) and (max-width: 991px){.visible-sm{display:block !important}table.visible-sm{display:table}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-block{display:block !important}} +@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline{display:inline !important}} +@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline-block{display:inline-block !important}} +@media (min-width: 992px) and (max-width: 1199px){.visible-md{display:block !important}table.visible-md{display:table}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-block{display:block !important}} +@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline{display:inline !important}} +@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline-block{display:inline-block !important}} +@media (min-width: 1200px){.visible-lg{display:block !important}table.visible-lg{display:table}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width: 1200px){.visible-lg-block{display:block !important}} +@media (min-width: 1200px){.visible-lg-inline{display:inline !important}} +@media (min-width: 1200px){.visible-lg-inline-block{display:inline-block !important}} +@media (max-width: 767px){.hidden-xs{display:none !important}}@media (min-width: 768px) and (max-width: 991px){.hidden-sm{display:none !important}}@media (min-width: 992px) and (max-width: 1199px){.hidden-md{display:none !important}}@media (min-width: 1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}} +.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}} +.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}} +@media print{.hidden-print{display:none !important}}.hook{z-index:999 !important}div.th-inner.sortable{color:#428bca}th a:hover,div.th-inner.sortable:hover{text-decoration:none;color:#6c5196}code{font-size:100%}.has-success .form-control{border-color:#ff00ff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #82f;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #82f;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #82f}.has-success .form-control:focus{border-color:#ff00ff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 20px #80f;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 20px #80f;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 20px #80f}.navbar{filter:none !important}.modal-backdrop{position:fixed}.btn-menu{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;text-transform:uppercase;padding:5px 10px;font-size:14px;line-height:1.42857;border-radius:3px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding-top:4px !important;padding-bottom:5px !important}.btn-menu:hover{text-decoration:none}.btn{text-transform:uppercase;font-size:1em;font-size:1em;line-height:2em;padding-left:1.5em;padding-right:1.5em}.dropdown-menu>li>a{display:block;padding:5px 20px}.loader{margin-left:40px;width:20px}@media (max-width: 767px){.loader{margin-left:130px}} +.logo{position:absolute;left:7px;top:7px;width:36px;height:36px;cursor:pointer;z-index:10000;background:url(../img/icon36.png) no-repeat;background-size:36px 36px}@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3 / 2), only screen and (min--moz-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5){.logo{background:url(../img/icon72.png) no-repeat;background-size:36px 36px}} +.cogmenu li,.thmenu li{padding-bottom:2px;text-align:left}.cogon{color:#5cb85c !important}.cogoff{color:#d9534f !important}.thmenuon{color:#5cb85c !important}.thmenuoff{color:#d9534f !important}.clearable{background-image:url(../img/inputclear.png);background-repeat:no-repeat;background-position:right -10px center;transition:background 0.4s}.clearable.x{background-position:right 5px center}.clearable.onX{cursor:pointer}.icon-spin{-webkit-animation:kf-spin 2s infinite linear;animation:kf-spin 2s infinite linear}@-webkit-keyframes kf-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes kf-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}body{padding-top:50px;padding-bottom:20px}.containerwide{margin-left:2%;max-width:96%}.exportresult{height:auto;max-height:200px;overflow-x:hidden;overflow-y:auto}.jumbotronflat{padding:10px;margin:0 !important;color:inherit;background-color:#eee}.jumbotronflat .progress{margin:0}textarea.test{font-family:monospace, serif}.hyphen{white-space:pre-wrap;-ms-word-break:break-all;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.hook *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0 auto}.hook{display:none;height:85px;background:url(hook-bgs.png) repeat;box-shadow:0 -8px 5px -5px #999 inset;clear:both;position:relative;z-index:9999;width:100%;overflow:hidden;text-align:center}.hook-text{font-size:14px;font-family:Arial, Helvetica, sans-serif;color:#666;font-weight:normal;text-shadow:0 1px #fff}.hook-loader{padding:25px 0}.hook-spinner{display:block;background:url(hook-spinner.png) no-repeat center;width:32px;height:32px;-webkit-animation:spin 1s linear infinite both;-moz-animation:spin 1s linear infinite both;-o-animation:spin 1s linear infinite both;animation:spin 1s linear infinite both}.hook-with-text .hook-loader{padding:10px 0}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2 / 1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){.hook-spinner{background:url(hook-spinner@2x.png) no-repeat center;background-size:32px 32px}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(360deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.navbar-nav .sub-arrow,.navbar-nav .collapsible .sub-arrow{position:static;margin-top:0;margin-right:0;margin-left:6px;display:inline-block;width:0;height:0;overflow:hidden;vertical-align:middle;border-top:4px solid;border-right:4px dashed transparent;border-bottom:4px dashed transparent;border-left:4px dashed transparent}.navbar-fixed-bottom .sub-arrow{margin-top:-5px;border-top:4px dashed transparent;border-bottom:4px solid}.navbar-nav ul .sub-arrow{position:absolute;right:0;margin-top:6px;margin-right:15px;border-top:4px dashed transparent;border-bottom:4px dashed transparent;border-left:4px solid}.navbar-nav ul a.has-submenu{padding-right:30px}.navbar-nav span.scroll-up,.navbar-nav span.scroll-down{position:absolute;display:none;visibility:hidden;height:20px;overflow:hidden;text-align:center}.navbar-nav span.scroll-up-arrow,.navbar-nav span.scroll-down-arrow{position:absolute;top:-2px;left:50%;margin-left:-8px;width:0;height:0;overflow:hidden;border-top:7px dashed transparent;border-right:7px dashed transparent;border-bottom:7px solid;border-left:7px dashed transparent}.navbar-nav span.scroll-down-arrow{top:6px;border-top:7px solid;border-right:7px dashed transparent;border-bottom:7px dashed transparent;border-left:7px dashed transparent}.navbar-nav .collapsible ul .dropdown-menu>li>a,.navbar-nav .collapsible ul .dropdown-menu .dropdown-header{padding-left:35px}.navbar-nav .collapsible ul ul .dropdown-menu>li>a,.navbar-nav .collapsible ul ul .dropdown-menu .dropdown-header{padding-left:45px}.navbar-nav .collapsible ul ul ul .dropdown-menu>li>a,.navbar-nav .collapsible ul ul ul .dropdown-menu .dropdown-header{padding-left:55px}.navbar-nav .collapsible ul ul ul ul .dropdown-menu>li>a,.navbar-nav .collapsible ul ul ul ul .dropdown-menu .dropdown-header{padding-left:65px}.navbar-nav .dropdown-menu>li>a{white-space:normal}.navbar-nav ul.sm-nowrap>li>a{white-space:nowrap}.navbar-right ul.dropdown-menu{left:0;right:auto}.navbar-nav .collapsible ul{display:none;position:static !important;top:auto !important;left:auto !important;margin-left:0 !important;margin-top:0 !important;width:auto !important;min-width:0 !important;max-width:none !important}.navbar-nav .collapsible ul.sm-nowrap>li>a{white-space:normal}.navbar-nav .collapsible iframe{display:none} diff --git a/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.eot b/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.eot new file mode 100644 index 00000000..4a4ca865 Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.eot differ diff --git a/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.svg b/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.svg new file mode 100644 index 00000000..25691af8 --- /dev/null +++ b/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.ttf b/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.ttf new file mode 100644 index 00000000..67fa00bf Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.ttf differ diff --git a/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.woff b/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.woff new file mode 100644 index 00000000..8c54182a Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/bootstrap/glyphicons-halflings-regular.woff differ diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.eot b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.eot new file mode 100644 index 00000000..a344d78c Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.eot differ diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.svg b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.svg new file mode 100644 index 00000000..d4f8d549 --- /dev/null +++ b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.svg @@ -0,0 +1,1207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.ttf b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.ttf new file mode 100644 index 00000000..849da94b Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.ttf differ diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.woff b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.woff new file mode 100644 index 00000000..90425c82 Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.woff differ diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.woff2 b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.woff2 new file mode 100644 index 00000000..04ca43e0 Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/ubuntu-l-webfont.woff2 differ diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-demo.html b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-demo.html new file mode 100644 index 00000000..965cf265 --- /dev/null +++ b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-demo.html @@ -0,0 +1,614 @@ + + + + + + + + + + + + + Ubuntu Mono Regular Specimen + + + + + + +
+ + + +
+ + +
+ +
+
+
AaBb
+
+
+ +
+
A​B​C​D​E​F​G​H​I​J​K​L​M​N​O​P​Q​R​S​T​U​V​W​X​Y​Z​a​b​c​d​e​f​g​h​i​j​k​l​m​n​o​p​q​r​s​t​u​v​w​x​y​z​1​2​3​4​5​6​7​8​9​0​&​.​,​?​!​@​(​)​#​$​%​*​+​-​=​:​;
+
+
+
+ + + + + + + + + + + + + + + + +
10abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
11abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
12abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
13abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
14abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
16abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
18abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
20abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
24abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
30abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
36abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
48abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
60abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
72abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
90abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+ +
+ +
+ + + +
+ + +
+
◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼body
body
body
body
+
+ bodyUbuntu Mono Regular +
+
+ bodyArial +
+
+ bodyVerdana +
+
+ bodyGeorgia +
+ + + +
+ + +
+ +
+

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+ +
+
+
+

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+ +
+ +
+ +
+
+

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+
+

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+ +
+ +
+ +
+
+

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+
+ +
+ + + +
+
+

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+ +
+ +
+
+

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+ +
+
+ +
+ +
+
+

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+
+

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+ +
+ +
+ +
+
+

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

+
+
+ +
+ + + + +
+ +
+ +
+ +
+

Lorem Ipsum Dolor

+

Etiam porta sem malesuada magna mollis euismod

+ + +
+
+
+
+

Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

+ + +

Pellentesque ornare sem

+ +

Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.

+ +

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

+ +

Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur.

+ +

Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla.

+ +

Cras mattis consectetur

+ +

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum.

+ +

Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.

+
+ + +
+ +
+ + + + + + +
+
+
+ +

Language Support

+

The subset of Ubuntu Mono Regular in this kit supports the following languages:
+ + Albanian, Basque, Breton, Chamorro, Danish, Dutch, English, Faroese, Finnish, French, Frisian, Galician, German, Icelandic, Italian, Malagasy, Norwegian, Portuguese, Spanish, Swedish

+

Glyph Chart

+

The subset of Ubuntu Mono Regular in this kit includes all the glyphs listed below. Unicode entities are included above each glyph to help you insert individual characters into your layout.

+
+ +

&#29;


+

&#13;

+

&#29;


+

&#32;

+

&#33;

!
+

&#34;

"
+

&#35;

#
+

&#36;

$
+

&#37;

%
+

&#38;

&
+

&#39;

'
+

&#40;

(
+

&#41;

)
+

&#42;

*
+

&#43;

+
+

&#44;

,
+

&#45;

-
+

&#46;

.
+

&#47;

/
+

&#48;

0
+

&#49;

1
+

&#50;

2
+

&#51;

3
+

&#52;

4
+

&#53;

5
+

&#54;

6
+

&#55;

7
+

&#56;

8
+

&#57;

9
+

&#58;

:
+

&#59;

;
+

&#60;

<
+

&#61;

=
+

&#62;

>
+

&#63;

?
+

&#64;

@
+

&#65;

A
+

&#66;

B
+

&#67;

C
+

&#68;

D
+

&#69;

E
+

&#70;

F
+

&#71;

G
+

&#72;

H
+

&#73;

I
+

&#74;

J
+

&#75;

K
+

&#76;

L
+

&#77;

M
+

&#78;

N
+

&#79;

O
+

&#80;

P
+

&#81;

Q
+

&#82;

R
+

&#83;

S
+

&#84;

T
+

&#85;

U
+

&#86;

V
+

&#87;

W
+

&#88;

X
+

&#89;

Y
+

&#90;

Z
+

&#91;

[
+

&#92;

\
+

&#93;

]
+

&#94;

^
+

&#95;

_
+

&#96;

`
+

&#97;

a
+

&#98;

b
+

&#99;

c
+

&#100;

d
+

&#101;

e
+

&#102;

f
+

&#103;

g
+

&#104;

h
+

&#105;

i
+

&#106;

j
+

&#107;

k
+

&#108;

l
+

&#109;

m
+

&#110;

n
+

&#111;

o
+

&#112;

p
+

&#113;

q
+

&#114;

r
+

&#115;

s
+

&#116;

t
+

&#117;

u
+

&#118;

v
+

&#119;

w
+

&#120;

x
+

&#121;

y
+

&#122;

z
+

&#123;

{
+

&#124;

|
+

&#125;

}
+

&#126;

~
+

&#160;

 
+

&#161;

¡
+

&#162;

¢
+

&#163;

£
+

&#164;

¤
+

&#165;

¥
+

&#166;

¦
+

&#167;

§
+

&#168;

¨
+

&#169;

©
+

&#170;

ª
+

&#171;

«
+

&#172;

¬
+

&#173;

­
+

&#174;

®
+

&#175;

¯
+

&#176;

°
+

&#177;

±
+

&#178;

²
+

&#179;

³
+

&#180;

´
+

&#181;

µ
+

&#182;

+

&#183;

·
+

&#184;

¸
+

&#185;

¹
+

&#186;

º
+

&#187;

»
+

&#188;

¼
+

&#189;

½
+

&#190;

¾
+

&#191;

¿
+

&#192;

À
+

&#193;

Á
+

&#194;

Â
+

&#195;

Ã
+

&#196;

Ä
+

&#197;

Å
+

&#198;

Æ
+

&#199;

Ç
+

&#200;

È
+

&#201;

É
+

&#202;

Ê
+

&#203;

Ë
+

&#204;

Ì
+

&#205;

Í
+

&#206;

Î
+

&#207;

Ï
+

&#208;

Ð
+

&#209;

Ñ
+

&#210;

Ò
+

&#211;

Ó
+

&#212;

Ô
+

&#213;

Õ
+

&#214;

Ö
+

&#215;

×
+

&#216;

Ø
+

&#217;

Ù
+

&#218;

Ú
+

&#219;

Û
+

&#220;

Ü
+

&#221;

Ý
+

&#222;

Þ
+

&#223;

ß
+

&#224;

à
+

&#225;

á
+

&#226;

â
+

&#227;

ã
+

&#228;

ä
+

&#229;

å
+

&#230;

æ
+

&#231;

ç
+

&#232;

è
+

&#233;

é
+

&#234;

ê
+

&#235;

ë
+

&#236;

ì
+

&#237;

í
+

&#238;

î
+

&#239;

ï
+

&#240;

ð
+

&#241;

ñ
+

&#242;

ò
+

&#243;

ó
+

&#244;

ô
+

&#245;

õ
+

&#246;

ö
+

&#247;

÷
+

&#248;

ø
+

&#249;

ù
+

&#250;

ú
+

&#251;

û
+

&#252;

ü
+

&#253;

ý
+

&#254;

þ
+

&#255;

ÿ
+

&#338;

Œ
+

&#339;

œ
+

&#376;

Ÿ
+

&#710;

ˆ
+

&#732;

˜
+

&#8192;

 
+

&#8193;

+

&#8194;

+

&#8195;

+

&#8196;

+

&#8197;

+

&#8198;

+

&#8199;

+

&#8200;

+

&#8201;

+

&#8202;

+

&#8208;

+

&#8209;

+

&#8210;

+

&#8211;

+

&#8212;

+

&#8216;

+

&#8217;

+

&#8218;

+

&#8220;

+

&#8221;

+

&#8222;

+

&#8226;

+

&#8230;

+

&#8239;

+

&#8249;

+

&#8250;

+

&#8287;

+

&#8364;

+

&#8482;

+

&#9724;

+

&#64257;

+

&#64258;

+
+
+ + +
+
+ + +
+ +
+ +
+
+
+

Installing Webfonts

+ +

Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.

+ +

1. Upload your webfonts

+

You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.

+ +

2. Include the webfont stylesheet

+

A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the Fontspring blog post about it. The code for it is as follows:

+ + + +@font-face{ + font-family: 'MyWebFont'; + src: url('WebFont.eot'); + src: url('WebFont.eot?#iefix') format('embedded-opentype'), + url('WebFont.woff') format('woff'), + url('WebFont.ttf') format('truetype'), + url('WebFont.svg#webfont') format('svg'); +} + + +

We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:

+ <link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" /> + +

3. Modify your own stylesheet

+

To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:

+p { font-family: 'WebFont', Arial, sans-serif; } + +

4. Test

+

Getting webfonts to work cross-browser can be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.

+
+ + +
+ +
+ +
+ +
+ + diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.eot b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.eot new file mode 100644 index 00000000..97c18b2d Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.eot differ diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.svg b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.svg new file mode 100644 index 00000000..bb6fb52d --- /dev/null +++ b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.ttf b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.ttf new file mode 100644 index 00000000..2f01d97c Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.ttf differ diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.woff b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.woff new file mode 100644 index 00000000..6c05113a Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.woff differ diff --git a/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.woff2 b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.woff2 new file mode 100644 index 00000000..4d577174 Binary files /dev/null and b/vendor/potsky/pimp-my-log/fonts/ubuntumono-r-webfont.woff2 differ diff --git a/vendor/potsky/pimp-my-log/humans.txt b/vendor/potsky/pimp-my-log/humans.txt new file mode 100644 index 00000000..792106dd --- /dev/null +++ b/vendor/potsky/pimp-my-log/humans.txt @@ -0,0 +1,11 @@ +# humanstxt.org/ +# The humans responsible & technology colophon + +# TEAM + + Potsky -- Developer -- @potskymac + +# TECHNOLOGY COLOPHON + + HTML5, CSS3, jQuery, PHP + diff --git a/vendor/potsky/pimp-my-log/img/1x1blue.gif b/vendor/potsky/pimp-my-log/img/1x1blue.gif new file mode 100644 index 00000000..73d36ee6 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1blue.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1blue_low.gif b/vendor/potsky/pimp-my-log/img/1x1blue_low.gif new file mode 100644 index 00000000..c2913a4e Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1blue_low.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1green.gif b/vendor/potsky/pimp-my-log/img/1x1green.gif new file mode 100644 index 00000000..bd865dc5 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1green.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1green_low.gif b/vendor/potsky/pimp-my-log/img/1x1green_low.gif new file mode 100644 index 00000000..36420359 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1green_low.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1orange.gif b/vendor/potsky/pimp-my-log/img/1x1orange.gif new file mode 100644 index 00000000..064593f7 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1orange.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1orange_low.gif b/vendor/potsky/pimp-my-log/img/1x1orange_low.gif new file mode 100644 index 00000000..54858e97 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1orange_low.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1pink.gif b/vendor/potsky/pimp-my-log/img/1x1pink.gif new file mode 100644 index 00000000..2f083c7e Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1pink.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1pink_low.gif b/vendor/potsky/pimp-my-log/img/1x1pink_low.gif new file mode 100644 index 00000000..528d5cf1 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1pink_low.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1red.gif b/vendor/potsky/pimp-my-log/img/1x1red.gif new file mode 100644 index 00000000..e9139a68 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1red.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1red_low.gif b/vendor/potsky/pimp-my-log/img/1x1red_low.gif new file mode 100644 index 00000000..d3c237c6 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1red_low.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1yellow.gif b/vendor/potsky/pimp-my-log/img/1x1yellow.gif new file mode 100644 index 00000000..febbafa6 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1yellow.gif differ diff --git a/vendor/potsky/pimp-my-log/img/1x1yellow_low.gif b/vendor/potsky/pimp-my-log/img/1x1yellow_low.gif new file mode 100644 index 00000000..9c6ac4a8 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/1x1yellow_low.gif differ diff --git a/vendor/potsky/pimp-my-log/img/apple-touch-icon-114x114.png b/vendor/potsky/pimp-my-log/img/apple-touch-icon-114x114.png new file mode 100644 index 00000000..7e6e7af2 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/apple-touch-icon-114x114.png differ diff --git a/vendor/potsky/pimp-my-log/img/apple-touch-icon-120x120.png b/vendor/potsky/pimp-my-log/img/apple-touch-icon-120x120.png new file mode 100644 index 00000000..26562d20 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/apple-touch-icon-120x120.png differ diff --git a/vendor/potsky/pimp-my-log/img/apple-touch-icon-144x144.png b/vendor/potsky/pimp-my-log/img/apple-touch-icon-144x144.png new file mode 100644 index 00000000..f79f5544 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/apple-touch-icon-144x144.png differ diff --git a/vendor/potsky/pimp-my-log/img/apple-touch-icon-152x152.png b/vendor/potsky/pimp-my-log/img/apple-touch-icon-152x152.png new file mode 100644 index 00000000..72700d3a Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/apple-touch-icon-152x152.png differ diff --git a/vendor/potsky/pimp-my-log/img/apple-touch-icon-57x57.png b/vendor/potsky/pimp-my-log/img/apple-touch-icon-57x57.png new file mode 100644 index 00000000..02c760d2 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/apple-touch-icon-57x57.png differ diff --git a/vendor/potsky/pimp-my-log/img/apple-touch-icon-72x72.png b/vendor/potsky/pimp-my-log/img/apple-touch-icon-72x72.png new file mode 100644 index 00000000..3f064072 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/apple-touch-icon-72x72.png differ diff --git a/vendor/potsky/pimp-my-log/img/apple-touch-icon-76x76.png b/vendor/potsky/pimp-my-log/img/apple-touch-icon-76x76.png new file mode 100644 index 00000000..840f36b4 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/apple-touch-icon-76x76.png differ diff --git a/vendor/potsky/pimp-my-log/img/apple-touch-icon.png b/vendor/potsky/pimp-my-log/img/apple-touch-icon.png new file mode 100644 index 00000000..02c760d2 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/apple-touch-icon.png differ diff --git a/vendor/potsky/pimp-my-log/img/favicon.ico b/vendor/potsky/pimp-my-log/img/favicon.ico new file mode 100644 index 00000000..553fc100 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/favicon.ico differ diff --git a/vendor/potsky/pimp-my-log/img/icon36.png b/vendor/potsky/pimp-my-log/img/icon36.png new file mode 100644 index 00000000..aed3e6ce Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/icon36.png differ diff --git a/vendor/potsky/pimp-my-log/img/icon72.png b/vendor/potsky/pimp-my-log/img/icon72.png new file mode 100644 index 00000000..3450a875 Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/icon72.png differ diff --git a/vendor/potsky/pimp-my-log/img/inputclear.png b/vendor/potsky/pimp-my-log/img/inputclear.png new file mode 100644 index 00000000..affc73ba Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/inputclear.png differ diff --git a/vendor/potsky/pimp-my-log/img/loader.gif b/vendor/potsky/pimp-my-log/img/loader.gif new file mode 100644 index 00000000..084aae0a Binary files /dev/null and b/vendor/potsky/pimp-my-log/img/loader.gif differ diff --git a/vendor/potsky/pimp-my-log/inc/classes/Feedcreator.php b/vendor/potsky/pimp-my-log/inc/classes/Feedcreator.php new file mode 100644 index 00000000..a55d5a08 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/classes/Feedcreator.php @@ -0,0 +1,853 @@ + + * @since 1.3 + */ +class FeedItem extends HtmlDescribable { + /** + * Mandatory attributes of an item. + */ + var $title, $description, $link; + + /** + * Optional attributes of an item. + */ + var $author, $authorEmail, $image, $category, $comments, $guid, $source, $creator; + + /** + * Publishing date of an item. May be in one of the following formats: + * + * RFC 822: + * "Mon, 20 Jan 03 18:05:41 +0400" + * "20 Jan 03 18:05:41 +0000" + * + * ISO 8601: + * "2003-01-20T18:05:41+04:00" + * + * Unix: + * 1043082341 + */ + var $date; + + /** + * Add element tag RSS 2.0 + * modified by : Mohammad Hafiz bin Ismail (mypapit@gmail.com) + * + * + * display : + * + * + */ + var $enclosure; + + /** + * Any additional elements to include as an assiciated array. All $key => $value pairs + * will be included unencoded in the feed item in the form + * <$key>$value + * Again: No encoding will be used! This means you can invalidate or enhance the feed + * if $value contains markup. This may be abused to embed tags not implemented by + * the FeedCreator class used. + */ + var $additionalElements = Array(); + + // on hold + // var $source; +} + +class EnclosureItem extends HtmlDescribable { + /* + * + * core variables + * + **/ + var $url,$length,$type; + + /* + * For use with another extension like Yahoo mRSS + * Warning : + * These variables might not show up in + * later release / not finalize yet! + * + */ + var $width, $height, $title, $description, $keywords, $thumburl; + + var $additionalElements = Array(); + +} + + +/** + * An FeedImage may be added to a FeedCreator feed. + * @author Kai Blankenhorn + * @since 1.3 + */ +class FeedImage extends HtmlDescribable { + /** + * Mandatory attributes of an image. + */ + var $title, $url, $link; + + /** + * Optional attributes of an image. + */ + var $width, $height, $description; +} + + + +/** + * An HtmlDescribable is an item within a feed that can have a description that may + * include HTML markup. + */ +class HtmlDescribable { + /** + * Indicates whether the description field should be rendered in HTML. + */ + var $descriptionHtmlSyndicated; + + /** + * Indicates whether and to how many characters a description should be truncated. + */ + var $descriptionTruncSize; + + /** + * Returns a formatted description field, depending on descriptionHtmlSyndicated and + * $descriptionTruncSize properties + * @return string the formatted description + */ + function getDescription() { + $descriptionField = new FeedHtmlField($this->description); + $descriptionField->syndicateHtml = $this->descriptionHtmlSyndicated; + $descriptionField->truncSize = $this->descriptionTruncSize; + return $descriptionField->output(); + } + +} + + + +/** + * An FeedHtmlField describes and generates + * a feed, item or image html field (probably a description). Output is + * generated based on $truncSize, $syndicateHtml properties. + * @author Pascal Van Hecke + * @version 1.6 + */ +class FeedHtmlField { + /** + * Mandatory attributes of a FeedHtmlField. + */ + var $rawFieldContent; + + /** + * Optional attributes of a FeedHtmlField. + * + */ + var $truncSize, $syndicateHtml; + + /** + * Creates a new instance of FeedHtmlField. + * @param $string: if given, sets the rawFieldContent property + */ + function FeedHtmlField($parFieldContent) { + if ($parFieldContent) { + $this->rawFieldContent = $parFieldContent; + } + } + + + /** + * Creates the right output, depending on $truncSize, $syndicateHtml properties. + * @return string the formatted field + */ + function output() { + // when field available and syndicated in html we assume + // - valid html in $rawFieldContent and we enclose in CDATA tags + // - no truncation (truncating risks producing invalid html) + if (!$this->rawFieldContent) { + $result = ""; + } elseif ($this->syndicateHtml) { + $result = "rawFieldContent."]]>"; + } else { + if ($this->truncSize and is_int($this->truncSize)) { + $result = FeedCreator::iTrunc(htmlspecialchars($this->rawFieldContent),$this->truncSize); + } else { + $result = htmlspecialchars($this->rawFieldContent); + } + } + return $result; + } + +} + + + +/** + * UniversalFeedCreator lets you choose during runtime which + * format to build. + * For general usage of a feed class, see the FeedCreator class + * below or the example above. + * + * @since 1.3 + * @author Kai Blankenhorn + */ +class UniversalFeedCreator extends FeedCreator { + + var $_feed; + + function _setMIME($format) { + switch (strtoupper($format)) { + + case "RSS": + case "2.0": + // fall through + case "RSS2.0": + header('Content-type: text/xml', true); + break; + + case "ATOM": + // fall through: always the latest ATOM version + case "ATOM1.0": + header('Content-type: application/xml', true); + break; + + default: + case "0.91": + // fall through + case "RSS0.91": + header('Content-type: text/xml', true); + break; + } + } + + function _setFormat($format) { + switch (strtoupper($format)) { + + case "RSS": + case "2.0": + // fall through + case "RSS2.0": + $this->_feed = new RSSCreator20(); + break; + + case "0.91": + // fall through + case "RSS0.91": + $this->_feed = new RSSCreator091(); + break; + + case "ATOM": + // fall through: always the latest ATOM version + case "ATOM1.0": + $this->_feed = new AtomCreator10(); + break; + + default: + $this->_feed = new RSSCreator091(); + break; + } + + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + // prevent overwriting of properties "contentType", "encoding"; do not copy "_feed" itself + if (!in_array($key, array("_feed", "contentType", "encoding"))) { + $this->_feed->{$key} = $this->{$key}; + } + } + } + + /** + * Creates a syndication feed based on the items previously added. + * + * @see FeedCreator::addItem() + * @param string format format the feed should comply to. Valid values are: + * "PIE0.1", "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM0.3", "HTML", "JS" + * @return string the contents of the feed. + */ + function createFeed($format = "RSS0.91") { + $this->_setFormat($format); + return $this->_feed->createFeed(); + } + + + /** + * Outputs feed to the browser - needed for on-the-fly feed generation (like it is done in WordPress, etc.) + * + * @param format string format the feed should comply to. Valid values are: + * "PIE0.1" (deprecated), "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM0.3". + */ + function outputFeed( $timezone , $format='RSS2.0' ) { + $this->_setFormat( $format ); + $this->_setMIME( $format ); + $this->_feed->outputFeed( $timezone , $format ); + } + +} + + +/** + * FeedCreator is the abstract base implementation for concrete + * implementations that implement a specific format of syndication. + * + * @abstract + * @author Kai Blankenhorn + * @since 1.4 + */ +class FeedCreator extends HtmlDescribable { + + /** + * Mandatory attributes of a feed. + */ + var $title, $description, $link; + + + /** + * Optional attributes of a feed. + */ + var $syndicationURL, $image, $language, $copyright, $pubDate, $lastBuildDate, $editor, $editorEmail, $webmaster, $category, $docs, $ttl, $rating, $skipHours, $skipDays; + + /** + * The url of the external xsl stylesheet used to format the naked rss feed. + * Ignored in the output when empty. + */ + var $xslStyleSheet = ""; + + + /** + * @access private + */ + var $items = Array(); + + + /** + * This feed's MIME content type. + * @since 1.4 + * @access private + */ + var $contentType = "application/xml"; + + + /** + * This feed's character encoding. + * @since 1.6.1 + **/ + var $encoding = "UTF-8"; + + + /** + * Any additional elements to include as an assiciated array. All $key => $value pairs + * will be included unencoded in the feed in the form + * <$key>$value + * Again: No encoding will be used! This means you can invalidate or enhance the feed + * if $value contains markup. This may be abused to embed tags not implemented by + * the FeedCreator class used. + */ + var $additionalElements = Array(); + + + /** + * Adds an FeedItem to the feed. + * + * @param object FeedItem $item The FeedItem to add to the feed. + * @access public + */ + function addItem($item) { + $this->items[] = $item; + } + + + /** + * Truncates a string to a certain length at the most sensible point. + * First, if there's a '.' character near the end of the string, the string is truncated after this character. + * If there is no '.', the string is truncated after the last ' ' character. + * If the string is truncated, " ..." is appended. + * If the string is already shorter than $length, it is returned unchanged. + * + * @static + * @param string string A string to be truncated. + * @param int length the maximum length the string should be truncated to + * @return string the truncated string + */ + function iTrunc($string, $length) { + if (strlen($string)<=$length) { + return $string; + } + + $pos = strrpos($string,"."); + if ($pos>=$length-4) { + $string = substr($string,0,$length-4); + $pos = strrpos($string,"."); + } + if ($pos>=$length*0.4) { + return substr($string,0,$pos+1)." ..."; + } + + $pos = strrpos($string," "); + if ($pos>=$length-4) { + $string = substr($string,0,$length-4); + $pos = strrpos($string," "); + } + if ($pos>=$length*0.4) { + return substr($string,0,$pos)." ..."; + } + + return substr($string,0,$length-4)." ..."; + + } + + + /** + * Creates a comment indicating the generator of this feed. + * The format of this comment seems to be recognized by + * Syndic8.com. + */ + function _createGeneratorComment() { + return "\n"; + } + + + /** + * Creates a string containing all additional elements specified in + * $additionalElements. + * @param elements array an associative array containing key => value pairs + * @param indentString string a string that will be inserted before every generated line + * @return string the XML tags corresponding to $additionalElements + */ + function _createAdditionalElements($elements, $indentString="") { + $ae = ""; + if (is_array($elements)) { + foreach($elements AS $key => $value) { + $ae.= $indentString."<$key>$value\n"; + } + } + return $ae; + } + + function _createStylesheetReferences() { + $xml = ""; + if ( isset( $this->cssStyleSheet ) ) $xml .= "cssStyleSheet."\" type=\"text/css\"?>\n"; + if ( isset( $this->xslStyleSheet ) ) $xml .= "xslStyleSheet."\" type=\"text/xsl\"?>\n"; + return $xml; + } + + + /** + * Builds the feed's text. + * @abstract + * @return string the feed's complete text + */ + function createFeed( $timezone ) { + } + + /** + * Generate a filename for the feed cache file. The result will be $_SERVER["PHP_SELF"] with the extension changed to .xml. + * For example: + * + * echo $_SERVER["PHP_SELF"]."\n"; + * echo FeedCreator::_generateFilename(); + * + * would produce: + * + * /rss/latestnews.php + * latestnews.xml + * + * @return string the feed cache filename + * @since 1.4 + * @access private + */ + function _generateFilename() { + $fileInfo = pathinfo($_SERVER["PHP_SELF"]); + return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".xml"; + } + + + /** + * @since 1.4 + * @access private + */ + function _redirect($filename) { + // attention, heavily-commented-out-area + + // maybe use this in addition to file time checking + //Header("Expires: ".date("r",time()+$this->_timeout)); + + /* no caching at all, doesn't seem to work as good: + Header("Cache-Control: no-cache"); + Header("Pragma: no-cache"); + */ + + // HTTP redirect, some feed readers' simple HTTP implementations don't follow it + //Header("Location: ".$filename); + + Header("Content-Type: ".$this->contentType."; charset=".$this->encoding."; filename=".basename($filename)); + Header("Content-Disposition: inline; filename=".basename($filename)); + readfile($filename, "r"); + die(); + } + + /** + * Outputs this feed directly to the browser - for on-the-fly feed generation + * @since 1.7.2-mod + * + * still missing: proper header output - currently you have to add it manually + */ + function outputFeed( $timezone , $format='RSS2.0' ) { + echo $this->createFeed( $timezone ); + } + +} + + +/** + * FeedDate is an internal class that stores a date for a feed or feed item. + * Usually, you won't need to use this. + */ +class FeedDate { + var $unix; + + /** + * Creates a new instance of FeedDate representing a given date. + * Accepts RFC 822, ISO 8601 date formats as well as unix time stamps. + * @param mixed $dateString optional the date this FeedDate will represent. If not specified, the current date and time is used. + */ + function FeedDate($dateString="") { + if ($dateString=="") $dateString = date("r"); + + if (is_numeric($dateString)) { + $this->unix = $dateString; + return; + } + if (preg_match("~(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s+)?(\\d{1,2})\\s+([a-zA-Z]{3})\\s+(\\d{4})\\s+(\\d{2}):(\\d{2}):(\\d{2})\\s+(.*)~",$dateString,$matches)) { + $months = Array("Jan"=>1,"Feb"=>2,"Mar"=>3,"Apr"=>4,"May"=>5,"Jun"=>6,"Jul"=>7,"Aug"=>8,"Sep"=>9,"Oct"=>10,"Nov"=>11,"Dec"=>12); + $this->unix = mktime($matches[4],$matches[5],$matches[6],$months[$matches[2]],$matches[1],$matches[3]); + if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') { + $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60; + } else { + if (strlen($matches[7])==1) { + $oneHour = 3600; + $ord = ord($matches[7]); + if ($ord < ord("M")) { + $tzOffset = (ord("A") - $ord - 1) * $oneHour; + } elseif ($ord >= ord("M") AND $matches[7]!="Z") { + $tzOffset = ($ord - ord("M")) * $oneHour; + } elseif ($matches[7]=="Z") { + $tzOffset = 0; + } + } + switch ($matches[7]) { + case "UT": + case "GMT": $tzOffset = 0; + } + } + $this->unix += $tzOffset; + return; + } + if (preg_match("~(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(.*)~",$dateString,$matches)) { + $this->unix = mktime($matches[4],$matches[5],$matches[6],$matches[2],$matches[3],$matches[1]); + if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') { + $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60; + } else { + if ($matches[7]=="Z") { + $tzOffset = 0; + } + } + $this->unix += $tzOffset; + return; + } + $this->unix = 0; + } + + + + + + /** + * Gets the date stored in this FeedDate as an RFC 822 date. + * + * @return a date in RFC 822 format + */ + function rfc822() { + $d = new DateTime( "@" . $this->unix ); + + return $d->format( 'r' ); + } + + /** + * Gets the date stored in this FeedDate as an ISO 8601 date. + * + * @return a date in ISO 8601 (RFC 3339) format + */ + function iso8601() { + $d = new DateTime( "@" . $this->unix ); + + return $d->format( 'c' ); + } + + + /** + * Gets the date stored in this FeedDate as unix time stamp. + * + * @return a date as a unix time stamp + */ + function unix() { + return $this->unix; + } +} + + + + +/** + * RSSCreator091 is a FeedCreator that implements RSS 0.91 Spec, revision 3. + * + * @see http://my.netscape.com/publish/formats/rss-spec-0.91.html + * @since 1.3 + * @author Kai Blankenhorn + */ +class RSSCreator091 extends FeedCreator { + + /** + * Stores this RSS feed's version number. + * @access private + */ + var $RSSVersion; + + function RSSCreator091() { + $this->_setRSSVersion("0.91"); + $this->contentType = "application/rss+xml"; + } + + /** + * Sets this RSS feed's version number. + * @access private + */ + function _setRSSVersion($version) { + $this->RSSVersion = $version; + } + + /** + * Builds the RSS feed's text. The feed will be compliant to RDF Site Summary (RSS) 1.0. + * The feed will contain all items previously added in the same order. + * @return string the feed's complete text + */ + function createFeed( $timezone ) { + $feed = "encoding."\"?>\n"; + $feed.= $this->_createGeneratorComment(); + $feed.= $this->_createStylesheetReferences(); + $feed.= "RSSVersion."\">\n"; + $feed.= " \n"; + $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->title),100)."\n"; + $this->descriptionTruncSize = 500; + $feed.= " ".$this->getDescription()."\n"; + $feed.= " ".htmlspecialchars($this->link)."\n"; + + $now = new DateTime( '@' . time() ); + + $feed.= " ".htmlspecialchars( $now->format( 'r' ) )."\n"; + $feed.= " ".FEEDCREATOR_VERSION."\n"; + + if ($this->image!=null) { + $feed.= " \n"; + $feed.= " ".$this->image->url."\n"; + $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->image->title),100)."\n"; + $feed.= " ".htmlspecialchars($this->image->link)."\n"; + if ($this->image->width!="") { + $feed.= " ".$this->image->width."\n"; + } + if ($this->image->height!="") { + $feed.= " ".$this->image->height."\n"; + } + if ($this->image->description!="") { + $feed.= " ".$this->image->getDescription()."\n"; + } + $feed.= " \n"; + } + if ($this->language!="") { + $feed.= " ".$this->language."\n"; + } + if ($this->copyright!="") { + $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->copyright),100)."\n"; + } + if ($this->editor!="") { + $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->editor),100)."\n"; + } + if ($this->webmaster!="") { + $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->webmaster),100)."\n"; + } + if ($this->pubDate!="") { + $pubDate = new FeedDate($this->pubDate); + $feed.= " ".htmlspecialchars($pubDate->rfc822())."\n"; + } + if ($this->category!="") { + $feed.= " ".htmlspecialchars($this->category)."\n"; + } + if ($this->docs!="") { + $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->docs),500)."\n"; + } + if ($this->ttl!="") { + $feed.= " ".htmlspecialchars($this->ttl)."\n"; + } + if ($this->rating!="") { + $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->rating),500)."\n"; + } + if ($this->skipHours!="") { + $feed.= " ".htmlspecialchars($this->skipHours)."\n"; + } + if ($this->skipDays!="") { + $feed.= " ".htmlspecialchars($this->skipDays)."\n"; + } + $feed.= $this->_createAdditionalElements($this->additionalElements, " "); + + for ($i=0;$iitems);$i++) { + $feed.= " \n"; + $feed.= " ".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100)."\n"; + $feed.= " ".htmlspecialchars($this->items[$i]->link)."\n"; + $feed.= " ".$this->items[$i]->getDescription()."\n"; + + if ($this->items[$i]->author!="") { + $feed.= " ".htmlspecialchars($this->items[$i]->author)."\n"; + } + /* + // on hold + if ($this->items[$i]->source!="") { + $feed.= " ".htmlspecialchars($this->items[$i]->source)."\n"; + } + */ + if ($this->items[$i]->category!="") { + $feed.= " ".htmlspecialchars($this->items[$i]->category)."\n"; + } + if ($this->items[$i]->comments!="") { + $feed.= " ".htmlspecialchars($this->items[$i]->comments)."\n"; + } + if ($this->items[$i]->date!="") { + $itemDate = new FeedDate($this->items[$i]->date); + $feed.= " ".htmlspecialchars($itemDate->rfc822())."\n"; + } + if ($this->items[$i]->guid!="") { + $feed.= " ".htmlspecialchars($this->items[$i]->guid)."\n"; + } + $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " "); + + if ($this->RSSVersion == "2.0" && $this->items[$i]->enclosure != NULL) + { + $feed.= " items[$i]->enclosure->url; + $feed.= "\" length=\""; + $feed.= $this->items[$i]->enclosure->length; + $feed.= "\" type=\""; + $feed.= $this->items[$i]->enclosure->type; + $feed.= "\"/>\n"; + } + + + + $feed.= " \n"; + } + + $feed.= " \n"; + $feed.= "\n"; + return $feed; + } +} + + +/** + * RSSCreator20 is a FeedCreator that implements RDF Site Summary (RSS) 2.0. + * + * @see http://backend.userland.com/rss + * @since 1.3 + * @author Kai Blankenhorn + */ +class RSSCreator20 extends RSSCreator091 { + + function RSSCreator20() { + parent::_setRSSVersion("2.0"); + } + +} + + +/** + * AtomCreator10 is a FeedCreator that implements the atom specification, + * as in http://www.atomenabled.org/developers/syndication/atom-format-spec.php + * Please note that just by using AtomCreator10 you won't automatically + * produce valid atom files. For example, you have to specify either an editor + * for the feed or an author for every single feed item. + * + * Some elements have not been implemented yet. These are (incomplete list): + * author URL, item author's email and URL, item contents, alternate links, + * other link content types than text/html. Some of them may be created with + * AtomCreator10::additionalElements. + * + * @see FeedCreator#additionalElements + * @since 1.7.2-mod (modified) + * @author Mohammad Hafiz Ismail (mypapit@gmail.com) + */ + class AtomCreator10 extends FeedCreator { + + function AtomCreator10() { + $this->contentType = "application/atom+xml"; + $this->encoding = "utf-8"; + } + + function createFeed( $timezone ) { + $feed = "encoding."\"?>\n"; + $feed.= $this->_createGeneratorComment(); + $feed.= $this->_createStylesheetReferences(); + $feed.= "language!="") { + $feed.= " xml:lang=\"".$this->language."\""; + } + $feed.= ">\n"; + $feed.= " ".htmlspecialchars($this->title)."\n"; + $feed.= " ".htmlspecialchars($this->description)."\n"; + $feed.= " link)."\"/>\n"; + $feed.= " ".htmlspecialchars($this->link)."\n"; + $now = new DateTime( '@' . time() ); + $feed.= " ".htmlspecialchars( $now->format( 'c' ) )."\n"; + if ($this->editor!="") { + $feed.= " \n"; + $feed.= " ".$this->editor."\n"; + if ($this->editorEmail!="") { + $feed.= " ".$this->editorEmail."\n"; + } + $feed.= " \n"; + } + $feed.= " ".FEEDCREATOR_VERSION."\n"; + $feed.= "syndicationURL) . "\" />\n"; + $feed.= $this->_createAdditionalElements($this->additionalElements, " "); + for ($i=0;$iitems);$i++) { + $feed.= " \n"; + $feed.= " ".htmlspecialchars(strip_tags($this->items[$i]->title))."\n"; + $feed.= " items[$i]->link)."\"/>\n"; + if ($this->items[$i]->date=="") { + $this->items[$i]->date = time(); + } + $itemDate = new FeedDate($this->items[$i]->date); + $feed.= " ".htmlspecialchars($itemDate->iso8601())."\n"; + $feed.= " ".htmlspecialchars($itemDate->iso8601())."\n"; + $feed.= " ".htmlspecialchars($this->items[$i]->link)."\n"; + $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " "); + if ($this->items[$i]->author!="") { + $feed.= " \n"; + $feed.= " ".htmlspecialchars($this->items[$i]->author)."\n"; + $feed.= " \n"; + } + if ($this->items[$i]->description!="") { + $feed.= " ".htmlspecialchars($this->items[$i]->description)."\n"; + } + if ($this->items[$i]->enclosure != NULL) { + $feed.=" items[$i]->enclosure->url ."\" type=\"". $this->items[$i]->enclosure->type."\" length=\"". $this->items[$i]->enclosure->length . "\" />\n"; + } + $feed.= " \n"; + } + $feed.= "\n"; + return $feed; + } + + +} + +?> diff --git a/vendor/potsky/pimp-my-log/inc/classes/LogParser.php b/vendor/potsky/pimp-my-log/inc/classes/LogParser.php new file mode 100644 index 00000000..8c4886d4 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/classes/LogParser.php @@ -0,0 +1,464 @@ += 0 ) { + $filem = new DateTime( ); + $filem->setTimestamp( filemtime( $file_path ) ); + } else { + $filem = new DateTime( "@" . filemtime( $file_path ) ); + } + if ( ! is_null( $tz ) ) { + $filem->setTimezone( new DateTimeZone( $tz ) ); + } + $filemu = $filem->format( 'U' ); + $filem = $filem->format( 'Y/m/d H:i:s' ); + $filesize = filesize( $file_path ); + + /* + |-------------------------------------------------------------------------- + | Try to guess if the seach expression is a regexp or not + |-------------------------------------------------------------------------- + | + */ + if ($search !== '') { + $test = @preg_match( $search , 'this is just a test !' ); + $regsearch = ( $test === false ) ? false : true; + } + + /* + |-------------------------------------------------------------------------- + | Read file + |-------------------------------------------------------------------------- + | + */ + for ($x_pos = $start_offset, $ln = 0, $line = '', $still = true; $still ; $x_pos--) { + + // We have reached the beginning of file + // Validate the previous read chars by simulating a NL + if ( fseek( $fl, $x_pos, $start_from ) === -1 ) { + $still = false; + $char = "\n"; + } + + // Read a char on a log line + else { + $char = fgetc( $fl ); + } + + + + // If the read char if a NL, we need to manage the previous buffered chars as a line + if ( $char === "\n" ) { + + // Copy the log line as an utf8 line + $deal = ( mb_check_encoding( $line , 'UTF-8' ) === false ) ? utf8_encode( $line ) : $line; + + // Reset the line for future reads + $line = ''; + + // Manage the new line + if ($deal !== '') { + + // Get the last line of the file to compute the hash of this line + if ( $search_lastline === true ) { + $file_lastline = sha1( $deal ); + $search_lastline = false; + } + + // Check if we have reach the previous line in normal mode + // We don't have to manage this when loading older logs + if ( $load_more === false ) { + + // We have reach the count bytes to manage + if ( $bytes > $data_to_parse ) { + + // So the new line should be the last line of the previous time + if ( $old_lastline !== sha1( $deal ) ) { + + // This is not the case, so the file has been rotated and the new log file is bigger than the previous time + // So we have to continue computing to find the user wanted count of lines (and alert user about the file change) + $logs['notice'] = 1; + $full = true; + } + + // Ok lines are the same so just stop and return new found lines + else { + break; + } + } + } + + // Parse the new line + $log = self::parseLine( $regex , $match , $deal , $types , $tz ); + + // The line has been successfully parsed by the parser (user regex ok) + if ( is_array( $log ) ) { + + // We will get this log by default but search can exclude this log later + $return_log = true; + + // If we previously have parsed some multilines, we need now to include them + $last_field_append = ( count( $buffer ) > 0 ) ? "\n" . implode( "\n" , array_reverse( $buffer ) ) : '';; + $buffer = array(); + + foreach ($log as $key => $value) { + + // Manage multilines + if ( $key === $multiline ) { + $value .= $last_field_append; + $deal .= $last_field_append; + $log[ $key ] = $value; + } + + // Is this log excluded ? + if ( ( isset( $exclude[ $key ] ) ) && ( is_array( $exclude[ $key ] ) ) ) { + foreach ($exclude[ $key ] as $ekey => $reg) { + try { + if ( preg_match( $reg , $value ) ) { + $return_log = false; + break 2; + } + } catch ( Exception $e ) { + } + } + } + } + + // This line should be skipped because it has been excluded by user configuration + if ($return_log === false) { + $skip++; + } + + // Filter now this line by search + else { + + if ( ! empty( $search ) ) { + + // Regex + if ($regsearch) { + $return_log = preg_match( $search , $deal . $last_field_append ); + if ( $return_log === 0 ) $return_log = false; + } + + // Simple search + else { + $return_log = strpos( $deal . $last_field_append, $search ); + } + } + + // Search excludes this line + if ($return_log === false) { + $skip++; + } + + // Search includes this line + else { + $found = true; + $log[ 'pml' ] = $deal . $last_field_append; + $log[ 'pmlo' ] = ftell($fl); + $logs[ 'logs' ][] = $log; + $ln++; + } + } + } + + // The line has not been successfully parsed by the parser but multiline feature is enabled so we treat this line as a multiline + elseif ( $multiline !== '' ) { + $buffer[] = $deal; + } + + // No multiline feature and unknown line : add this line as an error + else { + $error++; + } + + // Break if we have found the wanted count of logs + if ( $ln >= $wanted_lines ) { + break; + } + } + + // Break if time computing is too high + if ( microtime( true ) - $start > $max_search_log_time ) { + $abort = true; + break; + } + + // continue directly without keeping the \n + continue; + } + + // Prepend the read char to the previous buffered chars + $line = $char . $line; + $bytes++; + } + + // We need to store this value for load more when a search is active + // The last searched line to display os certainly not the first line of the file + // So if the value of $last_parsed_offset is 1, even if the last displayed line is not at offset 0 or 1, we must disable the Load More button + $last_parsed_offset = ftell($fl); + + fclose( $fl ); + + /* + |-------------------------------------------------------------------------- + | Return + |-------------------------------------------------------------------------- + | + */ + $logs['found'] = $found; + $logs['abort'] = $abort; + $logs['regsearch'] = $regsearch; + $logs['search'] = $search; + $logs['full'] = $full; + $logs['lpo'] = $last_parsed_offset; + $logs['count'] = $ln; + $logs['bytes'] = $bytes; + $logs['skiplines'] = $skip; + $logs['errorlines'] = $error; + $logs['fingerprint'] = md5( serialize( @$logs['logs'] ) ); // Used to avoid notification on full refresh when nothing has finally changed + $logs['lastline'] = $file_lastline; + $logs['duration'] = (int) ( ( microtime( true ) - $start ) * 1000 ); + $logs['filesize'] = $filesize; + $logs['filemodif'] = $filem; + $logs['filemodifu'] = $filemu; + + return $logs; + } + + + + + + /** + * Read lines from the bottom of giver file + * + * @param string $file the file path + * @param integer $count the count of wanted lines + * + * @return array an array of read lines or false of fiel error + */ + public static function getLinesFromBottom( $file , $count = 1 ) + { + $fl = @fopen( $file , "r" ); + $lines = array(); + $bytes = 0; + + if ( $fl === false ) return false; + + $count = max( 1 , (int) $count ); + + for ($x_pos = 0, $ln = 0, $line = '', $still = true; $still ; $x_pos--) { + + if ( fseek( $fl, $x_pos, SEEK_END ) === -1 ) { + $still = false; + $char = "\n"; + } + else { + $char = fgetc( $fl ); + } + + if ($char === "\n") { + + $deal = utf8_encode( $line ); + $line = ''; + + if ($deal !== '') { + $lines[] = $deal; + $count--; + if ( $count === 0 ) $still = false; + } + + // continue directly without keeping the \n + continue; + } + $line = $char . $line; + $bytes++; + } + + fclose( $fl ); + + return $lines; + } + + + /** + * A line of log parser + * + * @param string $regex The regex which describes the user log format + * @param array $match An array which links internal tokens to regex matches + * @param string $log The text log + * @param string $types A array of types for fields + * @param string $tz A time zone identifier + * + * @return mixed An array where keys are internal tokens and values the corresponding values extracted from the log file. Or false if line is not matchable. + */ + public static function parseLine( $regex , $match , $log , $types , $tz = NULL ) + { + // If line is non matchable, return + preg_match_all( $regex , $log , $out, PREG_PATTERN_ORDER ); + if ( @count( $out[0] ) === 0 ) { + return false; + } + + $result = array(); + $timestamp = 0; + + foreach ($match as $token => $key) { + + $type = ( isset ( $types[ $token ] ) ) ? $types[ $token ] : 'txt'; + + if ( substr( $type , 0 , 4 ) === 'date' ) + { + + // Date is an array description with keys ( 'Y' : 5 , 'M' : 2 , ... ) + if ( is_array( $key ) && ( is_assoc( $key ) ) ) + { + $newdate = array(); + foreach ($key as $k => $v) + { + $newdate[ $k ] = @$out[ $v ][ 0 ]; + } + + if ( isset( $newdate['U'] ) ) + { + $str = date( 'Y/m/d H:i:s' , $newdate['U'] ); + } + else if ( isset( $newdate['r'] ) ) + { + $str = date( 'Y/m/d H:i:s' , $newdate['r'] ); + } + else if ( isset( $newdate['c'] ) ) + { + $str = date( 'Y/m/d H:i:s' , $newdate['c'] ); + } + else if ( isset( $newdate['M'] ) ) + { + $str = trim( $newdate['M'] . ' ' . $newdate['d'] . ' ' . $newdate['H'] . ':' . $newdate['i'] . ':' . $newdate['s'] . ' ' . $newdate['Y'] . ' ' . @$newdate['z'] ); + } + elseif ( isset( $newdate['m'] ) ) + { + $str = trim( $newdate['Y'] . '/' . $newdate['m'] . '/' . $newdate['d'] . ' ' . $newdate['H'] . ':' . $newdate['i'] . ':' . $newdate['s'] . ' ' . @$newdate['z'] ); + } + } + + // Date is an array description without keys ( 2 , ':' , 3 , '-' , ... ) + else if ( is_array( $key ) ) + { + $str = ''; + foreach ($key as $v) + { + $str .= ( is_string( $v ) ) ? $v : @$out[ $v ][0]; + } + } + + else + { + $str = @$out[ $key ][0]; + } + + // remove part next to the last / + $dateformat = ( substr( $type , 0 , 5 ) === 'date:' ) ? substr( $type , 5 ) : 'Y/m/d H:i:s'; + + if ( ( $p = strrpos( $dateformat , '/' ) ) !== false ) + { + $dateformat = substr( $dateformat , 0 , $p ); + } + + if ( ( $timestamp = strtotime( $str ) ) === false ) + { + $formatted_date = "ERROR ! Unable to convert this string to date : $str"; + $timestamp = 0; + } + + else + { + + if ( version_compare( PHP_VERSION , '5.3.0' ) >= 0 ) + { + $date = new DateTime(); + $date->setTimestamp( $timestamp ); + } + else + { + $date = new DateTime( "@" . $timestamp ); + } + + if ( ! is_null( $tz ) ) + { + $date->setTimezone( new DateTimeZone( $tz ) ); + } + + $formatted_date = $date->format( $dateformat ); + $timestamp = (int)$date->format('U'); + } + + $result[ $token ] = $formatted_date; + } + // Array description without keys ( 2 , ':' , 3 , '-' , ... ) + else if ( is_array( $key ) ) { + $r = ''; + foreach ($key as $v) { + $r .= ( is_string( $v ) ) ? $v : @$out[ $v ][0]; + } + $result[ $token ] = $r; + } else { + $result[ $token ] = @$out[ $key ][0]; + } + } + + if ( $timestamp > 0 ) { + $result[ 'pmld' ] = $timestamp; + } + + return $result; + } +} diff --git a/vendor/potsky/pimp-my-log/inc/classes/Sentinel.php b/vendor/potsky/pimp-my-log/inc/classes/Sentinel.php new file mode 100644 index 00000000..2b0c9019 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/classes/Sentinel.php @@ -0,0 +1,935 @@ + date('U'), + 'security' => self::generateSecurityToken(), + 'anonymous' => array(), + 'users' => array(), + ); + + self::save(); + + return true; + } + + /** + * Delete a user + * + * @param string $username the username to delete + * + * @return array the deleted user or false if not exists + */ + public static function deleteUser($username) + { + if ( ! self::userExists( $username ) ) return false; + + $deleted_user = self::$auth['users'][ $username ]; + unset( self::$auth['users'][ $username ] ); + + return $deleted_user; + } + + /** + * Destroy the authentication file + * + * @return boolean success or not + */ + public static function destroy() + { + self::sessionDestroy(); + + if ( self::isAuthSet() ) { + return @unlink( self::$authFile ); + } + + return true; + } + + /** + * Get the path of the authentication file + * + * @return string the fila path + */ + public static function getAuthFilePath() + { + return PML_CONFIG_BASE . DIRECTORY_SEPARATOR . AUTH_CONFIGURATION_FILE; + } + + /** + * Get the local ip address of the current client according to proxy and more... + * + * @return string an ip address + */ + public static function getClientIp() + { + $ip = ''; + if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { + $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; + } elseif ( isset( $_SERVER['HTTP_X_FORWARDED'] ) ) { + $ip = $_SERVER['HTTP_X_FORWARDED']; + } elseif ( isset( $_SERVER['HTTP_FORWARDED_FOR'] ) ) { + $ip = $_SERVER['HTTP_FORWARDED_FOR']; + } elseif ( isset( $_SERVER['HTTP_FORWARDED'] ) ) { + $ip = $_SERVER['HTTP_FORWARDED']; + } elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) { + $ip = $_SERVER['REMOTE_ADDR']; + } + + return $ip; + } + + /** + * Get the current logged in user or null + * + * @return string the current username or null + */ + public static function getCurrentUsername() + { + if ( is_array( self::$api_session ) ) { + $auth = self::$api_session; + } else { + $auth = self::sessionRead(); + } + return ( isset( $auth['username'] ) ) ? $auth['username'] : null; + } + + /** + * Return the log array + * + * @return array data + */ + public static function getLogs() + { + if ( isset( self::$auth['logs'] ) ) { + return self::$auth['logs']; + } + + return array(); + } + + /** + * Return the data of a user + * + * @param string $username the username + * + * @return array data or null if not exists + */ + public static function getUser($username = null) + { + if ( is_null( $username ) ) $username = self::getCurrentUsername(); + + if ( self::userExists( $username ) ) { + return self::$auth['users'][ $username ]; + } + + return null; + } + + /** + * Find a user from its access token + * + * @param string $accesstoken the access token + * + * @return string username or null + */ + public static function getUsernameFromAccessToken( $accesstoken ) { + $users = self::getUsers(); + foreach( $users as $username => $user ) { + if ( $user['at'] === $accesstoken ) { + return $username; + } + } + return null; + } + + /** + * Return the array of users + * + * @return array the users + */ + public static function getUsers() + { + if ( ! is_array( self::$auth ) ) throw new Exception( 'Authentication not initialized' ); + + if ( isset( self::$auth['users'] ) ) { + return self::$auth['users']; + } else { + return array(); + } + } + + /** + * Return the users count + * + * @return integer the user count + */ + public static function getUsersCount() + { + return count( self::getUsers() ); + } + + /** + * Set file, etc... + * + * @return boolean success or not + */ + public static function init() + { + self::$authFile = self::getAuthFilePath(); + if ( self::isAuthSet() ) { + self::reload(); + } + + return true; + } + + /** + * Tell if a user is admin or not. Being admin is having role "admin" + * + * @param string $username a username or current logged in user if not set + * + * @return boolean + */ + public static function isAdmin($username = null) + { + return self::userHasRole( 'admin' , $username ); + } + + /** + * Tell if at least one log file is accessible anonymously + * + * The $files parameter is optional. If given, it will check if all anonymous files still exist. + * If they do not exist, they must be erased and they do not count for this check. + * + * @param array $files The files configuration + * + * @return boolean + */ + public static function isAnonymousEnabled( $files = false ) + { + if ( ! isset( self::$auth['anonymous'] ) ) return false; + if ( ! is_array( self::$auth['anonymous'] ) ) return false; + + if ( $files === false ) { + return ( count( self::$auth['anonymous'] ) > 0 ); + } + + $found = 0; + foreach( self::$auth['anonymous'] as $file ) { + if ( isset( $files[ $file ] ) ) $found++; + } + + return ( $found > 0 ); + } + + /** + * Tell whether authentication file exists or not + * + * @return boolean [description] + */ + public static function isAuthSet() + { + return file_exists( self::$authFile ); + } + + /** + * Tell if a log file is accessible anonymously + * + * @param string $log the fileid + * + * @return boolean + */ + public static function isLogAnonymous( $log ) + { + return ( in_array( $log , self::$auth['anonymous'] ) ); + } + + /** + * Tell whether a signature is valid for given values or not + * + * @param string $given_sign the provided signature + * @param array $values an array of values to certify + * @param string $username the user who has signed values or null for an instance signature + * + * @return boolean ok or not + */ + public static function isSignValid( $given_sign , $values , $username = null ) { + return ( self::sign( $values , $username ) === $given_sign ); + } + + /** + * Tell whether a password is valid or not + * + * @param string $username the username + * @param string $password the password + * + * @return boolean + */ + public static function isValidPassword($username , $password) + { + if ( ! self::userExists( $username ) ) return false; + $compute = self::getPasswordHash($username , $password); + + return ( $compute === self::$auth['users'][$username]['pwd'] ); + } + + /** + * Log an action + * + * @param string $action the action text + * @param string $username the username or null for the current logged in user + * @param int $timestamp the action timestamp + * @param string $ip the IP address + * @param string $useragent the user agent + * + * @return bool true + */ + public static function log($action , $username = null , $timestamp = null , $ip = null , $useragent = null) + { + if ( ! is_array( self::$auth ) ) throw new Exception( 'Authentication not initialized' ); + + if ( ! isset( self::$auth['logs'] ) ) { + self::$auth['logs'] = array(); + } + + if ( is_null( $username ) ) $username = self::getCurrentUsername(); + if ( is_null( $timestamp ) ) $timestamp = date("U"); + if ( is_null( $ip ) ) $ip = self::getClientIp(); + if ( is_null( $useragent ) ) $useragent = ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) ? $_SERVER['HTTP_USER_AGENT'] : ''; + + self::$auth['logs'] = array_slice( array_merge( array( array( $action , $username , $timestamp , $ip , $useragent ) ) , self::$auth['logs'] ) , 0 , abs( (int) AUTH_LOG_FILE_COUNT ) ); + + return true; + } + + /** + * Release the database file + * + * @return boolean + */ + public static function release() + { + if ( is_null( self::$authFileP ) ) return; + $a = @flock( self::$authFileP , LOCK_UN ); + @fclose( self::$authFileP ); + self::$authFileP = null; + self::$currentlylocked = false; + } + + /** + * Just get the auth file and load it in the $auth variable + * + * @return boolean success or not + */ + public static function reload() + { + $content = preg_replace('/^.+\n/', '', self::read() ); + $array = json_decode( $content , true ); + if ( is_null( $array ) ) { + return false; + } + self::$auth = $array; + + return true; + } + + /** + * Save modifications on disk + * + * @return boolean + */ + public static function save() + { + self::lock(); + if ( is_null( self::$authFileP ) ) throw new Exception( 'No lock has been requested' ); + + $file = '' . "\n"; + $file.= json_encode( self::$auth ); + + self::write( $file ); + + return true; + } + + /** + * Set an admin + * + * @param string $username username + * @param string $password password + * @param array $logs an array of credentials for log files + * + * @return boolean true + */ + public static function setAdmin($username , $password = null , $logs = null) + { + return self::setUser( $username , $password , $roles = array('admin') , $logs ); + } + + /** + * Set a log anonymous or not + * + * @param string $log the fileid + * @param boolean $anonymous true or false + * + * @return boolean true + */ + public static function setLogAnonymous( $log , $anonymous ) + { + if ( ! is_array( self::$auth ) ) throw new Exception( 'Authentication not initialized' ); + + $anon = array(); + + if ( isset( self::$auth['anonymous'] ) ) { + $anon = self::$auth['anonymous']; + } + + if ( $anonymous === true ) { + $anon[] = $log; + $anon = array_unique( $anon ); + } + else { + $anon = array_diff( $anon, array( $log ) ); + } + + self::$auth['anonymous'] = $anon; + + return true; + } + + /** + * Set a user + * + * @param string $username username + * @param string $password password + * @param array $roles an array of global roles + * @param array $logs an array of credentials for log files + * @param boolean $regeneratetokens whether access tokens should be regenerated or not + * + * @return boolean true + */ + public static function setUser($username , $password = null , $roles = null , $logs = null , $regeneratetokens = false ) + { + if ( ! is_array( self::$auth ) ) throw new Exception( 'Authentication not initialized' ); + + if ( isset( self::$auth['users'][ $username ] ) ) { + $user = self::$auth['users'][ $username ]; + } else { + $user = array( + 'roles' => array('user'), + 'pwd' => '', + 'logs' => array(), + 'cd' => date('U'), + 'cb' => self::getCurrentUsername(), + 'at' => self::generateSecurityToken(32), // Access token + 'hp' => self::generateSecurityToken(16), // Presalt for this user, postsalt is the instance security token + ); + } + if ( $regeneratetokens === true ) { + $user['at'] = self::generateSecurityToken(32); + $user['hp'] = self::generateSecurityToken(16); + } + if ( ! is_null( $password ) ) $user['pwd'] = self::getPasswordHash( $username , $password ); + if ( is_array( $logs ) ) $user['logs'] = $logs; + if ( is_array( $roles ) ) $user['roles'] = $roles; + + self::$auth['users'][ $username ] = $user; + + return true; + } + + /** + * Sign values + * + * @param array $values an array of key values to sign + * @param string $username the user who signs values or null for an instance signature + * + * @return string the signature of false if a problem occurs + */ + public static function sign( $values , $username = null ) + { + if ( ! is_array( self::$auth ) ) throw new Exception( 'Authentication not initialized' ); + + $presalt = self::$auth['security']; + $values = json_encode( $values ); + $postsalt = ''; + + if ( ! is_null( $username ) ) { + $user = self::getUser( $username ); + $postsalt = $user['hp']; + } + + return sha1( $presalt . $values . $postsalt ); + } + + /** + * Sign in user + * + * @param string $username the user to sign in + * @param string $password its password + * + * @return array the user informations or false if failed + */ + public static function signIn($username , $password) + { + $ip = self::getClientIp(); + $ua = ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) ? $_SERVER['HTTP_USER_AGENT'] : ''; + $ts = date("U"); + + if ( self::isValidPassword($username , $password) ) { + + self::sessionWrite( array( 'username' => $username ) ); + self::reload(); + self::$auth['users'][ $username ]['logincount'] = (int) @self::$auth['users'][ $username ]['logincount'] + 1; + self::$auth['users'][ $username ]['lastlogin'] = array( + 'ip' => $ip, + 'ua' => $ua, + 'ts' => $ts + ); + self::log( 'signin' , $username , $ts , $ip , $ua ); + self::save(); + + return self::$auth['users'][ $username ]; + } + + self::log( 'signinerr' , $username , $ts , $ip , $ua ); + self::save(); + + return false; + } + + /** + * Sign in as a user + * + * @param string $username the user to sign in + * + * @return array the user informations or false if failed + */ + public static function signInAs($username) + { + $ip = self::getClientIp(); + $ua = ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) ? $_SERVER['HTTP_USER_AGENT'] : ''; + $ts = date("U"); + $cu = self::getCurrentUsername(); + + self::sessionWrite( array( 'username' => $username ) ); + self::reload(); + + self::log( 'signinas ' . $username , $cu, $ts , $ip , $ua ); + self::save(); + + return self::$auth['users'][ $username ]; + } + + /** + * Sign in user with its access token + * The signin is only available for this call, no session + * + * @param string $accesstoken + * + * @return array the user informations or false if failed + */ + public static function signInWithAccessToken( $accesstoken ) + { + $username = self::getUsernameFromAccessToken( $accesstoken ); + + if ( is_null( $username ) ) return false; + + $ip = self::getClientIp(); + $ua = ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) ? $_SERVER['HTTP_USER_AGENT'] : ''; + $ts = date("U"); + $cu = get_current_url( true ); + + self::$api_session = array( 'username' => $username ); + + self::$auth['users'][ $username ]['api_logincount'] = (int) @self::$auth['users'][ $username ]['api_logincount'] + 1; + self::$auth['users'][ $username ]['api_lastlogin'] = array( + 'ip' => $ip, + 'ua' => $ua, + 'ts' => $ts + ); + if ( ! is_null( $cu ) ) self::$auth['users'][ $username ]['api_lastlogin']['ur'] = $cu; + self::save(); + + return self::$auth['users'][ $username ]; + } + + /** + * Sign out the current user and return its username + * + * @return string the logged out username + */ + public static function signOut() + { + $username = self::getCurrentUsername(); + + if ( ! is_null( $username ) ) { + $ip = self::getClientIp(); + $ua = ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) ? $_SERVER['HTTP_USER_AGENT'] : ''; + $ts = date("U"); + + self::log( 'signout' , $username , $ts , $ip , $ua ); + self::save(); + + self::sessionDestroy(); + } + + return $username; + } + + /** + * Tell if a user has an access on a log file + * + * @param string $log the fileid + * @param string $action the name of the action + * @param string $value the value for this action + * @param string $username the username + * + * @return boolean + */ + public static function userCanOnLogs( $log , $action , $value , $username = null) + { + if ( is_null( $username ) ) $username = self::getCurrentUsername(); + if ( is_null( $username ) ) return false; + if ( ! self::userExists( $username ) ) return false; + if ( in_array( 'admin' , self::$auth['users'][ $username ]['roles'] ) ) return true; + if ( ! isset( self::$auth['users'][ $username ]['logs'][ $log ][ $action ] ) ) return false; + return ( self::$auth['users'][ $username ]['logs'][ $log ][ $action ] === $value ); + } + + /** + * Tell whether user exists or not + * + * @param string $username the username + * + * @return boolean + */ + public static function userExists($username) + { + if ( ! is_array( self::$auth ) ) throw new Exception( 'Authentication not initialized' ); + return ( isset( self::$auth['users'][ $username ] ) ); + } + + /** + * Tell if a user has a role or not + * + * @param string $username a username or current logged in user if not set + * + * @return boolean + */ + public static function userHasRole($role , $username = null) + { + if ( is_null( $username ) ) $username = self::getCurrentUsername(); + if ( is_null( $username ) ) return false; + if ( ! self::userExists( $username ) ) return false; + if ( in_array( 'admin' , self::$auth['users'][ $username ]['roles'] ) ) return true; + return ( in_array( $role , self::$auth['users'][ $username ]['roles'] ) ); + } + + /** + * Generate a security token + * + * @return string the security token + */ + private static function generateSecurityToken( $len = 64 ) + { + return mt_rand_str( $len ); + } + + /** + * Get the date when authentication file has been ganarated + * + * @return integer the generated date + */ + private static function getGenerated() + { + if ( ! is_array( self::$auth ) ) throw new Exception( 'Authentication not initialized' ); + return self::$auth['generated']; + } + + /** + * Generate a hash from a username and a password + * + * @param string $username username + * @param string $password password + * + * @return string the hash + */ + private static function getPasswordHash($username , $password) + { + return sha1( self::getSecurityToken() . $username . self::getGenerated() . $password ); + } + + /** + * Get the security token + * + * @return string the security token + */ + private static function getSecurityToken() + { + if ( ! is_array( self::$auth ) ) throw new Exception( 'Authentication not initialized' ); + return self::$auth['security']; + } + + /** + * Lock the database file + * + * @return boolean + */ + private static function lock() + { + if (self::$currentlylocked === false) { + self::$authFileP = fopen( self::$authFile , "a+" ); + if ( flock( self::$authFileP , LOCK_EX ) ) { + self::$currentlylocked = true; + } else { + throw new Exception( 'Unable to lock file' ); + } + } + + return true; + } + + /** + * Read the database + * + * @return string the database content + */ + private static function read() + { + self::lock(); + if ( is_null( self::$authFileP ) ) throw new Exception( 'No lock has been requested' ); + return stream_get_contents( self::$authFileP , -1 , 0 ); + } + + /** + * Destroy a session + * + * Need a wrapper to manage session with phpunit + * + * @return void + */ + private static function sessionDestroy() + { + // Web + if ( isset( $_SERVER['SERVER_PROTOCOL'] ) ) { + Session::start(); + unset( $_SESSION['auth'] ); + $_SESSION['auth'] = array(); + Session::write_close(); + } + // CLI + else { + $value = array(); + file_put_contents( '_cli_fake_session' , json_encode( $value ) ); + } + } + + /** + * Read a session + * + * @return array the array with all auth informations + */ + private static function sessionRead() + { + // Web + if ( isset( $_SERVER['SERVER_PROTOCOL'] ) ) { + + if ( isset( $_SESSION['auth'] ) ) return $_SESSION['auth']; + + Session::start(); + + return ( isset( $_SESSION['auth'] ) ) ? $_SESSION['auth'] : array(); + } + // CLI + else { + $json = @file_get_contents( '_cli_fake_session' ); + $value = json_decode( $json , true ); + + return ( is_array( $value ) ) ? $value : array(); + } + } + + + /** + * Write the session array + * + * @param array $value the array to store + * + * @return void + */ + private static function sessionWrite($value) + { + // Web + if ( isset( $_SERVER['SERVER_PROTOCOL'] ) ) { + Session::start(); + $_SESSION[ 'auth' ] = $value; + Session::write_close(); + } + // CLI + else { + file_put_contents( '_cli_fake_session' , json_encode( $value ) ); + } + } + + /** + * Write the database + * + * @param string $content the database content + * + * @return boolean + */ + private static function write($content) + { + self::lock(); + if ( is_null( self::$authFileP ) ) throw new Exception( 'No lock has been requested' ); + ftruncate( self::$authFileP , 0 ); + fwrite( self::$authFileP , $content ); + + return fflush( self::$authFileP ); + } + +} + +/** + * On shutdown, release the lock ! + * + * @return void + */ +function pml_sentinel_shutdown() +{ + Sentinel::release(); +} + +register_shutdown_function('pml_sentinel_shutdown'); + +Sentinel::init(); diff --git a/vendor/potsky/pimp-my-log/inc/classes/Session.php b/vendor/potsky/pimp-my-log/inc/classes/Session.php new file mode 100644 index 00000000..19327ba8 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/classes/Session.php @@ -0,0 +1,69 @@ + false, + 'next' => false, + ); + + $config_file = PML_CONFIG_BASE . DIRECTORY_SEPARATOR . CONFIG_FILE_NAME; + $config_file_temp = PML_CONFIG_BASE . DIRECTORY_SEPARATOR . CONFIG_FILE_TEMP; + + try { + + /* + |-------------------------------------------------------------------------- + | Alert user if he has not configured the date.timezone setting + |-------------------------------------------------------------------------- + | + */ + set_error_handler( function($errno, $errstr, $errfile, $errline, array $errcontext) { throw new ErrorException($errstr, 0, $errno, $errfile, $errline); }); + $a = date('U'); + restore_error_handler(); + + + switch ( $_POST['s'] ) { + + /* + |-------------------------------------------------------------------------- + | Auth 1. Ask for authentication + |-------------------------------------------------------------------------- + | + */ + case 'auth': + + // Destroy a previous session + Sentinel::destroy(); + + $return[ 'notice' ] = + '

' . __( 'Setup admin account') . '

' + . '
' + . __('You can use Pimp my Log without authentication. You will be able to add this feature later from the debugger web interface.') . '
' + . '
' + . __('Setup an admin account will let you create other users later and give them access to certain log files only.') . '
' + . '
' + . __( 'Do you want to create an admin account now?') . '
' + . '
' + . '
' + . '' . __('Create an admin account') . '' + . '  ' + . '' . __('No') . ''; + + break; + + + /* + |-------------------------------------------------------------------------- + | Auth 2. Touch auth file + |-------------------------------------------------------------------------- + | + */ + case 'authtouch': + + if ( Sentinel::isAuthSet() === true ) { + $path = Sentinel::getAuthFilePath(); + $return[ 'notice' ] = + sprintf( __( 'File %s already exists!') , AUTH_CONFIGURATION_FILE ) + . '

' + . __( 'Please remove it from the root directory:' ) + . '
' + . '
' . 'mv \'' . $path . '\' \'' . $path . '.bck\'
' + . '
' . __('Copy to clipboard') . '
' + . '
'; + $return[ 'reload' ] = true; + } + + // Auth file is touched so return the form to ask yes or no + else if ( Sentinel::create() === true ) { + $return[ 'authform' ] = + '

' . __( 'Setup admin account') . '

' + . '
' + . __( 'Please choose a username and a password for the admin account.') + . '

' + . '
' + . '
' + . '
' + . '
+ + +
' + . '
' + . '
' + . '
' + . '
+ + +
' + . '
' + . '
' + . '
' + . '
+ + +
' + . '
' + . '
' + . '

' + . '' + . '
' + ; + } + + // Unable to touch, return an error + else { + $return[ 'notice' ] = + '
' + . sprintf( __( 'Unable to create file %s') , AUTH_CONFIGURATION_FILE ) + . '
' + . __( 'Please give temporary write access to the root directory:' ) + . '
' + . '
' . 'chmod 777 ' . dirname( dirname( __FILE__ ) ) . '
' + . '
' . __('Copy to clipboard') . '
' + . '
'; + $return[ 'reload' ] = true; + } + + break; + + + /* + |-------------------------------------------------------------------------- + | Auth 3. Save data + |-------------------------------------------------------------------------- + | + */ + case 'authsave': + if ( ( mb_strlen( $_POST['u'] ) > 0 ) && ( mb_strlen( $_POST['p'] ) >= 6 ) ) { + Sentinel::setAdmin( $_POST['u'] , $_POST['p'] ); + $return[ 'notice' ] = Sentinel::save(); + } + break; + + + /* + |-------------------------------------------------------------------------- + | Logs 1. Check if $config_file already exists + |-------------------------------------------------------------------------- + | + */ + case 'exist': + $config_file_name = get_config_file_name(); + if ( ! is_null( $config_file_name ) ) { + $return[ 'notice' ] = + __( 'Please remove it manually if you want me to create it:' ) + . '

' + . '
' + . '
' . 'rm \'' . get_config_file_path() . '\'
' + . '
' . __('Copy to clipboard') . '
' + . '
'; + throw new Exception( sprintf( __( 'File %s already exists.') , $config_file_name ) ); + } + break; + + + /* + |-------------------------------------------------------------------------- + | Logs 2. Try to touch $config_file_temp + |-------------------------------------------------------------------------- + | + */ + case 'touch': + if ( ! @touch( $config_file_temp ) ) { + $return[ 'notice' ] = + '
' + . sprintf( __( 'Unable to create file %s') , $config_file_temp) + . '
' + . __( 'Please give temporary write access to the root directory:' ) + . '
' + . '
' . 'chmod 777 ' . dirname( dirname( __FILE__ ) ) . '
' + . '
' . __('Copy to clipboard') . '
' + . '
'; + $return[ 'reload' ] = true; + } + break; + + + /* + |-------------------------------------------------------------------------- + | Logs 3. Return a list of software that the user could install + |-------------------------------------------------------------------------- + | + */ + case 'soft' : + $return[ 'notice' ] = '

' . __( 'Choose softwares to search log files for') . '

'; + $return[ 'notice' ].= '
'; + $return[ 'notice' ].= '
'; + $return[ 'next' ] = true; + $return[ 'sofn' ] = count( $softwares_all ); + $return[ 'soft' ] = $softwares_all; + break; + + + /* + |-------------------------------------------------------------------------- + | Logs 4. Check for configuration files + |-------------------------------------------------------------------------- + | + */ + case 'find': + $software = $_POST['so']; + $softuser = array(); + $tried = array(); + $found = 0; + $software_paths = '../cfg/' . $software . '.paths.php'; + $software_pathsuser = '../cfg/' . $software . '.paths.user.php'; + $return[ 'notice' ] = '

' . sprintf( __( 'Software %s') , $softwares_all[ $software ]['name'] ) . '

'; + + if ( file_exists( $software_pathsuser ) ) { + include $software_pathsuser; + $software_paths = $software_pathsuser; + } + else if ( file_exists( $software_paths ) ) { + include $software_paths; + } + else { + throw new Exception( sprintf( __( 'Files %s or %s do not exist. Please review your software configuration.') , $software_paths , $software_pathsuser ) ) ; + } + + foreach ( $paths as $userpath ) { + + $gpaths = glob( $userpath , GLOB_MARK | GLOB_NOCHECK | GLOB_ONLYDIR ); + + if ( is_array( $gpaths ) ) { + + foreach( $gpaths as $path ) { + + $tried[ $software ][ $path ] = false; + + if ( is_dir( $path ) ) { + + $found = 1; + $tried[ $software ][ $path ] = true; + + foreach ( $files as $type => $fpaths) { + + foreach ( $fpaths as $userfile ) { + + $gfiles = glob( $path . $userfile , GLOB_MARK | GLOB_NOCHECK ); + + if ( is_array( $gfiles ) ) { + + foreach( $gfiles as $file ) { + + $file = basename( $file ); + $allfiles[ $file ] = $file; + + if ( ( is_readable( $path . $file ) ) && ( ! is_dir( $path . $file ) ) ) { + + if ( ! is_array( $tried[ $software ][ $path ] ) ) { + $tried[ $software ][ $path ] = array(); + } + + $tried[ $software ][ $path ][ $type ][] = $file; + $found = 2; + } + + } + } + } + } + } + } + } + } + + $softuser[ $software ] = array(); + foreach ( $files as $type => $fpaths) { + $softuser[ $software ][ $type ] = 1; + } + + $return[ 'files' ] = $tried; + $return[ 'found' ] = $found; + + if ( $found == 0 ) { + $return[ 'notice' ].= '
' . __( 'Unable to find any directory.') . '
'; + $return[ 'notice' ].= __( 'Check in the following list if these directories are readable by the webserver user and refresh this page' ); + } + else if ( $found == 1 ) { + $return[ 'notice' ].= '
' . __( 'Directories are available but unable to find files inside.') . '
'; + $return[ 'notice' ].= __( 'Check in the following list if these directories contain readable files by the webserver user and refresh this page.' ) . ' '; + $return[ 'notice' ].= __( 'Don\'t forget that to read a file, ALL parent directories have to be accessible too!' ) . ' '; + + $allfiles = array(); + foreach ( $files as $type => $fpaths) { + foreach ( $fpaths as $file ) { + $allfiles[] = $file; + } + } + $allfiles = '' . json_encode( $allfiles ) . ''; + + $return[ 'notice' ].= sprintf( __( 'These files have been checked in all paths: %s ' ) , $allfiles ); + } + else { + $return[ 'notice' ].= '
' . __( 'Log files have been found!') . '
'; + $return[ 'notice' ].= __( 'Check in the following list which files you want to configure.' ); + $return[ 'notice' ].= '
'; + $return[ 'notice' ].= __( 'If files or directories are missing, verify that they are readable by the webserver user' ); + } + + $user = get_server_user(); + $return[ 'notice' ] .= ( $user == '' ) + ? ' (' . __( 'unable to detect web server user') . '):' + : ' (' . sprintf( __( 'web server user seems to be %s') , $user ) . '):'; + + $return[ 'notice' ] .= '

'; + $return[ 'notice' ] .= '
'; + $return[ 'notice' ] .= __('You can also type log files path in the text area below separated by coma:'); + $return[ 'notice' ] .= '

'; + $return[ 'notice' ] .= '
'; + foreach( $softuser as $software => $types ) { + foreach ( $types as $type => $dumb ) { + $return[ 'notice' ] .= ''; + } + } + $return[ 'notice' ] .= '
' . __( 'Type' ) . '' . __( 'Custom paths' ) . '
' . $type . '
'; + + $return[ 'next' ] = true; + $return[ 'reload' ] = true; + break; + + + /* + |-------------------------------------------------------------------------- + | Logs 5. Check for user files + |-------------------------------------------------------------------------- + | + */ + case 'check': + $user_files = $_POST['uf']; + if ( ! is_array( $user_files ) ) { + throw new Exception( __( 'Unknown error') ) ; + } + + $found = array(); + $notfound = array(); + foreach ( $user_files as $files ) { + $software = $files['s']; + $type = $files['t']; + $file = realpath( $files['f'] ); + if ( ( is_readable( $file ) ) && ( ! is_dir( $file ) ) ) { + $found[] = $files; + } + else { + $notfound[] = $files['f']; + } + } + + + if ( count( $notfound ) > 0 ) { + $return[ 'notice' ] = __( 'Custom files below are not readable, please remove them or verify that they are readable by the webserver user' ); + $user = get_server_user(); + $return[ 'notice' ] .= ( $user == '' ) + ? ' (' . __( 'unable to detect web server user') . '):' + : ' (' . sprintf( __( 'web server user seems to be %s') , $user ) . '):
    '; + foreach( $notfound as $file ) { + $return[ 'notice' ] .= '
  • ' . $file . '
  • '; + } + $return[ 'notice' ] .= '
'; + $return[ 'next' ] = true; + } + else { + $return[ 'found' ] = $found; + } + + break; + + + /* + |-------------------------------------------------------------------------- + | Logs 6. Check for user files + |-------------------------------------------------------------------------- + | + */ + case 'configure': + $logs = $_POST['l']; + + if ( ! is_array( $logs ) ) { + throw new Exception( __( 'Unknown error') ) ; + } + + if ( count( $logs ) == 0 ) { + throw new Exception( __( 'Unknown error') ) ; + } + + // Configure all logs + $counter = 0; + $config_files = array(); + foreach( $logs as $log ) { + $type = $log['t']; + $software = $log['s']; + $file = $log['f']; + $get_config = $software . '_get_config'; + $counter = $counter + 1; + $config = '../cfg/' . $software . '.config.php'; + $configuser = PML_CONFIG_BASE . DIRECTORY_SEPARATOR . 'cfg' . DIRECTORY_SEPARATOR . $software . '.config.user.php'; + + if ( file_exists( $configuser ) ) { + include_once $configuser; + $config = $configuser; + } + else if ( file_exists( $config ) ) { + include_once $config; + } + else { + @unlink( $config_file_temp ); + throw new Exception( sprintf( __( 'Files %s or %s do not exist. Please review your software configuration.') , $config , $configuser ) ) ; + } + + if ( function_exists( $get_config ) ) { + $config_files[] = call_user_func( $get_config , $type , $file , $software , $counter ); + } + else { + @unlink( $config_file_temp ); + throw new Exception( sprintf( __( 'File %s does not define function %s. Please review your software configuration.') , $config , $get_config ) ) ; + } + } + + // Create and install file + if ( count( $config_files ) > 0 ) { + $base = file_get_contents( '../cfg/pimpmylog.config.php' ); + file_put_contents( $config_file_temp , str_replace( '"FILES":"FILES"' , implode( ",\n" , $config_files ) , $base ) ); + rename( $config_file_temp , $config_file ); + chmod( $config_file , CONFIG_FILE_MODE ); + $return[ 'next' ] = true; + } + else { + throw new Exception( __( 'No configuration found for softwares!') ) ; + } + break; + + + /* + |-------------------------------------------------------------------------- + | Unknown action + |-------------------------------------------------------------------------- + | + */ + default: + throw new Exception( __( 'Unknown action, abort.' ) ); + break; + } + + } catch (Exception $e) { + // Error message for timezone not configured + if ( strpos( $e->getMessage() , 'date.timezone' ) ) { + $return[ 'error' ] = $e->getMessage() . '
' . sprintf( __('You should take a look on this %spage%s.') , '' , '' ); + } + // Other error messages + else { + $return[ 'error' ] = $e->getMessage() . '
' . sprintf( __('You should take a look on this %spage%s.') , '' , '' ); + } + } + + + /* + |-------------------------------------------------------------------------- + | Check if we have returned something + |-------------------------------------------------------------------------- + | + | If not, a command has stopped the execution and we need to alert user about + | its configuration + | + */ + $check = 0; + if ( $return['reload'] === true ) $check++; + if ( $return['next'] === true ) $check++; + + if ( count( $return ) === $check ) { + $return[ 'error' ] = __( 'Your PHP installation is not correctly configured to run Pimp My Log.' ) . '
' . sprintf( __('You should take a look on this %spage%s.') , '' , '' ); + } + + + header( 'Content-type: application/json' ); + echo json_encode( $return ); + die(); +} + + + +/* +|-------------------------------------------------------------------------- +| Javascript Lemma +|-------------------------------------------------------------------------- +| +*/ +$lemma = array( + 'complete' => __( '%s% Complete' ), + 'error' => __( 'An error occurs!' ), + 'pleasewait' => __( 'Please wait...' ), + 'software' => __( 'Software' ), + 'path' => __( 'Path' ), + 'file' => __( 'File' ), + 'readable' => __( 'Readable' ), + 'type' => __( 'Type' ), + 'no' => __( 'No' ), + 'yes' => __( 'Yes' ), + 'name' => __( 'Name' ), + 'description' => __( 'Description' ), + 'notes' => __( 'Notes' ), + 'choosesoftware' => __( 'You have to select at least one software to configure!' ), + 'chooselog' => __( 'You have to select at least one log file or type the path of a log file!' ), + 'suhosin' => sprintf( __('You should take a look on this %spage%s.') , '' , '' ), +); + + +/* +|-------------------------------------------------------------------------- +| HTML +|-------------------------------------------------------------------------- +| +*/ +?><?php echo TITLE;?>

'; + echo sprintf( __('Suhosin extension is loaded, according to its configuration, Pimp My Log could not run normally... More information %shere%s.') , '' , '' ); + echo '
'; + } + ?>

   


\ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/error.inc.php b/vendor/potsky/pimp-my-log/inc/error.inc.php new file mode 100644 index 00000000..0565c69e --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/error.inc.php @@ -0,0 +1,18 @@ +<?php echo TITLE;?>
\ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/favicon.inc.php b/vendor/potsky/pimp-my-log/inc/favicon.inc.php new file mode 100644 index 00000000..7143bfb3 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/favicon.inc.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/getlog.pml.php b/vendor/potsky/pimp-my-log/inc/getlog.pml.php new file mode 100644 index 00000000..a3a58381 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/getlog.pml.php @@ -0,0 +1,312 @@ + $errstr ) ); + exit( 1 ); + break; + + case E_USER_WARNING: + $return['singlewarning'] = sprintf( __('PHP Warning [%s] %s') , $errno , $errstr ); + break; + + case E_USER_NOTICE: + $return['singlenotice'] = sprintf( __('PHP Notice [%s] %s') , $errno , $errstr ); + break; + + default: + $return['singlewarning'] = sprintf( __('PHP Unknown error [%s] %s') , $errno , $errstr ); + break; + } + + return true; +} + +$old_error_handler = set_error_handler( "myErrorHandler" ); + +register_shutdown_function( 'shutdown' ); + +function shutdown() +{ + $error = error_get_last(); + if ($error['type'] === E_ERROR) { + echo json_encode( + array( + 'error' => sprintf( __('PHP Error line %s: %s') , $error['line'] , $error['message'] ) + ) + ); + } +} +*/ + +/* +|-------------------------------------------------------------------------- +| Prepare +|-------------------------------------------------------------------------- +| +*/ +$return = array(); +$file_id = $_POST['file']; +$load_default_values = $_POST['ldv']; +$max = (int) $_POST['max']; +$reset = (int) @$_POST['reset']; +$old_file_size = (int) @$_POST['filesize']; +$search = @$_POST['search']; +$old_lastline = @$_POST['lastline']; + +header('Content-type: application/json'); + +if ( ! csrf_verify() ) { + $return['error'] = __( 'Please refresh the page.' ); + echo json_encode( $return ); + die(); +} + +if ( ! isset( $files[$file_id] ) ) { + $return['error'] = sprintf( __( 'File ID %s does not exist, please review your configuration file and stop playing!' ) , $file_id ); + echo json_encode( $return ); + die(); +} + +$file_path = @$files[$file_id]['path']; +if ( ! file_exists( $file_path ) ) { + $return['error'] = sprintf( __( 'File %s for file ID %s does not exist, please review your configuration file.' ) , $file_path , $file_id ); + echo json_encode( $return ); + die(); +} + +$errors = config_check( $files ); +if ( is_array( $errors ) ) { + $return['error'] = __( 'Configuration file has changed and is buggy now. Please refresh the page.' ); + echo json_encode( $return ); + die(); +} + +$regex = $files[ $file_id ][ 'format' ][ 'regex' ]; +$match = $files[ $file_id ][ 'format' ][ 'match' ]; +$types = $files[ $file_id ][ 'format' ][ 'types' ]; +$multiline = ( isset( $files[ $file_id ][ 'format' ][ 'multiline' ] ) ) ? $files[ $file_id ][ 'format' ][ 'multiline' ] : ''; +$exclude = ( isset( $files[ $file_id ][ 'format' ][ 'exclude' ] ) ) ? $files[ $file_id ][ 'format' ][ 'exclude' ] : array(); + +/* +|-------------------------------------------------------------------------- +| Timezone +|-------------------------------------------------------------------------- +| +*/ +$now = new DateTime(); +if ( ! is_null( $tz ) ) { + $now->setTimezone( new DateTimeZone( $tz ) ); +} +$now = $now->format( 'Y/m/d H:i:s' ); + +/* +|-------------------------------------------------------------------------- +| Set the beginning of the parser +|-------------------------------------------------------------------------- +| +| If sp parameter exists, we need to beginning to count from the top and put an offset of sp +| If not set, just begin at the end of the file +| +*/ +if ( isset( $_POST['sp'] ) ) { + $start_offset = (float)$_POST['sp'] - 1; + $start_from = SEEK_SET; + $load_more = true; +} +else { + $start_offset = 0; + $start_from = SEEK_END; + $load_more = false; +} + +/* +|-------------------------------------------------------------------------- +| Check how many bytes we have to read +|-------------------------------------------------------------------------- +| +*/ +$new_file_size = filesize( $file_path ); // Must be the nearest of fseek ! +$full = false; +if ($reset === 1) { + $full = true; + $data_to_parse = $new_file_size; +} +else { + $data_to_parse = $new_file_size - $old_file_size; + if ($data_to_parse < 0) { // Log file has been rotated, read all. It is not possible on apache because server is restarted gracefully but perhaps user has done something... + $data_to_parse = $new_file_size; + $full = true; + $return['notice'] = ''. $now . ' : ' . sprintf( __('Log file has been rotated (previous size was %s and new one is %s)') , human_filesize($old_file_size) , human_filesize($new_file_size) ); + } + if ($old_file_size === 0) { + $full = true; + } +} + +/* +|-------------------------------------------------------------------------- +| Get logs +|-------------------------------------------------------------------------- +| +*/ +$logs = LogParser::getNewLines( $regex , $match , $types , $tz , $max , $exclude , $file_path , $start_offset , $start_from , $load_more , $old_lastline , $multiline , $search , $data_to_parse , $full , MAX_SEARCH_LOG_TIME ); + +/* +|-------------------------------------------------------------------------- +| Error while getting logs +|-------------------------------------------------------------------------- +| +*/ +if ( ! is_array( $logs ) ) { + switch ( $logs ) { + case '1': + $return['error'] = sprintf( __( 'File %s for file ID %s does not exist anymore...' ) , $file_path , $file_id ); + break; + + default: + $return['error'] = sprintf( __( 'Unknown error %s' ) , $logs ); + break; + } +} + +/* +|-------------------------------------------------------------------------- +| Return +|-------------------------------------------------------------------------- +| +*/ +else { + + $return = array_merge( $return , $logs ); + $ln = $return['count']; + $filem = $return['filemodif']; + + if ( @$logs['notice'] === 1 ) { + $return[ 'notice' ] = ''. $now . ' > ' . __('Log file has been rotated'); + } + + + /* + |-------------------------------------------------------------------------- + | Return headers if logs have been found + |-------------------------------------------------------------------------- + | + */ + if ( $logs[ 'found' ] === true ) { + foreach ( $match as $k => $v ) { + $return['headers'][ $k ] = __( $k ); + } + } + + /* + |-------------------------------------------------------------------------- + | Only mark the lastline for new logs, not when we load older log lines + |-------------------------------------------------------------------------- + | + */ + if ( $load_more === true ) { + unset( $return['lastline'] ); + } else { + $return['newfilesize'] = $new_file_size; + } + + /* + |-------------------------------------------------------------------------- + | Footer + |-------------------------------------------------------------------------- + | + */ + $return['footer'] = sprintf( __( '%s in %sms with %s of logs, %s skipped line(s), %s unreadable line(s).
File %s was last modified on %s at %s, size is %s%s' ) + , ( $load_more === false ) + ? ( + ( $ln > 1 ) + ? sprintf( __('%s new logs found') , $ln ) + : ( + ( $ln === 0 ) + ? __( 'no new log found') + : __( '1 new log found') + ) + ) + : ( + ( $ln > 1 ) + ? sprintf( __('%s old logs found') , $ln ) + : ( + ( $ln === 0 ) + ? __( 'no old log found') + : __( '1 olg log found') + ) + ) + , $return['duration'] + , human_filesize( $return['bytes'] ) + , $return['skiplines'] + , $return['errorlines'] + , $file_path + , $filem + , $tz + , human_filesize( $new_file_size ) + , ( isset( $files[ $file_id ][ 'format' ][ 'type' ] ) ) + ? ', ' . sprintf( __('log type is %s') , $files[ $file_id ][ 'format' ][ 'type' ] ) + : '' + ); + +} + +echo json_encode( $return ); + +die(); + + +?> \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/global.inc.php b/vendor/potsky/pimp-my-log/inc/global.inc.php new file mode 100644 index 00000000..18ebdca4 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/global.inc.php @@ -0,0 +1,1390 @@ + $v) { + unset($process[$key][$k]); + if (is_array($v)) { + $process[$key][stripslashes($k)] = $v; + $process[] = &$process[$key][stripslashes($k)]; + } else { + $process[$key][stripslashes($k)] = stripslashes($v); + } + } + } + unset($process); +} + + +/* +|-------------------------------------------------------------------------- +| Global internal parameters +|-------------------------------------------------------------------------- +| +| These constants are defined for internal use only, do not change +| +*/ +define( 'YEAR' , @date( "Y" ) ); +define( 'PHP_VERSION_REQUIRED' , '5.2' ); +define( 'CONFIG_FILE_MODE' , 0444 ); +define( 'AUTH_CONFIGURATION_FILE' , 'config.auth.user.php' ); +define( 'CONFIG_FILE_NAME' , 'config.user.php' ); +define( 'CONFIG_FILE_NAME_BEFORE_1_5_0' , 'config.user.json' ); +define( 'CONFIG_FILE_TEMP' , 'config.user.tmp.php' ); + + +/* +|-------------------------------------------------------------------------- +| Global internal default parameters +|-------------------------------------------------------------------------- +| +| These constants are defined for internal use only. +| These constants will overwrite custom global parameters if their are not defined +| Overwrite them in your configuration file, not in this code directly ! +| +*/ +define( 'DEFAULT_AUTH_LOG_FILE_COUNT' , 100 ); +define( 'DEFAULT_AUTO_UPGRADE' , false ); +define( 'DEFAULT_CHECK_UPGRADE' , true ); +define( 'DEFAULT_EXPORT' , true ); +define( 'DEFAULT_FILE_SELECTOR' , 'bs' ); +define( 'DEFAULT_FOOTER' , '© Potsky 2007-' . YEAR . ' - Pimp my Log'); +define( 'DEFAULT_FORGOTTEN_YOUR_PASSWORD_URL' , 'http://support.pimpmylog.com/kb/misc/forgotten-your-password' ); +define( 'DEFAULT_GEOIP_URL' , 'http://www.geoiptool.com/en/?IP=%p' ); +define( 'DEFAULT_GOOGLE_ANALYTICS' , 'UA-XXXXX-X' ); +define( 'DEFAULT_HELP_URL' , 'http://pimpmylog.com' ); +define( 'DEFAULT_LOCALE' , 'gb_GB' ); +define( 'DEFAULT_LOGS_MAX' , 50 ); +define( 'DEFAULT_LOGS_REFRESH' , 0 ); +define( 'DEFAULT_MAX_SEARCH_LOG_TIME' , 5 ); +define( 'DEFAULT_NAV_TITLE' , '' ); +define( 'DEFAULT_NOTIFICATION' , true ); +define( 'DEFAULT_NOTIFICATION_TITLE' , 'New logs [%f]' ); +define( 'DEFAULT_PIMPMYLOG_ISSUE_LINK' , 'https://github.com/potsky/PimpMyLog/issues/' ); +define( 'DEFAULT_PIMPMYLOG_VERSION_URL' , 'http://demo.pimpmylog.com/version.js' ); +define( 'DEFAULT_PULL_TO_REFRESH' , true ); +define( 'DEFAULT_SORT_LOG_FILES' , 'default' ); +define( 'DEFAULT_TAG_DISPLAY_LOG_FILES_COUNT' , true ); +define( 'DEFAULT_TAG_NOT_TAGGED_FILES_ON_TOP' , true ); +define( 'DEFAULT_TAG_SORT_TAG' , 'displayiasc' ); +define( 'DEFAULT_TITLE' , 'Pimp my Log' ); +define( 'DEFAULT_TITLE_FILE' , 'Pimp my Log [%f]' ); +define( 'DEFAULT_UPGRADE_MANUALLY_URL' , 'http://pimpmylog.com/getting-started/#update' ); +define( 'DEFAULT_USER_CONFIGURATION_DIR' , 'config.user.d' ); + + +/* +|-------------------------------------------------------------------------- +| Lang parameters +|-------------------------------------------------------------------------- +| +| $locale_numeraljs is an associative array to convert a PHP locale to a +| javascript locale used by numeralJS +| +*/ +$tz_available = DateTimeZone::listIdentifiers(); +$locale_default = 'en_GB'; +$locale_available = array( + 'en_GB' => 'English', + 'fr_FR' => 'Français', + 'pt_BR' => 'Português do Brasil', +); +$locale_numeraljs = array( + 'en_GB' => 'en-gb', + 'fr_FR' => 'fr', + 'pt_BR' => 'pt-br', +); + + +/* +|-------------------------------------------------------------------------- +| Class autoloader +|-------------------------------------------------------------------------- +| +*/ +function my_autoloader( $ClassName ) { + @include( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . $ClassName . ".php"); +} +spl_autoload_register('my_autoloader'); + + +/* +|-------------------------------------------------------------------------- +| Functions declarations +|-------------------------------------------------------------------------- +| +| Will be removed in PML v2.0, all functions will be grouped in classes +| +*/ + +/** + * htmlentities + * + * @param string $text the text key + * + * @return string the translation + */ +function h($text) +{ + return htmlentities( $text ,ENT_QUOTES,'UTF-8'); +} + +/** + * htmlentities for echo + * + * @param string $text the text key + * + * @return string the translation + */ +function _h($text) +{ + _e( htmlentities( $text ,ENT_QUOTES,'UTF-8') ); +} + +/** + * Simply return a localized text or empty string if the key is empty + * Useful when localize variable which can be empty + * + * @param string $text the text key + * + * @return string the translation + */ +function __($text) +{ + if ( empty( $text ) ) + return ''; + else + return gettext( $text ); +} + +/** + * Simply echo a localized text + * + * @param string $text the text key + * + * @return void + */ +function _e($text) +{ + echo __( $text ); +} + +/** + * Load all unset constants + * + * @return + */ +function load_default_constants() +{ + $defaults = array( + 'AUTH_LOG_FILE_COUNT', + 'AUTO_UPGRADE', + 'CHECK_UPGRADE', + 'DEFAULT_HELP_URL', + 'EXPORT', + 'FILE_SELECTOR', + 'FOOTER', + 'FORGOTTEN_YOUR_PASSWORD_URL', + 'GEOIP_URL', + 'GOOGLE_ANALYTICS', + 'LOCALE', + 'LOGS_MAX', + 'LOGS_REFRESH', + 'MAX_SEARCH_LOG_TIME', + 'NAV_TITLE', + 'NOTIFICATION', + 'NOTIFICATION_TITLE', + 'PIMPMYLOG_ISSUE_LINK', + 'PIMPMYLOG_VERSION_URL', + 'PULL_TO_REFRESH', + 'SORT_LOG_FILES', + 'TAG_SORT_TAG', + 'TAG_NOT_TAGGED_FILES_ON_TOP', + 'TAG_DISPLAY_LOG_FILES_COUNT', + 'TITLE', + 'TITLE_FILE', + 'UPGRADE_MANUALLY_URL', + 'USER_CONFIGURATION_DIR', + ); + foreach ($defaults as $d) { + if ( ! defined( $d ) ) { + if ( defined( 'DEFAULT_' . $d ) ) { + define( $d , constant( 'DEFAULT_' . $d ) ); + } else { + die( "Constant 'DEFAULT_$d' is not defined!" ); + } + } + } +} + +/** + * Try to find the main configuration file path + * Configuration file can be a PHP file since 1.5.0 or a json file below + * Both files contains a JSON configuration array but the PHP version is not callable by a guest on web + * + * @return string the path in a string or null if not found + */ +function get_config_file_path() +{ + $files = array( + CONFIG_FILE_NAME, + CONFIG_FILE_NAME_BEFORE_1_5_0, + ); + foreach ($files as $f) { + if ( file_exists( PML_CONFIG_BASE . DIRECTORY_SEPARATOR . $f ) ) { + return realpath( PML_CONFIG_BASE . DIRECTORY_SEPARATOR . $f ); + } + } + + return null; +} + +/** + * Return the configuration file name + * + * @param string $path the configuration file path or false if the function has to compute itself + * + * @return string the file name or null if configuration not found + */ +function get_config_file_name($path = false) +{ + if ( $path === false ) $path = get_config_file_path(); + if ( is_null( $path ) ) return null; + return basename( $path ); +} + +/** + * Return the configuration array of a configuration file + * + * @param string $path the configuration file path or false to let the function load the global configuration + * + * @return array the configuration array or null if file is invalid or if configuration file does not exist + */ +function get_config_file($path = false) +{ + if ( $path === false ) $path = get_config_file_path(); + if ( is_null( $path ) ) return null; + + if ( strtolower( substr( $path , -3 , 3 ) ) === 'php' ) { + ob_start(); + require $path; + $string = ob_get_clean(); + } else { + $string = @file_get_contents( $path ); + } + + return json_decode( $string , true ); +} + +/** + * Load config file + * + * @param string $path the configuration file path + * @param boolean $load_user_configuration_dir do we have to parse all user configuration files ? No for upgrade for example... + * + * @return array [ badges , files ] + */ +function config_load($load_user_configuration_dir = true) +{ + $badges = false; + $files = false; + + // Read config file + $config = get_config_file(); + + if ( is_null( $config ) ) { + return array( $badges , $files ); + } + + // Get badges + $badges = $config[ 'badges' ]; + + // Set user constant + foreach ($config[ 'globals' ] as $cst => $val) { + if ( $cst == strtoupper( $cst ) ) { + @define( $cst , $val ); + } + } + + // Set unset constants + load_default_constants(); + + // Set time limit + @set_time_limit( MAX_SEARCH_LOG_TIME + 2 ); + + // Append files from the USER_CONFIGURATION_DIR + if ( $load_user_configuration_dir === true ) { + if ( is_dir( PML_CONFIG_BASE . DIRECTORY_SEPARATOR . USER_CONFIGURATION_DIR ) ) { + $dir = PML_CONFIG_BASE . DIRECTORY_SEPARATOR . USER_CONFIGURATION_DIR; + $userfiles = new RegexIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $dir , RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::SELF_FIRST, + RecursiveIteratorIterator::CATCH_GET_CHILD // Ignore "Permission denied" + ), + '/^.+\.(json|php)$/i', + RecursiveRegexIterator::GET_MATCH + ); + foreach ($userfiles as $userfile) { + $filepath = realpath( $userfile[0] ); + $c = get_config_file( $filepath ); + if ( ! is_null( $c ) ) { + foreach ($c as $k => $v) { + $fileid = get_slug( str_replace( PML_CONFIG_BASE , '' , $filepath ) . '/' . $k ); + $config[ 'files' ][ $fileid ] = $v; + $config[ 'files' ][ $fileid ]['included_from'] = $filepath; + } + } + } + } + } + + // Oups, there is no file... abort + if ( ! isset( $config[ 'files' ] ) ) { + return array( $badges , $files ); + } + + // Try to generate the files tree if there are globs... + $files_tmp = $config[ 'files' ]; + $files = array(); + + foreach ($files_tmp as $fileid => $file) { + + $path = $file['path']; + $count = max( 1 , @(int) $file['count']); + $gpaths = glob( $path , GLOB_MARK | GLOB_NOCHECK ); + + if ( count( $gpaths ) == 0 ) { + } + else if ( count( $gpaths ) == 1 ) { + $files[ $fileid ] = $file; + $files[ $fileid ]['path'] = $gpaths[0]; + } + else { + $new_paths = array(); + $i = 1; + + foreach ($gpaths as $path) { + $new_paths[ $path ] = filemtime( $path ); + } + + // The most recent file will be the first + arsort( $new_paths , SORT_NUMERIC ); + + // The first file id is the ID of the configuration file then others files are suffixed with _2, _3, etc... + foreach ( $new_paths as $path => $lastmodified ) { + $ext = ( $i > 1 ) ? '_' . $i : ''; + + $files[ $fileid . $ext ] = $file; + $files[ $fileid . $ext ]['oid'] = $fileid; + $files[ $fileid . $ext ]['odisplay'] = $files[ $fileid . $ext ]['display']; + $files[ $fileid . $ext ]['path'] = $path; + $files[ $fileid . $ext ]['display'] .= ' > ' . basename( $path ); + if ($i >= $count) { + break; + } + $i++; + } + } + } + + // Remove forbidden files + if ( Sentinel::isAuthSet() ) { // authentication is enabled on this instance + + $username = Sentinel::getCurrentUsername(); + $final = array(); + + // Anonymous access only + if ( is_null( $username ) ) { + foreach ( $files as $fileid => $file ) { + $a = $fileid; + // glob file + if ( isset( $files[ $fileid ]['oid'] ) ) { + $a = $files[ $fileid ]['oid']; + } + if ( Sentinel::isLogAnonymous( $a ) ) { + $final[ $fileid ] = $file; + } + } + } + + // Anonymous access + User access + else { + foreach ( $files as $fileid => $file ) { + $a = $fileid; + // glob file + if ( isset( $files[ $fileid ]['oid'] ) ) { + $a = $files[ $fileid ]['oid']; + } + if ( ( Sentinel::userCanOnLogs( $a , 'r' , true , $username ) ) || ( Sentinel::isLogAnonymous( $a ) ) ) { + $final[ $fileid ] = $file; + } + } + } + + $files = $final; + } + + // Fix missing values with defaults + foreach ( $files as $fileid => $file ) { + foreach (array( + 'max' => LOGS_MAX, + 'refresh' => LOGS_REFRESH, + 'notify' => NOTIFICATION, + ) as $fix => $value ) { + if ( ! isset( $file[ $fix ] ) ) { + $files[ $fileid ][ $fix ] = $value; + } + } + } + + // Finally sort files + if ( ! function_exists( 'display_asc' ) ) { function display_asc($a, $b) { return strcmp( $a["display"] , $b["display"] ); } } + if ( ! function_exists( 'display_desc' ) ) { function display_desc($a, $b) { return strcmp( $b["display"] , $a["display"] ); } } + if ( ! function_exists( 'display_insensitive_asc' ) ) { function display_insensitive_asc($a, $b) { return strcmp( $a["display"] , $b["display"] ); } } + if ( ! function_exists( 'display_insensitive_desc' ) ) { function display_insensitive_desc($a, $b) { return strcmp( $b["display"] , $a["display"] ); } } + switch ( trim( str_replace( array( '-' , '_' , ' ' , 'nsensitive' ) , '' , SORT_LOG_FILES ) ) ) { + case 'display': + case 'displayasc': + usort( $files , 'display_asc' ); + break; + case 'displayi': + case 'displayiasc': + usort( $files , 'display_insensitive_asc' ); + break; + case 'displaydesc': + usort( $files , 'display_desc' ); + break; + case 'displayidesc': + usort( $files , 'display_insensitive_desc' ); + break; + default: + # do not sort + break; + } + + return array( $badges , $files ); +} + +/** + * Check the $files array and fix it with default values + * If there is a problem, return an array of errors + * If everything is ok, return true; + * + * @param array $files log files + * + * @return mixed true if ok, otherwise an array of errors + */ +function config_check( $files ) +{ + $errors = array(); + + if ( ! is_array( $files ) ) { + if ( Sentinel::isAuthSet() ) return false; + + $errors[] = __( 'No file is defined in files array' ); + + return $errors; + } + + if ( count( $files ) === 0 ) { + if ( Sentinel::isAuthSet() ) return false; + + $errors[] = __( 'No file is defined in files array' ); + + return $errors; + } + + foreach ($files as $file_id => &$file) { + // error + foreach ( array( 'display' , 'path' , 'format' ) as $mandatory ) { + if ( ! isset( $file[ $mandatory ] ) ) { + $errors[] = sprintf( __( '%s is mandatory for file ID %s' ) , $mandatory , $file_id ); + } + } + } + + if ( count($errors) == 0 ) { + return true; + } else { + return $errors; + } +} + +/** + * Extract tags from the confiuration files + * + * @param array $files the files configuration array + * + * @return array an array of tags with fileids + */ +function config_extract_tags( $files ) { + $tags = array( '_' => array() ); + + foreach ( $files as $fileid => $file ) { + // Tag found + if ( isset( $file['tags'] ) ) { + if ( is_array( $file['tags'] ) ) { + foreach ( $file['tags'] as $tag ) { + $tags[ strval( $tag ) ][] = $fileid; + } + } else { + $tags[ strval( $file['tags'] ) ][] = $fileid; + } + } + // No tag + else { + $tags[ '_' ][] = $fileid; + } + } + + switch ( trim( str_replace( array( '-' , '_' , ' ' , 'nsensitive' ) , '' , TAG_SORT_TAG ) ) ) { + case 'display': + case 'displayasc': + if ( version_compare( PHP_VERSION , '5.4.0' ) >= 0 ) { + ksort( $tags , SORT_NATURAL ); + } else { + ksort( $tags ); + } + break; + case 'displayi': + case 'displayiasc': + if ( version_compare( PHP_VERSION , '5.4.0' ) >= 0 ) { + ksort( $tags , SORT_NATURAL | SORT_FLAG_CASE ); + } else { + ksort( $tags ); + } + break; + case 'displaydesc': + if ( version_compare( PHP_VERSION , '5.4.0' ) >= 0 ) { + krsort( $tags , SORT_NATURAL ); + } else { + krsort( $tags ); + } + break; + case 'displayidesc': + if ( version_compare( PHP_VERSION , '5.4.0' ) >= 0 ) { + krsort( $tags , SORT_NATURAL | SORT_FLAG_CASE ); + } else { + krsort( $tags ); + } + break; + default: + # do not sort + break; + } + + return $tags; +} + + +/** + * Get the list of refresh duration + * The list is the default one below + : + * - a custom value defined by user in PHP constant LOGS_REFRESH + * - a custom value defined by user in all files in PHP array $files + * The list must by unique and sorted + * + * @param array $files log files + * + * @return array the list of selectable values + */ +function get_refresh_options($files) +{ + $options = array( + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 10 => 10, + 15 => 15, + 30 => 30, + 45 => 45, + 60 => 60 + ); + $options[ (int) LOGS_REFRESH ] = (int) LOGS_REFRESH; + foreach ($files as $file_id => $file) { + $options[ (int) @$file['refresh'] ] = (int) @$file['refresh']; + } + unset( $options[0] ); + sort( $options ); + + return $options; +} + +/** + * Get the list of displayed logs count + * The list is the default one below + : + * - a custom value defined by user in PHP constant LOGS_MAX + * - a custom value defined by user in all files in PHP array $files + * The list must by unique and sorted + * + * @param array $files log files + * + * @return array the list of selectable values + */ +function get_max_options($files) +{ + $options = array( + 5 => 5, + 10 => 10, + 20 => 20, + 50 => 50, + 100 => 100, + 200 => 200 + ); + $options[ (int) LOGS_MAX ] = (int) LOGS_MAX; + foreach ($files as $file_id => $file) { + $options[ (int) @$file['max'] ] = (int) @$file['max']; + } + unset( $options[0] ); + sort( $options ); + + return $options; +} + +/** + * Return a human representation of a size + * + * @param string $bytes the string representation (can be an int) + * @param integer $decimals the number of digits in the float part + * + * @return string the human size + */ +function human_filesize( $bytes , $decimals = 0 ) +{ + $sz = __( 'B KBMBGBTBPB' ); + $factor = floor( ( strlen( $bytes ) - 1 ) / 3 ); + + return sprintf( "%.{$decimals}f" , $bytes / pow( 1024, $factor ) ) . @$sz[ (int)$factor * 2 ]; +} + +/** + * Get a Cross Script Request Forgery token + * + * @return string a token + */ +function csrf_get() +{ + Session::start(); + if ( ! isset( $_SESSION[ 'csrf_token' ] ) ) { + $_SESSION[ 'csrf_token' ] = md5( uniqid( '' , true ) ); + } + Session::write_close(); + + return $_SESSION[ 'csrf_token' ]; +} + +/** + * Verify a Cross Script Request Forgery token + * + * @return boolean verified ? + */ +function csrf_verify() +{ + Session::start(); + $s = @$_SESSION[ 'csrf_token' ]; + Session::write_close(); + if ( ! isset( $_POST[ 'csrf_token' ] ) ) + return false; + return ( $s === @$_POST[ 'csrf_token' ] ); +} + +/** + * [get_slug description] + * + * @param string $string the string to slugify + * @param string $separator the separator + * + * @return string th slugified string + */ +function get_slug($string, $separator = '-') +{ + $accents_regex = '~&([a-z]{1,2})(?:acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i'; + $special_cases = array( '&' => 'and'); + $string = mb_strtolower( trim( $string ), 'UTF-8' ); + $string = str_replace( array_keys($special_cases), array_values( $special_cases), $string ); + $string = preg_replace( $accents_regex, '$1', htmlentities( $string, ENT_QUOTES, 'UTF-8' ) ); + $string = preg_replace("/[^a-z0-9]/u", "$separator", $string); + $string = preg_replace("/[$separator]+/u", "$separator", $string); + + return $string; +} + +/** + * Indents a flat JSON string to make it more human-readable. + * For PHP < 5.4 + * + * @param string $json The original JSON string to process. + * + * @return string Indented version of the original JSON string. + */ +function json_indent($json) +{ + $result = ''; + $pos = 0; + $strLen = strlen($json); + $indentStr = ' '; + $newLine = "\n"; + $prevChar = ''; + $outOfQuotes = true; + for ($i=0; $i<=$strLen; $i++) { + $char = substr($json, $i, 1); + if ($char == '"' && $prevChar != '\\') { + $outOfQuotes = !$outOfQuotes; + } elseif (($char == '}' || $char == ']') && $outOfQuotes) { + $result .= $newLine; + $pos --; + for ($j=0; $j<$pos; $j++) { + $result .= $indentStr; + } + } + $result .= $char; + if ( ( $char == ',' || $char == '{' || $char == '[' ) && $outOfQuotes ) { + $result .= $newLine; + if ($char == '{' || $char == '[') { + $pos ++; + } + for ($j = 0 ; $j < $pos ; $j++) { + $result .= $indentStr; + } + } + $prevChar = $char; + } + + return $result; +} + +/** + * Remove jsonp callback from a version file + * + * @param string $data the json file with callback + * + * @return string the json file without callback + */ +function clean_json_version($data) +{ + return str_replace( array( '/*PSK*/pml_version_cb(/*PSK*/' , '/*PSK*/);/*PSK*/' , '/*PSK*/)/*PSK*/' ) , array( '' , '' , '' ) , $data ); +} + +/** + * Do nothing for set_error_handler PHP5.2 style + * + * @param integer $errno + * @param string $errstr + * + * @return void + */ +function dumb_test($errno, $errstr) +{ + +} + +/** + * Try to guess who runs the server + * + * @return string a user information + */ +function get_server_user() +{ + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') + { + return ''; + } + else if ( SAFE_MODE === true ) + { + // for Suhosin + return ''; + } + else + { + // for PHP disabled_func + set_error_handler( "dumb_test" ); + + $a = exec( 'whoami' ); + restore_error_handler(); + + return $a; + } +} + +/** + * Tell whether this is a associative array (object in javascript) or not (array in javascript) + * + * @param array $arr the array to test + * + * @return boolean true if $arr is an associative array + */ +function is_assoc($arr) +{ + return array_keys( $arr ) !== range( 0 , count( $arr ) - 1 ); +} + +/** + * Generate a random string + * + * @param integer $l the string length + * @param string $c a list of char in a string taken to generate the string + * + * @return string a random string of $l chars + */ +function mt_rand_str($l, $c = 'abcdefghijklmnopqrstuvwxyz1234567890_-ABCDEFGHIJKLMNOPQRSTUVWXYZ') +{ + for ($s = '', $cl = strlen($c)-1, $i = 0; $i < $l; $s .= $c[mt_rand(0, $cl)], ++$i); + + return $s; +} + + +/** + * Get the local ip address of the current client according to proxy and more... + * + * @return string an ip address + */ +function get_client_ip() { + $ip = ''; + if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } + else if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { + $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; + } + else if ( isset( $_SERVER['HTTP_X_FORWARDED'] ) ) { + $ip = $_SERVER['HTTP_X_FORWARDED']; + } + else if ( isset( $_SERVER['HTTP_FORWARDED_FOR'] ) ) { + $ip = $_SERVER['HTTP_FORWARDED_FOR']; + } + else if ( isset( $_SERVER['HTTP_FORWARDED'] ) ) { + $ip = $_SERVER['HTTP_FORWARDED']; + } + else if ( isset( $_SERVER['REMOTE_ADDR'] ) ) { + $ip = $_SERVER['REMOTE_ADDR']; + } + return $ip; +} + +/** + * Get the current url + * + * @param boolean $q include the query string + * + * @return string current url + */ +function get_current_url( $q = false ) { + if ( isset( $_SERVER['SERVER_PROTOCOL'] ) ) { // only web, not unittests + $s = &$_SERVER; + $ssl = (!empty($s['HTTPS']) && $s['HTTPS'] == 'on') ? true:false; + $sp = strtolower(@$s['SERVER_PROTOCOL']); + $protocol = substr($sp, 0, strpos($sp, '/')) . (($ssl) ? 's' : ''); + $port = @$s['SERVER_PORT']; + $port = ((!$ssl && $port=='80') || ($ssl && $port=='443')) ? '' : ':'.$port; + $host = isset( $s['HTTP_X_FORWARDED_HOST'] ) ? $s['HTTP_X_FORWARDED_HOST'] : ( isset( $s['HTTP_HOST'] ) ? $s['HTTP_HOST'] : null ); + $host = isset($host) ? $host : @$s['SERVER_NAME'] . $port; + $uri = $protocol . '://' . $host . @$s['REQUEST_URI']; + $segments = explode('?', $uri, 2); + $url = $segments[0]; + if ( $q === true ) { + if ( isset( $_SERVER['QUERY_STRING'] ) ) { + $url .= '?' . $_SERVER['QUERY_STRING']; + } + } + return $url; + } + return null; +} + +/** + * Tell if the provided IP address is local or not + * + * @param string $ip an ipv4 address + * + * @return boolean true if address is local + */ +function is_not_local_ip( $ip ) { + $ip = trim( $ip ); + if ( $ip === '127.0.0.1' ) { + return false; + } + return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE); +} + +/** + * Return a 404 error + * + * @return void + */ +function http404() { + header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found'); + die(); +} + +/** + * Return a 403 error + * + * @return void + */ +function http403() { + header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 403 Forbidden'); + die(); +} +/** + * Return a 500 error + * + * @return void + */ +function http500() { + header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 500 Internal Server Error'); + die(); +} + + +/** + * Clean an array recursivly + * + * @param array $input the array to clean up + * + * @return array the cleaned array + */ +function array_filter_recursive($input) { + foreach ($input as &$value) { + if (is_array($value)) { + $value = array_filter_recursive($value); + } + else if (is_object($value)) { + $value = array_filter_recursive((array)$value); + } + } + + return array_filter($input); +} + +/** + * Return the current Pimp My Log Version + * + * @return string the version string or empty if not available + */ +function get_current_pml_version() { + $v = ''; + $file = dirname( __FILE__ ) . '/../version.js'; + if ( file_exists( $file ) ) { + $j = json_decode( clean_json_version( @file_get_contents( $file ) ) , true ); + $v = @$j[ 'version' ]; + } + return $v; +} + +/** + * Return the current Pimp My Log Version + * + * @return string the version string or empty if not available + */ +function get_current_pml_version_infos() { + $i = array(); + $file = dirname( __FILE__ ) . '/../version.js'; + if ( file_exists( $file ) ) { + $j = json_decode( clean_json_version( @file_get_contents( $file ) ) , true ); + $v = @$j[ 'version' ]; + $i = @$j[ 'changelog' ][ $v ]; + $i['v'] = $v; + } + return $i; +} + +/** + * Generate a xml string of the provided array + * + * @param array $array the array to convert in XML + * @param string $node_name the node name for numerical arrays + * + * @return string the xml string + */ +function generate_xml_from_array( $array, $node_name ) { + $xml = ''; + if (is_array($array) || is_object($array)) { + foreach ($array as $key=>$value) { + if (is_numeric($key)) { + $key = $node_name; + } + + $xml .= '<' . $key . '>' . generate_xml_from_array( $value, $node_name) . ''; + } + } else { + $xml = htmlspecialchars($array, ENT_QUOTES); + } + + return $xml; +} + + +/** + * Return a csv file from an array + * + * @param array $array + * + * @return null|string + */ +function array2csv( $array ) { + if ( count( $array ) == 0 ) { + return null; + } + ob_start(); + $df = fopen( "php://output" , 'w' ); + fputcsv( $df , array_keys( reset( $array ) ) , "\t" ); + foreach ( $array as $row ) { + fputcsv( $df , $row , "\t" ); + } + fclose( $df ); + return ob_get_clean(); +} + + +/** + * Return a UTC timestamp from a timestamp computed in a specific timezone + * + * @param integer $timestamp the epoch timestamp + * @param string $tzfrom the timezone where the timesamp has been computed + * + * @return integer the epoch in UTC + */ +function get_non_UTC_timstamp( $timestamp = null , $tzfrom = null ) +{ + if ( is_null( $tzfrom ) ) { + $tzfrom = date_default_timezone_get(); + } + if ( is_null( $timestamp ) ) { + $timestamp = time(); + } + + $d = new DateTime( "@" . $timestamp ); + $d->setTimezone( new DateTimeZone( $tzfrom ) ); + + return $timestamp - $d->getOffset(); +} + +/** + * Try to guess if Pimp My Log is installed with composer + * + * @return boolean + */ +function upgrade_is_composer() { + $a = false; + + // Catch errors for people who has activated open_basedir restrictions + set_error_handler( "dumb_test" ); + + if ( basename( PML_BASE ) !== 'pimp-my-log' ) $a = false; + else if ( ! is_dir( PML_BASE . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'potsky' ) ) $a = false; + else if ( ! is_dir( PML_BASE . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' ) ) $a = false; + else if ( ! file_exists( PML_BASE . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'composer.json' ) ) $a = false; + else $a = true; + + restore_error_handler(); + + return $a; +} + +/** + * Try to guess if Pimp My Log is installed with git + * + * @return boolean + */ +function upgrade_is_git() { + if ( SAFE_MODE === true ) return false; + + if ( ! is_dir( PML_BASE . DIRECTORY_SEPARATOR . '.git' ) ) return false; + + return true; +} + +/** + * Try to guess if Pimp My Log can pull with git + * + * @return mixed + */ +function upgrade_can_git_pull() { + if ( SAFE_MODE === true ) return false; + + $base = PML_BASE; + + // Check if git is callable and if all files are not changed + $a = exec('cd ' . escapeshellarg( $base ) . '; git status -s' , $lines , $code ); + + // Error while executing this comand + if ( $code !== 0 ) return array( $code , $lines); + + // Error, files have been modified + if ( count( $lines ) !== 0 ) return array( $code , $lines); + + // can write all files with this webserver user ? + $canwrite = true; + $lines = array(); + $git = mb_strlen( realpath( $base ) ) + 1; + $pmlfiles = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $base ), + RecursiveIteratorIterator::SELF_FIRST + ); + + foreach ($pmlfiles as $f) { + + // Ignore all .git/* files + if ( ( mb_substr( $f->getPathname() , $git , 4 ) ) === '.git' ) continue; + + // check if this file is writable + if ( ! $f->isWritable() ) { + + // check if it ignored or not + $b = exec( "git ls-files " . escapeshellarg( $f->getPathname() ) ); + if ( ! empty( $b ) ) { + $canwrite = false; + $lines[] = $f->getPathname(); + } + } + } + + if ( $canwrite === false ) return array( 2706 , $lines ); + + return true; +} + + +/* +|-------------------------------------------------------------------------- +| Uniq ID +|-------------------------------------------------------------------------- +| +| We generate a uniq ID for the current user in order to track how many people +| are currently using Pimp My Log. This value is stored in a cookie in order to +| keep it +| +*/ +if ( isset( $_SERVER['SERVER_PROTOCOL'] ) ) { // only web, not unittests + if ( ! isset( $_COOKIE['u'] ) ) { + $uuid = sha1( json_encode( $_SERVER ) . uniqid( '' , true ) ); + setcookie( 'u' , $uuid , time()+60*60*24*3000 , '/' ); + } else { + $uuid = $_COOKIE['u']; + } +} + + +/* +|-------------------------------------------------------------------------- +| Timezone +|-------------------------------------------------------------------------- +| +*/ +$tz = ''; +if ( isset( $_POST['tz'] ) ) { + $tz = $_POST['tz']; +} elseif ( isset( $_GET['tz'] ) ) { + $tz = $_GET['tz']; +} elseif ( defined( 'USER_TIME_ZONE' ) ) { + $tz = USER_TIME_ZONE; +} +if ( ! in_array( $tz , $tz_available ) ) { + $tz = @date('e'); +} + + +/* +|-------------------------------------------------------------------------- +| Define locale and translation +|-------------------------------------------------------------------------- +| +*/ +$lang = ''; +$locale = $locale_default; +$localejs = $locale_numeraljs[ $locale_default ]; + +if ( function_exists( 'bindtextdomain' ) ) { + + if ( isset( $_GET['l'] ) ) { + $locale = $_GET['l']; + } elseif ( isset( $_COOKIE['pmllocale'] ) ) { + $locale = $_COOKIE['pmllocale']; + } elseif ( defined( 'LOCALE' ) ) { + $locale = LOCALE; + } elseif ( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) { + @list( $locale, $dumb ) = @explode( ',', $_SERVER['HTTP_ACCEPT_LANGUAGE'], 2 ); + } + + $locale = str_replace( '-', '_', $locale ); + @list( $lang, $b ) = explode( '_', $locale ); + $locale = strtolower( $lang ).'_'.strtoupper( $b ); + + if ( ! array_key_exists( $locale, $locale_available ) ) { + $locale = $locale_default; + } + + putenv( 'LC_ALL=' . $locale ); + putenv( 'LANGUAGE=' . $locale ); + + if ( ( ! isset( $_COOKIE['pmllocale'] ) ) || ( $_COOKIE['pmllocale'] !== $locale ) ) { + if ( isset( $_SERVER['SERVER_PROTOCOL'] ) ) { // only web, not unittests + setcookie( 'pmllocale' , $locale , time()+60*60*24*3000 ); + } + } + + if ($lang == 'fr') { + setlocale( LC_ALL , $locale , $locale . '.utf8' , 'fra' ); + } elseif ($lang == 'de') { + setlocale( LC_ALL , $locale , $locale . '.utf8' , 'deu_deu' , 'de' , 'ge' ); + } else { + setlocale( LC_ALL , $locale , $locale . '.utf8' ); + } + + bindtextdomain( 'messages' , dirname( __FILE__ ) . '/../lang' ); + bind_textdomain_codeset( 'messages' , 'UTF-8' ); + textdomain( 'messages' ); + + define( 'GETTEXT_SUPPORT' , true ); +} +else { + /** + * Fallback function for retrieving texts + * + * @param string $text the string to display + * + * @return string the same string but not translated! + */ + function gettext($text) + { + return $text; + } + + define( 'GETTEXT_SUPPORT' , false ); + +} + +?> \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/index.html b/vendor/potsky/pimp-my-log/inc/index.html new file mode 100644 index 00000000..e69de29b diff --git a/vendor/potsky/pimp-my-log/inc/login.inc.php b/vendor/potsky/pimp-my-log/inc/login.inc.php new file mode 100644 index 00000000..d689c18b --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/login.inc.php @@ -0,0 +1,57 @@ +<?php echo TITLE;?>
'; + echo __('Your username or password is not correct'); + echo '
'; + } + else if ( (int)@$error === 2 ) { + echo '
'; + echo __('Please try again...'); + echo '
'; + } + else if ( (int)@$error === 3 ) { + echo '
'; + echo __('You have been logged out'); + echo '
'; + } + else if ( $_SERVER['SERVER_NAME'] === 'demo.pimpmylog.com' ) { + echo '
'; + echo '
'; + echo sprintf( __('You can use %s as the username and %s as the password to test the demo account') , 'demo' , 'pimpmylog' ); + echo '
'; + echo '
'; + } + ?>


0 ) ? htmlentities( $_POST['username'] , ENT_QUOTES , 'UTF-8' ) : ''; + if ( strlen( "" . @$_POST['username'] ) === 0 ) { + echo ''; + } else { + echo ''; + } + ?>

'; + } else { + echo ''; + } + ?>



\ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/rss.php b/vendor/potsky/pimp-my-log/inc/rss.php new file mode 100644 index 00000000..9e07dd77 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/rss.php @@ -0,0 +1,196 @@ + $file_id ) , $username ) ) { + http403(); + } + } + +} +else if ( ( ! isset( $_GET['t'] ) ) && ( isset( $_GET['h'] ) ) ) { + http404(); +} +else if ( ( isset( $_GET['t'] ) ) && ( ! isset( $_GET['h'] ) ) ) { + http404(); +} + + + +/* +|-------------------------------------------------------------------------- +| Load config +|-------------------------------------------------------------------------- +| +*/ +list( $badges , $files ) = config_load(); + +if ( ! isset( $files[ $file_id ] ) ) { + http403(); +} + +if ( ( isset( $files[ $file_id ]['export'] ) ) && ( $files[ $file_id ]['export'] === false ) ) { + http403(); +} + +if ( ( EXPORT === false ) && ( ! isset( $files[ $file_id ]['export'] ) ) ) { + http403(); +} + + +/* +|-------------------------------------------------------------------------- +| Get logs +|-------------------------------------------------------------------------- +| +*/ +$search = ( isset( $_GET['search'] ) ) ? $_GET['search'] : ''; +$format = ( isset( $_GET['format'] ) ) ? $_GET['format'] : 'JSON'; +$count = ( isset( $_GET['count'] ) ) ? $_GET['count'] : ( ( isset( $files[ $file_id ][ 'max' ] ) ) ? $files[ $file_id ][ 'max' ] : LOGS_MAX ); +$timeout = ( isset( $_GET['timeout'] ) ) ? $_GET['timeout'] : MAX_SEARCH_LOG_TIME; + +$regex = $files[ $file_id ][ 'format' ][ 'regex' ]; +$match = $files[ $file_id ][ 'format' ][ 'match' ]; +$types = $files[ $file_id ][ 'format' ][ 'types' ]; +$multiline = ( isset( $files[ $file_id ][ 'format' ][ 'multiline' ] ) ) ? $files[ $file_id ][ 'format' ][ 'multiline' ] : ''; +$exclude = ( isset( $files[ $file_id ][ 'format' ][ 'exclude' ] ) ) ? $files[ $file_id ][ 'format' ][ 'exclude' ] : array(); +$title = ( isset( $files[ $file_id ][ 'format' ][ 'export_title' ] ) ) ? $files[ $file_id ][ 'format' ][ 'export_title' ] : ''; +$file_path = $files[$file_id]['path']; +$start_offset = 0; +$start_from = SEEK_END; +$load_more = false; +$old_lastline = ''; +$data_to_parse = filesize( $file_path ); +$full = true; +$logs = LogParser::getNewLines( $regex , $match , $types , $tz , $count , $exclude , $file_path , $start_offset , $start_from , $load_more , $old_lastline , $multiline , $search , $data_to_parse , $full , $timeout ); + +/* +|-------------------------------------------------------------------------- +| Error while getting logs +|-------------------------------------------------------------------------- +| +*/ +if ( ! is_array( $logs ) ) { + http500(); +} + +/* +|-------------------------------------------------------------------------- +| Return +|-------------------------------------------------------------------------- +| +*/ +$link = str_replace( 'inc/rss.php' , '' , get_current_url( true ) ); + +header( "Pragma: public" ); +header( "Expires: 0" ); +header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" ); + +switch ( $format ) { + + case 'ATOM': + case 'RSS': + require( 'classes/Feedcreator.php' ); + define( 'TIME_ZONE' , $tz ); + define( 'FEEDCREATOR_VERSION', 'Pimp My Log v' . get_current_pml_version() ); + $rss = new UniversalFeedCreator(); + $rss->title = sprintf( __( "Pimp My Log : %s" ) , $files[ $file_id ][ 'display' ] ); + $rss->description = ( empty( $search ) ) + ? sprintf( __( "Pimp logs for file %s" ), $files[ $file_id ][ 'path' ] ) + : sprintf( __( "Pimp logs for file %s with search %s" ), $files[ $file_id ][ 'path' ] , $search ); + $rss->descriptionTruncSize = 500; + $rss->descriptionHtmlSyndicated = true; + $rss->link = $link; + $rss->syndicationURL = get_current_url( true ); + $image = new FeedImage(); + $image->title = $rss->title; + $image->url = str_replace( 'inc/rss.php' , 'img/icon72.png' , get_current_url() ); + $image->link = $link; + $image->description = __( "Feed provided by Pimp My Log" ); + $image->descriptionTruncSize = 500; + $image->descriptionHtmlSyndicated = true; + $rss->image = $image; + if ( ( isset( $logs['logs'] ) ) && ( is_array( $logs['logs'] ) ) ) { + foreach( array_reverse( $logs['logs'] ) as $log ) { + $item = new FeedItem(); + $description = ''; + foreach( $log as $key => $value ) { + if ( substr( $key , 0 , 3) !== 'pml' ) { + $description .= '' . h( $key ) . ' : ' . h( $value ) . '
'; + } + } + $item->description = $description; + if ( isset( $log['pmld'] ) ) { + $item->date = $log['pmld']; + } + if ( isset( $log[ $title ] ) ) { + $item->title = $log[ $title ]; + } else { + $item->title = current( $log ) . ' - ' . sha1( serialize( $log ) ); + } + if ( $format === 'ATOM' ) { + $item->author = 'PmL'; + } + $item->link = $link . '&' . $log['pmlo']; + $item->guid = $link . '&' . $log['pmlo']; + $item->descriptionTruncSize = 500; + $item->descriptionHtmlSyndicated = true; + $rss->addItem($item); + } + } + $rss->outputFeed( $tz , $format ); + break; + + case 'CSV': + header( "Content-Transfer-Encoding: binary" ); + header( "Content-Disposition: attachment;filename=PimpMyLog_" . get_slug( $file_id) . "_" . date( "Y-m-d-His" ) . '.csv' ); + header( "Content-type: application/vnd.ms-excel; charset=UTF-16LE" ); + echo chr( 255 ) . chr( 254 ) . mb_convert_encoding( array2csv( $logs['logs'] ) , 'UTF-16LE' , 'UTF-8' ); + break; + + case 'XML': + header('Content-type: application/xml', true); + $xml = ''; $xml .= ''; $xml .= generate_xml_from_array( $logs , 'log' ); $xml .= ''; echo $xml; break; case 'JSONPR': header('Content-type: application/json', true); if ( version_compare( PHP_VERSION , '5.4.0' ) >= 0 ) { echo json_encode( $logs , JSON_PRETTY_PRINT ); } else { echo json_indent( json_encode( $logs ) ); } break; case 'JSONP': header('Content-type: application/javascript', true); echo ( isset( $_GET['callback'] ) ) ? $_GET['callback'] : '?'; echo '('; echo json_encode( $logs ); echo ')'; break; case 'JSON': default: header('Content-type: application/json', true); echo json_encode( $logs ); break; } ?> \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/rss.pml.php b/vendor/potsky/pimp-my-log/inc/rss.pml.php new file mode 100644 index 00000000..29b4893b --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/rss.pml.php @@ -0,0 +1,194 @@ + $errstr ) ); + exit( 1 ); + break; + + case E_USER_WARNING: + $return['singlewarning'] = sprintf( __('PHP Warning [%s] %s') , $errno , $errstr ); + break; + + case E_USER_NOTICE: + $return['singlenotice'] = sprintf( __('PHP Notice [%s] %s') , $errno , $errstr ); + break; + + default: + $return['singlewarning'] = sprintf( __('PHP Unknown error [%s] %s') , $errno , $errstr ); + break; + } + return true; +} + +$old_error_handler = set_error_handler( "myErrorHandler" ); + +register_shutdown_function( 'shutdown' ); + +function shutdown() { + $error = error_get_last(); + if ( $error['type'] === E_ERROR ) { + echo json_encode( + array( + 'error' => sprintf( __('PHP Error line %s: %s') , $error['line'] , $error['message'] ) + ) + ); + } +} + + +/* +|-------------------------------------------------------------------------- +| Prepare +|-------------------------------------------------------------------------- +| +*/ +header('Content-type: application/json'); + +$return = array(); + +if ( ! csrf_verify() ) { + $return['error'] = __( 'Please refresh the page.' ); + echo json_encode( $return ); + die(); +} + + +if ( ! isset( $files[ $_POST['file'] ] ) ) { + $return['error'] = __( 'This log file does not exist.' ); + echo json_encode( $return ); + die(); +} +$file_id = $_POST['file']; +$format = $_POST['format']; + +/* +|-------------------------------------------------------------------------- +| Actions +|-------------------------------------------------------------------------- +| +*/ +switch ( @$_POST['action'] ) { + + /* + |-------------------------------------------------------------------------- + | Generate the RSS Link + |-------------------------------------------------------------------------- + | + */ + case 'get_rss_link': + + $url = get_current_url(); + + switch ( $format ) { + case 'ATOM': + case 'RSS': + // http://en.wikipedia.org/wiki/Feed_URI_scheme + /* + $url = str_replace( + array( 'http://' , 'https://' ), + array( 'feed://' , 'feed://' ), + $url + ); + */ + $method = 'nd'; + break; + case 'CSV': + $method = 'nd'; + break; + default: + $method = 'nw'; + break; + } + + $url = str_replace( + array( 'rss.pml.php' ), + array( 'rss.php' ), + $url + ) + . '?f=' . urlencode( $file_id ) + . '&l=' . urlencode( ( isset( $_GET['l'] ) ) ? $_GET['l'] : $lang ) + . '&tz=' . urlencode( $tz ) + . '&format=' . urlencode( $format ) + . '&count=' . ( ( isset( $files[ $file_id ][ 'max' ] ) ) ? urlencode( $files[ $file_id ][ 'max' ] ) : urlencode( LOGS_MAX ) ) + . '&timeout=' . urlencode( MAX_SEARCH_LOG_TIME ) + . '&search=' . urlencode( @$_POST['search'] ) + ; + + $current_user = Sentinel::attempt( $files ); + + // We authenticate the url if a user is logged in + // -> if log is anonymous, the request will be authenticated and if an admin remove + // the anonymous log, this user will always be able to get it + // -> if the log file is protected, this user will be able to get ot according to its rights + if ( ! is_null( $current_user ) ) { + $username = Sentinel::getCurrentUsername(); + $user = Sentinel::getUser( $username ); + $token = $user[ 'at' ]; + $hash = Sentinel::sign( array( 'f' => $_POST['file'] ) , $username ); + + $url = $url . '&t=' . urlencode( $token ) . '&h=' . urlencode( $hash ); + } + + $u = parse_url($url); + $ip = $u['host']; + + if ( filter_var( $ip , FILTER_VALIDATE_IP ) ) { + $return['war'] = ( ! is_not_local_ip( $ip ) ); + } else if ( $ip === 'localhost' ) { + $return['war'] = true; + } else { + $return['war'] = false; + } + + $return['url'] = $url; + $return['met'] = $method; + + break; + + + /* + |-------------------------------------------------------------------------- + | Unknown action... + |-------------------------------------------------------------------------- + | + */ + default: + error_log( 'Unknown action ' . @$_POST['action'] ); + break; +} + + +/* +|-------------------------------------------------------------------------- +| End tuning +|-------------------------------------------------------------------------- +| +*/ +echo json_encode( $return ); +die(); + +?> \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/test.php b/vendor/potsky/pimp-my-log/inc/test.php new file mode 100644 index 00000000..86a661dc --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/test.php @@ -0,0 +1,422 @@ +' . $type . ''; + $r .= '
';
+	$r .= ( $headers === true ) ? 'Regex: ' . $regex . "\n" : '';
+	$r .= ( $headers === true ) ? 'Log  : ' . $logs . "\n" : '';
+	$r .= ( $headers === true ) ? "\n" : '';
+
+	$logs   = array_reverse( explode( "\n" , $logs ) );
+	$rank   = 0;
+	$size   = count( strval( count($logs) ) ) + 2;
+	$blan   = str_pad( '' , $size );
+	$buffer = array();
+
+	foreach( $logs as $log ) {
+
+		$tokens = LogParser::parseLine( $regex , $match , $log , $types );
+
+		if ( is_array( $tokens ) ) {
+			$rank++;
+			$disp = ( $headers ) ? '' : str_pad( '#' . $rank , $size );
+
+			$maxlength = 0;
+			foreach ( $tokens as $token => $value ) $maxlength = max( $maxlength , strlen( $token ) );
+
+			$r .= ( $headers ) ? '' : '' . $disp . $log . "\n";
+
+			foreach ( $tokens as $token => $value ) {
+				if ( substr( $token , 0 , 3 ) === 'pml' ) continue;
+
+				$r .= $blan . '' . str_pad( $token , $maxlength ) . ': ' . $value;
+
+				if ( $token === $multiline ) {
+					if ( count( $buffer ) > 0 ) {
+						$buffer = array_reverse( $buffer );
+						foreach ( $buffer as $append ) {
+							$r .= "\n" . $blan . str_pad( '' , $maxlength ) . '  ' . $append;
+						}
+					}
+				}
+
+				$r .= "\n";
+			}
+
+			$r .= "\n";
+			$buffer = array();
+		}
+
+		else {
+			$buffer[] = $log;
+		}
+
+	}
+
+	$r .= '
'; + return $r; +} + + +/** + * Ajax return for regexp tester + */ +if ( ( @$_POST['action'] === 'regextest' ) && ( file_exists( $access_file ) ) ) { + + $return = array(); + $match = @json_decode( $_POST['m'] , true ); + $types = @json_decode( $_POST['t'] , true ); + $regex = $_POST['r']; + $log = $_POST['l']; + $multiline = $_POST['u']; + + if ( ! is_array( $match ) ) { + $return['err'] = 'inputMatch'; + $return['msg'] = '
' . __('Error') . ' '. __('Match is not a valid associative array') . '
'; + echo json_encode( $return ); + die(); + } + + if ( ! is_array( $types ) ) { + $return['err'] = 'inputTypes'; + $return['msg'] = '
' . __('Error') . ' '. __('Types is not a valid associative array') . '
'; + echo json_encode( $return ); + die(); + } + + if ( @preg_match( $regex , 'this is just a test !' ) === false ) { + $return['err'] = 'inputRegEx'; + $return['msg'] = '
' . __('Error') . ' '. __('RegEx is not a valid PHP PCRE regular expression') . '
'; + echo json_encode( $return ); + die(); + } + + header('Content-type: application/json'); + $return['msg'] = test( '' , $regex , $match, $types, $log , false , $multiline ); + + echo json_encode( $return ); + die(); +} + + +////////////////////// +// Javascript Lemma // +////////////////////// +$lemma = array( + "configuration_copied" => __( "Configuration array has been copied to your clipboard!" ), +); + + +?><?php echo TITLE;?>

'; + echo '
'; + echo __( 'This page is protected for security reasons.'); + echo '
'; + if ( Sentinel::isAuthSet() ) { + echo sprintf( __('%sSign in%s as an administrator to view this page or follow instructions below.') , '' , '' ) . '
'; + } + echo '
' . __('To grant access, please create this temporary file on your server:'); + echo '

'; + echo '
'; + echo '
touch \'' . dirname( __FILE__ ) . DIRECTORY_SEPARATOR . $access_file . '\'
'; + echo ''; + echo '
'; + echo '
' . __("Then reload this page.") . '

'; + echo ''; + echo '
'; + echo '
'; +} +else { +?>
 
1, + 'IP' => 4, + 'Log' => 5, + 'Severity' => 2, + 'Referer' => 7, +); +echo test( $type , $regex , $match , $types , $log ); + +$type = 'Error Apache 2.2 server restart'; +$log = '[Thu Nov 28 14:03:10 2013] [notice] SIGHUP received. Attempting to restart'; +echo test( $type , $regex , $match , $types , $log ); + +$type = 'Error Apache 2.2 without referer'; +$log = '[Wed Nov 27 09:30:11 2013] [error] [client 127.0.0.1] PHP 1. {main}() /Users/potsky/Private/Work/GitHub/PHPApacheLogViewer/inc/get_logs.php:0'; +echo test( $type , $regex , $match , $types , $log ); + +$type = 'Error Apache 2.4 with referer and without module name'; +$log = "[Fri Oct 11 04:41:06.897613 2013] [:error] [pid 61939] [client 192.168.207.71:44171] script '/usr/local/www/apache24/data/test.php' not found or unable to stat, referer: [localhost]"; +$regex = '|^\[(.*) (.*) (.*) (.*):(.*):(.*)\.(.*) (.*)\] \[(.*):(.*)\] \[pid (.*)\] .*\[client (.*):(.*)\] (.*)(, referer: (.*))*$|U'; +$match = array( + 'Date' => array( + 'M' => 2, + 'd' => 3, + 'H' => 4, + 'i' => 5, + 's' => 6, + 'Y' => 8, + ), + 'IP' => 12, + 'Log' => 14, + 'Severity' => 10, + 'Referer' => 16, +); +echo test( $type , $regex , $match , $types , $log ); + +$type = 'Error Apache 2.4 without referer and without module name'; +$log = "[Fri Oct 11 04:41:06.897613 2013] [:error] [pid 61939] [client 192.168.207.71:44171] script '/usr/local/www/apache24/data/test.php' not found or unable to stat"; +echo test( $type , $regex , $match , $types , $log ); + +$type = 'Error Apache 2.4 with referer and with module name'; +$log = "[Sat Nov 24 23:24:18.318257 2012] [authz_core:debug] [pid 21841:tid 140204006696704] mod_authz_core.c(802): [client 80.8.82.242:62269] AH01626: authorization result of Require all granted: granted, referer: http://www.adza.com/"; +$match = array( + 'Date' => array( + 'M' => 2, + 'd' => 3, + 'H' => 4, + 'i' => 5, + 's' => 6, + 'Y' => 8, + ), + 'IP' => 12, + 'Log' => array( ' >>> ' , 9 , 14), + 'Severity' => 10, + 'Referer' => 16, +); +echo test( $type , $regex , $match , $types , $log ); + +$type = 'Access Apache 2.2 with referer and user agent'; +$log = '127.0.0.1 - - [27/Nov/2013:10:20:40 +0100] "GET /~potsky/PHPApacheLogViewer/inc/get_logs.php?ldv=false&file=access&max=27 HTTP/1.1" 200 33 "http://localhost/~potsky/PHPApacheLogViewer/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9) AppleWebKit/537.71 (KHTML, like Gecko) Version/7.0 Safari/537.71"'; +$regex = '|^((\S*) )*(\S*) (\S*) (\S*) \[(.*)\] "(\S*) (.*) (\S*)" ([0-9]*) (.*)( "(.*)" "(.*)"( [0-9]*/([0-9]*))*)*$|U'; +$match = array( + 'CMD' => 7, + 'Code' => 10, + 'Date' => 6, + 'IP' => 3, + 'Referer' => 13, + 'Size' => 11, + 'UA' => 14, + 'URL' => 8, + 'User' => 5, + 'Time' => 16, +); +echo test( $type , $regex , $match , $types , $log ); + +$type = 'Access Apache 2.2 with virtual host referer and user agent'; +$log = 'potsky.com 62.129.4.154 - rb [19/Dec/2013:16:11:22 +0100] "POST /P1mpmyL0g-dev/inc/getlog.pml.php?1387465882519 HTTP/1.1" 200 7660 "https://home.potsky.com/P1mpmyL0g-dev/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11"'; +echo test( $type , $regex , $match , $types , $log ); + +$type = 'Access Apache 2.2 with tuning options'; +$log = '62.129.4.154 - - [29/Nov/2013:18:13:22 +0100] "GET /PimpMyLogs/inc/getlog.pml.php?ldv=true&file=access&max=20 HTTP/1.1" 500 96 "http://www.potsky.com/PimpMyLogs/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9) AppleWebKit/537.71 (KHTML, like Gecko) Version/7.0 Safari/537.71" 10/10003980'; +echo test( $type , $regex , $match , $types , $log ); + +$type = 'Access Apache 2.2 dummy SSL connection'; +$log = '::1 - - [27/Nov/2013:12:02:08 +0100] "OPTIONS * HTTP/1.0" 200 - "-" "Apache/2.2.25 (Unix) mod_ssl/2.2.26 OpenSSL/1.0.1e DAV/2 PHP/5.3.27 (internal dummy connection)"'; +echo test( $type , $regex , $match , $types , $log ); +?>
= 0 ) {
+											echo json_encode( $files , JSON_PRETTY_PRINT );
+										} else {
+											echo json_indent( json_encode( $logs ) );
+										}
+									?>

get_config_file_path(), + ); + if ( is_array( @$files ) ) { + foreach ( $files as $fileid => $file ) { + $paths[ '--> ' . $fileid ] = @$file['path']; + $dir_name = realpath( $file['path'] ); + if ( file_exists( $dir_name ) ) { + while ( $dir_name != dirname( $dir_name ) ) { + $dir_name = dirname( $dir_name ); + $paths[ $dir_name ] = $dir_name; + } + } + } + } + + echo '
'; + echo ''; + echo ''; + foreach ( $paths as $id => $file ) { + + set_error_handler( function($errno, $errstr, $errfile, $errline, array $errcontext) {}); + if ( is_readable($file) ) { + $r = __('Yes'); + $rc = 'success'; + } else { + $r = __('No'); + $rc = 'danger'; + } + if ( is_writable($file) ) { + $w = __('Yes'); + $wc = 'success'; + } else { + $w = __('No'); + $wc = 'danger'; + } + $rp = realpath($file); + restore_error_handler(); + + if ( empty( $rp ) ) { + $rc = 'default'; + $wc = 'default'; + $rp = __('Not allowed by open_basedir restriction'); + } + + echo ' + + + + + + '; + } + echo ''; + echo '
'.__('Read').''.__('Write').'ID'.__('Path').''.__('Real path').'
'. $r .''. $w .''.$id.''.$file.''.$rp.'
'; + + ?>
'; + + echo '
  • '; + if ( MB_SUPPORT === true ) { + echo 'Multibyte String ' . __('Yes') . ''; + } else { + echo 'Multibyte String ' . __('No') . ''; + echo ' (' . sprintf( __('Follow instructions %shere%s to enable') , '' , '' ) . ')'; + } + echo '
  • '; + + echo '
  • '; + if ( GETTEXT_SUPPORT === true ) { + echo 'Gettext ' . __('Yes') . ''; + } else { + echo 'Gettext ' . __('No') . ''; + echo ' (' . sprintf( __('Follow instructions %shere%s to enable') , '' , '' ) . ')'; + } + echo '
  • '; + + echo ''; + ?>
    (.*?).*?(.*)%s', ob_get_clean(), $matches); + echo $matches[2]; + ?>
    '; + } + else if ( $_POST['password'] !== $_POST['password2'] ) { + $return = '
    '; + } + else if ( mb_strlen( $_POST['password'] ) < 6 ) { + $return = '
    '; + } + else { + Sentinel::setAdmin( $_POST['username'] , $_POST['password'] ); + Sentinel::save(); + $return = '
    '; + } + } + ?>

    ';?>






    ' . sprintf( __('User %s does not exist!') , '' . $_POST['username'] . '' ) . '
    '; + } + else if ( $_POST['password'] !== $_POST['password2'] ) { + echo ''; + } + else if ( mb_strlen( $_POST['password'] ) < 6 ) { + echo ''; + } + else { + Sentinel::setUser( $_POST['username'] , $_POST['password'] ); + Sentinel::save(); + echo ''; + } + } + ?>






    \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/upgrade.pml.php b/vendor/potsky/pimp-my-log/inc/upgrade.pml.php new file mode 100644 index 00000000..ff5315b8 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/upgrade.pml.php @@ -0,0 +1,443 @@ + $lines ) ); + die(); + } + else { + echo json_encode( array( 'error' => __('GIT is no more availble, please refresh the page') ) ); + die(); + } + } + else { + echo json_encode( array( 'error' => __('GIT is no more availble, please refresh the page') ) ); + die(); + } + default: + die(); + break; + } +} + + +/* +|-------------------------------------------------------------------------- +| Let's go +|-------------------------------------------------------------------------- +| +*/ +$upgrade = array( + 'footer' => '', + 'alert' => '', + 'current' => '', + 'to' => '', + 'messages' => '', + 'messagesto' => '', +); + +if ( file_exists( '../version.js' ) ) { + $JSl_version = json_decode( clean_json_version( @file_get_contents( '../version.js' ) ) , true ); + $upgrade['current'] = $JSl_version[ 'version' ]; + $default = sprintf ( __( 'Current version %s' ) , $upgrade['current'] ); + $upgrade['footer'] = $default; +} + +else { + $upgrade['footer'] = '' . __( 'Unable to check your current version!') . '';; + echo json_encode( $upgrade ); + die(); +} + + +/* +|-------------------------------------------------------------------------- +| Retrieve remote server upgrade informations +|-------------------------------------------------------------------------- +| +*/ +try { + $args = array( 'http' => array( 'timeout' => 5 ) ); + $args['http']['header'] = "User-Agent: " . $_SERVER[ 'HTTP_USER_AGENT' ] . "\r\n"; + $args['http']['header'] .= "Referer: " . $_SERVER['HTTP_REFERER'] . "\r\n"; + $ctx = stream_context_create( $args ); + $JSr_version = json_decode( clean_json_version( @file_get_contents( PIMPMYLOG_VERSION_URL . '?v=' . $upgrade['current'] . '&w=' . $uuid . '&' . date("U") , false , $ctx ) ), true ); + if ( is_null( $JSr_version ) ) { + throw new Exception( 'Unable to fetch remote version' , 1); + } + + /* + |-------------------------------------------------------------------------- + | Update disabled + |-------------------------------------------------------------------------- + | + | If admin does not want to check for update, quit now + | + */ + if ( false === CHECK_UPGRADE ) { + echo json_encode( $upgrade ); + die(); + } + + + /* + |-------------------------------------------------------------------------- + | Manage messaging system + |-------------------------------------------------------------------------- + | + | We can send a message to all pml users to give them important informations + | about security features, etc... + | + | PML get the local last message and will display new messages only available remotely + | + */ + $local_messages = @$JSl_version['messages']; + $remote_messages = @$JSr_version['messages']; + + if ( ! is_array( $local_messages ) ) $local_messages = array(); + + if ( is_array( $remote_messages ) ) { + + $local_messages_version = 0; + $remote_messages_version = 0; + foreach ( $local_messages as $local_messages_version => $m ) break; + foreach ( $remote_messages as $remote_messages_version => $m ) break; + + // Uncomment this to show all remote messages + // and remote cookie if needed... + //$local_messages_version = 0; + + $new_messages = array(); + $max_messages = 3; + $upgrade['messagesto'] = $remote_messages_version; + $show_only_greater_then = (int)@$_COOKIE['messageshide']; + + // New messages are available, + foreach ( $remote_messages as $version => $message ) { + if ( ( (int)$local_messages_version >= (int)$version ) || ( $max_messages === 0 ) ) break; + if ( (int)$version > $show_only_greater_then ) { + $new_messages[ $version ] = $message; + $max_messages--; + } + } + + if ( count( $new_messages ) > 0 ) { + $message = '
    '; + $message .= ''; + $message .= '' . __( 'Messages from the development team') . ''; + foreach ( $new_messages as $date => $content ) { + $message .= '
    '; + $message .= '
    '; + $message .= '
    '; + $message .= '' . substr( $date , 0 , 4 ) . '-' . substr( $date , 4 , 2 ) . '-' . substr( $date , 6 , 2 ) . ''; + $message .= '
    '; + $message .= '
    '; + $message .= $content; + $message .= '
    '; + $message .= '
    '; + } + $message .= '
    '; + $message .= '
    '; + $message .= ' ' . __("Mark as read") . ''; + $message .= '
    '; + $message .= '
    '; + $message .= '
    '; + + $upgrade['messages'] = $message; + } + + } + + + /* + |-------------------------------------------------------------------------- + | Manage upgrade now + |-------------------------------------------------------------------------- + | + */ + $upgrade['to'] = $JSr_version[ 'version' ]; + + if ( version_compare( $upgrade['current'] , $upgrade['to'] ) < 0 ) { + + $notices = array(); + $html = '
      '; + + if ( ! isset( $JSr_version[ 'changelog' ] ) ) { + $upgrade['footer'] = $default . ' - ' . __( 'Remote version broken!') . ''; + echo json_encode( $upgrade ); + die(); + } + + if ( ! is_array( $JSr_version[ 'changelog' ] ) ) { + $upgrade['footer'] = $default . ' - ' . __( 'Remote version broken!') . ''; + echo json_encode( $upgrade ); + die(); + } + + foreach ( $JSr_version[ 'changelog' ] as $version => $version_details ) { + + if ( version_compare( $upgrade['current'] , $version ) >= 0 ) { + break; + } + + if ( ! is_array( $version_details ) ) { + continue; + } + + if ( isset( $version_details['notice'] ) ) { + $notices[ $version ] = $version_details['notice']; + } + + $html .= '
    • '; + $html .= ( isset( $version_details['released'] ) ) + ? sprintf( __( 'Version %s released on %s' ) , '' . $version . '' , '' . $version_details['released'] . '' ) + : sprintf( __( 'Version %s' ) , '' . $version . '' ) ; + $html .= '
        '; + + foreach ( array( 'new' => 'New' , 'changed' => 'Changed' , 'fixed' => 'Fixed' ) as $type => $type_display ) { + + if ( isset( $version_details[ $type ] ) ) { + + if ( is_array( $version_details[ $type ] ) ) { + + $html .= '
      • ' . $type_display; + $html .= '
          '; + + foreach ( $version_details[ $type ] as $issue ) { + $html .= '
        • ' . preg_replace( '/#([0-9]+)/i' , '#$1' , $issue) . '
        • '; + } + + $html .= '
        '; + $html .= '
      • '; + + } + + } + + } + + $html .= '
      '; + $html .= '
    • '; + + } + + $html .= '
    '; + + $severity = ( count( $notices ) > 0 ) ? 'danger' : 'info'; + + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '' . __( 'An upgrade is available !') . ' '; + $upgrade['alert'] .= sprintf( __( 'You have version %s and version %s is available' ) , '' . $upgrade['current'] . '' , '' . $upgrade['to'] . ''); + $upgrade['alert'] .= ' (' . __( 'release notes') . ')'; + + $upgrade['alert'] .= '
    '; + + // Installation has been done via composer + if ( upgrade_is_composer() ) { + $upgrade['alert'] .= __('Simply composer update in the installation directory'); + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    cd ' . escapeshellarg( realpath( PML_BASE . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR ) ) . '; composer update
    '; + $upgrade['alert'] .= '

    ' . $html . '
    '; + + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + } + + // Auto Update is forbidden when AUTO_UPDATE is false or when auth is enabled but user is not an admin + else if ( ( AUTO_UPGRADE === false ) || ( ( Sentinel::isAuthSet() ) && ( ! Sentinel::isAdmin( Sentinel::getCurrentUsername() ) ) ) ) { + $upgrade['alert'] .= sprintf( __('Simply git pull in your directory or follow instructions %shere%s') , '' , ''); + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    cd ' . PML_BASE . '; git pull
    '; + $upgrade['alert'] .= '

    ' . $html . '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + } + + // .git exists so an upgrade via git pull is perhaps possible + else if ( upgrade_is_git() ) { + + $can_pull = upgrade_can_git_pull(); + + // .git exists and all tests have passed, so we can upgrade + if ( ! is_array( $can_pull ) ) { + $upgrade['alert'] .= '

    ' . $html . '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + } + + // .git exists but there is a problem + else { + + $upgrade['alert'] .= sprintf( __('Your GIT installation cannot be upgraded automatically because of %sthese problems%s.') , '' , '' ); + $upgrade['alert'] .= '

    '; + + switch ( strval( $can_pull[0] ) ) { + + case '2706': + + $upgrade['alert'] .= __('These files are not writable by the web server user:'); + $upgrade['alert'] .= '
      '; + foreach ( $can_pull[1] as $file ) { + $upgrade['alert'] .= '
    • ' . $file . '
    • '; + } + $upgrade['alert'] .= '
    '; + break; + + case '127': + + $upgrade['alert'] .= __('The git command is not in the webserver path'); + break; + + case '0': + + $upgrade['alert'] .= __('You have modified these files:'); + $upgrade['alert'] .= '
      '; + foreach ( $can_pull[1] as $file ) { + $upgrade['alert'] .= '
    • ' . $file . '
    • '; + } + $upgrade['alert'] .= '
    '; + break; + + default: + + $upgrade['alert'] .= __('Unknown error, here is the problem output:'); + $upgrade['alert'] .= '
      '; + foreach ( $can_pull[1] as $file ) { + $upgrade['alert'] .= '
    • ' . $file . '
    • '; + } + $upgrade['alert'] .= '
    '; + break; + } + + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= __('Simply git pull in your installation directory.'); + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    cd ' . PML_BASE . '; git pull
    '; + $upgrade['alert'] .= '

    ' . $html . '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + } + } + + // Standalone version from a tarball file, cannot upgrade now + else { + $upgrade['alert'] .= sprintf( __('Follow instructions %shere%s') , '' , '' ); + $upgrade['alert'] .= '

    ' . $html . '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= ''; + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '
    '; + } + + + if ( count( $notices ) > 0 ) { + + $upgrade['alert'] .= '
    '; + $upgrade['alert'] .= '' . __( 'You should upgrade right now:') . '
      '; + + foreach ( $notices as $version => $notice ) { + $upgrade['alert'] .= '
    • ' . $version . ' : ' . $notice . '
    • '; + } + + $upgrade['alert'] .= '
    '; + + } + + $upgrade['alert'] .= '
    '; + $upgrade['footer'] = '' . sprintf ( __( 'Your version %s is out of date' ) , $upgrade['current'] ) . ''; + + } + + else { + $upgrade['footer'] = sprintf ( __( 'Your version %s is up to date' ) , $upgrade['current'] ); + } + +} + +catch ( Exception $e ) { + $upgrade['footer'] = $default . ' - ' . __( 'Unable to check remote version!') . ''; + echo json_encode( $upgrade ); + die(); +} + +echo json_encode( $upgrade ); +die(); + +?> \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/inc/users.pml.php b/vendor/potsky/pimp-my-log/inc/users.pml.php new file mode 100644 index 00000000..e545e8c9 --- /dev/null +++ b/vendor/potsky/pimp-my-log/inc/users.pml.php @@ -0,0 +1,609 @@ + $errstr ) ); + exit( 1 ); + break; + + case E_USER_WARNING: + $return['singlewarning'] = sprintf( __('PHP Warning [%s] %s') , $errno , $errstr ); + break; + + case E_USER_NOTICE: + $return['singlenotice'] = sprintf( __('PHP Notice [%s] %s') , $errno , $errstr ); + break; + + default: + $return['singlewarning'] = sprintf( __('PHP Unknown error [%s] %s') , $errno , $errstr ); + break; + } + return true; +} + +$old_error_handler = set_error_handler( "myErrorHandler" ); + +register_shutdown_function( 'shutdown' ); + +function shutdown() { + $error = error_get_last(); + if ( $error['type'] === E_ERROR ) { + echo json_encode( + array( + 'error' => sprintf( __('PHP Error line %s: %s') , $error['line'] , $error['message'] ) + ) + ); + } +} + + +/* +|-------------------------------------------------------------------------- +| Prepare +|-------------------------------------------------------------------------- +| +*/ +header('Content-type: application/json'); + +$return = array(); + +if ( ! csrf_verify() ) { + $return['error'] = __( 'Please refresh the page.' ); + echo json_encode( $return ); + die(); +} + + +/* +|-------------------------------------------------------------------------- +| Actions +|-------------------------------------------------------------------------- +| +*/ +switch ( @$_POST['action'] ) { + + /* + |-------------------------------------------------------------------------- + | Change password + |-------------------------------------------------------------------------- + | + */ + case 'change_password': + $password1 = $_POST['password1']; + $password2 = $_POST['password2']; + $password3 = $_POST['password3']; + $username = Sentinel::getCurrentUsername(); + $errors = array(); + $fields = array(); + $doit = true; + + if ( ! Sentinel::isValidPassword( $username , $password1 ) ) { + $errors[] = __( 'Current password is not valid' ); + $fields[] = 'password1'; + $doit = false; + } + if ( mb_strlen( $password2 ) < 6 ) { + $errors[] = __( 'Password must contain at least 6 chars' ); + $fields[] = 'password2'; + $doit = false; + } + if ( $password2 !== $password3 ) { + $errors[] = __( 'Password confirmation is not the same' ); + $fields[] = 'password3'; + $doit = false; + } + + if ( $doit === true ) { + if ( $_SERVER['SERVER_NAME'] === 'demo.pimpmylog.com' ) { + $return['ok'] = __( 'Password has been fakely changed on the demo!' ); + } else { + $return['ok'] = __( 'Password has been successfully changed!' ); + Sentinel::changePassword( $username , $password2 ); + } + } + else { + $return['errors'] = $errors; + $return['fields'] = $fields; + } + + break; + + /* + |-------------------------------------------------------------------------- + | List users + |-------------------------------------------------------------------------- + | + */ + case 'users_list': + + if ( ! Sentinel::isAdmin() ) { + $return['error'] = 'vaffanculo'; + break; + } + + $users = array(); + + foreach ( Sentinel::getUsers() as $username => $user ) { + unset( $user['pwd'] ); + if (isset($user[ 'lastlogin' ]['ts'])) $user[ 'lastlogin' ]['ts'] = date( 'Y/m/d H:i:s' , (int)$user[ 'lastlogin' ]['ts'] ); + if (isset($user[ 'api_lastlogin' ]['ts'])) $user[ 'api_lastlogin' ]['ts'] = date( 'Y/m/d H:i:s' , (int)$user[ 'api_lastlogin' ]['ts'] ); + $user[ 'cd' ] = date( 'Y/m/d H:i:s' , (int)$user[ 'cd' ] ); + $user[ 'u' ] = $username; + $users[] = $user; + } + $return['b'] = $users; + break; + + + /* + |-------------------------------------------------------------------------- + | View a single user + |-------------------------------------------------------------------------- + | + */ + case 'users_view': + + if ( ! Sentinel::isAdmin() ) { + $return['error'] = 'vaffanculo'; + break; + } + + $username = $_POST['u']; + $user = Sentinel::getUser( $username ); + + if ( is_null( $user ) ) { + $return['e'] = sprintf( __('User %s does not exist') , '' . $username . '' ); + } + else { + unset( $user['pwd'] ); + if (isset($user[ 'lastlogin' ]['ts'])) $user[ 'lastlogin' ]['ts'] = date( 'Y/m/d H:i:s' , (int)$user[ 'lastlogin' ]['ts'] ); + if (isset($user[ 'api_lastlogin' ]['ts'])) $user[ 'api_lastlogin' ]['ts'] = date( 'Y/m/d H:i:s' , (int)$user[ 'api_lastlogin' ]['ts'] ); + $user[ 'cd' ] = date( 'Y/m/d H:i:s' , (int)$user[ 'cd' ] ); + $return['b'] = $user; + $return['b']['u'] = $username; + } + + break; + + + /* + |-------------------------------------------------------------------------- + | Edit a single user + |-------------------------------------------------------------------------- + | + */ + case 'users_edit': + + if ( ! Sentinel::isAdmin() ) { + $return['error'] = 'vaffanculo'; + break; + } + + $username = $_POST['u']; + $user = Sentinel::getUser( $_POST['u'] ); + + if ( is_null( $user ) ) { + $return['e'] = sprintf( __('User %s does not exist') , '' . $username . '' ); + } + else { + unset( $user['pwd'] ); + unset( $user['cb'] ); + unset( $user['cd'] ); + unset( $user['lastlogin'] ); + unset( $user['logincount'] ); + $user['pwd'] = ''; + $user['pwd2'] = ''; + $return['b'] = $user; + } + + break; + + + /* + |-------------------------------------------------------------------------- + | Save a user + |-------------------------------------------------------------------------- + | + */ + case 'users_add': + + if ( ! Sentinel::isAdmin() ) { + $return['error'] = 'vaffanculo'; + break; + } + + $username = trim($_POST['username']); + $password = $_POST['password']; + $password2 = $_POST['password2']; + $roles = $_POST['roles']; + $type = $_POST['add-type']; + + unset( $_POST['csrf_token'] ); + unset( $_POST['action'] ); + unset( $_POST['username'] ); + unset( $_POST['password'] ); + unset( $_POST['password2'] ); + unset( $_POST['roles'] ); + unset( $_POST['add-type'] ); + + $logfiles = $_POST; + + $errors = array(); + + if ( empty( $username ) ) { + $errors[ 'username' ] = __( 'Username is required' ); + } + else if ( ( $type === 'add' ) && ( Sentinel::userExists( $username ) ) ) { + $errors[ 'username' ] = sprintf( __('User %s already exists') , '' . $username . '' ); + } + + if ( ( ( $type === 'edit' ) && ( ! empty( $password ) ) ) || ( $type === 'add' ) ) { + if ( mb_strlen( $password ) < 6 ) { + $errors[ 'password' ] = __( 'Password must contain at least 6 chars' ); + } + if ( $password !== $password2 ) { + $errors[ 'password2' ] = __( 'Password confirmation is not the same' ); + } + } + + if ( count( $errors ) === 0 ) { + if ( empty( $password ) ) $password = null; + + if ( $roles === 'admin' ) { + Sentinel::setAdmin( $username , $password ); + if ( $type === 'add' ) Sentinel::log( 'addadmin ' . $username , $current_user ); + } + else { + $logs = array(); + foreach( $logfiles as $fileid => $access ) { + if ( substr( $fileid , 0 , 2 ) === 'f-' ) { + if ( (int)$access === 1 ) { + $logs[ substr( $fileid , 2 ) ] = array( 'r' => true ); + } + } + else if ( substr( $fileid , 0 , 2 ) === 't-' ) { + if ( (int)$access === 1 ) { + $tags[ substr( $fileid , 2 ) ] = array( 'r' => true ); + } + } + } + Sentinel::setUser( $username , $password , array('user') , $logs ); + if ( $type === 'add' ) Sentinel::log( 'adduser ' . $username , $current_user ); + } + Sentinel::save(); + + } + + $return['c'] = count( $errors ); + $return['e'] = $errors; + + break; + + + /* + |-------------------------------------------------------------------------- + | Delete a single user + |-------------------------------------------------------------------------- + | + */ + case 'users_delete': + + if ( ! Sentinel::isAdmin() ) { + $return['error'] = 'vaffanculo'; + break; + } + + $username = $_POST['u']; + + if ( $username === $current_user ) { + $return['error'] = __('Please do not shoot yourself in the foot!'); + } + else { + Sentinel::deleteUser( $username ); + Sentinel::log( 'deleteuser ' . $username , $current_user ); + Sentinel::save(); + } + + break; + + + /* + |-------------------------------------------------------------------------- + | Sign in as a user + |-------------------------------------------------------------------------- + | + */ + case 'users_signinas': + + if ( ! Sentinel::isAdmin() ) { + $return['error'] = 'vaffanculo'; + break; + } + + $username = $_POST['u']; + + if ( $username === $current_user ) { + $return['error'] = 'You are a recursive guy, right?!'; + } + else { + Sentinel::signInAs( $username ); + } + + break; + + + /* + |-------------------------------------------------------------------------- + | List auth logs + |-------------------------------------------------------------------------- + | + */ + case 'authlog': + + if ( ! Sentinel::isAdmin() ) { + $return['error'] = 'vaffanculo'; + break; + } + + $logs = array(); + foreach ( Sentinel::getLogs() as $log ) { + $log[ 2 ] = date( 'Y/m/d H:i:s' , (int)$log[ 2 ] ); + $logs[] = $log; + } + $return['b'] = $logs; + break; + + + /* + |-------------------------------------------------------------------------- + | Anonymous list files + |-------------------------------------------------------------------------- + | + */ + case 'anonymous_list': + + if ( ! Sentinel::isAdmin() ) { + $return['error'] = 'vaffanculo'; + break; + } + + if ( Sentinel::isAnonymousEnabled( $files ) ) { + $r = '
    ' . __('Anonymous access is enabled. Genuine users have to click on the user menu to sign in and access more logs.') . '
    '; + } else { + $r = '
    ' . __('Anonymous access is disabled. All users have to sign in from the sign in screen.') . '
    '; + } + + $r .= '
    '; + $r .= ' '; + $r .= '
    '; + $r .= __("Select which log files can be viewed without being logged."); + $r .= ' ('. __('Toggle all log files') . ')'; + $r .= '
    '; + $r .= '
    '; + + foreach( $files as $file_id => $file ) { + + $fid = h( $file_id ); + $display = $files[ $file_id ][ 'display' ]; + $paths = $files[ $file_id ][ 'path' ]; + $color = 'default'; + + if ( isset( $files[ $file_id ][ 'oid' ] ) ) { + if ( $files[ $file_id ][ 'oid' ] !== $file_id ) continue; + $display = $files[ $file_id ][ 'odisplay' ]; + if ( isset( $files[ $file_id ][ 'count' ] ) ) { + $remain = (int)$files[ $file_id ][ 'count' ] - 1; + if ( $remain === 1 ) { + $paths .= ' ' . __( 'and an other file defined by glob pattern' ); + } + else if ( $remain > 1 ) { + $paths .= ' ' . sprintf( __( 'and %s other possible files defined by glob pattern' ) , $remain ); + } + } + $color = 'warning'; + } + + if ( Sentinel::isLogAnonymous( $file_id ) ) { + $e = 'active btn-success'; + $d = 'btn-default'; + $ec = ' checked="checked"'; + $dc = ''; + } else { + $e = 'btn-default'; + $d = 'active btn-danger'; + $ec = ''; + $dc = ' checked="checked"'; + } + + $r .= '
    '; + $r .= ' '; + $r .= '
    '; + $r .= '
    '; + $r .= ' '; + $r .= ' '; + $r .= '
    '; + $r .= ' '; + $r .= '
    '; + $r .= '
    '; + } + + $r .= ''; + + $return['b'] = $r; + + break; + + /* + |-------------------------------------------------------------------------- + | Anonymous save + |-------------------------------------------------------------------------- + | + */ + case 'anonymous_save': + + if ( ! Sentinel::isAdmin() ) { + $return['error'] = 'vaffanculo'; + break; + } + + unset( $_POST['csrf_token'] ); + unset( $_POST['action'] ); + + $logfiles = $_POST; + + foreach( $logfiles as $fileid => $access ) { + if ( substr( $fileid , 0 , 2 ) === 'f-' ) { + Sentinel::setLogAnonymous( substr( $fileid , 2 ) , ( (int)$access === 1 ) ); + } + } + + Sentinel::save(); + + break; + + /* + |-------------------------------------------------------------------------- + | Profile get + |-------------------------------------------------------------------------- + | + */ + case 'profile_get': + $user = Sentinel::getUser( $current_user ); + $accesstoken = $user['at']; + $hashpresalt = $user['hp']; + $r = ''; + + $r .= '
    '; + $r .= ''; + $r .= '
    '; + $r .= '
    '; + $r .= ''; + $r .= ''; + $r .= '
    '; + $r .= '
    '; + $r .= '
    '; + $r .= '' . __('Copy to clipboard') . ''; + $r .= '
    '; + $r .= '
    '; + $r .= ''; + + $r .= '
    '; + $r .= ''; + $r .= '
    '; + $r .= '
    '; + $r .= ''; + $r .= ''; + $r .= '
    '; + $r .= '
    '; + $r .= '
    '; + $r .= '' . __('Copy to clipboard') . ''; + $r .= '
    '; + $r .= '
    '; + $r .= ''; + + $r .= '
    '; + $r .= ''; + $r .= '
    '; + $r .= ' ' . __('Check to generate both new Access token and new Presalt key'); + $r .= '
    '; + $r .= '
    '; + + $r .= '
    '; + + if ( ( ! isset( $user['api_logincount'] ) ) || ( (int)$user['api_logincount'] === 0 ) ) { + $r .= __('Your credentials have not been used'); + } + else { + $r .= '

    '; + if ( (int)$user['api_logincount'] === 1 ) { + $r .= __('API has been called 1 time'); + } else { + $r .= sprintf( __('API has been called %s times') , (int)$user['api_logincount'] ); + } + $r .= '

    '; + + $r .= sprintf( __('Last API call has been done at %s by IP address %s with user agent %s on URL %s') + , '' . date( 'Y/m/d H:i:s' , (int)$user[ 'api_lastlogin' ]['ts'] ) . '' + , '' . $user[ 'api_lastlogin' ]['ip'] . '' + , '' . $user[ 'api_lastlogin' ]['ua'] . '' + , '' . $user[ 'api_lastlogin' ]['ur'] . '' + ); + } + + $return['b'] = $r; + + break; + + /* + |-------------------------------------------------------------------------- + | Profile save + |-------------------------------------------------------------------------- + | + */ + case 'profile_save': + + if ( @$_POST['regenerate'] === '1' ) { + Sentinel::setUser( $current_user , null , null , null , true ); + Sentinel::save(); + } + + break; + + /* + |-------------------------------------------------------------------------- + | Unknown action... + |-------------------------------------------------------------------------- + | + */ + default: + error_log( 'Unknown action ' . @$_POST['action'] ); + break; +} + + +/* +|-------------------------------------------------------------------------- +| End tuning +|-------------------------------------------------------------------------- +| +*/ +echo json_encode( $return ); +die(); + +?> \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/index.php b/vendor/potsky/pimp-my-log/index.php new file mode 100644 index 00000000..a099c4ea --- /dev/null +++ b/vendor/potsky/pimp-my-log/index.php @@ -0,0 +1,326 @@ +'; + $message .= __( 'Pimp my Log is not configured.'); + $message .= '

    '; + $message .= ' '; + $message .= sprintf( __( 'You can manually copy cfg/config.example.php to %s in the root directory and change parameters. Then refresh this page.' ) , '' . CONFIG_FILE_NAME . '' ); + $message .= '

    '; + $message .= ' '; + $message .= __( 'Or let me try to configure it for you!' ); + $message .= '

    '; + if ( SUHOSIN_LOADED === true ) { + $message .= '
    '; + $message .= sprintf( __('Suhosin extension is loaded, according to its configuration, Pimp My Log could not run normally... More information %shere%s.') , '' , '' ); + $message .= '
    '; + $message .= '

    '; + } + $link_url = 'inc/configure.php?' . $_SERVER['QUERY_STRING']; + $link_msg = __('Configure now'); + include_once 'inc/error.inc.php'; + die(); +} + + +/* +|-------------------------------------------------------------------------- +| Load config and constants +|-------------------------------------------------------------------------- +| +*/ +list( $badges , $files ) = config_load(); + + +/* +|-------------------------------------------------------------------------- +| Login +|-------------------------------------------------------------------------- +| +*/ +$current_user = Sentinel::attempt( $files ); + + +/* +|-------------------------------------------------------------------------- +| Check configuration +|-------------------------------------------------------------------------- +| +*/ +$errors = config_check( $files ); + +if ( $errors === false ) { + $title = __( 'Oups!' ); + $message = '
    '; + $message .= __( 'Your access is disabled, you cannot view any log file.' ); + $message .= '
    '; + $message .= __( 'Please contact your administrator.' ); + $message .= '

    '; + $link_url = '?signout&l=' . $locale; + $link_msg = __('Sign out'); + include_once 'inc/error.inc.php'; + die(); +} + +if ( is_array( $errors ) ) { + $title = __( 'Oups!' ); + $message = '
    '; + $message .= __( 'config.user.json configuration file is buggy:' ) . '
      '; + foreach ( $errors as $error ) { + $message .= '
    • ' . $error . '
    • '; + } + $message .= '
    '; + $message .= '
    '; + $message .= __( 'If you want me to build the configuration for you, please remove file config.user.json at root and click below.' ); + $message .= '

    '; + $link_url = 'inc/configure.php?' . $_SERVER['QUERY_STRING']; + $link_msg = __('Configure now'); + include_once 'inc/error.inc.php'; + die(); +} + + +/* +|-------------------------------------------------------------------------- +| Javascript lemma +|-------------------------------------------------------------------------- +| +*/ +$lemma = array( + 'action' => __( 'Action' ), + 'addadmin' => __( 'Add admin' ), + 'adduser' => __( 'Add user' ), + 'all_access' => __( 'All accesses granted' ), + 'anonymous_ok' => __( 'Anonymous access has been successfully saved!' ), + 'authlogerror' => __( 'There is no log to display and your are connected... It seems that global parameter AUTH_LOG_FILE_COUNT is set to 0. Change this parameter to a higher value to display logs.' ), + 'changepwd' => __( 'Password changed' ), + 'createdby' => __( 'Created by' ), + 'creationdate' => __( 'Created at' ), + 'date' => __( 'Date' ), + 'deleteuser' => __( 'Delete user' ), + 'display_log' => __( '1 log displayed,' ), + 'display_nlogs' => __( '%s logs displayed,' ), + 'error' => __( 'An error occurs!' ), + 'form_invalid' => __( 'Form is invalid:' ), + 'ip' => __( 'IP' ), + 'lastlogin' => __( 'Last login' ), + 'loadmore' => __( 'Still %s to load'), + 'logincount' => __( 'Logins' ), + 'new_log' => __( '1 new log is available' ), + 'new_logs' => __( 'New logs are available' ), + 'new_nlogs' => __( '%s new logs are available' ), + 'no_log' => __( 'No log has been found.' ), + 'notification_deny' => __( 'Notifications are denied for this site. Go to your browser preferences to enable notifications for this site.' ), + 'profile_ok' => __( 'Your profile has been successfully saved!' ), + 'reallydeleteuser' => __( 'Confirm' ), + 'reallysigninuser' => __( 'Confirm' ), + 'regex_invalid' => __( 'Search was done with regular engine' ), + 'regex_valid' => __( 'Search was done with RegEx engine' ), + 'resultcopied' => __( 'Result copied!' ), + 'roles' => __( 'Roles' ), + 'search_no_regex' => __( 'No log has been found with Reg Ex search %s' ), + 'search_no_regular' => __( 'No log has been found with regular search %s' ), + 'signin' => __( 'Sign in' ), + 'signinas' => __( 'Sign in as' ), + 'signinerr' => __( 'Sign in error' ), + 'signinuser' => __('Sign in as'), + 'signout' => __( 'Sign out' ), + 'system' => __( 'System' ), + 'toggle_column' => __( 'Toggle column %s' ), + 'urlcopied' => __( 'URL copied!' ), + 'user' => __( 'User' ), + 'user_add_ok' => __( 'User has been successfully saved!' ), + 'user_api_lastlogin' => __( 'Last API call' ), + 'user_api_logincount' => __( 'API calls' ), + 'user_at' => __( 'Access token' ), + 'user_cb' => __( 'Created by' ), + 'user_cd' => __( 'Created at' ), + 'user_delete_ok' => __( 'User has been successfully deleted!' ), + 'user_hp' => __( 'Presalt key' ), + 'user_lastlogin' => __( 'Last login' ), + 'user_logincount' => __( 'Logins' ), + 'user_logs' => __( 'Log access' ), + 'user_roles' => __( 'Roles' ), + 'useragent' => __( 'User agent' ), + 'username' => __( 'User name' ), + 'users' => __( 'Users' ), + 'youhavebeendisconnected' => __( 'You need to sign in' ), +); + + +/* +|-------------------------------------------------------------------------- +| Session +|-------------------------------------------------------------------------- +| +*/ +$csrf = csrf_get(); + + +/* +|-------------------------------------------------------------------------- +| HTML +|-------------------------------------------------------------------------- +| +*/ +?><?php echo TITLE;?>'; + echo file_get_contents( PML_CONFIG_BASE . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'config.inc.user.css' ); + echo ''; + } else { + echo ''; + } + ?>
    ' . sprintf( __('Welcome in version %s') , $infos['v'] ) . ''; + if ( isset( $infos['welcome'] ) ) { + $print .= '
    ' . $infos['welcome'] . '
    '; + } + $print .= '
    '; + $print .= sprintf( __('The changelog and all informations about this version are available on the %sblog%s.') , '' , '' ); + ?>




     -   - 
    \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/js/ZeroClipboard.swf b/vendor/potsky/pimp-my-log/js/ZeroClipboard.swf new file mode 100644 index 00000000..13bf8e39 Binary files /dev/null and b/vendor/potsky/pimp-my-log/js/ZeroClipboard.swf differ diff --git a/vendor/potsky/pimp-my-log/js/configure.min.js b/vendor/potsky/pimp-my-log/js/configure.min.js new file mode 100644 index 00000000..ec846adb --- /dev/null +++ b/vendor/potsky/pimp-my-log/js/configure.min.js @@ -0,0 +1,8 @@ +/*! pimpmylog - 1.7.9 - 10b502eaf17be208850be61febb044c2fdb86207*/ +/* + * pimpmylog + * http://pimpmylog.com + * + * Copyright (c) 2015 Potsky, contributors + * Licensed under the GPLv3 license. + */var user_wants_auth,user_wants_soft=[],logs_list=[],progressbar_active=function(){"use strict";$(".progress").addClass("progress-striped active")},progressbar_deactive=function(){"use strict";$(".progress").removeClass("progress-striped active")},progressbar_set=function(a){"use strict";var b=Math.max(0,Math.min(100,parseInt(a,10)));$(".progress .sr-only").text(lemma.complete.replace("%s",b)),$(".progress-bar").attr("aria-valuenow",b).width(b+"%")},progressbar_color=function(a){"use strict";$(".progress-bar").removeClass("progress-bar-success progress-bar-info progress-bar-warning progress-bar-success progress-bar-danger"),void 0!==a&&$(".progress-bar").addClass("progress-bar-"+a)},fatal_error=function(a,b){"use strict";a||(a=lemma.error),b?$("#user").html(b):$("#user").text(""),progressbar_color("danger"),progressbar_set(100),$('
    '+a+"
    ").appendTo("#error"),$("#reload").hide(),$("#next").hide()},pml_action=function(a,b,c){"use strict";progressbar_active(),$.ajax({url:"configure.php?"+(new Date).getTime()+"&"+querystring,data:a,type:"POST",dataType:"json"}).fail(function(a,b,c){progressbar_color("danger"),progressbar_set(100),$('
    '+c.message+'
    '+lemma.suhosin+"
    "+$("
    ").html(a.responseText).text()+"
    ").appendTo("#error")}).done(function(a){a.error?fatal_error(a.error,a.notice?a.notice:""):b(a,c)}).always(function(a){a.reload?$("#reload").show():$("#reload").hide(),a.next?$("#next").show():$("#next").hide().unbind("click"),progressbar_deactive()})},action_configure_now=function(a){"use strict";$("#error").text(""),$("#user").text(""),$(".alert").remove(),progressbar_set(90),pml_action({s:"configure",l:a},function(){for(var b in a)user_wants_soft.push([a[b].s,a[b].t]);var c=$.param({a:user_wants_auth,w:uuid,b:user_wants_soft});progressbar_set(100),progressbar_color("success"),$("#congratulations").append('').show(),$("#process").hide(),$("#error").hide(),$("#user").hide(),$("#buttons").hide()})},action_select_logs=function(a){"use strict";var b=a.a,c=a.b,d=a.c,e=40,f=80,g=parseInt(e+(d-c.length)/d*(f-e),10);if(progressbar_set(g),0===c.length)return void action_configure_now(logs_list);var h=c.shift();pml_action({s:"find",so:h},function(a){$("#user").html(a.notice);var e="";0===a.found?progressbar_color("danger"):1===a.found?progressbar_color("warning"):(progressbar_color(),e='style="display:none;"'),$("#find").addClass("table table-striped table-bordered table-hover").append(''+lemma.path+""+lemma.file+""+lemma.type+""+lemma.readable+"");for(var f in a.files)for(var g in a.files[f])if(a.files[f][g]===!0)$("#find tbody").append(""+g+''+lemma.yes+"");else if(a.files[f][g]===!1)$("#find tbody").append(""+g+''+lemma.no+"");else for(var h in a.files[f][g])for(var i in a.files[f][g][h]){var j=a.files[f][g][h][i];$("").data("file",{s:f,f:g+j,t:h}).html(''+g+""+j+""+h+''+lemma.yes+"").appendTo("#find tbody")}$("#find tbody tr").click(function(){$(this).find('input[type="checkbox"]').click()}),$('#find tbody tr input[type="checkbox"]').click(function(a){a.stopPropagation();var b=$(this).prop("checked");b?$(this).parents("tr").addClass("success"):$(this).parents("tr").removeClass("success")}),$("#find #all").click(function(){var a=$(this).prop("checked");$("#find tbody tr").each(function(){var b=$(this).find('input[type="checkbox"]');1===b.length&&($(b).prop("checked",a),a?$(this).addClass("success"):$(this).removeClass("success"))})}),$("#find #all").click(),$("#next").unbind("click").click(function(){var a=!1,e=[];return $(".userpaths").each(function(){var a=$.trim($(this).val()).split(/[\n,]+/);if(a.length>0)for(var b in a){var c=$.trim(a[b]);""!==c&&e.push({s:$(this).data("soft"),t:$(this).data("type"),f:c})}}),e.length>0?void pml_action({s:"check",uf:e},function(a){a.notice?$(".alert").removeClass("alert-success").addClass("alert-danger").html(a.notice):($("#error").text(""),$(".alert").remove(),$('#find tbody tr input[type="checkbox"]:checked').each(function(){logs_list.push($(this).parents("tr").data("file"))}),logs_list=$.merge(logs_list,a.found),action_select_logs({a:b,b:c,c:d}))}):($('#find tbody tr input[type="checkbox"]:checked').each(function(){logs_list.push($(this).parents("tr").data("file")),a=!0}),void(a===!0?($("#error").text(""),$(".alert").remove(),action_select_logs({a:b,b:c,c:d})):$(".alert").removeClass("alert-success").addClass("alert-danger").html(lemma.chooselog)))})})},process_authentication=function(){progressbar_set(5),pml_action({s:"auth"},function(a){$("#user").html(a.notice)})},process_authentication_no=function(){user_wants_auth=!1,process_select_logs()},process_authentication_yes=function(){user_wants_auth=!0,progressbar_set(10),pml_action({s:"authtouch"},function(a){a.notice?($("#user").html(a.notice),progressbar_color("warning")):(progressbar_set(15),$("#user").html(a.authform),$("#authsave").submit(function(a){var b=$("#username").val(),c=$("#password").val(),d=$("#password2").val(),e=!0;return $("#usernamegroup").removeClass("has-error").removeClass("has-success").tooltip("hide"),$("#passwordgroup").removeClass("has-error").removeClass("has-success").tooltip("hide"),$("#password2group").removeClass("has-error").removeClass("has-success").tooltip("hide"),0===b.length&&($("#usernamegroup").addClass("has-error").tooltip("show"),e=!1),c.length<6&&($("#passwordgroup").addClass("has-error").tooltip("show"),e=!1),d!==c&&($("#password2group").addClass("has-error").tooltip("show"),e=!1),e===!0&&process_authentication_save(b,c),a.preventDefault(),!1}))})},process_authentication_save=function(a,b){progressbar_set(25),pml_action({s:"authsave",u:a,p:b},function(a){a.notice===!0?process_select_logs():fatal_error()})},process_select_logs=function(){progressbar_set(30),pml_action({s:"touch"},function(a){a.notice?($("#user").html(a.notice),progressbar_color("warning")):($("#user").html(lemma.pleasewait),progressbar_set(35),pml_action({s:"soft"},function(a){if(a.sofn>0){var b=[];if(a.sofn>1){$("#user").html(a.notice),$("#soft").addClass("table table-striped table-bordered table-hover").append(''+lemma.name+""+lemma.description+""+lemma.notes+"");for(var c in a.soft){var d=a.soft[c].home,e=void 0===d||""===d?a.soft[c].name:''+a.soft[c].name+"";$("").data("softid",c).data("load",a.soft[c].load).html(''+e+""+a.soft[c].desc+""+a.soft[c].notes+"").appendTo("#soft tbody")}$("#soft tbody tr").click(function(){$(this).find('input[type="checkbox"]').click()}),$('#soft tbody tr input[type="checkbox"]').click(function(a){a.stopPropagation();var b=$(this).prop("checked");b?$(this).parents("tr").addClass("success"):$(this).parents("tr").removeClass("success")}),$('#soft tbody tr input[type="checkbox"]').each(function(){$(this).parents("tr").data("load")&&($(this).prop("checked",!0),$(this).parents("tr").addClass("success"))}),$("#soft #all").click(function(){var a=$(this).prop("checked");$("#soft tbody tr").each(function(){var b=$(this).find('input[type="checkbox"]');$(b).prop("checked",a),a?$(this).addClass("success"):$(this).removeClass("success")})}),$("#next").unbind("click").click(function(){$('#soft tbody tr input[type="checkbox"]:checked').each(function(){b.push($(this).parents("tr").data("softid"))}),b.length>0?($("#error").text(""),action_select_logs({a:a.soft,b:b,c:b.length})):$('
    '+lemma.choosesoftware+"
    ").appendTo("#error")})}else{for(var f in a.soft)b.push(f);action_select_logs({a:a.soft,b:b,c:b.length})}}else fatal_error()}))})},clipboard_enable=function(a,b,c,d){$(a).zclip({path:"../js/ZeroClipboard.swf",copy:function(){return $(b).text()},afterCopy:function(){$(a).popover({html:!0,animation:!0,placement:c,container:"body",delay:{show:100,hide:5e3},content:d}).popover("show"),$(a).on("hidden.bs.popover",function(){$(a).show()})}})};$(function(){"use strict";$(".logo").click(function(){location.reload()}),progressbar_set(5),pml_action({s:"exist"},function(){process_authentication()})}); \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/js/login.min.js b/vendor/potsky/pimp-my-log/js/login.min.js new file mode 100644 index 00000000..c3105be5 --- /dev/null +++ b/vendor/potsky/pimp-my-log/js/login.min.js @@ -0,0 +1,8 @@ +/*! pimpmylog - 1.7.9 - 10b502eaf17be208850be61febb044c2fdb86207*/ +/* + * pimpmylog + * http://pimpmylog.com + * + * Copyright (c) 2015 Potsky, contributors + * Licensed under the GPLv3 license. + */$(function(){"use strict";$(".logo").click(function(){document.location.href="?"})}); \ No newline at end of file diff --git a/vendor/potsky/pimp-my-log/js/main.min.js b/vendor/potsky/pimp-my-log/js/main.min.js new file mode 100644 index 00000000..b7930884 --- /dev/null +++ b/vendor/potsky/pimp-my-log/js/main.min.js @@ -0,0 +1,9 @@ +/*! pimpmylog - 1.7.9 - 10b502eaf17be208850be61febb044c2fdb86207*/ +/* + * pimpmylog + * http://pimpmylog.com + * + * Copyright (c) 2015 Potsky, contributors + * Licensed under the GPLv3 license. + */function s(a,b){"use strict";sort=a,sorto=b,reload_page(!0),get_logs(!1,!0)}function get_top_offset(){return parseInt($("#logsbody").find("tr:last-child").data("offset"),10)}function log_selector_init(){$(".logs-selector-yes").click(function(){$(this).parent().find("label.logs-selector-no").removeClass("btn-danger").addClass("btn-default"),$(this).parent().find("label.logs-selector-yes").addClass("btn-success")}),$(".logs-selector-no").click(function(){$(this).parent().find("label.logs-selector-yes").removeClass("btn-success").addClass("btn-default"),$(this).parent().find("label.logs-selector-no").addClass("btn-danger")}),$(".logs-selector-toggler").click(function(){var a=$(this).parents(".logs-selector").find("label.logs-selector-yes:first").hasClass("active");a===!0?$(this).parents(".logs-selector").find("label.logs-selector-no").click():$(this).parents(".logs-selector").find("label.logs-selector-yes").click()})}function get_alert(a,b,c){var d='"}$(function(){"use strict";$("#changeLogModal").on("show.bs.modal",function(){$.ajax({url:"version.js?local=true&callback=?",type:"GET",dataType:"jsonp",jsonp:"pml_version_cb"}).done(function(){}).fail(function(){}).always(function(){})})});var pml_version_cb=function(a){"use strict";var b={fixed:{name:"Fixed","class":"success"},"new":{name:"New","class":"warning"},changed:{name:"Changed","class":"info"}},c='
    Star me on Github if you me!
    ';for(var d in a.changelog){c+='
    ',c+='
    ',c+='

    ',c+=" Version "+d+"",c+=void 0!==a.changelog[d].released?" - released on "+a.changelog[d].released:"",c+="

    ",c+="
    ",c+='
    ',void 0!==a.changelog[d].notice&&(c+='
    '+a.changelog[d].notice+"

    ");for(var e in b)if(void 0!==a.changelog[d][e])for(var f in a.changelog[d][e]){var g=a.changelog[d][e][f].replace(/#([0-9]+)/g,'#$1');c+='
    '+b[e].name+'
    '+g+"
    "}c+="
    ",c+="
    ",c+="
    "}c+='Congrats, you have read the full change log. Here is a for you!',$("#changeLogModal .modal-body").html(c)},type_parser=function(a){"use strict";var b="txt",c="",d=0;if(void 0!==a){var e=a.split("/");void 0!==e[1]?(d=parseInt(a.split("/").slice(-1),10),b=a.split("/").slice(0,-1).join("/")):b=a,e=b.split(":"),void 0!==e[1]&&(c=b.split(":").slice(1).join(":"),b=e[0])}return{parser:b,param:c,cut:d}},val_cut=function(a,b){"use strict";return void 0===b?a:0===b?a:a.length<=Math.abs(b)?a:b>0?a.substr(0,b)+"…":"…"+a.substr(b)},get_logs=function(load_default_values,load_full_file,load_from_get,load_more){"use strict";var wanted_lines;if($(".loadmore").button("loading"),null!==auto_refresh_timer&&(clearTimeout(auto_refresh_timer),auto_refresh_timer=null),load_default_values===!0){if(load_from_get===!0){var found;set_notification("true"===query_parameters.n?!0:"false"===query_parameters.n?!1:files[file].notify),found=files[file].max,void 0!==typeof query_parameters.m&&$("#max option").each(function(){this.value===query_parameters.m&&(found=query_parameters.m)}),set_max(found),found=files[file].refresh,void 0!==typeof query_parameters.r&&$("#autorefresh option").each(function(){this.value===query_parameters.r&&(found=query_parameters.r)}),set_auto_refresh(found),set_columns(void 0!==query_parameters.t?query_parameters.t.split(","):files[file].thinit),sort=void 0!==query_parameters.o?query_parameters.o:files[file].sort,sorto=void 0!==query_parameters.p?query_parameters.p:files[file].order}else set_max(files[file].max),set_auto_refresh(files[file].refresh),set_notification(files[file].notify),set_columns(files[file].thinit),sort=files[file].sort;files[file].export===!1?($("#export").hide(),$("#noexport").show()):export_default!==!1||files[file].export?($("#noexport").hide(),$("#export").show()):($("#export").hide(),$("#noexport").show()),load_full_file=!0}else load_default_values=!1;reload_page(!0),load_full_file===!0?(reset=1,file_size=0,load_more=!1,last_line="",has_loaded_more=!1):(reset=0,load_full_file=!1),$(".loader").toggle(),loading=!0,wanted_lines=$("#max").val();var post_values={ldv:load_default_values,file:file,filesize:file_size,max:wanted_lines,search:$("#search").val(),csrf_token:csrf_token,lastline:last_line,reset:reset};load_more===!0&&(post_values.sp=get_top_offset(),has_loaded_more=!0),$.ajax({url:"inc/getlog.pml.php?"+(new Date).getTime()+"&"+querystring,data:post_values,type:"POST",dataType:"json"}).fail(function(a){return $(".loader").toggle(),loading=!1,a.responseText.indexOf("Pimp My Log Login Match")>-1?(notify("Pimp my Logs ["+files[file].display+"]",lemma.youhavebeendisconnected),void document.location.reload()):a.error?($(".result").hide(),$("#error").show(),$("#errortxt").html(a.responseText),void notify("Pimp my Logs ["+files[file].display+"]",lemma.error)):void 0}).done(function(logs){if($(".loader").toggle(),loading=!1,logs.lastline&&(last_line=logs.lastline,file_size=logs.newfilesize),logs.error)return $(".result").hide(),$("#error").show(),$("#errortxt").html(logs.error),void notify(notification_title.replace("%i",file).replace("%f",files[file].display),lemma.error);if(logs.warning&&pml_alert(logs.warning,"warning"),logs.notice&&pml_alert(logs.notice,"info"),logs.singlewarning&&pml_singlealert(logs.singlewarning,"warning"),logs.singlenotice&&pml_singlealert(logs.singlenotice,"info"),$("#error").hide(),$(".result").show(),logs.full)if(logs.found===!1){var nolog=lemma.no_log;""!==logs.search&&(nolog=logs.regsearch?lemma.search_no_regex.replace("%s",""+logs.search+""):lemma.search_no_regular.replace("%s",""+logs.search+"")),$("#nolog").html(nolog).show(),$("#logshead").hide()}else $("#nolog").text("").hide(),$("#logshead").show();else logs.logs&&($("#nolog").text("").hide(),$("#logshead").show());if(logs.regsearch?($("#searchctn").addClass("has-success"),$("#searchctn").prop("title",lemma.regex_valid)):($("#searchctn").removeClass("has-success"),$("#searchctn").prop("title",lemma.regex_invalid)),logs.headers){$("#logshead").text(""),$(".thmenucol").remove();var thtr=$("").addClass(file);sorto=1===parseInt(sorto,10)?1:-1;var sortn=-1;for(var h in logs.headers){$(".thmenuicon").removeClass("text-danger");var ic="";if(sort===h){sortn=-1*sorto;var q=1===sorto?"up":"down";ic=' '}var a=$('"+logs.headers[h]+ic+"").addClass(h).appendTo(thtr),f;is_column_displayed(h)?f="on":($(a).hide(),f="off"),$('
  • '+logs.headers[h]+"
  • ").appendTo(".thmenu")}thtr.appendTo("#logshead"),set_column_icon(),$(".thmenuitem").click(function(a){a.stopPropagation(),$(this).hasClass("thmenuon")?remove_column($(this).attr("data-h")):add_column($(this).attr("data-h"))})}logs.full&&$("#logsbody").text(""),void 0!==logs.logs&&$("#logsbody tr").removeClass("newlog");var uaparser=new UAParser,rowidx=0,rows=[];for(var log in logs.logs){var tr=$("").addClass(file).data("log",logs.logs[log].pml).data("offset",logs.logs[log].pmlo);for(var c in logs.logs[log])if("pml"!==c&&"pmlo"!==c&&"pmld"!==c){var type=type_parser(files[file].format.types[c]),val=logs.logs[log][c],title=val,output_html=!0;if("-"===val&&(val=""),"uaw3c"===type.parser&&(type.parser="ua",val=val.replace(/\+/g," ")),"badge"===type.parser){var clas;"http"===type.param?clas=badges[type.param][logs.logs[log][c].substr(0,1)]:"severity"===type.param&&(clas=badges[type.param][logs.logs[log][c].toLowerCase()],void 0===clas&&(clas=badges[type.param][logs.logs[log][c]])),void 0===clas&&(clas="default"),val=''+val_cut(val,type.cut)+""}else if("date"===type.parser)title=logs.logs[log].pml,val=val_cut(val,type.cut);else if("numeral"===type.parser)""!==val&&""!==type.param&&(val=numeral(val).format(type.param));else if("ip"===type.parser)val="geo"===type.param?''+val_cut(val,type.cut)+"":''+val_cut(val,type.cut)+"";else if("link"===type.parser)val=''+val_cut(val,type.cut)+"";else if("ua"===type.parser){var ua=uaparser.setUA(val).getResult(),uas=type.param.match(/\{[a-zA-Z.]*\}/g),uaf=!1;for(var k in uas){var d;try{d=eval("ua."+uas[k].replace("{","").replace("}","")),void 0===d&&(d="")}catch(e){d=""}""!==d&&(uaf=!0,type.param=type.param.replace(uas[k],d))}uaf===!0&&(val=$.trim(type.param))}else"preformatted"===type.parser?(val=val_cut(val.replace(/(?:\\r\\n|\\r|\\n)/g,"\n").replace(/\\t/g," "),type.cut),output_html=!1):"prefake"===type.parser?val=val_cut(val.replace(/(?:\r\n|\r|\n)/g,"
    "),type.cut):(val=val_cut(val,type.cut),output_html=!1);output_html===!1&&(val=val.replace(/…/g,"..."));var b=output_html===!0?$("").html(val):$("").text(val);b=b.prop("title",title).addClass("pml-"+c+" pml-"+type.parser).appendTo(tr),is_column_displayed(c)||$(b).hide()}logs.full||(tr.addClass("newlog"),rowidx++),rows.push(tr)}if(logs.full||load_more===!0)$("#logsbody").append(rows);else if($("#logsbody").prepend(rows),has_loaded_more!==!0){var rowd=$("#logsbody tr").length;rowd>wanted_lines&&(rowd-=wanted_lines,$("#logsbody").find("tr:nth-last-child(-n+"+rowd+")").remove())}var older_line_offset=get_top_offset();if(1>=older_line_offset||""!==logs.search&&parseInt(logs.lpo,10)<=1?$(".loadmore").text($(".loadmore").data("nomore-text")).addClass("disabled").prop("disabled","disabled").attr("title",""):$(".loadmore").button("reset").attr("title",sprintf(lemma.loadmore,numeral(older_line_offset).format("0 b"))),void 0!==sort){var i=0,col=-1;if($("#logshead tr th").each(function(){$(this).hasClass(sort)&&(col=i),i++}),col>=0){var tbody=document.getElementById("logsbody"),trs=Array.prototype.slice.call(tbody.rows,0);for(trs=trs.sort(function(a,b){return a=a.cells[col].getAttribute("title"),b=b.cells[col].getAttribute("title"),$.isNumeric(a)&&$.isNumeric(b)?sorto*(parseFloat(a)-parseFloat(b)):sorto*a.toLowerCase().localeCompare(b.toLowerCase())}),i=0;i1&&(rowct=lemma.display_nlogs.replace("%s",rowc)+" "),$("#footer").html(rowct+logs.footer),first_launch===!1&&(logs.full?logs.fingerprint!==fingerprint&&(notify(notification_title.replace("%i",file).replace("%f",files[file].display),lemma.new_logs),fingerprint=logs.fingerprint):1===rowidx?notify(notification_title.replace("%i",file).replace("%f",files[file].display),lemma.new_log):rowidx>1&¬ify(notification_title.replace("%i",file).replace("%f",files[file].display),lemma.new_nlogs.replace("%s",rowidx))),first_launch=!1;var p=Math.max(0,parseInt($("#autorefresh").val(),10));p>0&&(auto_refresh_timer=setTimeout(function(){get_logs()},1e3*p))}).always(function(){})},notification_class="warning",set_notification=function(a){"use strict";void 0===a&&(a=notification),a===!0?($("#notification").removeClass("btn-warning btn-success btn-danger btn-default").addClass("active btn-"+notification_class),notification=!0):($("#notification").removeClass("btn-warning btn-success btn-danger active").addClass("btn-default"),notification=!1)},is_notification=function(){"use strict";return $("#notification").hasClass("active")},notify=function(a,b){"use strict";if("webkitNotifications"in window){var c=window.webkitNotifications.checkPermission();if(0===c){if(notification_class="success",set_notification(),notification===!0&&void 0!==a&¬ification_displayed===!1){notification_displayed=!0;var d=window.webkitNotifications.createNotification("img/icon72.png",a,b);d.onclick=function(){window.focus(),d.close()},d.onclose=function(){notification_displayed=!1},d.show(),setTimeout(function(){try{d.close()}catch(a){}},5e3)}}else 2===c?(notification_class="danger",set_notification()):(notification_class="warning",set_notification(),window.webkitNotifications.requestPermission(function(){notify(a,b)}))}else if("Notification"in window)if("default"===window.Notification.permission)notification_class="warning",set_notification(),window.Notification.requestPermission(function(){notify(a,b)});else if("granted"===window.Notification.permission){if(notification_class="success",set_notification(),notification===!0&&void 0!==a&¬ification_displayed===!1){notification_displayed=!0;var e=new window.Notification(a,{body:b,tag:"Pimp My Log"});e.onclick=function(){this.close()},e.onclose=function(){notification_displayed=!1}}}else if("denied"===window.Notification.permission)return notification_class="danger",void set_notification()},refresh_rss=function(){$("#exModalRefresh").button("loading"),$.ajax({url:$("#exModalUrl").text(),dataType:"text",success:function(a){$("#exModalRefresh").button("reset"),$("#exModalCtn").text(a)}})},get_rss=function(a){return $("#exModalResultLoading").show(),$("#exModalResult").hide(),$("#exModalRefresh").button("loading"),$.ajax({url:"inc/rss.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{csrf_token:csrf_token,action:"get_rss_link",file:file,search:$("#search").val(),format:a}}).always(function(){}).fail(function(a){$("#prBody").html(get_alert("danger",c.message+"
    "+a.responseText,!1))}).done(function(b){b.singlewarning?pml_singlealert(b.singlewarning,"warning"):b.singlenotice?pml_singlealert(b.singlenotice,"info"):b.error?pml_singlealert(b.error,"danger"):"if"===b.met?document.body.innerHTML+="":("nd"===b.met?($("#exModalResultLoading").hide(),$("#exModalResult").hide(),$("#exModalRefresh").button("reset")):$.ajax({url:b.url,dataType:"text",success:function(a){$("#exModalCtn").text(a),$("#exModalResultLoading").hide(),$("#exModalResult").show(),$("#exModalRefresh").button("reset")}}),$("#exModalFormat").text(a),$("#exModalUrl").text(b.url),b.war===!1?$("#exModalWar").hide():$("#exModalWar").show(),$("#exModalOpen").attr("href",b.url),$("#exModal").modal("show"))}),!1};$(function(){"use strict";$("#prModal").on("show.bs.modal",function(){a()});var a=function(a,b,c){return $("#prAlert").html(void 0!==a?get_alert(a,b,c):""),$("#prBody").html(''),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{csrf_token:csrf_token,action:"profile_get"}}).always(function(){}).fail(function(a){$("#prBody").html(get_alert("danger",a.responseText,!1))}).done(function(a){$("#prBody").html(a.singlewarning?get_alert("warning",a.singlewarning,!1):a.singlenotice?get_alert("info",a.singlenotice,!1):a.error?get_alert("danger",a.error,!1):a.b)}),!1};$("#prForm").on("submit",function(a){return b(),a.preventDefault(),!1});var b=function(){$("#prSave").button("loading");var b={csrf_token:csrf_token,action:"profile_save"};return $.each($("#prForm").serializeArray(),function(a,c){b[c.name]=c.value}),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:b}).always(function(){$("#prSave").button("reset")}).fail(function(a){$("#prAlert").html(get_alert("danger",a.responseText,!1))}).done(function(b){return b.singlewarning?($("#prBody").html(get_alert("warning",b.singlewarning,!1)),!1):b.singlenotice?($("#prBody").html(get_alert("info",b.singlenotice,!1)),!1):b.error?($("#prBody").html(get_alert("danger",b.error,!1)),!1):(a("success",lemma.profile_ok,!0),!1)}),!1};$("#cpModal").on("show.bs.modal",function(){$("#cpErr").hide(),$("#password1group").removeClass("has-error"),$("#password2group").removeClass("has-error"),$("#password3group").removeClass("has-error"),$("#cpSave").button("reset"),$("#password1").val(""),$("#password2").val(""),$("#password3").val("")}),$("#changepassword").on("submit",function(a){return $("#password1group").removeClass("has-error"),$("#password2group").removeClass("has-error"),$("#password3group").removeClass("has-error"),$("#cpErr").hide(),$("#cpSave").button("loading"),$("#cpErr").hide(),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{password1:$("#password1").val(),password2:$("#password2").val(),password3:$("#password3").val(),csrf_token:csrf_token,action:"change_password"}}).always(function(){$("#cpSave").button("reset")}).fail(function(a){$("#cpErrM").html(a.responseText).show(),$("#cpErr").show()}).done(function(a){if(a.singlewarning)$("#cpErr").html(get_alert("warning",a.singlewarning,!1)).show();else if(a.singlenotice)$("#cpErr").html(get_alert("info",a.singlenotice,!1)).show();else if(a.error)$("#cpErr").html(get_alert("danger",a.error,!1)).show();else if(a.ok)$("#notice").html(get_alert("success",a.ok,!0)),$("#cpModal").modal("hide");else if(a.errors){$("#cpErrUl").html("");for(var b in a.errors)$("#cpErrUl").append("
  • "+a.errors[b]+"
  • ");for(var b in a.fields)$("#"+a.fields[b]+"group").addClass("has-error");$("#cpErr").show()}}),a.preventDefault(),!1}),$("#umModal").on("show.bs.modal",function(){users_load($("#usermanagement div.tab-pane.active").attr("id"))}),$('#usermanagement a[data-toggle="pill"]').on("shown.bs.tab",function(a){users_load($(a.target).attr("href"))}),$(".roles-user").click(function(){$(this).parent().find("label.roles-admin").removeClass("btn-danger").addClass("btn-default"),$(this).parent().find("label.roles-user").addClass("btn-primary"),$(this).parent().parent().parent().parent().find(".logs-selector").show()}),$(".roles-admin").click(function(){$(this).parent().find("label.roles-user").removeClass("btn-primary").addClass("btn-default"),$(this).parent().find("label.roles-admin").addClass("btn-danger"),$(this).parent().parent().parent().parent().find(".logs-selector").hide()}),$("#umUsersAddForm").on("submit",function(a){return a.preventDefault(),users_add_save(this)}),log_selector_init(),$("#umAnonymousForm").on("submit",function(a){return a.preventDefault(),anonymous_save(this)})});var users_load=function(a){switch(a.replace(/#/g,"")){case"umUsers":users_list();break;case"umAnonymous":anonymous_list();break;case"umAuthLog":users_authlog();break;default:console.log("Oups ! User action "+a+" is unknown !")}},users_list=function(a,b,c){return $("#umUsersListAlert").html(void 0!==a?get_alert(a,b,c):""),$("#umUsersListBody").html(''),$("#umUsersList").show(),$("#umUsersView").hide(),$("#umUsersEdit").hide(),$("#umUsersAdd").hide(),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{csrf_token:csrf_token,action:"users_list"}}).always(function(){}).fail(function(a){$("#umUsersListBody").html(get_alert("danger",a.responseText,!1))}).done(function(a){if(a.singlewarning)$("#umUsersListBody").html(get_alert("warning",a.singlewarning,!1));else if(a.singlenotice)$("#umUsersListBody").html(get_alert("info",a.singlenotice,!1));else if(a.error)$("#umUsersListBody").html(get_alert("danger",a.error,!1));else{var b="",c=a.b.length;b+='
    ',b+='

    '+c+" ",b+=c>1?lemma.users:lemma.user,b+="

    ",b+='
    ',b+=''+lemma.adduser+"",b+="
    ",b+="
    ",b+='
    ',b+='',b+="",b+="",b+='",b+='",b+='",b+='",b+="",b+="",b+="";for(var d in a.b){var e=a.b[d],f=e.u,g=e.roles,h=e.cd,i=(e.logs,e.lastlogin),j="";for(var k in g)switch(g[k]){case"admin":j+=''+g[k]+"";break;case"user":j+=''+g[k]+"";break;default:j+=''+g[k]+""}i=void 0!==i?i.ts:"",b+="",b+='",b+="",b+="",b+="",b+=""}b+="",b+="
    '+lemma.username+"'+lemma.roles+"'+lemma.creationdate+"'+lemma.lastlogin+"
    '+f+""+j+""+h+""+i+"
    ",b+="
    ",$("#umUsersListBody").html(b),$("#userlisttable").bootstrapTable().bootstrapTable("hideLoading")}}),!1},users_view=function(a){$("#umUsersViewBody").html(''),$("#umUsersViewAlert").html(""),$("#umUsersList").hide(),$("#umUsersView").show(),$("#umUsersEdit").hide(),$("#umUsersAdd").hide();var b=$(a).data("user")?$(a).data("user"):$(a).text();return $("#umUserEditBtn").data("user",b).show(),currentuser===b?$("#umUserEditBtn").attr("disabled","disabled"):$("#umUserEditBtn").removeAttr("disabled"),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{csrf_token:csrf_token,action:"users_view",u:b}}).always(function(){}).fail(function(a){$("#umUsersViewBody").html(get_alert("danger",a.responseText,!1))}).done(function(a){if(a.singlewarning)return $("#umUsersViewBody").html(get_alert("warning",a.singlewarning,!1)),!1;if(a.singlenotice)return $("#umUsersViewBody").html(get_alert("info",a.singlenotice,!1)),!1;if(a.error)return $("#umUsersViewBody").html(get_alert("danger",a.error,!1)),!1;if(a.e)return $("#umUsersViewBody").html(get_alert("danger",a.e,!1)),!1;var c="",d=new UAParser,e=$.inArray("admin",a.b.roles)>-1;c+='
    ',c+='

    '+a.b.u+"

    ",c+='
    ',currentuser!==b&&(c+='
    ',c+=' ",c+=' ",c+="
    ",c+=" ",c+='
    ',c+=' ",c+=' ",c+="
    "),c+="
    ",c+="
    ",c+='',c+="";for(var f in a.b){var g=a.b[f];if("api_lastlogin"===f){var h=d.setUA(g.ua).getResult();g='").text(g.ur).html()+'" target="_blank" class="hyphen">'+g.ur+"
    "+g.ts+"
    "+g.ip+"
    "+h.browser.name+" "+h.browser.version+" - "+h.os.name+" "+h.os.version}else if("lastlogin"===f){var h=d.setUA(g.ua).getResult();g=g.ts+"
    "+g.ip+"
    "+h.browser.name+" "+h.browser.version+" - "+h.os.name+" "+h.os.version}else if("cb"===f)g||(g=''+lemma.system+"");else{if("u"===f)continue;if("at"===f)continue;if("hp"===f)continue;if("logs"===f)if(e===!0)g=lemma.all_access;else{var i="";for(var j in g)files[j]&&(i+=g[j].r===!0?'").text(files[j].path).html()+'">'+files[j].display+" ":'").text(files[j].path).html()+'">'+files[j].display+" ");g=i}else if("roles"===f){var k="";for(var j in g)switch(g[j]){case"admin":k+=''+g[j]+"";break;case"user":k+=''+g[j]+"";break;default:k+=''+g[j]+""}g=k}}c+="",c+="",c+="",c+=""}c+="",c+="
    "+lemma["user_"+f]+""+g+"
    ",$("#umUsersViewBody").html(c),$(function(){$('[data-toggle="tooltip"]').tooltip()})}),!1},users_add=function(){return $("#umUsersList").hide(),$("#umUsersView").hide(),$("#umUsersEdit").hide(),$("#umUsersAdd").show(),$("#umUsersAddLoader").hide(),$("#umUsersAddBody").show(),$("#umUsersAddPwdHelp").hide(),$("#umUsersAdd").find("label.logs-selector-yes").click(),$("#add-roles-user").click(),$("#add-username").val("").removeAttr("readonly"),$("#add-password").val(""),$("#add-password2").val(""),$("#umUsersAddAlert").html(""),$.each($("#umUsersAddForm").serializeArray(),function(a,b){$("#add-"+b.name+"-group").removeClass("has-error")}),$("#add-type").val("add"),$("#umUsersAddBtn").show(),$("#umUsersViewBtn").hide(),!1},users_add_save=function(){$("#umUsersAddSave").button("loading");var a={csrf_token:csrf_token,action:"users_add"};return $.each($("#umUsersAddForm").serializeArray(),function(b,c){$("#add-"+c.name+"-group").removeClass("has-error"),a[c.name]=c.value}),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:a}).always(function(){$("#umUsersAddSave").button("reset")}).fail(function(a){$("#umUsersAddAlert").html(get_alert("danger",a.responseText,!1))}).done(function(a){if(a.singlewarning)return $("#umUsersAddAlert").html(get_alert("warning",a.singlewarning,!1)),!1;if(a.singlenotice)return $("#umUsersAddAlert").html(get_alert("info",a.singlenotice,!1)),!1;if(a.error)return $("#umUsersAddAlert").html(get_alert("danger",a.error,!1)),!1;if(a.c>0){var b=""+lemma.form_invalid+"
      ";for(var c in a.e)b+="
    • "+a.e[c]+"
    • ",$("#add-"+c+"-group").addClass("has-error");return b+="
    ",$("#umUsersAddAlert").html(get_alert("danger",b,!1)),!1}users_list("success",lemma.user_add_ok,!0)}),!1},users_edit=function(a){var b=$(a).data("user");return $("#umUsersList").hide(),$("#umUsersView").hide(),$("#umUsersEdit").hide(),$("#umUsersAdd").show(),$("#umUsersAddLoader").show(),$("#umUsersAddBody").hide(),$("#umUsersAddPwdHelp").show(),$("#umUsersAdd").find("label.logs-selector-no").click(),$("#add-roles-user").click(),$("#add-username").val(b).attr("readonly","readonly"),$("#add-password").val(""),$("#add-password2").val(""),$("#umUsersAddAlert").html(""),$.each($("#umUsersAddForm").serializeArray(),function(a,b){$("#add-"+b.name+"-group").removeClass("has-error")}),$("#add-type").val("edit"),$("#umUsersAddBtn").hide(),$("#umUsersViewBtn").show().data("user",b),$("#umUsersAddSave").button("loading"),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{csrf_token:csrf_token,action:"users_edit",u:b}}).always(function(){$("#umUsersAddSave").button("reset"),$("#umUsersAddLoader").hide(),$("#umUsersAddBody").show()}).fail(function(a){$("#umUsersAddBody").html(get_alert("danger",a.responseText,!1))}).done(function(a){if(a.b.roles)for(var b in a.b.roles)$("#add-roles-"+a.b.roles[b]).click();if(a.b.logs)for(var b in a.b.logs)$("#add-logs-f-"+b+"-"+a.b.logs[b].r).click()}),!1},users_delete=function(a){var b=$(a).parents(".del_base").find("p.lead").text();return $.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{csrf_token:csrf_token,action:"users_delete",u:b}}).always(function(){}).fail(function(a){$("#umUsersViewAlert").html(get_alert("danger",a.responseText,!1))}).done(function(a){return a.singlewarning?($("#umUsersViewAlert").html(get_alert("warning",a.singlewarning,!1)),!1):a.singlenotice?($("#umUsersViewAlert").html(get_alert("info",a.singlenotice,!1)),!1):a.error?($("#umUsersViewAlert").html(get_alert("danger",a.error,!1)),!1):(users_list("success",lemma.user_delete_ok,!0),!1)}),!1},users_signinas=function(a){var b=$(a).parents(".del_base").find("p.lead").text();return $.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{csrf_token:csrf_token,action:"users_signinas",u:b}}).always(function(){}).fail(function(a){$("#umUsersViewAlert").html(get_alert("danger",a.responseText,!1))}).done(function(a){return a.singlewarning?($("#umUsersViewAlert").html(get_alert("warning",a.singlewarning,!1)),!1):a.singlenotice?($("#umUsersViewAlert").html(get_alert("info",a.singlenotice,!1)),!1):a.error?($("#umUsersViewAlert").html(get_alert("danger",a.error,!1)),!1):(document.location.reload(),!1)}),!1},users_logfiles=function(){},users_authlog=function(){return $("#umAuthLogBody").html(''),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{csrf_token:csrf_token,action:"authlog"}}).always(function(){}).fail(function(a){$("#umAuthLogBody").html(get_alert("danger",a.responseText,!1))}).done(function(a){if(a.singlewarning)return $("#umAuthLogBody").html(get_alert("warning",a.singlewarning,!1)),!1;if(a.singlenotice)return $("#umAuthLogBody").html(get_alert("info",a.singlenotice,!1)),!1;if(a.error)return $("#umAuthLogBody").html(get_alert("danger",a.error,!1)),!1;var b=a.b.length,c="";if(b>0){var d=new UAParser;c+='
    ',c+='',c+="",c+="",c+='",c+='",c+='",c+='",c+='",c+="",c+="",c+="";for(var e=0;b>e;e++){var f=a.b[e][2],g=a.b[e][0],h=a.b[e][1],i=a.b[e][3],j=d.setUA(a.b[e][4]).getResult();switch(j=j.browser.name+" "+j.browser.version+" - "+j.os.name+" "+j.os.version,g){case"signin":g=''+lemma.signin+"";break;case"signinerr":g=''+lemma.signinerr+"";break;case"signout":g=''+lemma.signout+"";break;case"changepwd":g=''+lemma.changepwd+""}g=g.replace(/^addadmin/,''+lemma.addadmin+""),g=g.replace(/^adduser/,''+lemma.adduser+""),g=g.replace(/^signinas/,''+lemma.signinas+""),g=g.replace(/^deleteuser/,''+lemma.deleteuser+""),c+="",c+="",c+="",c+="",c+="",c+='",c+=""}c+="",c+="
    '+lemma.date+"'+lemma.username+"'+lemma.action+"'+lemma.ip+"'+lemma.useragent+"
    "+f+""+h+""+g+""+i+"").text(a.b[e][4]).html()+'">'+j+"
    ",c+="
    "}else c=get_alert("info",lemma.authlogerror,!1);$("#umAuthLogBody").html(c),$("#authlogtable").bootstrapTable().bootstrapTable("hideLoading")}),!1},anonymous_save=function(){$("#umAnonymousSave").button("loading");var a={csrf_token:csrf_token,action:"anonymous_save"};return $.each($("#umAnonymousForm").serializeArray(),function(b,c){a[c.name]=c.value}),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:a}).always(function(){$("#umAnonymousSave").button("reset") +}).fail(function(a){$("#umAnonymousAlert").html(get_alert("danger",a.responseText,!1))}).done(function(a){return a.singlewarning?($("#umAnonymousBody").html(get_alert("warning",a.singlewarning,!1)),!1):a.singlenotice?($("#umAnonymousBody").html(get_alert("info",a.singlenotice,!1)),!1):a.error?($("#umAnonymousBody").html(get_alert("danger",a.error,!1)),!1):(anonymous_list("success",lemma.anonymous_ok,!0),!1)}),!1},anonymous_list=function(a,b,c){return $("#umAnonymousAlert").html(void 0!==a?get_alert(a,b,c):""),$("#umAnonymousBody").html(''),$.ajax({url:"inc/users.pml.php?"+(new Date).getTime()+"&"+querystring,type:"POST",dataType:"json",data:{csrf_token:csrf_token,action:"anonymous_list"}}).always(function(){}).fail(function(a){$("#umAnonymousBody").html(get_alert("danger",a.responseText,!1))}).done(function(a){a.singlewarning?$("#umAnonymousBody").html(get_alert("warning",a.singlewarning,!1)):a.singlenotice?$("#umAnonymousBody").html(get_alert("info",a.singlenotice,!1)):a.error?$("#umAnonymousBody").html(get_alert("danger",a.error,!1)):($("#umAnonymousBody").html(a.b),log_selector_init())}),!1},file,notification,displayed_th,auto_refresh_timer,fingerprint,first_launch,file_size,last_line,loading,reset,sort,sorto,has_loaded_more=!1,notification_displayed=!1,query_parameters=function(){for(var a,b=[],c=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),d=0;d0?$(".thmenuicon").addClass("text-danger"):$(".thmenuicon").removeClass("text-danger")},parse_columns=function(){var a=[];$(".thmenuon").each(function(){a.push($(this).data("h"))}),set_columns(a),set_column_icon(),reload_page(!0)},custom_columns=function(){"use strict";return $.isArray(displayed_th)},is_column_displayed=function(a){"use strict";return $.isArray(displayed_th)===!0?$.inArray(a,displayed_th)>-1:!0},remove_column=function(a){"use strict";$('.thmenuitem[data-h="'+a+'"]').removeClass("thmenuon"),$('.thmenuitem[data-h="'+a+'"]').addClass("thmenuoff"),$("."+a).hide(),$(".pml-"+a).hide(),parse_columns()},add_column=function(a){"use strict";$('.thmenuitem[data-h="'+a+'"]').removeClass("thmenuoff"),$('.thmenuitem[data-h="'+a+'"]').addClass("thmenuon"),$("."+a).show(),$(".pml-"+a).show(),parse_columns()},set_title=function(){"use strict";document.title=title_file.replace("%i",file).replace("%f",files[file].display)},pml_alert=function(a,b){"use strict";$('
    '+a+"
    ").appendTo("#notice")},pml_singlealert=function(a,b){"use strict";$("#singlenotice").html('
    '+a+"
    ")};$(function(){"use strict";function a(a){return a?"addClass":"removeClass"}$("#singlelog").length?(file=$("#singlelog").data("file"),set_title(),get_logs(!0)):"bs"===file_selector?($(".file_menup.active").length||$(".file_menup:first").addClass("active"),$("#file_selector").text($(".file_menup.active:first a").text()),file=$(".file_menup.active").data("file"),set_title(),$(".file_menu").click(function(){$("#file_selector").text($(this).text()),$(".file_menup").removeClass("active"),$(this).parent().addClass("active"),file=$(this).parent().data("file"),set_title(),get_logs(!0)})):(file=$("#file_selector_big").val(),set_title(),$("#file_selector_big").change(function(){file=$("#file_selector_big").val(),set_title(),get_logs(!0)})),$(".logo").click(function(){document.location.href="?"}),$("#refresh").click(function(){notify(),get_logs()}),$(".cog").click(function(){switch($(this).data("cog")){case"wideview":"on"===$(this).data("value")?($(this).data("value","off"),$(this).find(".cogon").hide(),$(this).find(".cogoff").show(),$(".tableresult").removeClass("containerwide").addClass("container")):($(this).data("value","on"),$(this).find(".cogoff").hide(),$(this).find(".cogon").show(),$(".tableresult").addClass("containerwide").removeClass("container"))}reload_page(!0)}),$(".cog").each(function(){"on"===$(this).data("value")?($(this).find(".cogon").show(),$(this).find(".cogoff").hide(),$(".tableresult").addClass("containerwide").removeClass("container")):($(this).find(".cogon").hide(),$(this).find(".cogoff").show(),$(".tableresult").addClass("container").removeClass("containerwide"))}),$("#cog-lang").change(function(){reload_page()}),$("#cog-tz").change(function(){reload_page()}),$(document).keypress(function(a){$(a.target).is("input, textarea")||(114===a.which?(notify(),get_logs()):(102===a.which||47===a.which)&&(a.preventDefault(),$("#search").focus()))}),$(document).on("input",".clearable",function(){$(this)[a(this.value)]("x")}).on("mousemove",".x",function(b){$(this)[a(this.offsetWidth-18d;d++)E[c[d]]=!!(c[d]in u);return E.list&&(E.list=!(!b.createElement("datalist")||!a.HTMLDataListElement)),E}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),o.inputtypes=function(a){for(var d,e,f,g=0,h=a.length;h>g;g++)u.setAttribute("type",e=a[g]),d="text"!==u.type,d&&(u.value=v,u.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(e)&&u.style.WebkitAppearance!==c?(q.appendChild(u),f=b.defaultView,d=f.getComputedStyle&&"textfield"!==f.getComputedStyle(u,null).WebkitAppearance&&0!==u.offsetHeight,q.removeChild(u)):/^(search|tel)$/.test(e)||(d=/^(url|email)$/.test(e)?u.checkValidity&&u.checkValidity()===!1:u.value!=v)),D[a[g]]=!!d;return D}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var l,m,n="2.8.3",o={},p=!0,q=b.documentElement,r="modernizr",s=b.createElement(r),t=s.style,u=b.createElement("input"),v=":)",w={}.toString,x=" -webkit- -moz- -o- -ms- ".split(" "),y="Webkit Moz O ms",z=y.split(" "),A=y.toLowerCase().split(" "),B={svg:"http://www.w3.org/2000/svg"},C={},D={},E={},F=[],G=F.slice,H=function(a,c,d,e){var f,g,h,i,j=b.createElement("div"),k=b.body,l=k||b.createElement("body");if(parseInt(d,10))for(;d--;)h=b.createElement("div"),h.id=e?e[d]:r+(d+1),j.appendChild(h);return f=["­",'"].join(""),j.id=r,(k?j:l).innerHTML+=f,l.appendChild(j),k||(l.style.background="",l.style.overflow="hidden",i=q.style.overflow,q.style.overflow="hidden",q.appendChild(l)),g=c(j,a),k?j.parentNode.removeChild(j):(l.parentNode.removeChild(l),q.style.overflow=i),!!g},I=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b)&&c(b).matches||!1;var d;return H("@media "+b+" { #"+r+" { position: absolute; } }",function(b){d="absolute"==(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle).position}),d},J=function(){function a(a,e){e=e||b.createElement(d[a]||"div"),a="on"+a;var g=a in e;return g||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(a,""),g=f(e[a],"function"),f(e[a],"undefined")||(e[a]=c),e.removeAttribute(a))),e=null,g}var d={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return a}(),K={}.hasOwnProperty;m=f(K,"undefined")||f(K.call,"undefined")?function(a,b){return b in a&&f(a.constructor.prototype[b],"undefined")}:function(a,b){return K.call(a,b)},Function.prototype.bind||(Function.prototype.bind=function(a){var b=this;if("function"!=typeof b)throw new TypeError;var c=G.call(arguments,1),d=function(){if(this instanceof d){var e=function(){};e.prototype=b.prototype;var f=new e,g=b.apply(f,c.concat(G.call(arguments)));return Object(g)===g?g:f}return b.apply(a,c.concat(G.call(arguments)))};return d}),C.flexbox=function(){return j("flexWrap")},C.flexboxlegacy=function(){return j("boxDirection")},C.canvas=function(){var a=b.createElement("canvas");return!(!a.getContext||!a.getContext("2d"))},C.canvastext=function(){return!(!o.canvas||!f(b.createElement("canvas").getContext("2d").fillText,"function"))},C.webgl=function(){return!!a.WebGLRenderingContext},C.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:H(["@media (",x.join("touch-enabled),("),r,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=9===a.offsetTop}),c},C.geolocation=function(){return"geolocation"in navigator},C.postmessage=function(){return!!a.postMessage},C.websqldatabase=function(){return!!a.openDatabase},C.indexedDB=function(){return!!j("indexedDB",a)},C.hashchange=function(){return J("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},C.history=function(){return!(!a.history||!history.pushState)},C.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},C.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},C.rgba=function(){return d("background-color:rgba(150,255,150,.5)"),g(t.backgroundColor,"rgba")},C.hsla=function(){return d("background-color:hsla(120,40%,100%,.5)"),g(t.backgroundColor,"rgba")||g(t.backgroundColor,"hsla")},C.multiplebgs=function(){return d("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(t.background)},C.backgroundsize=function(){return j("backgroundSize")},C.borderimage=function(){return j("borderImage")},C.borderradius=function(){return j("borderRadius")},C.boxshadow=function(){return j("boxShadow")},C.textshadow=function(){return""===b.createElement("div").style.textShadow},C.opacity=function(){return e("opacity:.55"),/^0.55$/.test(t.opacity)},C.cssanimations=function(){return j("animationName")},C.csscolumns=function(){return j("columnCount")},C.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return d((a+"-webkit- ".split(" ").join(b+a)+x.join(c+a)).slice(0,-a.length)),g(t.backgroundImage,"gradient")},C.cssreflections=function(){return j("boxReflect")},C.csstransforms=function(){return!!j("transform")},C.csstransforms3d=function(){var a=!!j("perspective");return a&&"webkitPerspective"in q.style&&H("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b){a=9===b.offsetLeft&&3===b.offsetHeight}),a},C.csstransitions=function(){return j("transition")},C.fontface=function(){var a;return H('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&0===g.indexOf(d.split(" ")[0])}),a},C.generatedcontent=function(){var a;return H(["#",r,"{font:0/0 a}#",r,':after{content:"',v,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},C.video=function(){var a=b.createElement("video"),c=!1;try{(c=!!a.canPlayType)&&(c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,""))}catch(d){}return c},C.audio=function(){var a=b.createElement("audio"),c=!1;try{(c=!!a.canPlayType)&&(c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,""))}catch(d){}return c},C.localstorage=function(){try{return localStorage.setItem(r,r),localStorage.removeItem(r),!0}catch(a){return!1}},C.sessionstorage=function(){try{return sessionStorage.setItem(r,r),sessionStorage.removeItem(r),!0}catch(a){return!1}},C.webworkers=function(){return!!a.Worker},C.applicationcache=function(){return!!a.applicationCache},C.svg=function(){return!!b.createElementNS&&!!b.createElementNS(B.svg,"svg").createSVGRect},C.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==B.svg},C.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(w.call(b.createElementNS(B.svg,"animate")))},C.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(w.call(b.createElementNS(B.svg,"clipPath")))};for(var L in C)m(C,L)&&(l=L.toLowerCase(),o[l]=C[L](),F.push((o[l]?"":"no-")+l));return o.input||k(),o.addTest=function(a,b){if("object"==typeof a)for(var d in a)m(a,d)&&o.addTest(d,a[d]);else{if(a=a.toLowerCase(),o[a]!==c)return o;b="function"==typeof b?b():b,"undefined"!=typeof p&&p&&(q.className+=" "+(b?"":"no-")+a),o[a]=b}return o},d(""),s=u=null,function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=s.elements;return"string"==typeof a?a.split(" "):a}function e(a){var b=r[a[p]];return b||(b={},q++,a[p]=q,r[q]=b),b}function f(a,c,d){if(c||(c=b),k)return c.createElement(a);d||(d=e(c));var f;return f=d.cache[a]?d.cache[a].cloneNode():o.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!f.canHaveChildren||n.test(a)||f.tagUrn?f:d.frag.appendChild(f)}function g(a,c){if(a||(a=b),k)return a.createDocumentFragment();c=c||e(a);for(var f=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)f.createElement(h[g]);return f}function h(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return s.shivMethods?f(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(s,b.frag)}function i(a){a||(a=b);var d=e(a);return!s.shivCSS||j||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),k||h(a,d),a}var j,k,l="3.7.0",m=a.html5||{},n=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,o=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,p="_html5shiv",q=0,r={};!function(){try{var a=b.createElement("a");a.innerHTML="",j="hidden"in a,k=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){j=!0,k=!0}}();var s={elements:m.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:l,shivCSS:m.shivCSS!==!1,supportsUnknownElements:k,shivMethods:m.shivMethods!==!1,type:"default",shivDocument:i,createElement:f,createDocumentFragment:g};a.html5=s,i(b)}(this,b),o._version=n,o._prefixes=x,o._domPrefixes=A,o._cssomPrefixes=z,o.mq=I,o.hasEvent=J,o.testProp=function(a){return h([a])},o.testAllProps=j,o.testStyles=H,o.prefixed=function(a,b,c){return b?j(a,b,c):j(a,"pfx")},q.className=q.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(p?" js "+F.join(" "):""),o}(this,this.document),function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),void(h=a.setTimeout(u,p));g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b0&&b-1 in a}function d(a,b,c){if(_.isFunction(b))return _.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return _.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(hb.test(b))return _.filter(b,a,c);b=_.filter(b,a)}return _.grep(a,function(a){return U.call(b,a)>=0!==c})}function e(a,b){for(;(a=a[b])&&1!==a.nodeType;);return a}function f(a){var b=ob[a]={};return _.each(a.match(nb)||[],function(a,c){b[c]=!0}),b}function g(){Z.removeEventListener("DOMContentLoaded",g,!1),a.removeEventListener("load",g,!1),_.ready()}function h(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=_.expando+h.uid++}function i(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(ub,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:tb.test(c)?_.parseJSON(c):c}catch(e){}sb.set(a,b,c)}else c=void 0;return c}function j(){return!0}function k(){return!1}function l(){try{return Z.activeElement}catch(a){}}function m(a,b){return _.nodeName(a,"table")&&_.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function n(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function o(a){var b=Kb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function p(a,b){for(var c=0,d=a.length;d>c;c++)rb.set(a[c],"globalEval",!b||rb.get(b[c],"globalEval"))}function q(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(rb.hasData(a)&&(f=rb.access(a),g=rb.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)_.event.add(b,e,j[e][c])}sb.hasData(a)&&(h=sb.access(a),i=_.extend({},h),sb.set(b,i))}}function r(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&_.nodeName(a,b)?_.merge([a],c):c}function s(a,b){var c=b.nodeName.toLowerCase();"input"===c&&yb.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}function t(b,c){var d,e=_(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:_.css(e[0],"display");return e.detach(),f}function u(a){var b=Z,c=Ob[a];return c||(c=t(a,b),"none"!==c&&c||(Nb=(Nb||_("

    And don’t worry, Pimp my Log will be in open-source forever.

    potsky

    ", + "20141011" : "This message will never be shown. Never Gonna Give You Up !" + }, + "changelog" : { + "1.7.9" : { + "released" : "2015-05-27", + "new" : [ + "Support for date formats (parsing): U,c and r" + ] + }, + "1.7.8" : { + "released" : "2015-05-23", + "fixed" : [ + "Restore support for PHP 5.2" + ] + }, + "1.7.7" : { + "released" : "2014-12-23", + "fixed" : [ + "Cut text in modes txt, pre and preformatted displays hellip; instead of ..." + ] + }, + "1.7.6" : { + "released" : "2014-12-23", + "new" : [ + "New type 'preformatted': it is the same type as 'pre' but new lines and tabs strings are replaced by real new lines and real tabs. var_dump and other debug functions returns are now well displayed!" + ], + "changed" : [ + "Type output change: txt, pre and preformatted types (not prefake) are now in full raw text format. HTML is no more parsed. Use 'prefake' to let HTML in your logs be parsed", + "Dev: javascript files are split now, Grunt will concat them before uglifying" + ], + "fixed" : [ + "Configuration : max, refresh and notifiy parameters was no more optional since Pimp My Log 1.6" + ] + }, + "1.7.5" : { + "released" : "2014-12-03", + "new" : [ + "Rick has hidden an Easter Egg. In december, I know... but will you find it?" + ], + "fixed" : [ + "Bug with the open_basedir ini parameter set with non composer installations (more info)" + ], + "changed" : [ + "Session rewrite to alert user if the PHP session path is not in the open_basedir ini parameter", + "Admins can connect to the debugger without creating a lock file", + "Add the changelog link in the footer (#90)", + "Debugger configuration rights check now manages unallowed paths due to open_basedir restrictions", + "Add the debugger link in the footer" + ] + }, + "1.7.4" : { + "released" : "2014-12-01", + "fixed" : [ + "Ugly error on installations without authentication" + ] + }, + "1.7.3" : { + "released" : "2014-11-30", + "fixed" : [ + "Fix PHP error log format for PHP 5.3 (#89)", + "Fix a javascript error when viewing a user who is granted on a non-existing log file", + "Signout was an UI infinite loop when the last anonymous file did not exist anymore" + ] + }, + "1.7.2" : { + "released" : "2014-11-29", + "new" : [ + "Alert message when Suhosin PHP extension is loaded", + "Enable the SAFE_MODE latch file to let users run Pimp My Log on ultra secured PHP installations (more info)" + ], + "changed" : [ + "Glob files are now considered as unique for user access" + ], + "fixed" : [ + "Fix the user management popup in Firefox (more info)" + ] + }, + "1.7.1" : { + "released" : "2014-11-23", + "changed" : [ + "Debugger : add new informations in the configuration panel", + "User management : add tooltips to show the log file path", + "Unit tests : add IIS and nginx logs" + ], + "fixed" : [ + "PML now supports PHP installations without multibytes functions (mb_) in degraded mode", + "Fix the modal scrolling when admin has to manage to many log files" + ] + }, + "1.7.0" : { + "released" : "2014-11-22", + "new" : [ + "Install with composer is now operational, you can update without losing your configuration files (#85)" + ], + "fixed" : [ + "Password recovery check on password length was buggy" + ] + }, + "1.6.4" : { + "released" : "2014-11-21", + "changed" : [ + "Add commands to upgrade via git and via composer", + "Add a copy to clipboard buttons for upgrade commands" + ] + }, + "1.6.3" : { + "released" : "2014-11-21", + "new" : [ + "Install with composer" + ] + }, + "1.6.2" : { + "released" : "2014-11-21", + "new" : [ + "Change autoupgrade message : add a link to the blog for the change log and more" + ] + }, + "1.6.1" : { + "released" : "2014-11-19", + "fixed" : [ + "Tag sorting on PHP 5.2 and PHP 5.3" + ] + }, + "1.6.0" : { + "released" : "2014-11-18", + "new" : [ + "Tag your log files and organize them in folders (#80)", + "Auto-upgrade fr GIT installs (#81)" + ], + "fixed" : [ + "Remove a debug trace (#83)", + "Cannot scroll anymore in the log list (#82)", + "Fix RSS and ATOM exports (#84)" + ] + }, + "1.5.2" : { + "released" : "2014-11-14", + "fixed" : [ + "Add a text for the user menu when displayed on a mobile", + "Restore PHP 5.2 support (more info and here)" + ], + "changed" : [ + "Display the export popup for all formats to let users copy the link instead of opening it directly", + "Add an open button for all export formats", + "Display JSON pretty print also for PHP 5.2 and 5.3", + "Add a shortcut CTRL-R in the regex editor in the debugger to launch the test instead of clicking on the TEST button", + "Add loaders for possible long operations", + "Add log timezone support by default (more info)" + ] + }, + "1.5.1" : { + "released" : "2014-11-14", + "fixed" : [ + "Configuration file split fixed" + ] + }, + "1.5.0" : { + "released" : "2014-11-13", + "new" : [ + "Feature #64 : More logs", + "Feature #61 : Authentication (Take a look here to enable authentication on an existing instance)", + "Feature #76 : Anonymous access to let guest view some logs and protect other logs for genuine users", + "Feature #72 : Access token to secure exports and for API coming in v2.0", + "Feature #71 : Admins can sign in as other users to check their accounts", + "Feature #70 : Export logs in Atom, CSV, JSON, JSONP, Pretty JSON, RSS and XML", + "Authentication : admin can view logs to check who sign in, sign out, ...", + "Authentication : change password feature", + "Authentication : reset access token in the profile menu", + "Debugger : new debugger available at /inc/test.php", + "Debugger : enable/disable authentication for the instance", + "Debugger : reset a user password", + "Debugger : new security management", + "New global parameter AUTH_LOG_FILE_COUNT to set the maximum number of entries in the auth log file. Set this value to 0 if you want to disable authentication logs", + "New global parameter SORT_LOG_FILES to sort your log files in the log selector on top left. Documentation is here.", + "New global parameter EXPORT to disable export feature globally", + "New global parameter FORGOTTEN_YOUR_PASSWORD_URL to set the Password Forgotten link on the login page", + "New configuration file with extension .php instead of .json to avoid direct calls in web browser and then people can see where are located your log files. .json files are still supported. Upgrading your configuration files is really straightforward, instructions are here.", + "New configuration parameter export_title to set the RSS title field per log file" + ], + "fixed" : [ + "Apache 2.4 configurator could have problems to find logs format in some case, now Pimp My Log write a log and read it to parse its format" + ], + "changed" : [ + "New pretty design with new Ubuntu font, I hope you like it", + "Increase menu accessibility on mobile devices", + "Start refactoring for version 2.0 with unit tests" + ] + }, + "1.3.0" : { + "released" : "2014-10-12", + "new" : [ + "Configuration file can be splitted in several files in subfolder config.user.d. Documentation is here. (#62)", + "New messaging system for development team... First message scheduled for version 1.3 :-)" + ], + "fixed" : [ + "Remove debug logs while configuration", + "Numeral language was not set correctly" + ], + "changed" : [ + "Change design on some elements", + "Upgrade Bootstrap to 3.2.0, jQuery to 2.1.1 and ZClip to 1.1.5", + "Global refactoring to make it faster", + "Remove LESS and use SASS now", + "Loading icon is now animating (not in IE8 and IE9)" + ] + }, + "1.2.1" : { + "released" : "2014-09-21", + "fixed" : [ + "Support non ASCII log files" + ] + }, + "1.2" : { + "released" : "2014-08-31", + "new" : [ + "Column sorting #5" + ], + "changed" : [ + "Left colored new log marker is displayed until new logs are displayed and is no more removed on next refresh" + ] + }, + "1.1.1" : { + "released" : "2014-07-25", + "new" : [ + "You can now choose which columns to display at runtime #19" + ] + }, + "1.1" : { + "released" : "2014-07-14", + "new" : [ + "New parser type prefake to simulate a pre field. This is a workaround for copy/paste logs from Firefox", + "Log marker: click on the date field to toggle a row marker", + "Log marker: new button in settings to clear all markers" + ], + "fixed" : [ + "Brazilian Portuguese is now available in the settings menu", + "You can now copy/paste all logs in Excel, formatting is preserved" + ] + }, + "1.0.6" : { + "released" : "2014-07-06", + "fixed" : [ + "Add Brazilian Portuguese by Cassio Santos" + ] + }, + "1.0.5" : { + "released" : "2014-06-05", + "fixed" : [ + "Apache 2.4 error in auto-configuation" + ] + }, + "1.0.4" : { + "released" : "2014-04-20", + "fixed" : [ + "PHP error file now support referer", + "Severity badges are case insensitive now, really this time !" + ] + }, + "1.0.3" : { + "released" : "2014-04-20", + "new" : [ + "Severity badges are case insensitive now" + ] + }, + "1.0.2" : { + "released" : "2014-01-31", + "changed" : [ + "Clean PHP code" + ] + }, + "1.0.1" : { + "released" : "2014-01-30", + "changed" : [ + "Add a link in the upgrade message to the Pimp My Log upgrade documentation #56" + ], + "new" : [ + "Add support for the LOCALE global parameter", + "Add user settings button", + "Add language selector user setting", + "Add timezone selector user setting", + "Add wide log table setting", + "Now clicking on the logo will reload Pimp My Log with default user settings", + "The url is automatically updated with the current user settings, so you can bookmark any view" + ] + }, + "1.0.0" : { + "released" : "2014-01-28", + "fixed" : [ + "PHP Notice while configuration process when apache log file is empty #51" + ], + "changed" : [ + "Several custom log file paths can be separated by a coma or by a new line now", + "Configuration paths can now use globs", + "Log table is now full width streched" + ], + "new" : [ + "Support PHP logs #52", + "Support NGINX server logs #53", + "Support IIS server logs #54", + "Add log type format in the footer" + ] + }, + "0.9.9" : { + "released" : "2014-01-22", + "changed" : [ + "Clean code again and reduce files size", + "You can now click on the logo in the debugger and in the configurator" + ], + "new" : [ + "Add new global parameter TITLE_FILE to customize the page title according to the current displayed file #50", + "Add a copy to clipboard action when configuring Pimp My Log", + "PML can be launched with any log file by default, not the first defined only (use http://pml/?i=apache2 for example)" + ] + }, + "0.9.8" : { + "released" : "2014-01-20", + "changed" : [ + "Clean code" + ], + "fixed" : [ + "Wrong path for chrome notification image #49" + ] + }, + "0.9.7" : { + "released" : "2014-01-14", + "new" : [ + "Copy the result of your debugger work to your clipboard. Then you just have to copy-paste in your configuration file." + ], + "changed" : [ + "Update french language" + ], + "fixed" : [ + "Unencoded file paths in the configuration process" + ] + }, + "0.9.6" : { + "notice" : "The separator feature in the match array has been replaced by a concatenation of all tokens and provided strings. Your configuration will not be broken but the displayed result will differ.", + "released" : "2014-01-14", + "new" : [ + "Multiline support closes (#46)", + "Enable multiline and types customization in the debugger", + "Debugger now supports exactly same features as the production parser", + "Match array separator no longer exists and is replaced by a concatenator which is more powerful (#47)", + "New file selector option (deactivate by default) to support several hundreds of log files (#45)" + ], + "changed" : [ + "Change the upgrade url, github is really too slow..." + ], + "fixed" : [ + "Fix a bug in the apache 2.4 configuration file" + ] + }, + "0.9.5" : { + "notice" : "The date format in the match array has changed. Please read documentation to upgrade your configuration file.", + "released" : "2014-01-10", + "changed" : [ + "Exclude object is optional now in configuration file", + "New date format support when month is a number" + ] + }, + "0.9.4" : { + "released" : "2014-01-09", + "fixed" : [ + "Change jsonp to js extension for IE9", + "Fixed bootstrap 3.0.3 striped lines bug", + "Add default color for undefined badges type", + "Use strict mode in javascript", + "Fix a bug when slash is in a date type format", + "HTTP command should not be wrapped" + ] + }, + "0.9.3" : { + "released" : "2014-01-08", + "fixed" : [ + "[Main] Desktop notifications broken" + ] + }, + "0.9.2" : { + "released" : "2014-01-01", + "fixed" : [ + "[Configuration] Cannot add custom logs when no default path is available #43" + ] + }, + "0.9.1" : { + "released" : "2013-12-28", + "new" : [ + "Add french translations #3" + ], + "changed" : [ + "Upgrade check management #41 #42" + ], + "fixed" : [ + "Language support in ajax requests" + ] + }, + "0.9.0" : { + "notice" : "First public pre-release of Pimp my Log!", + "released" : "2013-12-22" + } + } +} +/*PSK*/);/*PSK*/ diff --git a/vendor/robmorgan/phinx/.gitignore b/vendor/robmorgan/phinx/.gitignore new file mode 100644 index 00000000..e3471827 --- /dev/null +++ b/vendor/robmorgan/phinx/.gitignore @@ -0,0 +1,34 @@ +# These all appear to be hidden files from various IDEs & Operating Systems. +# These probably belong in ~/.gitignore_global and not in here. +# See https://help.github.com/articles/ignoring-files +.DS_Store +.buildpath +.project +.settings +.idea + +# These seem to be generated by the build tool for releasing a tagged version of this software +build +*.tgz +dist + +# Don't commit composer.phar, or the .phar our build tool generates +*.phar + +# this is the code coverage generated when running phpunit. +tests/log + +# not positive, but this probably generated by the unit tests? +migrations + +# sphinx generates HTML files for the documentation here +docs/_build + +# composer installed dependencies +vendor + +# this is user specific settings for running phpunit to override the defaults in phpunit.xml.dist +phpunit.xml + +# sqlite test database +phinx_testing.sqlite3 diff --git a/vendor/robmorgan/phinx/.scrutinizer.yml b/vendor/robmorgan/phinx/.scrutinizer.yml new file mode 100644 index 00000000..f09aa97b --- /dev/null +++ b/vendor/robmorgan/phinx/.scrutinizer.yml @@ -0,0 +1,2 @@ +tools: + external_code_coverage: true \ No newline at end of file diff --git a/vendor/robmorgan/phinx/.travis.yml b/vendor/robmorgan/phinx/.travis.yml new file mode 100644 index 00000000..f1791342 --- /dev/null +++ b/vendor/robmorgan/phinx/.travis.yml @@ -0,0 +1,29 @@ +before_script: + - composer self-update + - composer install + - mysql -e 'create database phinx_testing;' + - psql -c 'create database phinx_testing;' -U postgres +language: php +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm +addons: + postgresql: "9.2" +env: + - TESTS_PHINX_DB_ADAPTER_POSTGRES_ENABLED=true +script: + - phpunit --coverage-text --coverage-clover=coverage.clover + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover +matrix: + exclude: + - php: hhvm + env: TESTS_PHINX_DB_ADAPTER_POSTGRES_ENABLED=true + include: + - php: hhvm + env: TESTS_PHINX_DB_ADAPTER_POSTGRES_ENABLED=false + allow_failures: + - php: hhvm diff --git a/vendor/robmorgan/phinx/CONTRIBUTING.md b/vendor/robmorgan/phinx/CONTRIBUTING.md new file mode 100644 index 00000000..3b636644 --- /dev/null +++ b/vendor/robmorgan/phinx/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# How to contribute to Phinx + +Phinx relies heavily on external contributions in order to make it the best database migration +tool possible. Without the support of our 70+ contributors we wouldn't be where we are today! +We encourage anyone to submit documentation enhancements and code. + +Issues, feature requests and bugs should be submitted using the Github issue tool: +https://github.com/robmorgan/phinx/issues. + +This document briefly outlines the requirements to contribute code to Phinx. + +## Considerations + +Before you submit your pull request take a moment to answer the following questions. + +Answering '**YES**' to all questions will increase the likelihood of your PR being accepted! + +* Have I implemented my feature for as many database adapters as possible? +* Does my new feature improve Phinx's performance or keep it consistent? +* Does my feature fit within the database migration space? +* Is the code entirely my own and free from any commercial licensing? +* Am I happy to release my code under the MIT license? +* Is my code formatted using the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) coding standard? + +**Note:** We accept bug fixes much faster into our development branch than features. + +## Getting Started + +Great, so you want to contribute. Let's get started: + +1. Start by forking Phinx on GitHub: https://github.com/robmorgan/phinx + +1. Clone your repository to a local directory on your development box. + +1. If you do not have Composer set up already, install it: + + ``` + curl -s https://getcomposer.org/installer | php + ``` + +1. Change to your Phinx clone directory and pull the necessary dependencies: + + ``` + php composer.phar install + ``` + +1. Copy the `phpunit.xml.dist` template to `phpunit.xml` and change the configuration to suit your environment. If you are not using any particular adapter you can disable it in the `phpunit.xml` file. + +1. Run the unit tests locally to ensure they pass: + + ``` + php vendor/bin/phpunit --config phpunit.xml + ``` + +1. Write the code and unit tests for your bug fix or feature. + +1. Add any relevant documentation. + +1. Run the unit tests again and ensure they pass. + +1. Open a pull request on the Github project page. Ensure the code is being merged into the latest development branch (e.g: `0.5.x-dev`) and not `master`. + +## Documentation + +The Phinx documentation is stored in the **docs** directory using the [RestructedText](http://docutils.sourceforge.net/rst.html) format. All documentation merged to `master` is automatically published to the Phinx documentation site available +at: http://docs.phinx.org. Keep this in mind when submitting your PR, or ask someone to merge the development branch back down to master. diff --git a/vendor/robmorgan/phinx/LICENSE b/vendor/robmorgan/phinx/LICENSE new file mode 100644 index 00000000..26ade900 --- /dev/null +++ b/vendor/robmorgan/phinx/LICENSE @@ -0,0 +1,9 @@ +(The MIT license) + +Copyright (c) 2014 Rob Morgan + +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/robmorgan/phinx/README.md b/vendor/robmorgan/phinx/README.md new file mode 100644 index 00000000..3427be62 --- /dev/null +++ b/vendor/robmorgan/phinx/README.md @@ -0,0 +1,389 @@ +# [Phinx](https://phinx.org): Simple PHP Database Migrations + +[![Build Status](https://travis-ci.org/robmorgan/phinx.png?branch=0.2.x-dev)](https://travis-ci.org/robmorgan/phinx) +[![Build status](https://ci.appveyor.com/api/projects/status/9vag4892hfq6effr)](https://ci.appveyor.com/project/robmorgan/phinx) +[![Code Coverage](https://scrutinizer-ci.com/g/robmorgan/phinx/badges/coverage.png?s=9776e35b967f5adb0f4958bd72b617e0a9519f7d)](https://scrutinizer-ci.com/g/robmorgan/phinx/) +[![Latest Stable Version](https://poser.pugx.org/robmorgan/phinx/version.png)](https://packagist.org/packages/robmorgan/phinx) +[![Total Downloads](https://poser.pugx.org/robmorgan/phinx/d/total.png)](https://packagist.org/packages/robmorgan/phinx) + +Phinx makes it ridiculously easy to manage the database migrations for your PHP app. In less than 5 minutes you can install Phinx and create your first database migration. Phinx is just about migrations without all the bloat of a database ORM system or framework. + +**Check out http://docs.phinx.org for the comprehensive documentation.** + +![phinxterm](https://cloud.githubusercontent.com/assets/178939/3887559/e6b5e524-21f2-11e4-8256-0ba6040725fc.gif) + +### Features + +* Write database migrations using database agnostic PHP code. +* Migrate up and down. +* Migrate on deployment. +* Get going in less than 5 minutes. +* Stop worrying about the state of your database. +* Take advantage of SCM features such as branching. +* Integrate with any app. + +### Supported Adapters + +Phinx natively supports the following database adapters: + +* MySQL +* PostgreSQL +* SQLite +* Microsoft SQL Server + +## Install & Run + +### Composer + +The fastest way to install Phinx is to add it to your project using Composer (http://getcomposer.org/). + +1. Install Composer: + + ``` + curl -s https://getcomposer.org/installer | php + ``` + +1. Require Phinx as a dependency using Composer: + + ``` + php composer.phar require robmorgan/phinx + ``` + +1. Install Phinx: + + ``` + php composer.phar install + ``` + +1. Execute Phinx: + + ``` + php vendor/bin/phinx + ``` + +### As a Phar + +You can also use the Box application to build Phinx as a Phar archive (http://box-project.org/). + +1. Clone Phinx from GitHub + + ``` + git clone git://github.com/robmorgan/phinx.git + cd phinx + ``` + +1. Install Composer + + ``` + curl -s https://getcomposer.org/installer | php + ``` + +1. Install the Phinx dependencies + + ``` + php composer.phar install + ``` + +1. Install Box: + + ``` + curl -LSs https://box-project.github.io/box2/installer.php | php + ``` + +1. Create a Phar archive + + ``` + php box.phar build + ``` + +## Documentation + +Check out http://docs.phinx.org for the comprehensive documentation. + +## Contributing + +Please read the [CONTRIBUTING](CONTRIBUTING.md) document. + +## News & Updates + +Follow Rob (@\_rjm\_) on Twitter to stay up to date (http://twitter.com/_rjm_) + +## Misc + +### Version History + +**0.4.6** (Friday, 11th September 2015) + +* You can now set custom migration templates in the config files +* Support for MySQL unsigned booleans +* Support for Postgres `smallint` column types +* Support for `AFTER` when using `changeColumn()` with MySQL +* Support for `precision` and `scale` when using the Postgres `decimal` type +* Fixed a bug where duplicate migration names could be used +* The schema table is now created with a primary key +* Fixed issues when using the MySQL `STRICT_TRANS_TABLE` mode +* Improved the docs in the default migration template +* Made Box PHAR ignore the bundled `phinx.yml` configuration file +* Updated Box installer URL +* Internal code improvements +* Documentation improvements + +**0.4.5** (Tuesday, 1st September 2015) + +* The rollback command now supports a date argument +* Fixed DBLIB DSN strings for Microsoft SQL Server +* Postgres support for `jsonb` columns added +* The `addTimestamps()` helper method no longer updates the `created_at` column +* Fix for Postgres named foreign keys +* Unit test improvements (including strict warnings) +* Documentation improvements + +**0.4.4** (Sunday, 14th June 2015) + +* The `change` method is now the default +* Added a generic adapter insert method. Warning: The implementation will change! +* Updated Symfony depdencies to ~2.7 +* Support for MySQL `BLOB` column types +* SQLite migration fixes +* Documentation improvements + +**0.4.3** (Monday, 23rd Feburary 2015) + +* Postgres bugfix for modifying column DEFAULTs +* MySQL bugfix for setting column INTEGER lengths +* SQLite bugfix for creating multiple indexes with similar names + +**0.4.2.1** (Saturday, 7th Feburary 2015) + +* Proper release, updated docs + +**0.4.2** (Friday, 6th Feburary 2015) + +* Postgres support for `json` columns added +* MySQL support for `enum` and `set` columns added +* Allow setting `identity` option on columns +* Template configuration and generation made more extensible +* Created a base class for `ProxyAdapter` and `TablePrefixAdapter` +* Switched to PSR-4 + +**0.4.1** (Tuesday, 23rd December 2014) + +* MySQL support for reserved words in hasColumn and getColumns methods +* Better MySQL Adapter test coverage and performance fixes +* Updated dependent Symfony components to 2.6.x + +**0.4.0** (Sunday, 14th December 2014) + +* Adding initial support for running Phinx via a web interface +* Support for table prefixes and suffixes +* Bugfix for foreign key options +* MySQL keeps column default when renaming columns +* MySQL support for tiny/medium and longtext columns added +* Changed SQL Server binary columns to varbinary +* MySQL supports table comments +* Postgres supports column comments +* Empty strings are now supported for default column values +* Booleans are now supported for default column values +* Fixed SQL Server default constraint error when changing column types +* Migration timestamps are now created in UTC +* Locked Symfony Components to 2.5.0 +* Support for custom migration base classes +* Cleaned up source code formatting +* Migrations have access to the output stream +* Support for custom PDO connections when a PHP config +* Added support for Postgres UUID type +* Fixed issue with Postgres dropping foreign keys + +**0.3.8** (Sunday, 5th October 2014) + +* Added new CHAR & Geospatial column types +* Added MySQL unix socket support +* Added precision & scale support for SQL Server +* Several bug fixes for SQLite +* Improved error messages +* Overall code optimizations +* Optimizations to MySQL hasTable method + +**0.3.7** (Tuesday, 12th August 2014) + +* Smarter configuration file support +* Support for Postgres Schemas +* Fixed charset support for Microsoft SQL Server +* Fix for Unique indexes in all adapters +* Improvements for MySQL foreign key migration syntax +* Allow MySQL column types with extra info +* Fixed SQLite autoincrement behaviour +* PHPDoc improvements +* Documentation improvements +* Unit test improvements +* Removing primary_key as a type + +**0.3.6** (Sunday, 29th June 2014) + +* Add custom adapter support +* Fix PHP 5.3 compatibility for SQL Server + +**0.3.5** (Saturday, 21st June 2014) + +* Added Microsoft SQL Server support +* Removed Primary Key column type +* Cleaned up and optimized many methods +* Updated Symfony dependencies to v2.5.0 +* PHPDoc improvements + +**0.3.4** (Sunday, 27th April 2014) + +* Added support MySQL unsigned integer, biginteger, float and decimal types +* Added JSON output support for the status command +* Fix a bug where Postgres couldnt rollback foreign keys +* Moved Phinx type references to interface constants +* Fixed a bug with SQLite in-memory databases + +**0.3.3** (Saturday, 22nd March 2014) + +* Added support for JSON configuration +* Named index support for all adapters (thanks @archer308) +* Updated Composer dependencies +* Fix for SQLite Integer Type +* Fix for MySQL port option + +**0.3.2** (Monday, 24th February 2014) + +* Adding better Postgres type support + +**0.3.1** (Sunday, 23rd February 2014) + +* Adding MySQL charset support to the YAML config +* Removing trailing spaces + +**0.3.0** (Sunday, 2nd February 2014) + +* PSR-2 support +* Method to add timestamps easily to tables +* Support for column comments in the Postgres adapter +* Fixes for MySQL driver options +* Fixes for MySQL biginteger type + +**0.2.9** (Saturday, 16th November 2013) + +* Added SQLite Support +* Improving the unit tests, especially on Windows + +**0.2.8** (Sunday, 25th August 2013) + +* Added PostgresSQL Support + +**0.2.7** (Saturday, 24th August 2013) + +* Critical fix for a token parsing bug +* Removed legacy build system +* Improving docs + +**0.2.6** (Saturday, 24th August 2013) + +* Added support for environment vars in config files +* Added support for environment vars to set the Phinx Env +* Improving docs +* Fixed a bug with column names in indexes +* Changes for developers in regards to the unit tests + +**0.2.5** (Sunday, 26th May 2013) + +* Added support for Box Phar Archive Packaging +* Added support for MYSQL_ATTR driver options +* Fixed a bug where foreign keys cannot be removed +* Added support for MySQL table collation +* Updated Composer dependencies +* Removed verbosity options, now relies on Symfony instead +* Improved unit tests + +**0.2.4** (Saturday, 20th April 2013) + +* The Rollback command supports the verbosity parameter +* The Rollback command has more detailed output +* Table::dropForeignKey now returns the table instance + +**0.2.3** (Saturday, 6th April 2013) + +* Fixed a reporting bug when Phinx couldn't connect to a database +* Added support for the MySQL 'ON UPDATE' function +* Phinx timestamp is now mapped to MySQL timestamp instead of datetime +* Fixed a docs typo for the minimum PHP version +* Added UTF8 support for migrations +* Changed regex to handle migration names differently +* Added support for custom MySQL table engines such as MyISAM +* Added the change method to the migration template + +**0.2.2** (Sunday, 3rd March 2013) + +* Added a new verbosity parameter to see more output when migrating +* Support for PHP config files + +**0.2.1** (Sunday, 3rd March 2013) + +* Broken Release. Do not use! +* Unit tests no longer rely on the default phinx.yml file +* Running migrate for the first time does not give php warnings +* `default_migration_table` is now actually supported +* Updated docblocks to 2013. + +**0.2.0** (Sunday, 13th January 2013) + +* First Birthday Release +* Added Reversible Migrations +* Removed options parameter from AdapterInterface::hasColumn() + +**0.1.7** (Tuesday, 8th January 2013) + +* Improved documentation on the YAML configuration file +* Removed options parameter from AdapterInterface::dropIndex() + +**0.1.6** (Sunday, 9th December 2012) + +* Added foreign key support +* Removed PEAR support +* Support for auto_increment on custom id columns +* Bugfix for column default value 0 +* Documentation improvements + +**0.1.5** (Sunday, 4th November 2012) + +* Added a test command +* Added transactions for adapters that support it +* Changing the Table API to use pending column methods +* Fixed a bug when defining multiple indexes on a table + +**0.1.4** (Sunday, 21st October 2012) + +* Documentation Improvements + +**0.1.3** (Saturday, 20th October 2012) + +* Fixed broken composer support + +**0.1.2** (Saturday, 20th October 2012) + +* Added composer support +* Now forces migrations to be in CamelCase format +* Now specifies the database name when migrating +* Creates the internal log table using its API instead of raw SQL + +**0.1.1** (Wednesday, 13th June 2012) + +* First point release. Ready for limited production use. + +**0.1.0** (Friday, 13th January 2012) + +* Initial public release. + +### License + +(The MIT license) + +Copyright (c) 2015 Rob Morgan + +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/robmorgan/phinx/app/phinx.php b/vendor/robmorgan/phinx/app/phinx.php new file mode 100644 index 00000000..9e211ab3 --- /dev/null +++ b/vendor/robmorgan/phinx/app/phinx.php @@ -0,0 +1,36 @@ + 'getStatus', + 'migrate' => 'getMigrate', + 'rollback' => 'getRollback', + ]; + +// Extract the requested command from the URL, default to "status". +$command = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/'); +if (!$command) { + $command = 'status'; +} + +// Verify that the command exists, or list available commands. +if (!isset($routes[$command])) { + $commands = implode(', ', array_keys($routes)); + header('Content-Type: text/plain', true, 404); + die("Command not found! Valid commands are: {$commands}."); +} + +// Get the environment and target version parameters. +$env = isset($_GET['e']) ? $_GET['e'] : null; +$target = isset($_GET['t']) ? $_GET['t'] : null; + +// Check if debugging is enabled. +$debug = !empty($_GET['debug']) && filter_var($_GET['debug'], FILTER_VALIDATE_BOOLEAN); + +// Execute the command and determine if it was successful. +$output = call_user_func([$wrap, $routes[$command]], $env, $target); +$error = $wrap->getExitCode() > 0; + +// Finally, display the output of the command. +header('Content-Type: text/plain', true, $error ? 500 : 200); +if ($debug) { + // Show what command was executed based on request parameters. + $args = implode(', ', [var_export($env, true), var_export($target, true)]); + echo "DEBUG: $command($args)" . PHP_EOL . PHP_EOL; +} +echo $output; diff --git a/vendor/robmorgan/phinx/appveyor.yml b/vendor/robmorgan/phinx/appveyor.yml new file mode 100644 index 00000000..7bd2dd8f --- /dev/null +++ b/vendor/robmorgan/phinx/appveyor.yml @@ -0,0 +1,71 @@ +build: false +shallow_clone: false +platform: 'x86' +clone_folder: C:\projects\phinx +environment: + global: + PHP: "C:/PHP" + TESTS_PHINX_DB_ADAPTER_SQLSRV_ENABLED: true + TESTS_PHINX_DB_ADAPTER_SQLSRV_HOST: .\SQL2012SP1 + TESTS_PHINX_DB_ADAPTER_SQLSRV_USERNAME: "sa" + TESTS_PHINX_DB_ADAPTER_SQLSRV_PASSWORD: "Password12!" + TESTS_PHINX_DB_ADAPTER_SQLSRV_DATABASE: "phinxtesting" + matrix: + - db: 2012 + db_dsn: 'sqlserver://sa:Password12!@.\SQL2012SP1/phinxtesting?MultipleActiveResultSets=false' +services: + - mssql2012sp1 +init: + - SET PATH=C:\php\;%PATH% +install: + - cd c:\ + - ps: Start-FileDownload 'http://ci.cakephp.org/php.zip' + - 7z x php.zip -oc:\php + - cd c:\php + - copy php.ini-production php.ini + - echo date.timezone="UTC" >> php.ini + - echo extension_dir=ext >> php.ini + - echo extension=php_openssl.dll >> php.ini + - echo extension=php_sqlsrv.dll >> php.ini + - echo extension=php_pdo_sqlsrv.dll >> php.ini + - cd C:\projects\phinx + - php -r "readfile('https://getcomposer.org/installer');" | php + - php composer.phar install --prefer-dist --no-interaction --dev +before_test: +# This script solves the "Database 'model' is being recovered. Waiting until recovery is finished." +# This solution comes from https://gist.github.com/jonathanhickford/1cb0d6665adab8b9c664 +# and is follow by http://help.appveyor.com/discussions/suggestions/264-database-mssqlsystemresource-is-being-recovered-waiting-for-sql-server-to-start +- ps: >- + $tries = 5; + + $pause = 10; # Seconds to wait between tries + + While ($tries -gt 0) { + try { + $ServerConnectionString = "Data Source=(local)\SQL2012SP1;Initial Catalog=master;User Id=sa;PWD=Password12!"; + $ServerConnection = new-object system.data.SqlClient.SqlConnection($ServerConnectionString); + $query = "exec sp_configure 'clr enabled', 1;`n" + $query = $query + "RECONFIGURE;`n" + $cmd = new-object system.data.sqlclient.sqlcommand($query, $ServerConnection); + $ServerConnection.Open(); + "Running:" + $query + if ($cmd.ExecuteNonQuery() -ne -1) { + "SQL Error"; + } else { + "Success" + } + $ServerConnection.Close(); + $tries = 0; + } catch { + "Error:" + $_.Exception.Message + "Retry in $pause seconds. Attempts left: $tries"; + Start-Sleep -s $pause; + } + $tries = $tries -1; + } +test_script: + - sqlcmd -S ".\SQL2012SP1" -U sa -P Password12! -Q "create database %TESTS_PHINX_DB_ADAPTER_SQLSRV_DATABASE%;" + - cd C:\projects\phinx + - vendor\bin\phpunit.bat --no-configuration --bootstrap tests/phpunit-bootstrap.php tests/ diff --git a/vendor/robmorgan/phinx/bin/phinx b/vendor/robmorgan/phinx/bin/phinx new file mode 100644 index 00000000..ac371753 --- /dev/null +++ b/vendor/robmorgan/phinx/bin/phinx @@ -0,0 +1,28 @@ +#!/usr/bin/env php +run(); diff --git a/vendor/robmorgan/phinx/bin/phinx.bat b/vendor/robmorgan/phinx/bin/phinx.bat new file mode 100644 index 00000000..e7d7e7d0 --- /dev/null +++ b/vendor/robmorgan/phinx/bin/phinx.bat @@ -0,0 +1,41 @@ +@echo off + +rem This script will do the following: +rem - check for PHP_COMMAND env, if found, use it. +rem - if not found detect php, if found use it, otherwise err and terminate + +if "%OS%"=="Windows_NT" @setlocal + +rem %~dp0 is expanded pathname of the current script under NT +set DEFAULT_PHINX_HOME=%~dp0.. + +goto init +goto cleanup + +:init + +if "%PHINX_HOME%" == "" set PHINX_HOME=%DEFAULT_PHINX_HOME% +set DEFAULT_PHINX_HOME= + +if "%PHP_COMMAND%" == "" goto no_phpcommand + +goto run +goto cleanup + +:run +"%PHP_COMMAND%" -d html_errors=off -qC "%PHINX_HOME%\bin\phinx" %* +goto cleanup + +:no_phpcommand +rem PHP_COMMAND environment variable not found, assuming php.exe is on path. +set PHP_COMMAND=php.exe +goto init + +:err_home +echo ERROR: Environment var PHINX_HOME not set. Please point this +echo variable to your local phinx installation! +goto cleanup + +:cleanup +if "%OS%"=="Windows_NT" @endlocal +rem pause \ No newline at end of file diff --git a/vendor/robmorgan/phinx/box.json b/vendor/robmorgan/phinx/box.json new file mode 100644 index 00000000..8437280f --- /dev/null +++ b/vendor/robmorgan/phinx/box.json @@ -0,0 +1,33 @@ +{ + "alias": "phinx.phar", + "chmod": "0755", + "compactors": [ + "Herrera\\Box\\Compactor\\Json", + "Herrera\\Box\\Compactor\\Php" + ], + "directories": ["src","app"], + "files": [ + "LICENSE", + "phinx.yml" + ], + "finder": [ + { + "name": "*.php", + "exclude": [ + "File", + "mikey179", + "Net", + "phpunit", + "phpunit-test-case", + "Tester", + "Tests", + "tests" + ], + "in": "vendor" + } + ], + "git-version": "git_tag", + "main": "bin/phinx", + "output": "phinx-@git-version@.phar", + "stub": true +} diff --git a/vendor/robmorgan/phinx/composer.json b/vendor/robmorgan/phinx/composer.json new file mode 100644 index 00000000..05c99a4c --- /dev/null +++ b/vendor/robmorgan/phinx/composer.json @@ -0,0 +1,40 @@ +{ + "name": "robmorgan/phinx", + "type": "library", + "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", + "keywords": ["phinx", "migrations", "database", "db", "database migrations"], + "homepage": "https://phinx.org", + "license": "MIT", + "authors": [{ + "name": "Rob Morgan", + "email": "robbym@gmail.com", + "homepage": "http://robmorgan.id.au", + "role": "Lead Developer" + }, { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me", + "role": "Developer" + }], + "require": { + "php": ">=5.3.2", + "symfony/console": "~2.7", + "symfony/config": "~2.7", + "symfony/yaml": "~2.7" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "dev-phpcs-fixer" + }, + "autoload": { + "psr-4": { + "Phinx\\": "src/Phinx" + } + }, + "autoload-dev": { + "psr-4": { + "Test\\Phinx\\": "tests/Phinx" + } + }, + "bin": ["bin/phinx"] +} diff --git a/vendor/robmorgan/phinx/composer.lock b/vendor/robmorgan/phinx/composer.lock new file mode 100644 index 00000000..15923452 --- /dev/null +++ b/vendor/robmorgan/phinx/composer.lock @@ -0,0 +1,669 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "e8d8bdddecb8912fdf8334a1b615a086", + "packages": [ + { + "name": "symfony/config", + "version": "v2.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/Config.git", + "reference": "537e9912063e66aa70cbcddd7d6e6e8db61d98e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Config/zipball/537e9912063e66aa70cbcddd7d6e6e8db61d98e4", + "reference": "537e9912063e66aa70cbcddd7d6e6e8db61d98e4", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2015-05-15 13:33:16" + }, + { + "name": "symfony/console", + "version": "v2.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/Console.git", + "reference": "7f0bec04961c61c961df0cb8c2ae88dbfd83f399" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Console/zipball/7f0bec04961c61c961df0cb8c2ae88dbfd83f399", + "reference": "7f0bec04961c61c961df0cb8c2ae88dbfd83f399", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/phpunit-bridge": "~2.7", + "symfony/process": "~2.1" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2015-05-29 16:22:24" + }, + { + "name": "symfony/filesystem", + "version": "v2.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/Filesystem.git", + "reference": "ae4551fd6d4d4f51f2e7390fbc902fbd67f3b7ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Filesystem/zipball/ae4551fd6d4d4f51f2e7390fbc902fbd67f3b7ba", + "reference": "ae4551fd6d4d4f51f2e7390fbc902fbd67f3b7ba", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2015-05-15 13:33:16" + }, + { + "name": "symfony/yaml", + "version": "v2.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "4a29a5248aed4fb45f626a7bbbd330291492f5c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/4a29a5248aed4fb45f626a7bbbd330291492f5c3", + "reference": "4a29a5248aed4fb45f626a7bbbd330291492f5c3", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2015-05-02 15:21:08" + } + ], + "packages-dev": [ + { + "name": "phpunit/php-code-coverage", + "version": "1.2.18", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": ">=1.3.0@stable", + "phpunit/php-text-template": ">=1.2.0@stable", + "phpunit/php-token-stream": ">=1.1.3,<1.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*@dev" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2014-09-02 10:13:14" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a923bb15680d0089e2316f7a4af8f437046e96bb", + "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-04-02 05:19:05" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "Text/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2014-01-30 17:20:04" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", + "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2013-08-02 07:42:54" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2014-03-03 05:10:30" + }, + { + "name": "phpunit/phpunit", + "version": "3.7.38", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6", + "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpunit/php-code-coverage": "~1.2", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.1", + "phpunit/php-timer": "~1.0", + "phpunit/phpunit-mock-objects": "~1.2", + "symfony/yaml": "~2.0" + }, + "require-dev": { + "pear-pear.php.net/pear": "1.9.4" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "composer/bin/phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2014-10-17 09:04:17" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "1.2.3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "reference": "1.2.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-text-template": ">=1.1.1@stable" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2013-01-13 10:24:48" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "dev-phpcs-fixer", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "f93f17d21c9d594ffe906999df9d970d124305ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/f93f17d21c9d594ffe906999df9d970d124305ec", + "reference": "f93f17d21c9d594ffe906999df9d970d124305ec", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.1.2" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-phpcs-fixer": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenises PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2014-06-17 05:33:29" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "squizlabs/php_codesniffer": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.2" + }, + "platform-dev": [] +} diff --git a/vendor/robmorgan/phinx/docs/Makefile b/vendor/robmorgan/phinx/docs/Makefile new file mode 100644 index 00000000..6ce0b5da --- /dev/null +++ b/vendor/robmorgan/phinx/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Phinx.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Phinx.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Phinx" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Phinx" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/vendor/robmorgan/phinx/docs/commands.rst b/vendor/robmorgan/phinx/docs/commands.rst new file mode 100644 index 00000000..0e59eaa9 --- /dev/null +++ b/vendor/robmorgan/phinx/docs/commands.rst @@ -0,0 +1,183 @@ +.. index:: + single: Commands + +Commands +======== + +Phinx is run using a number of commands. + +The Create Command +------------------ + +The Create command is used to create a new migration file. It requires one +argument and that is the name of the migration. The migration name should be +specified in CamelCase format. + +.. code-block:: bash + + $ phinx create MyNewMigration + +Open the new migration file in your text editor to add your database +transformations. Phinx creates migration files using the path specified in your +``phinx.yml`` file. Please see the :doc:`Configuration ` chapter +for more information. + +You are able to override the template file used by Phinx by supplying an +alternative template filename. + +.. code-block:: bash + + $ phinx create MyNewMigration --template="" + +You can also supply a template generating class. This class must implement the +interface ``Phinx\Migration\CreationInterface``. + +.. code-block:: bash + + $ phinx create MyNewMigration --class="" + +In addition to providing the template for the migration, the class can also define +a callback that will be called once the migration file has been generated from the +template. + +You cannot use ``--template`` and ``--class`` together. + +The Init Command +---------------- + +The Init command (short for initialize) is used to prepare your project for +Phinx. This command generates the ``phinx.yml`` file in the root of your +project directory. + +.. code-block:: bash + + $ cd yourapp + $ phinx init . + +Open this file in your text editor to setup your project configuration. Please +see the :doc:`Configuration ` chapter for more information. + +The Migrate Command +------------------- + +The Migrate command runs all of the available migrations, optionally up to a +specific version. + +.. code-block:: bash + + $ phinx migrate -e development + +To migrate to a specific version then use the ``--target`` parameter or ``-t`` +for short. + +.. code-block:: bash + + $ phinx migrate -e development -t 20110103081132 + +The Rollback Command +-------------------- + +The Rollback command is used to undo previous migrations executed by Phinx. It +is the opposite of the Migrate command. + +You can rollback to the previous migration by using the ``rollback`` command +with no arguments. + +.. code-block:: bash + + $ phinx rollback -e development + +To rollback all migrations to a specific version then use the ``--target`` +parameter or ``-t`` for short. + +.. code-block:: bash + + $ phinx rollback -e development -t 20120103083322 + +Specifying 0 as the target version will revert all migrations. + +.. code-block:: bash + + $ phinx rollback -e development -t 0 + +The Status Command +------------------ + +The Status command prints a list of all migrations, along with their current +status. You can use this command to determine which migrations have been run. + +.. code-block:: bash + + $ phinx status -e development + +Configuration File Parameter +---------------------------- + +When running Phinx from the command line, you may specify a configuration file using the ``--configuration`` or ``-c`` parameter. In addition to YAML, the configuration file may be the computed output of a PHP file as a PHP array: + +.. code-block:: php + + array( + "migrations" => "application/migrations" + ), + "environments" => array( + "default_migration_table" => "phinxlog", + "default_database" => "dev", + "dev" => array( + "adapter" => "mysql", + "host" => $_ENV['DB_HOST'], + "name" => $_ENV['DB_NAME'], + "user" => $_ENV['DB_USER'], + "pass" => $_ENV['DB_PASS'], + "port" => $_ENV['DB_PORT'] + ) + ) + ); + +Phinx auto-detects which language parser to use for files with ``*.yml`` and ``*.php`` extensions. The appropriate +parser may also be specified via the ``--parser`` and ``-p`` parameters. Anything other than ``"php"`` is treated as YAML. + +When using a PHP array can you provide a ``connection`` key with an existing PDO instance. It is also important to pass +the database name too as Phinx requires this for certain methods such as ``hasTable()``: + +.. code-block:: php + + array( + "migrations" => "application/migrations" + ), + "environments" => array( + "default_migration_table" => "phinxlog", + "default_database" => "dev", + "dev" => array( + "name" => "dev_db", + "connection" => $pdo_instance + ) + ) + ); + +Running Phinx in a Web App +-------------------------- + +Phinx can also be run inside of a web application by using the ``Phinx\Wrapper\TextWrapper`` +class. An example of this is provided in ``app/web.php``, which can be run as a +standalone server: + +.. code-block:: bash + + $ php -S localhost:8000 vendor/robmorgan/phinx/app/web.php + +This will create local web server at ``__ which will show current +migration status by default. To run migrations up, use ``__ +and to rollback use ``__. + +**The included web app is only an example and should not be used in production!** + +.. note:: + + To modify configuration variables at runtime and overrid ``%%PHINX_DBNAME%%`` + or other another dynamic option, set ``$_SERVER['PHINX_DBNAME']`` before + running commands. Available options are documented in the Configuration page. diff --git a/vendor/robmorgan/phinx/docs/conf.py b/vendor/robmorgan/phinx/docs/conf.py new file mode 100644 index 00000000..c5462605 --- /dev/null +++ b/vendor/robmorgan/phinx/docs/conf.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- +# +# Phinx documentation build configuration file, created by +# sphinx-quickstart on Thu Jun 14 17:39:42 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Phinx' +copyright = u'2014, Rob Morgan' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.4' +# The full version, including alpha/beta/rc tags. +release = '0.4.6' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Phinxdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Phinx.tex', u'Phinx Documentation', + u'Rob Morgan', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'phinx', u'Phinx Documentation', + [u'Rob Morgan'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Phinx', u'Phinx Documentation', + u'Rob Morgan', 'Phinx', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/vendor/robmorgan/phinx/docs/configuration.rst b/vendor/robmorgan/phinx/docs/configuration.rst new file mode 100644 index 00000000..396f453f --- /dev/null +++ b/vendor/robmorgan/phinx/docs/configuration.rst @@ -0,0 +1,244 @@ +.. index:: + single: Configuration + +Configuration +============= + +When you initialize your project using the :doc:`Init Command`, Phinx +creates a default file called ``phinx.yml`` in the root of your project directory. +This file uses the YAML data serialization format. + +If a ``--configuration`` command line option is given, Phinx will load the +specified file. Otherwise, it will attempt to find ``phinx.php``, ``phinx.json`` or +``phinx.yml`` and load the first file found. See the :doc:`Commands ` +chapter for more information. + +.. warning:: + + Remember to store the configuration file outside of a publicly accessible + directory on your webserver. This file contains your database credentials + and may be accidentally served as plain text. + +Note that while JSON and YAML files are *parsed*, the PHP file is *included*. +This means that: + +* It must `return` an array of configuration items. +* The variable scope is local, i.e. you would need to explicitly declare + any global variables your initialization file reads or modifies. +* Its standard output is suppressed. +* Unlike with JSON and YAML, it is possible to omit environment connection details + and instead specify ``connection`` which must + contain an initialized PDO instance. This is useful when you want + your migrations to interact with your application and/or share the same. + connection. + +.. code-block:: php + + require 'app/init.php'; + + global $app; + $pdo = $app->getDatabase()->getPdo(); + + return array('environments' => array( + 'default_database' => 'development', + 'development' => array( + 'connection' => $pdo + ))); + +Migration Path +-------------- + +The first option specifies the path to your migration directory. Phinx uses +``%%PHINX_CONFIG_DIR%%/migrations`` by default. + +.. note:: + + ``%%PHINX_CONFIG_DIR%%`` is a special token and is automatically replaced + with the root directory where your ``phinx.yml`` file is stored. + +In order to overwrite the default ``%%PHINX_CONFIG_DIR%%/migrations``, you need +to add the following to the yaml configuration. + +.. code-block:: yaml + + paths: + migrations: /your/full/path + +You can also use the ``%%PHINX_CONFIG_DIR%%`` token in your path. + +.. code-block:: yaml + + paths: + migrations: %%PHINX_CONFIG_DIR%%/your/relative/path + +Custom Migration Base +--------------------- + +By default all migrations will extend from Phinx's `AbstractMigration` class. +This can be set to a custom class that extends from `AbstractMigration` by +setting ``migration_base_class`` in your config: + +.. code-block:: yaml + + migration_base_class: MyMagicalMigration + +Environments +------------ + +One of the key features of Phinx is support for multiple database environments. +You can use Phinx to create migrations on your development environment, then +run the same migrations on your production environment. Environments are +specified under the ``environments`` nested collection. For example: + +.. code-block:: yaml + + environments: + default_migration_table: phinxlog + default_database: development + production: + adapter: mysql + host: localhost + name: production_db + user: root + pass: '' + port: 3306 + charset: utf8 + collation: utf8_unicode_ci + +would define a new environment called ``production``. + +In a situation when multiple developers work on the same project and each has +a different environment (e.g. a convention such as ``--``), or when you need to have separate +environments for separate purposes (branches, testing, etc) use environment +variable `PHINX_ENVIRONMENT` to override the default environment in the yaml +file: + +.. code-block:: bash + + export PHINX_ENVIRONMENT=dev-`whoami`-`hostname` + + +Table Prefix and Suffix +------------------ + +You can define a table prefix and table suffix: + +.. code-block:: yaml + + environments: + development: + .... + table_prefix: dev_ + table_suffix: _v1 + testing: + .... + table_prefix: test_ + table_suffix: _v2 + + +Socket Connections +------------------ + +When using the MySQL adapter, it is also possible to use sockets instead of +network connections. The socket path is configured with ``unix_socket``: + +.. code-block:: yaml + + environments: + default_migration_table: phinxlog + default_database: development + production: + adapter: mysql + name: production_db + user: root + pass: '' + unix_socket: /var/run/mysql/mysql.sock + charset: utf8 + +External Variables +------------------ + +Phinx will automatically grab any environment variable prefixed with ``PHINX_`` +and make it available as a token in the config file. The token will have +exactly the same name as the variable but you must access it by wrapping two +``%%`` symbols on either side. e.g: ``%%PHINX_DBUSER%%``. This is especially +useful if you wish to store your secret database credentials directly on the +server and not in a version control system. This feature can be easily +demonstrated by the following example: + +.. code-block:: yaml + + environments: + default_migration_table: phinxlog + default_database: development + production: + adapter: mysql + host: %%PHINX_DBHOST%% + name: %%PHINX_DBNAME%% + user: %%PHINX_DBUSER%% + pass: %%PHINX_DBPASS%% + port: 3306 + charset: utf8 + +Supported Adapters +------------------ + +Phinx currently supports the following database adapters natively: + +* `MySQL `_: specify the ``mysql`` adapter. +* `PostgreSQL `_: specify the ``pgsql`` adapter. +* `SQLite `_: specify the ``sqlite`` adapter. +* `SQL Server `_: specify the ``sqlsrv`` adapter. + +SQLite +````````````````` + +Declaring an SQLite database uses a simplified structure: + +.. code-block:: yaml + + environments: + development: + adapter: sqlite + name: ./data/derby + testing: + adapter: sqlite + memory: true # Setting memory to *any* value overrides name + +SQL Server +````````````````` + +When using the ``sqlsrv`` adapter and connecting to a named instance you should +omit the ``port`` setting as SQL Server will negotiate the port automatically. +Additionally, omit the ``charset: utf8`` or change to ``charset: 65001`` which +corresponds to UTF8 for SQL Server. + +Custom Adapters +````````````````` + +You can provide a custom adapter by registering an implementation of the `Phinx\\Db\\Adapter\\AdapterInterface` +with `AdapterFactory`: + +.. code-block:: php + + $name = 'fizz'; + $class = 'Acme\Adapter\FizzAdapter'; + + AdapterFactory::instance()->registerAdapter($name, $class); + +Adapters can be registered any time before `$app->run()` is called, which normally +called by `bin/phinx`. + +Aliases +------- + +Template creation class names can be aliased and used with the ``--class`` command line option for the :doc:`Create Command `. + +The aliased classes will still be required to implement the ``Phinx\Migration\CreationInterface`` interface. + +.. code-block:: yaml + + aliases: + permission: \Namespace\Migrations\PermissionMigrationTemplateGenerator + view: \Namespace\Migrations\ViewMigrationTemplateGenerator diff --git a/vendor/robmorgan/phinx/docs/copyright.rst b/vendor/robmorgan/phinx/docs/copyright.rst new file mode 100644 index 00000000..71a6dfc1 --- /dev/null +++ b/vendor/robmorgan/phinx/docs/copyright.rst @@ -0,0 +1,29 @@ +.. index:: + single: Copyright + +Copyright +========= + +License + +(The MIT license) + +Copyright (c) 2012 Rob Morgan + +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/robmorgan/phinx/docs/goals.rst b/vendor/robmorgan/phinx/docs/goals.rst new file mode 100644 index 00000000..aaea2453 --- /dev/null +++ b/vendor/robmorgan/phinx/docs/goals.rst @@ -0,0 +1,13 @@ +.. index:: + single: Goals + +Goals +===== + +Phinx was developed with the following goals in mind: + +* Be portable amongst the most popular database vendors. +* Be PHP framework independent. +* Have a simple install process. +* Have an easy to use command-line operation. +* Integrate with various other PHP tools (Phing, PHPUnit) and web frameworks. diff --git a/vendor/robmorgan/phinx/docs/index.rst b/vendor/robmorgan/phinx/docs/index.rst new file mode 100644 index 00000000..ee0f9774 --- /dev/null +++ b/vendor/robmorgan/phinx/docs/index.rst @@ -0,0 +1,26 @@ +Phinx Documentation +=================== + +Phinx makes it ridiculously easy to manage the database migrations for your PHP app. In less than 5 minutes you can install Phinx using Composer and create your first database migration. Phinx is just about migrations without all the bloat of a database ORM system or application framework. + +Contents +======== + +.. toctree:: + :maxdepth: 2 + + intro + goals + install + migrations + commands + configuration + copyright + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/vendor/robmorgan/phinx/docs/install.rst b/vendor/robmorgan/phinx/docs/install.rst new file mode 100644 index 00000000..87ca4965 --- /dev/null +++ b/vendor/robmorgan/phinx/docs/install.rst @@ -0,0 +1,34 @@ +.. index:: + single: Installation + +Installation +============ + +Phinx should be installed using Composer. Composer is a tool for dependency +management in PHP. Please visit the `Composer `_ +website for more information. + +.. note:: + + Phinx requires at least PHP 5.3.2 (or later). + +To install Phinx, simply require it using Composer: + +.. code-block:: bash + + php composer.phar require robmorgan/phinx + +Then run Composer: + +.. code-block:: bash + + php composer.phar install --no-dev + +Create a folder in your project directory called ``migrations`` with adequate permissions. +It is where your migration files will live and should be writable. + +Phinx can now be executed from within your project: + +.. code-block:: bash + + php vendor/bin/phinx init diff --git a/vendor/robmorgan/phinx/docs/intro.rst b/vendor/robmorgan/phinx/docs/intro.rst new file mode 100644 index 00000000..12df39db --- /dev/null +++ b/vendor/robmorgan/phinx/docs/intro.rst @@ -0,0 +1,16 @@ +.. index:: + single: Introduction + +Introduction +============ + +Good developers always version their code using a SCM system, so why don't they +do the same for their database schema? + +Phinx allows developers to alter and manipulate databases in a clear and +concise way. It avoids the use of writing SQL by hand and instead offers a +powerful API for creating migrations using PHP code. Developers can then +version these migrations using their preferred SCM system. This makes Phinx +migrations portable between different database systems. Phinx keeps track of +which migrations have been run so you can worry less about the state of your +database and instead focus on building better software. \ No newline at end of file diff --git a/vendor/robmorgan/phinx/docs/make.bat b/vendor/robmorgan/phinx/docs/make.bat new file mode 100644 index 00000000..d9698819 --- /dev/null +++ b/vendor/robmorgan/phinx/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Phinx.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Phinx.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/vendor/robmorgan/phinx/docs/migrations.rst b/vendor/robmorgan/phinx/docs/migrations.rst new file mode 100644 index 00000000..73550494 --- /dev/null +++ b/vendor/robmorgan/phinx/docs/migrations.rst @@ -0,0 +1,1051 @@ +.. index:: + single: Writing Migrations + +Writing Migrations +================== + +Phinx relies on migrations in order to transform your database. Each migration +is represented by a PHP class in a unique file. It is preferred that you write +your migrations using the Phinx PHP API, but raw SQL is also supported. + +Creating a New Migration +------------------------ + +Let's start by creating a new Phinx migration. Run Phinx using the +``create`` command: + +.. code-block:: bash + + $ php vendor/bin/phinx create MyNewMigration + +This will create a new migration in the format +``YYYYMMDDHHMMSS_my_new_migration.php`` where the first 14 characters are +replaced with the current timestamp down to the second. + +Phinx automatically creates a skeleton migration file with two empty methods +and a commented out one: + +.. code-block:: php + + table('user_logins'); + $table->addColumn('user_id', 'integer') + ->addColumn('created', 'datetime') + ->create(); + } + + /** + * Migrate Up. + */ + public function up() + { + + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +When executing this migration Phinx will create the ``user_logins`` table on +the way up and automatically figure out how to drop the table on the way down. +Please be aware that when a ``change`` method exists Phinx will automatically +ignore the ``up`` and ``down`` methods. If you need to use these methods it is +recommended to create a separate migration file. + +.. note:: + + When creating or updating tables inside a ``change()`` method you must use + the Table ``create()`` and ``update()`` methods. Phinx cannot automatically + determine whether a ``save()`` call is creating a new table or modifying an + existing one. + +Phinx can only reverse the following commands: + +- createTable +- renameTable +- addColumn +- renameColumn +- addIndex +- addForeignKey + +If a command cannot be reversed then Phinx will throw a +``IrreversibleMigrationException`` exception when it's migrating down. + +Executing Queries +----------------- + +Queries can be executed with the ``execute()`` and ``query()`` methods. The +``execute()`` method returns the number of affected rows whereas the +``query()`` method returns the result as a +`PDOStatement `_ + +.. code-block:: php + + execute('DELETE FROM users'); // returns the number of affected rows + + // query() + $rows = $this->query('SELECT * FROM users'); // returns the result as an array + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +.. note:: + + These commands run using the PHP Data Objects (PDO) extension which + defines a lightweight, consistent interface for accessing databases + in PHP. Always make sure your queries abide with PDOs before using + the ``execute()`` command. This is especially important when using + DELIMITERs during insertion of stored procedures or triggers which + don't support DELIMITERs. + +Fetching Rows +------------- + +There are two methods available to fetch rows. The ``fetchRow()`` method will +fetch a single row, whilst the ``fetchAll()`` method will return multiple rows. +Both methods accept raw SQL as their only parameter. + +.. code-block:: php + + fetchRow('SELECT * FROM users'); + + // fetch an array of messages + $rows = $this->fetchAll('SELECT * FROM messages'); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Working With Tables +------------------- + +The Table Object +~~~~~~~~~~~~~~~~ + +The Table object is one of the most useful APIs provided by Phinx. It allows +you to easily manipulate database tables using PHP code. You can retrieve an +instance of the Table object by calling the ``table()`` method from within +your database migration. + +.. code-block:: php + + table('tableName'); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +You can then manipulate this table using the methods provided by the Table +object. + +Creating a Table +~~~~~~~~~~~~~~~~ + +Creating a table is really easy using the Table object. Let's create a table to +store a collection of users. + +.. code-block:: php + + table('users'); + $users->addColumn('username', 'string', array('limit' => 20)) + ->addColumn('password', 'string', array('limit' => 40)) + ->addColumn('password_salt', 'string', array('limit' => 40)) + ->addColumn('email', 'string', array('limit' => 100)) + ->addColumn('first_name', 'string', array('limit' => 30)) + ->addColumn('last_name', 'string', array('limit' => 30)) + ->addColumn('created', 'datetime') + ->addColumn('updated', 'datetime', array('null' => true)) + ->addIndex(array('username', 'email'), array('unique' => true)) + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Columns are added using the ``addColumn()`` method. We create a unique index +for both the username and email columns using the ``addIndex()`` method. +Finally calling ``save()`` commits the changes to the database. + +.. note:: + + Phinx automatically creates an auto-incrementing primary key column called ``id`` for every + table. + +The ``id`` option sets the name of the automatically created identity field, while the ``primary_key`` +option selects the field or fields used for primary key. The ``primary_key`` option always defaults to +the value of ``id``. Both can be disabled by setting them to false. + +To specify an alternate primary key you can specify the ``primary_key`` option +when accessing the Table object. Let's disable the automatic ``id`` column and +create a primary key using two columns instead: + +.. code-block:: php + + table('followers', array('id' => false, 'primary_key' => array('user_id', 'follower_id'))); + $table->addColumn('user_id', 'integer') + ->addColumn('follower_id', 'integer') + ->addColumn('created', 'datetime') + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Setting a single ``primary_key`` doesn't enable the ``AUTO_INCREMENT`` option. +To simply change the name of the primary key, we need to override the default ``id`` field name: + +.. code-block:: php + + table('followers', array('id' => 'user_id')); + $table->addColumn('follower_id', 'integer') + ->addColumn('created', 'datetime', array('default' => 'CURRENT_TIMESTAMP')) + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Valid Column Types +~~~~~~~~~~~~~~~~~~ + +Column types are specified as strings and can be one of: + +- biginteger +- binary +- boolean +- date +- datetime +- decimal +- float +- integer +- string +- text +- time +- timestamp +- uuid + +In addition, the MySQL adapter supports ``enum`` and ``set`` column types. + +In addition, the Postgres adapter supports ``smallint``, ``json``, ``jsonb`` and ``uuid`` column types +(PostgreSQL 9.3 and above). + +For valid options, see the `Valid Column Options`_ below. + +Determining Whether a Table Exists +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can determine whether or not a table exists by using the ``hasTable()`` +method. + +.. code-block:: php + + hasTable('users'); + if ($exists) { + // do something + } + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Dropping a Table +~~~~~~~~~~~~~~~~ + +Tables can be dropped quite easily using the ``dropTable()`` method. It is a +good idea to recreate the table again in the ``down()`` method. + +.. code-block:: php + + dropTable('users'); + } + + /** + * Migrate Down. + */ + public function down() + { + $users = $this->table('users'); + $users->addColumn('username', 'string', array('limit' => 20)) + ->addColumn('password', 'string', array('limit' => 40)) + ->addColumn('password_salt', 'string', array('limit' => 40)) + ->addColumn('email', 'string', array('limit' => 100)) + ->addColumn('first_name', 'string', array('limit' => 30)) + ->addColumn('last_name', 'string', array('limit' => 30)) + ->addColumn('created', 'datetime') + ->addColumn('updated', 'datetime', array('null' => true)) + ->addIndex(array('username', 'email'), array('unique' => true)) + ->save(); + } + } + +Renaming a Table +~~~~~~~~~~~~~~~~ + +To rename a table access an instance of the Table object then call the +``rename()`` method. + +.. code-block:: php + + table('users'); + $table->rename('legacy_users'); + } + + /** + * Migrate Down. + */ + public function down() + { + $table = $this->table('legacy_users'); + $table->rename('users'); + } + } + +Working With Columns +~~~~~~~~~~~~~~~~~~~~ + +Get a column list +~~~~~~~~~~~~~~~~~ + +To retrieve all table columns, simply create a `table` object and call `getColumns()` +method. This method will return an array of Column classes with basic info. Example below: + +.. code-block:: php + + table('users')->getColumns(); + ... + } + + /** + * Migrate Down. + */ + public function down() + { + ... + } + } + +Checking whether a column exists +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can check if a table already has a certain column by using the +``hasColumn()`` method. + +.. code-block:: php + + table('user'); + $column = $table->hasColumn('username'); + + if ($column) { + // do something + } + + } + } + +Renaming a Column +~~~~~~~~~~~~~~~~~ + +To rename a column access an instance of the Table object then call the +``renameColumn()`` method. + +.. code-block:: php + + table('users'); + $table->renameColumn('bio', 'biography'); + } + + /** + * Migrate Down. + */ + public function down() + { + $table = $this->table('users'); + $table->renameColumn('biography', 'bio'); + } + } + +Adding a Column After Another Column +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When adding a column you can dictate its position using the ``after`` option. + +.. code-block:: php + + table('users'); + $table->addColumn('city', 'string', array('after' => 'email')) + ->update(); + } + } + +Dropping a Column +~~~~~~~~~~~~~~~~~ + +To drop a column, use the ``removeColumn()`` method. + +.. code-block:: php + + table('users'); + $table->removeColumn('short_name') + ->update(); + } + } + + +Specifying a Column Limit +~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can limit the maximum length of a column by using the ``limit`` option. + +.. code-block:: php + + table('tags'); + $table->addColumn('short_name', 'string', array('limit' => 30)) + ->update(); + } + } + +Working with Indexes +~~~~~~~~~~~~~~~~~~~~ + +To add an index to a table you can simply call the ``addIndex()`` method on the +table object. + +.. code-block:: php + + table('users'); + $table->addColumn('city', 'string') + ->addIndex(array('city')) + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +By default Phinx instructs the database adapter to create a normal index. We +can pass an additional parameter to the ``addIndex()`` method to specify a +unique index. + +.. code-block:: php + + table('users'); + $table->addColumn('email', 'string') + ->addIndex(array('email'), array('unique' => true)) + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Removing indexes is as easy as calling the ``removeIndex()`` method. You must +call this method for each index. + +.. code-block:: php + + table('users'); + $table->removeIndex(array('email')); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +.. note:: + + There is no need to call the ``save()`` method when using + ``removeIndex()``. The index will be removed immediately. + +Working With Foreign Keys +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Phinx has support for creating foreign key constraints on your database tables. +Let's add a foreign key to an example table: + +.. code-block:: php + + table('tags'); + $table->addColumn('tag_name', 'string') + ->save(); + + $refTable = $this->table('tag_relationships'); + $refTable->addColumn('tag_id', 'integer') + ->addForeignKey('tag_id', 'tags', 'id', array('delete'=> 'SET_NULL', 'update'=> 'NO_ACTION')) + ->save(); + + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +"On delete" and "On update" actions are defined with a 'delete' and 'update' options array. Possibles values are 'SET_NULL', 'NO_ACTION', 'CASCADE' and 'RESTRICT'. + +We can also easily check if a foreign key exists: + +.. code-block:: php + + table('tag_relationships'); + $exists = $table->hasForeignKey('tag_id'); + if ($exists) { + // do something + } + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Finally to delete a foreign key use the ``dropForeignKey`` method. + +.. code-block:: php + + table('tag_relationships'); + $table->dropForeignKey('tag_id'); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Valid Column Options +~~~~~~~~~~~~~~~~~~~~ + +The following are valid column options: + +For any column type: + +======= =========== +Option Description +======= =========== +limit set maximum length for strings, also hints column types in adapters (see note below) +length alias for ``limit`` +default set default value or action +null allow ``NULL`` values (should not be used with primary keys!) +after specify the column that a new column should be placed after +comment set a text comment on the column +======= =========== + +For ``decimal`` columns: + +========= =========== +Option Description +========= =========== +precision combine with ``scale`` set to set decimial accuracy +scale combine with ``precision`` to set decimial accuracy +signed enable or disable the ``unsigned`` option *(only applies to MySQL)* +========= =========== + +For ``enum`` and ``set`` columns: + +========= =========== +Option Description +========= =========== +values Can be a comma separated list or an array of values +========= =========== + +For ``integer`` and ``biginteger`` columns: + +======== =========== +Option Description +======== =========== +identity enable or disable automatic incrementing +signed enable or disable the ``unsigned`` option *(only applies to MySQL)* +======== =========== + +For ``timestamp`` columns: + +======== =========== +Option Description +======== =========== +default set default value (use with ``CURRENT_TIMESTAMP``) +update set an action to be triggered when the row is updated (use with ``CURRENT_TIMESTAMP``) +timezone enable or disable the ``with time zone`` option for ``time`` and ``timestamp`` columns *(only applies to Postgres)* +======== =========== + +For ``boolean``columns: + +======== =========== +Option Description +======== =========== +signed enable or disable the ``unsigned`` option *(only applies to MySQL)* +======== =========== + +For foreign key definitions: + +====== =========== +Option Description +====== =========== +update set an action to be triggered when the row is updated +delete set an action to be triggered when the row is deleted +====== =========== + +You can pass one or more of these options to any column with the optional +third argument array. + +Limit Option and PostgreSQL +~~~~~~~~~~~~~~~~~~~~~~ + +When using the PostgreSQL adapter, additional hinting of database column type can be +made for ``integer`` columns. Using ``limit`` with one the following options will +modify the column type accordingly: + +============ ============== +Limit Column Type +============ ============== +INT_SMALL SMALLINT +============ ============== + +.. code-block:: php + + use Phinx\Db\Adapter\PostgresAdapter; + + //... + + $table = $this->table('cart_items'); + $table->addColumn('user_id', 'integer') + ->addColumn('subtype_id', 'integer', array('limit' => PostgresAdapter::INT_SMALL)) + ->create(); + +Limit Option and MySQL +~~~~~~~~~~~~~~~~~~~~~~ + +When using the MySQL adapter, additional hinting of database column type can be +made for ``integer``, ``text`` and ``binary`` columns. Using ``limit`` with +one the following options will modify the column type accordingly: + +============ ============== +Limit Column Type +============ ============== +BLOB_TINY TINYBLOB +BLOB_REGULAR BLOB +BLOB_MEDIUM MEDIUMBLOB +BLOB_LONG LONGBLOB +TEXT_TINY TINYTEXT +TEXT_REGULAR TEXT +TEXT_MEDIUM MEDIUMTEXT +TEXT_LONG LONGTEXT +INT_TINY TINYINT +INT_SMALL SMALLINT +INT_MEDIUM MEDIUMINT +INT_REGULAR INT +INT_BIG BIGINT +============ ============== + +.. code-block:: php + + use Phinx\Db\Adapter\MysqlAdapter; + + //... + + $table = $this->table('cart_items'); + $table->addColumn('user_id', 'integer') + ->addColumn('product_id', 'integer', array('limit' => MysqlAdapter::INT_BIG)) + ->addColumn('subtype_id', 'integer', array('limit' => MysqlAdapter::INT_SMALL)) + ->addColumn('quantity', 'integer', array('limit' => MysqlAdapter::INT_TINY)) + ->create(); + +The Save Method +~~~~~~~~~~~~~~~ + +When working with the Table object Phinx stores certain operations in a +pending changes cache. + +When in doubt it is recommended you call this method. It will commit any +pending changes to the database. diff --git a/vendor/robmorgan/phinx/docs/seeding.rst b/vendor/robmorgan/phinx/docs/seeding.rst new file mode 100644 index 00000000..d6c0890b --- /dev/null +++ b/vendor/robmorgan/phinx/docs/seeding.rst @@ -0,0 +1,1050 @@ +.. index:: + single: Database Seeding + +Database Seeding +================ + +Since version 0.5.0 Phinx supports seed classes to seed your database with dummy +data. Seed classes are stored in your `seeds` directory. This path can be +changed in your configuration file. + +.. note:: + + Database seeding is entirely optional and Phinx does not create a `seeds` + directory by default. + +To use this feature simply uncomment the `seeds` configuration parameter in your +`phinx.yml` file and create a `seed.php` file in your seeds directory. It should +contain code similar to the following: + +.. code-block:: php + + table('user_logins'); + $table->addColumn('user_id', 'integer') + ->addColumn('created', 'datetime') + ->create(); + } + + /** + * Migrate Up. + */ + public function up() + { + + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +When executing this migration Phinx will create the ``user_logins`` table on +the way up and automatically figure out how to drop the table on the way down. +Please be aware that when a ``change`` method exists Phinx will automatically +ignore the ``up`` and ``down`` methods. If you need to use these methods it is +recommended to create a separate migration file. + +.. note:: + + When creating or updating tables inside a ``change()`` method you must use + the Table ``create()`` and ``update()`` methods. Phinx cannot automatically + determine whether a ``save()`` call is creating a new table or modifying an + existing one. + +Phinx can only reverse the following commands: + +- createTable +- renameTable +- addColumn +- renameColumn +- addIndex +- addForeignKey + +If a command cannot be reversed then Phinx will throw a +``IrreversibleMigrationException`` exception when it's migrating down. + +Executing Queries +----------------- + +Queries can be executed with the ``execute()`` and ``query()`` methods. The +``execute()`` method returns the number of affected rows whereas the +``query()`` method returns the result as an array. + +.. code-block:: php + + execute('DELETE FROM users'); // returns the number of affected rows + + // query() + $rows = $this->query('SELECT * FROM users'); // returns the result as an array + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +.. note:: + + These commands run using the PHP Data Objects (PDO) extension which + defines a lightweight, consistent interface for accessing databases + in PHP. Always make sure your queries abide with PDOs before using + the ``execute()`` command. This is especially important when using + DELIMITERs during insertion of stored procedures or triggers which + don't support DELIMITERs. + +Fetching Rows +------------- + +There are two methods available to fetch rows. The ``fetchRow()`` method will +fetch a single row, whilst the ``fetchAll()`` method will return multiple rows. +Both methods accept raw SQL as their only parameter. + +.. code-block:: php + + fetchRow('SELECT * FROM users'); + + // fetch an array of messages + $rows = $this->fetchAll('SELECT * FROM messages'); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Working With Tables +------------------- + +The Table Object +~~~~~~~~~~~~~~~~ + +The Table object is one of the most useful APIs provided by Phinx. It allows +you to easily manipulate database tables using PHP code. You can retrieve an +instance of the Table object by calling the ``table()`` method from within +your database migration. + +.. code-block:: php + + table('tableName'); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +You can then manipulate this table using the methods provided by the Table +object. + +Creating a Table +~~~~~~~~~~~~~~~~ + +Creating a table is really easy using the Table object. Let's create a table to +store a collection of users. + +.. code-block:: php + + table('users'); + $users->addColumn('username', 'string', array('limit' => 20)) + ->addColumn('password', 'string', array('limit' => 40)) + ->addColumn('password_salt', 'string', array('limit' => 40)) + ->addColumn('email', 'string', array('limit' => 100)) + ->addColumn('first_name', 'string', array('limit' => 30)) + ->addColumn('last_name', 'string', array('limit' => 30)) + ->addColumn('created', 'datetime') + ->addColumn('updated', 'datetime', array('null' => true)) + ->addIndex(array('username', 'email'), array('unique' => true)) + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Columns are added using the ``addColumn()`` method. We create a unique index +for both the username and email columns using the ``addIndex()`` method. +Finally calling ``save()`` commits the changes to the database. + +.. note:: + + Phinx automatically creates an auto-incrementing primary key column called ``id`` for every + table. + +To specify an alternate primary key you can specify the ``primary_key`` option +when accessing the Table object. Let's disable the automatic ``id`` column and +create a primary key using two columns instead: + +.. code-block:: php + + table('followers', array('id' => false, 'primary_key' => array('user_id', 'follower_id'))); + $table->addColumn('user_id', 'integer') + ->addColumn('follower_id', 'integer') + ->addColumn('created', 'datetime') + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Setting a single ``primary_key`` doesn't enable the ``AUTO_INCREMENT`` option. +To do this, we need to override the default ``id`` field name: + +.. code-block:: php + + table('followers', array('id' => 'user_id')); + $table->addColumn('user_id', 'integer') + ->addColumn('follower_id', 'integer') + ->addColumn('created', 'datetime', array('default' => 'CURRENT_TIMESTAMP')) + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Valid Column Types +~~~~~~~~~~~~~~~~~~ + +Column types are specified as strings and can be one of: + +- biginteger +- binary +- boolean +- date +- datetime +- decimal +- float +- integer +- string +- text +- time +- timestamp +- uuid + +In addition, the MySQL adapter supports ``enum`` and ``set`` column types. + +In addition, the Postgres adapter supports ``json`` and ``jsonb`` column types +(PostgreSQL 9.3 and above). + +For valid options, see the `Valid Column Options`_ below. + +Determining Whether a Table Exists +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can determine whether or not a table exists by using the ``hasTable()`` +method. + +.. code-block:: php + + hasTable('users'); + if ($exists) { + // do something + } + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Dropping a Table +~~~~~~~~~~~~~~~~ + +Tables can be dropped quite easily using the ``dropTable()`` method. It is a +good idea to recreate the table again in the ``down()`` method. + +.. code-block:: php + + dropTable('users'); + } + + /** + * Migrate Down. + */ + public function down() + { + $users = $this->table('users'); + $users->addColumn('username', 'string', array('limit' => 20)) + ->addColumn('password', 'string', array('limit' => 40)) + ->addColumn('password_salt', 'string', array('limit' => 40)) + ->addColumn('email', 'string', array('limit' => 100)) + ->addColumn('first_name', 'string', array('limit' => 30)) + ->addColumn('last_name', 'string', array('limit' => 30)) + ->addColumn('created', 'datetime') + ->addColumn('updated', 'datetime', array('null' => true)) + ->addIndex(array('username', 'email'), array('unique' => true)) + ->save(); + } + } + +Renaming a Table +~~~~~~~~~~~~~~~~ + +To rename a table access an instance of the Table object then call the +``rename()`` method. + +.. code-block:: php + + table('users'); + $table->rename('legacy_users'); + } + + /** + * Migrate Down. + */ + public function down() + { + $table = $this->table('legacy_users'); + $table->rename('users'); + } + } + +Working With Columns +~~~~~~~~~~~~~~~~~~~~ + +Get a column list +~~~~~~~~~~~~~~~~~ + +To retrieve all table columns, simply create a `table` object and call `getColumns()` method. This method will return an array of Column classes with basic info. Example below: + +.. code-block:: php + + table('users')->getColumns(); + ... + } + + /** + * Migrate Down. + */ + public function down() + { + ... + } + } + +Checking whether a column exists +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can check if a table already has a certain column by using the +``hasColumn()`` method. + +.. code-block:: php + + table('user'); + $column = $table->hasColumn('username'); + + if ($column) + { + ... + } + + } + } + +Renaming a Column +~~~~~~~~~~~~~~~~~ + +To rename a column access an instance of the Table object then call the +``renameColumn()`` method. + +.. code-block:: php + + table('users'); + $table->renameColumn('bio', 'biography'); + } + + /** + * Migrate Down. + */ + public function down() + { + $table = $this->table('users'); + $table->renameColumn('biography', 'bio'); + } + } + +Adding a Column After Another Column +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When adding a column you can dictate it's position using the ``after`` option. + +.. code-block:: php + + table('users'); + $table->addColumn('city', 'string', array('after' => 'email')) + ->update(); + } + } + +Dropping a Column +~~~~~~~~~~~~~~~~~ + +To drop a column, use the ``removeColumn()`` method. + +.. code-block:: php + + table('users'); + $table->removeColumn('short_name') + ->update(); + } + } + + +Specifying a Column Limit +~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can limit the maximum length of a column by using the ``limit`` option. + +.. code-block:: php + + table('tags'); + $table->addColumn('short_name', 'string', array('limit' => 30)) + ->update(); + } + } + +Working with Indexes +~~~~~~~~~~~~~~~~~~~~ + +To add an index to a table you can simply call the ``addIndex()`` method on the +table object. + +.. code-block:: php + + table('users'); + $table->addColumn('city', 'string') + ->addIndex(array('city')) + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +By default Phinx instructs the database adapter to create a normal index. We +can pass an additional parameter to the ``addIndex()`` method to specify a +unique index. + +.. code-block:: php + + table('users'); + $table->addColumn('email', 'string') + ->addIndex(array('email'), array('unique' => true)) + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Removing indexes is as easy as calling the ``removeIndex()`` method. You must +call this method for each index. + +.. code-block:: php + + table('users'); + $table->removeIndex(array('email')); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +.. note:: + + There is no need to call the ``save()`` method when using + ``removeIndex()``. The index will be removed immediately. + +Working With Foreign Keys +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Phinx has support for creating foreign key constraints on your database tables. +Let's add a foreign key to an example table: + +.. code-block:: php + + table('tags'); + $table->addColumn('tag_name', 'string') + ->save(); + + $refTable = $this->table('tag_relationships'); + $refTable->addColumn('tag_id', 'integer') + ->addForeignKey('tag_id', 'tags', 'id', array('delete'=> 'SET_NULL', 'update'=> 'NO_ACTION')) + ->save(); + + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +"On delete" and "On update" actions are defined with a 'delete' and 'update' options array. Possibles values are 'SET_NULL', 'NO_ACTION', 'CASCADE' and 'RESTRICT'. + +We can also easily check if a foreign key exists: + +.. code-block:: php + + table('tag_relationships'); + $exists = $table->hasForeignKey('tag_id'); + if ($exists) { + // do something + } + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Finally to delete a foreign key use the ``dropForeignKey`` method. + +.. code-block:: php + + table('tag_relationships'); + $table->dropForeignKey('tag_id'); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + } + +Valid Column Options +~~~~~~~~~~~~~~~~~~~~ + +The following are valid column options: + +For any column type: + +======= =========== +Option Description +======= =========== +limit set maximum length for strings, also hints column types in adapters (see note below) +length alias for ``limit`` +default set default value or action +null allow ``NULL`` values (should not be used with primary keys!) +after specify the column that a new column should be placed after +comment set a text comment on the column +======= =========== + +For ``decimal`` columns: + +========= =========== +Option Description +========= =========== +precision combine with ``scale`` set to set decimial accuracy +scale combine with ``precision`` to set decimial accuracy +========= =========== + +For ``enum`` and ``set`` columns: + +========= =========== +Option Description +========= =========== +values Can be a comma separated list or an array of values +========= =========== + +For ``integer`` and ``biginteger`` columns: + +======== =========== +Option Description +======== =========== +identity enable or disable automatic incrementing +signed enable or disable the ``unsigned`` option *(only applies to MySQL)* +======== =========== + +For ``timestamp`` columns: + +======== =========== +Option Description +======== =========== +default set default value (use with ``CURRENT_TIMESTAMP``) +update set an action to be triggered when the row is updated (use with ``CURRENT_TIMESTAMP``) +timezone enable or disable the ``with time zone`` option for ``time`` and ``timestamp`` columns *(only applies to Postgres)* +======== =========== + +For foreign key definitions: + +====== =========== +Option Description +====== =========== +update set an action to be triggered when the row is updated +delete set an action to be triggered when the row is deleted +====== =========== + +You can pass one or more of these options to any column with the optional +third argument array. + +Limit Option and MySQL +~~~~~~~~~~~~~~~~~~~~~~ + +When using the MySQL adapter, additional hinting of database column type can be +made for ``integer``, ``text`` and ``binary`` columns. Using ``limit`` with +one the following options will modify the column type accordingly: + +============ ============== +Limit Column Type +============ ============== +BLOB_TINY TINYBLOB +BLOB_REGULAR BLOB +BLOB_MEDIUM MEDIUMBLOB +BLOB_LONG LONGBLOB +TEXT_TINY TINYTEXT +TEXT_REGULAR TEXT +TEXT_MEDIUM MEDIUMTEXT +TEXT_LONG LONGTEXT +INT_TINY TINYINT +INT_SMALL SMALLINT +INT_MEDIUM MEDIUMINT +INT_REGULAR INT +INT_BIG BIGINT +============ ============== + +.. code-block:: php + + use Phinx\Db\Adapter\MysqlAdapter; + + //... + + $table = $this->table('cart_items'); + $table->addColumn('user_id', 'integer') + ->addColumn('product_id', 'integer', array('limit' => MysqlAdapter::INT_BIG)) + ->addColumn('subtype_id', 'integer', array('limit' => MysqlAdapter::INT_SMALL)) + ->addColumn('quantity', 'integer', array('limit' => MysqlAdapter::INT_TINY)) + ->create(); + +The Save Method +~~~~~~~~~~~~~~~ + +When working with the Table object Phinx stores certain operations in a +pending changes cache. + +When in doubt it is recommended you call this method. It will commit any +pending changes to the database. diff --git a/vendor/robmorgan/phinx/phinx.yml b/vendor/robmorgan/phinx/phinx.yml new file mode 100644 index 00000000..be3140d0 --- /dev/null +++ b/vendor/robmorgan/phinx/phinx.yml @@ -0,0 +1,32 @@ +paths: + migrations: %%PHINX_CONFIG_DIR%%/migrations + +environments: + default_migration_table: phinxlog + default_database: development + production: + adapter: mysql + host: localhost + name: production_db + user: root + pass: '' + port: 3306 + charset: utf8 + + development: + adapter: mysql + host: localhost + name: development_db + user: root + pass: '' + port: 3306 + charset: utf8 + + testing: + adapter: mysql + host: localhost + name: testing_db + user: root + pass: '' + port: 3306 + charset: utf8 diff --git a/vendor/robmorgan/phinx/phpunit.xml.dist b/vendor/robmorgan/phinx/phpunit.xml.dist new file mode 100644 index 00000000..cf2d964d --- /dev/null +++ b/vendor/robmorgan/phinx/phpunit.xml.dist @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ./tests/ + + + + + src/Phinx/ + + + + + + + + \ No newline at end of file diff --git a/vendor/robmorgan/phinx/src/Phinx/Config/Config.php b/vendor/robmorgan/phinx/src/Phinx/Config/Config.php new file mode 100644 index 00000000..8c2da8d7 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Config/Config.php @@ -0,0 +1,371 @@ +configFilePath = $configFilePath; + $this->values = $this->replaceTokens($configArray); + } + + /** + * Create a new instance of the config class using a Yaml file path. + * + * @param string $configFilePath Path to the Yaml File + * @throws \RuntimeException + * @return Config + */ + public static function fromYaml($configFilePath) + { + $configFile = file_get_contents($configFilePath); + $configArray = Yaml::parse($configFile); + + if (!is_array($configArray)) { + throw new \RuntimeException(sprintf( + 'File \'%s\' must be valid YAML', + $configFilePath + )); + } + return new static($configArray, $configFilePath); + } + + /** + * Create a new instance of the config class using a JSON file path. + * + * @param string $configFilePath Path to the JSON File + * @throws \RuntimeException + * @return Config + */ + public static function fromJson($configFilePath) + { + $configArray = json_decode(file_get_contents($configFilePath), true); + if (!is_array($configArray)) { + throw new \RuntimeException(sprintf( + 'File \'%s\' must be valid JSON', + $configFilePath + )); + } + return new static($configArray, $configFilePath); + } + + /** + * Create a new instance of the config class using a PHP file path. + * + * @param string $configFilePath Path to the PHP File + * @throws \RuntimeException + * @return Config + */ + public static function fromPhp($configFilePath) + { + ob_start(); + /** @noinspection PhpIncludeInspection */ + $configArray = include($configFilePath); + + // Hide console output + ob_end_clean(); + + if (!is_array($configArray)) { + throw new \RuntimeException(sprintf( + 'PHP file \'%s\' must return an array', + $configFilePath + )); + } + + return new static($configArray, $configFilePath); + } + + /** + * {@inheritdoc} + */ + public function getEnvironments() + { + if (isset($this->values) && isset($this->values['environments'])) { + $environments = array(); + foreach ($this->values['environments'] as $key => $value) { + if (is_array($value)) { + $environments[$key] = $value; + } + } + + return $environments; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function getEnvironment($name) + { + $environments = $this->getEnvironments(); + + if (isset($environments[$name])) { + if (isset($this->values['environments']['default_migration_table'])) { + $environments[$name]['default_migration_table'] = + $this->values['environments']['default_migration_table']; + } + + return $environments[$name]; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function hasEnvironment($name) + { + return (null !== $this->getEnvironment($name)); + } + + /** + * {@inheritdoc} + */ + public function getDefaultEnvironment() + { + // The $PHINX_ENVIRONMENT variable overrides all other default settings + $env = getenv('PHINX_ENVIRONMENT'); + if (!empty($env)) { + if ($this->hasEnvironment($env)) { + return $env; + } + + throw new \RuntimeException(sprintf( + 'The environment configuration (read from $PHINX_ENVIRONMENT) for \'%s\' is missing', + $env + )); + } + + // if the user has configured a default database then use it, + // providing it actually exists! + if (isset($this->values['environments']['default_database'])) { + if ($this->getEnvironment($this->values['environments']['default_database'])) { + return $this->values['environments']['default_database']; + } + + throw new \RuntimeException(sprintf( + 'The environment configuration for \'%s\' is missing', + $this->values['environments']['default_database'] + )); + } + + // else default to the first available one + if (is_array($this->getEnvironments()) && count($this->getEnvironments()) > 0) { + $names = array_keys($this->getEnvironments()); + return $names[0]; + } + + throw new \RuntimeException('Could not find a default environment'); + } + + /** + * Get the aliased value from a supplied alias. + * + * @param string $alias + * + * @return string|null + */ + public function getAlias($alias){ + return !empty($this->values['aliases'][$alias]) ? $this->values['aliases'][$alias] : null; + } + + /** + * {@inheritdoc} + */ + public function getConfigFilePath() + { + return $this->configFilePath; + } + + /** + * {@inheritdoc} + */ + public function getMigrationPath() + { + if (!isset($this->values['paths']['migrations'])) { + throw new \UnexpectedValueException('Migrations path missing from config file'); + } + + return $this->values['paths']['migrations']; + } + + /** + * Gets the base class name for migrations. + * + * @param boolean $dropNamespace Return the base migration class name without the namespace. + * @return string + */ + public function getMigrationBaseClassName($dropNamespace = true) + { + $className = !isset($this->values['migration_base_class']) ? 'Phinx\Migration\AbstractMigration' : $this->values['migration_base_class']; + + return $dropNamespace ? substr(strrchr($className, '\\'), 1) : $className; + } + + /** + * Get the template file name. + * + * @return string|false + */ + public function getTemplateFile() + { + if (!isset($this->values['templates']['file'])) { + return false; + } + + return $this->values['templates']['file']; + } + + /** + * Get the template class name. + * + * @return string|false + */ + public function getTemplateClass() + { + if (!isset($this->values['templates']['class'])) { + return false; + } + + return $this->values['templates']['class']; + } + + /** + * Replace tokens in the specified array. + * + * @param array $arr Array to replace + * @return array + */ + protected function replaceTokens(array $arr) + { + // Get environment variables + // $_ENV is empty because variables_order does not include it normally + $tokens = array(); + foreach ($_SERVER as $varname => $varvalue) { + if (0 === strpos($varname, 'PHINX_')) { + $tokens['%%' . $varname . '%%'] = $varvalue; + } + } + + // Phinx defined tokens (override env tokens) + $tokens['%%PHINX_CONFIG_PATH%%'] = $this->getConfigFilePath(); + $tokens['%%PHINX_CONFIG_DIR%%'] = dirname($this->getConfigFilePath()); + + // Recurse the array and replace tokens + return $this->recurseArrayForTokens($arr, $tokens); + } + + /** + * Recurse an array for the specified tokens and replace them. + * + * @param array $arr Array to recurse + * @param array $tokens Array of tokens to search for + * @return array + */ + protected function recurseArrayForTokens($arr, $tokens) + { + $out = array(); + foreach ($arr as $name => $value) { + if (is_array($value)) { + $out[$name] = $this->recurseArrayForTokens($value, $tokens); + continue; + } + if (is_string($value)) { + foreach ($tokens as $token => $tval) { + $value = str_replace($token, $tval, $value); + } + $out[$name] = $value; + continue; + } + $out[$name] = $value; + } + return $out; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($id, $value) + { + $this->values[$id] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetGet($id) + { + if (!array_key_exists($id, $this->values)) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + return $this->values[$id] instanceof \Closure ? $this->values[$id]($this) : $this->values[$id]; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($id) + { + return isset($this->values[$id]); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($id) + { + unset($this->values[$id]); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php b/vendor/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php new file mode 100644 index 00000000..0ed83a2c --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php @@ -0,0 +1,111 @@ +null if no environments exist. + * + * @return array|null + */ + public function getEnvironments(); + + /** + * Returns the configuration for a given environment. + * + * This method returns null if the specified environment + * doesn't exist. + * + * @param string $name + * @return array|null + */ + public function getEnvironment($name); + + /** + * Does the specified environment exist in the configuration file? + * + * @param string $name Environment Name + * @return boolean + */ + public function hasEnvironment($name); + + /** + * Gets the default environment name. + * + * @throws \RuntimeException + * @return string + */ + public function getDefaultEnvironment(); + + /** + * Gets the config file path. + * + * @return string + */ + public function getConfigFilePath(); + + /** + * Gets the path of the migration files. + * + * @return string + */ + public function getMigrationPath(); + + /** + * Get the template file name. + * + * @return string|false + */ + public function getTemplateFile(); + + /** + * Get the template class name. + * + * @return string|false + */ + public function getTemplateClass(); + +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php b/vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php new file mode 100644 index 00000000..fa7a9fb8 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php @@ -0,0 +1,299 @@ + + */ +abstract class AbstractCommand extends Command +{ + /** + * The location of the default migration template. + */ + const DEFAULT_MIGRATION_TEMPLATE = '/../../Migration/Migration.template.php.dist'; + + /** + * @var ConfigInterface + */ + protected $config; + + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * @var Manager + */ + protected $manager; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->addOption('--configuration', '-c', InputOption::VALUE_REQUIRED, 'The configuration file to load'); + $this->addOption('--parser', '-p', InputOption::VALUE_REQUIRED, 'Parser used to read the config file. Defaults to YAML'); + } + + /** + * Bootstrap Phinx. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + public function bootstrap(InputInterface $input, OutputInterface $output) + { + if (!$this->getConfig()) { + $this->loadConfig($input, $output); + } + + $this->loadManager($output); + // report the migrations path + $output->writeln('using migration path ' . $this->getConfig()->getMigrationPath()); + } + + /** + * Sets the config. + * + * @param ConfigInterface $config + * @return AbstractCommand + */ + public function setConfig(ConfigInterface $config) + { + $this->config = $config; + return $this; + } + + /** + * Gets the config. + * + * @return Config + */ + public function getConfig() + { + return $this->config; + } + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter + * @return AbstractCommand + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Sets the migration manager. + * + * @param Manager $manager + * @return AbstractCommand + */ + public function setManager(Manager $manager) + { + $this->manager = $manager; + return $this; + } + + /** + * Gets the migration manager. + * + * @return Manager + */ + public function getManager() + { + return $this->manager; + } + + /** + * Returns config file path + * + * @param InputInterface $input + * @return string + */ + protected function locateConfigFile(InputInterface $input) + { + $configFile = $input->getOption('configuration'); + + $useDefault = false; + + if (null === $configFile || false === $configFile) { + $useDefault = true; + } + + $cwd = getcwd(); + + // locate the phinx config file (default: phinx.yml) + // TODO - In future walk the tree in reverse (max 10 levels) + $locator = new FileLocator(array( + $cwd . DIRECTORY_SEPARATOR + )); + + if (!$useDefault) { + // Locate() throws an exception if the file does not exist + return $locator->locate($configFile, $cwd, $first = true); + } + + $possibleConfigFiles = array('phinx.php', 'phinx.json', 'phinx.yml'); + foreach ($possibleConfigFiles as $configFile) { + try { + return $locator->locate($configFile, $cwd, $first = true); + } catch (\InvalidArgumentException $exception) { + $lastException = $exception; + } + } + throw $lastException; + } + + /** + * Parse the config file and load it into the config object + * + * @param InputInterface $input + * @param OutputInterface $output + * @throws \InvalidArgumentException + * @return void + */ + protected function loadConfig(InputInterface $input, OutputInterface $output) + { + $configFilePath = $this->locateConfigFile($input); + $output->writeln('using config file .' . str_replace(getcwd(), '', realpath($configFilePath))); + + $parser = $input->getOption('parser'); + + // If no parser is specified try to determine the correct one from the file extension. Defaults to YAML + if (null === $parser) { + $extension = pathinfo($configFilePath, PATHINFO_EXTENSION); + + switch (strtolower($extension)) { + case 'json': + $parser = 'json'; + break; + case 'php': + $parser = 'php'; + break; + case 'yml': + default: + $parser = 'yaml'; + break; + } + } + + switch (strtolower($parser)) { + case 'json': + $config = Config::fromJson($configFilePath); + break; + case 'php': + $config = Config::fromPhp($configFilePath); + break; + case 'yaml': + $config = Config::fromYaml($configFilePath); + break; + default: + throw new \InvalidArgumentException(sprintf('\'%s\' is not a valid parser.', $parser)); + } + + $output->writeln('using config parser ' . $parser); + + $this->setConfig($config); + } + + /** + * Load the migrations manager and inject the config + * + * @param OutputInterface $output + * @return void + */ + protected function loadManager(OutputInterface $output) + { + if (null === $this->getManager()) { + $manager = new Manager($this->getConfig(), $output); + $this->setManager($manager); + } + } + + /** + * Verify that the migration directory exists and is writable. + * + * @throws InvalidArgumentException + * @return void + */ + protected function verifyMigrationDirectory($path) + { + if (!is_dir($path)) { + throw new \InvalidArgumentException(sprintf( + 'Migration directory "%s" does not exist', + $path + )); + } + + if (!is_writeable($path)) { + throw new \InvalidArgumentException(sprintf( + 'Migration directory "%s" is not writeable', + $path + )); + } + } + + /** + * Returns the migration template filename. + * + * @return string + */ + protected function getMigrationTemplateFilename() + { + return __DIR__ . self::DEFAULT_MIGRATION_TEMPLATE; + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Console/Command/Create.php b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Create.php new file mode 100644 index 00000000..f3b26429 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Create.php @@ -0,0 +1,241 @@ +setName('create') + ->setDescription('Create a new migration') + ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the migration?') + ->setHelp(sprintf( + '%sCreates a new database migration%s', + PHP_EOL, + PHP_EOL + )); + + // An alternative template. + $this->addOption('template', 't', InputOption::VALUE_REQUIRED, 'Use an alternative template'); + + // A classname to be used to gain access to the template content as well as the ability to + // have a callback once the migration file has been created. + $this->addOption('class', 'l', InputOption::VALUE_REQUIRED, 'Use a class implementing "' . self::CREATION_INTERFACE . '" to generate the template'); + } + + /** + * Get the confirmation question asking if the user wants to create the + * migrations directory. + * + * @return ConfirmationQuestion + */ + protected function getCreateMigrationDirectoryQuestion() + { + return new ConfirmationQuestion('Create migrations directory? [y]/n ', true); + } + + /** + * Migrate the database. + * + * @param InputInterface $input + * @param OutputInterface $output + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->bootstrap($input, $output); + + // get the migration path from the config + $path = $this->getConfig()->getMigrationPath(); + + if (!file_exists($path)) { + $helper = $this->getHelper('question'); + $question = $this->getCreateMigrationDirectoryQuestion(); + + if ($helper->ask($input, $output, $question)) { + mkdir($path, 0755, true); + } + } + + $this->verifyMigrationDirectory($path); + + $path = realpath($path); + $className = $input->getArgument('name'); + + if (!Util::isValidMigrationClassName($className)) { + throw new \InvalidArgumentException(sprintf( + 'The migration class name "%s" is invalid. Please use CamelCase format.', + $className + )); + } + + if (!Util::isUniqueMigrationClassName($className, $path)) { + throw new \InvalidArgumentException(sprintf( + 'The migration class name "%s" already exists', + $className + )); + } + + // Compute the file path + $fileName = Util::mapClassNameToFileName($className); + $filePath = $path . DIRECTORY_SEPARATOR . $fileName; + + if (is_file($filePath)) { + throw new \InvalidArgumentException(sprintf( + 'The file "%s" already exists', + $filePath + )); + } + + // Get the alternative template and static class options, but only allow one of them. + $altTemplate = $input->getOption('template'); + if (!$altTemplate) { + $altTemplate = $this->getConfig()->getTemplateFile(); + } + + $creationClassName = $input->getOption('class'); + if (!$creationClassName) { + $creationClassName = $this->getConfig()->getTemplateClass(); + } + + if ($altTemplate && $creationClassName) { + throw new \InvalidArgumentException('Cannot use --template and --class at the same time'); + } + + // Verify the alternative template file's existence. + if ($altTemplate && !is_file($altTemplate)) { + throw new \InvalidArgumentException(sprintf( + 'The alternative template file "%s" does not exist', + $altTemplate + )); + } + + // Verify that the template creation class (or the aliased class) exists and that it implements the required interface. + $aliasedClassName = null; + if ($creationClassName) { + // Supplied class does not exist, is it aliased? + if (!class_exists($creationClassName)) { + $aliasedClassName = $this->getConfig()->getAlias($creationClassName); + if ($aliasedClassName && !class_exists($aliasedClassName)) { + throw new \InvalidArgumentException(sprintf( + 'The class "%s" via the alias "%s" does not exist', + $aliasedClassName, + $creationClassName + )); + } elseif (!$aliasedClassName) { + throw new \InvalidArgumentException(sprintf( + 'The class "%s" does not exist', + $creationClassName + )); + } + } + + // Does the class implement the required interface? + if (!$aliasedClassName && !is_subclass_of($creationClassName, self::CREATION_INTERFACE)) { + throw new \InvalidArgumentException(sprintf( + 'The class "%s" does not implement the required interface "%s"', + $creationClassName, + self::CREATION_INTERFACE + )); + } elseif ($aliasedClassName && !is_subclass_of($aliasedClassName, self::CREATION_INTERFACE)) { + throw new \InvalidArgumentException(sprintf( + 'The class "%s" via the alias "%s" does not implement the required interface "%s"', + $aliasedClassName, + $creationClassName, + self::CREATION_INTERFACE + )); + } + } + + // Use the aliased class. + $creationClassName = $aliasedClassName ?: $creationClassName; + + // Determine the appropriate mechanism to get the template + if ($creationClassName) { + // Get the template from the creation class + $creationClass = new $creationClassName(); + $contents = $creationClass->getMigrationTemplate(); + } else { + // Load the alternative template if it is defined. + $contents = file_get_contents($altTemplate ?: $this->getMigrationTemplateFilename()); + } + + // inject the class names appropriate to this migration + $classes = array( + '$useClassName' => $this->getConfig()->getMigrationBaseClassName(false), + '$className' => $className, + '$baseClassName' => $this->getConfig()->getMigrationBaseClassName(true), + ); + $contents = strtr($contents, $classes); + + if (false === file_put_contents($filePath, $contents)) { + throw new \RuntimeException(sprintf( + 'The file "%s" could not be written to', + $path + )); + } + + // Do we need to do the post creation call to the creation class? + if ($creationClassName) { + $creationClass->postMigrationCreation($filePath, $className, $this->getConfig()->getMigrationBaseClassName()); + } + + $output->writeln('using migration base class ' . $classes['$useClassName']); + + if (!empty($altTemplate)) { + $output->writeln('using alternative template ' . $altTemplate); + } elseif (!empty($creationClassName)) { + $output->writeln('using template creation class ' . $creationClassName); + } else { + $output->writeln('using default template'); + } + + $output->writeln('created .' . str_replace(getcwd(), '', $filePath)); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Console/Command/Init.php b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Init.php new file mode 100644 index 00000000..a2a15eae --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Init.php @@ -0,0 +1,107 @@ +setName('init') + ->setDescription('Initialize the application for Phinx') + ->addArgument('path', InputArgument::OPTIONAL, 'Which path should we initialize for Phinx?') + ->setHelp(sprintf( + '%sInitializes the application for Phinx%s', + PHP_EOL, + PHP_EOL + )); + } + + /** + * Initializes the application. + * + * @param InputInterface $input + * @param OutputInterface $output + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + // get the migration path from the config + $path = $input->getArgument('path'); + + if (null === $path) { + $path = getcwd(); + } + + $path = realpath($path); + + if (!is_writeable($path)) { + throw new \InvalidArgumentException(sprintf( + 'The directory "%s" is not writeable', + $path + )); + } + + // Compute the file path + $fileName = 'phinx.yml'; // TODO - maybe in the future we allow custom config names. + $filePath = $path . DIRECTORY_SEPARATOR . $fileName; + + if (file_exists($filePath)) { + throw new \InvalidArgumentException(sprintf( + 'The file "%s" already exists', + $filePath + )); + } + + // load the config template + if (is_dir(__DIR__ . '/../../../data/Phinx')) { + $contents = file_get_contents(__DIR__ . '/../../../data/Phinx/phinx.yml'); + } else { + $contents = file_get_contents(__DIR__ . '/../../../../phinx.yml'); + } + + if (false === file_put_contents($filePath, $contents)) { + throw new \RuntimeException(sprintf( + 'The file "%s" could not be written to', + $path + )); + } + + $output->writeln('created .' . str_replace(getcwd(), '', $filePath)); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php new file mode 100644 index 00000000..95871cbd --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php @@ -0,0 +1,117 @@ +addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment'); + + $this->setName('migrate') + ->setDescription('Migrate the database') + ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to migrate to') + ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to') + ->setHelp( +<<migrate command runs all available migrations, optionally up to a specific version + +phinx migrate -e development +phinx migrate -e development -t 20110103081132 +phinx migrate -e development -d 20110103 +phinx migrate -e development -v + +EOT + ); + } + + /** + * Migrate the database. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->bootstrap($input, $output); + + $version = $input->getOption('target'); + $environment = $input->getOption('environment'); + $date = $input->getOption('date'); + + if (null === $environment) { + $environment = $this->getConfig()->getDefaultEnvironment(); + $output->writeln('warning no environment specified, defaulting to: ' . $environment); + } else { + $output->writeln('using environment ' . $environment); + } + + $envOptions = $this->getConfig()->getEnvironment($environment); + if (isset($envOptions['adapter'])) { + $output->writeln('using adapter ' . $envOptions['adapter']); + } + + if (isset($envOptions['wrapper'])) { + $output->writeln('using wrapper ' . $envOptions['wrapper']); + } + + if (isset($envOptions['name'])) { + $output->writeln('using database ' . $envOptions['name']); + } + + if (isset($envOptions['table_prefix'])) { + $output->writeln('using table prefix ' . $envOptions['table_prefix']); + } + if (isset($envOptions['table_suffix'])) { + $output->writeln('using table suffix ' . $envOptions['table_suffix']); + } + + // run the migrations + $start = microtime(true); + if (null !== $date) { + $this->getManager()->migrateToDateTime($environment, new \DateTime($date)); + } else { + $this->getManager()->migrate($environment, $version); + } + $end = microtime(true); + + $output->writeln(''); + $output->writeln('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php new file mode 100644 index 00000000..5da21762 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php @@ -0,0 +1,110 @@ +addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment'); + + $this->setName('rollback') + ->setDescription('Rollback the last or to a specific migration') + ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to rollback to') + ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to rollback to') + ->setHelp( +<<rollback command reverts the last migration, or optionally up to a specific version + +phinx rollback -e development +phinx rollback -e development -t 20111018185412 +phinx rollback -e development -d 20111018 +phinx rollback -e development -v + +EOT + ); + } + + /** + * Rollback the migration. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->bootstrap($input, $output); + + $environment = $input->getOption('environment'); + $version = $input->getOption('target'); + $date = $input->getOption('date'); + + if (null === $environment) { + $environment = $this->getConfig()->getDefaultEnvironment(); + $output->writeln('warning no environment specified, defaulting to: ' . $environment); + } else { + $output->writeln('using environment ' . $environment); + } + + $envOptions = $this->getConfig()->getEnvironment($environment); + if (isset($envOptions['adapter'])) { + $output->writeln('using adapter ' . $envOptions['adapter']); + } + + if (isset($envOptions['wrapper'])) { + $output->writeln('using wrapper ' . $envOptions['wrapper']); + } + + if (isset($envOptions['name'])) { + $output->writeln('using database ' . $envOptions['name']); + } + + // rollback the specified environment + $start = microtime(true); + if (null !== $date) { + $this->getManager()->rollbackToDateTime($environment, new \DateTime($date)); + } else { + $this->getManager()->rollback($environment, $version); + } + $end = microtime(true); + + $output->writeln(''); + $output->writeln('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Console/Command/Status.php b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Status.php new file mode 100644 index 00000000..fe7136f9 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Status.php @@ -0,0 +1,86 @@ +addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment.'); + + $this->setName('status') + ->setDescription('Show migration status') + ->addOption('--format', '-f', InputOption::VALUE_REQUIRED, 'The output format: text or json. Defaults to text.') + ->setHelp( +<<status command prints a list of all migrations, along with their current status + +phinx status -e development +phinx status -e development -f json +EOT + ); + } + + /** + * Show the migration status. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->bootstrap($input, $output); + + $environment = $input->getOption('environment'); + $format = $input->getOption('format'); + + if (null === $environment) { + $environment = $this->getConfig()->getDefaultEnvironment(); + $output->writeln('warning no environment specified, defaulting to: ' . $environment); + } else { + $output->writeln('using environment ' . $environment); + } + if (null !== $format) { + $output->writeln('using format ' . $format); + } + + // print the status + $this->getManager()->printStatus($environment, $format); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Console/Command/Test.php b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Test.php new file mode 100644 index 00000000..446fa930 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Console/Command/Test.php @@ -0,0 +1,99 @@ + + */ +class Test extends AbstractCommand +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + parent::configure(); + + $this->addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment'); + + $this->setName('test') + ->setDescription('Verify the configuration file') + ->setHelp( +<<test command verifies the YAML configuration file and optionally an environment + +phinx test +phinx test -e development + +EOT + ); + } + + /** + * Verify configuration file + * + * @param InputInterface $input + * @param OutputInterface $output + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->loadConfig($input, $output); + $this->loadManager($output); + + $this->verifyMigrationDirectory($this->getConfig()->getMigrationPath()); + + $envName = $input->getOption('environment'); + if ($envName) { + if (!$this->getConfig()->hasEnvironment($envName)) { + throw new \InvalidArgumentException(sprintf( + 'The environment "%s" does not exist', + $envName + )); + } + + $output->writeln(sprintf('validating environment %s', $envName)); + $environment = new Environment( + $envName, + $this->getConfig()->getEnvironment($envName) + ); + // validate environment connection + $environment->getAdapter()->connect(); + } + + $output->writeln('success!'); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php b/vendor/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php new file mode 100644 index 00000000..d49e8c4c --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php @@ -0,0 +1,82 @@ + + */ +class PhinxApplication extends Application +{ + /** + * Class Constructor. + * + * Initialize the Phinx console application. + * + * @param string $version The Application Version + */ + public function __construct($version = '0.4.6') + { + parent::__construct('Phinx by Rob Morgan - https://phinx.org.', $version); + + $this->addCommands(array( + new Command\Init(), + new Command\Create(), + new Command\Migrate(), + new Command\Rollback(), + new Command\Status(), + new Command\Test(), + )); + } + + /** + * 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) + { + // always show the version information except when the user invokes the help + // command as that already does it + if (false === $input->hasParameterOption(array('--help', '-h')) && null !== $input->getFirstArgument()) { + $output->writeln($this->getLongVersion()); + $output->writeln(''); + } + + return parent::doRun($input, $output); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php new file mode 100644 index 00000000..c255951f --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php @@ -0,0 +1,183 @@ + + */ +class AdapterFactory +{ + /** + * @var AdapterFactory + */ + protected static $instance; + + /** + * Get the factory singleton instance. + * + * @return AdapterFactory + */ + public static function instance() + { + if (!static::$instance) { + static::$instance = new static(); + } + return static::$instance; + } + + /** + * Class map of database adapters, indexed by PDO::ATTR_DRIVER_NAME. + * + * @var array + */ + protected $adapters = array( + 'mysql' => 'Phinx\Db\Adapter\MysqlAdapter', + 'pgsql' => 'Phinx\Db\Adapter\PostgresAdapter', + 'sqlite' => 'Phinx\Db\Adapter\SQLiteAdapter', + 'sqlsrv' => 'Phinx\Db\Adapter\SqlServerAdapter', + ); + + /** + * Class map of adapters wrappers, indexed by name. + * + * @var array + */ + protected $wrappers = array( + 'prefix' => 'Phinx\Db\Adapter\TablePrefixAdapter', + 'proxy' => 'Phinx\Db\Adapter\ProxyAdapter', + ); + + /** + * Add or replace an adapter with a fully qualified class name. + * + * @throws \RuntimeException + * @param string $name + * @param string $class + * @return $this + */ + public function registerAdapter($name, $class) + { + if (!is_subclass_of($class, 'Phinx\Db\Adapter\AdapterInterface')) { + throw new \RuntimeException(sprintf( + 'Adapter class "%s" must be implement Phinx\\Db\\Adapter\\AdapterInterface', + $class + )); + } + $this->adapters[$name] = $class; + return $this; + } + + /** + * Get an adapter class by name. + * + * @throws \RuntimeException + * @param string $name + * @return string + */ + protected function getClass($name) + { + if (empty($this->adapters[$name])) { + throw new \RuntimeException(sprintf( + 'Adapter "%s" has not been registered', + $name + )); + } + return $this->adapters[$name]; + } + + /** + * Get an adapter instance by name. + * + * @param string $name + * @param array $options + * @return AdapterInterface + */ + public function getAdapter($name, array $options) + { + $class = $this->getClass($name); + return new $class($options); + } + + /** + * Add or replace a wrapper with a fully qualified class name. + * + * @throws \RuntimeException + * @param string $name + * @param string $class + * @return $this + */ + public function registerWrapper($name, $class) + { + if (!is_subclass_of($class, 'Phinx\Db\Adapter\WrapperInterface')) { + throw new \RuntimeException(sprintf( + 'Wrapper class "%s" must be implement Phinx\\Db\\Adapter\\WrapperInterface', + $class + )); + } + $this->wrappers[$name] = $class; + return $this; + } + + /** + * Get a wrapper class by name. + * + * @throws \RuntimeException + * @param string $name + * @return string + */ + protected function getWrapperClass($name) + { + if (empty($this->wrappers[$name])) { + throw new \RuntimeException(sprintf( + 'Wrapper "%s" has not been registered', + $name + )); + } + return $this->wrappers[$name]; + } + + /** + * Get a wrapper instance by name. + * + * @param string $name + * @param AdapterInterface $adapter + * @return AdapterInterface + */ + public function getWrapper($name, AdapterInterface $adapter) + { + $class = $this->getWrapperClass($name); + return new $class($adapter); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php new file mode 100644 index 00000000..116c072d --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php @@ -0,0 +1,460 @@ + + */ +interface AdapterInterface +{ + const PHINX_TYPE_STRING = 'string'; + const PHINX_TYPE_CHAR = 'char'; + const PHINX_TYPE_TEXT = 'text'; + const PHINX_TYPE_INTEGER = 'integer'; + const PHINX_TYPE_BIG_INTEGER = 'biginteger'; + const PHINX_TYPE_FLOAT = 'float'; + const PHINX_TYPE_DECIMAL = 'decimal'; + const PHINX_TYPE_DATETIME = 'datetime'; + const PHINX_TYPE_TIMESTAMP = 'timestamp'; + const PHINX_TYPE_TIME = 'time'; + const PHINX_TYPE_DATE = 'date'; + const PHINX_TYPE_BINARY = 'binary'; + const PHINX_TYPE_BOOLEAN = 'boolean'; + const PHINX_TYPE_JSON = 'json'; + const PHINX_TYPE_JSONB = 'jsonb'; + const PHINX_TYPE_UUID = 'uuid'; + const PHINX_TYPE_FILESTREAM = 'filestream'; + + // Geospatial database types + const PHINX_TYPE_GEOMETRY = 'geometry'; + const PHINX_TYPE_POINT = 'point'; + const PHINX_TYPE_LINESTRING = 'linestring'; + const PHINX_TYPE_POLYGON = 'polygon'; + + // only for mysql so far + const PHINX_TYPE_ENUM = 'enum'; + const PHINX_TYPE_SET = 'set'; + + /** + * Get all migrated version numbers. + * + * @return array + */ + public function getVersions(); + + /** + * Set adapter configuration options. + * + * @param array $options + * @return AdapterInterface + */ + public function setOptions(array $options); + + /** + * Get all adapter options. + * + * @return array + */ + public function getOptions(); + + /** + * Check if an option has been set. + * + * @param string $name + * @return boolean + */ + public function hasOption($name); + + /** + * Get a single adapter option, or null if the option does not exist. + * + * @param string $name + * @return mixed + */ + public function getOption($name); + + /** + * Sets the console output. + * + * @param OutputInterface $output Output + * @return AdapterInterface + */ + public function setOutput(OutputInterface $output); + + /** + * Gets the console output. + * + * @return OutputInterface + */ + public function getOutput(); + + /** + * Records a migration being run. + * + * @param MigrationInterface $migration Migration + * @param string $direction Direction + * @param int $startTime Start Time + * @param int $endTime End Time + * @return AdapterInterface + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime); + + /** + * Does the schema table exist? + * + * @deprecated use hasTable instead. + * @return boolean + */ + public function hasSchemaTable(); + + /** + * Creates the schema table. + * + * @return void + */ + public function createSchemaTable(); + + /** + * Returns the adapter type. + * + * @return string + */ + public function getAdapterType(); + + /** + * Initializes the database connection. + * + * @throws \RuntimeException When the requested database driver is not installed. + * @return void + */ + public function connect(); + + /** + * Closes the database connection. + * + * @return void + */ + public function disconnect(); + + /** + * Does the adapter support transactions? + * + * @return boolean + */ + public function hasTransactions(); + + /** + * Begin a transaction. + * + * @return void + */ + public function beginTransaction(); + + /** + * Commit a transaction. + * + * @return void + */ + public function commitTransaction(); + + /** + * Rollback a transaction. + * + * @return void + */ + public function rollbackTransaction(); + + /** + * Executes a SQL statement and returns the number of affected rows. + * + * @param string $sql SQL + * @return int + */ + public function execute($sql); + + /** + * Executes a SQL statement and returns the result as an array. + * + * @param string $sql SQL + * @return array + */ + public function query($sql); + + /** + * Executes a query and returns only one row as an array. + * + * @param string $sql SQL + * @return array + */ + public function fetchRow($sql); + + /** + * Executes a query and returns an array of rows. + * + * @param string $sql SQL + * @return array + */ + public function fetchAll($sql); + + /** + * Inserts data into the table + * + * @param Table $table where to insert data + * @param array $columns column names + * @param $data + */ + public function insert(Table $table, $columns, $data); + + /** + * Quotes a table name for use in a query. + * + * @param string $tableName Table Name + * @return string + */ + public function quoteTableName($tableName); + + /** + * Quotes a column name for use in a query. + * + * @param string $columnName Table Name + * @return string + */ + public function quoteColumnName($columnName); + + /** + * Checks to see if a table exists. + * + * @param string $tableName Table Name + * @return boolean + */ + public function hasTable($tableName); + + /** + * Creates the specified database table. + * + * @param Table $table Table + * @return void + */ + public function createTable(Table $table); + + /** + * Renames the specified database table. + * + * @param string $tableName Table Name + * @param string $newName New Name + * @return void + */ + public function renameTable($tableName, $newName); + + /** + * Drops the specified database table. + * + * @param string $tableName Table Name + * @return void + */ + public function dropTable($tableName); + + /** + * Returns table columns + * + * @param string $tableName Table Name + * @return Column[] + */ + public function getColumns($tableName); + + /** + * Checks to see if a column exists. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @return boolean + */ + public function hasColumn($tableName, $columnName); + + /** + * Adds the specified column to a database table. + * + * @param Table $table Table + * @param Column $column Column + * @return void + */ + public function addColumn(Table $table, Column $column); + + /** + * Renames the specified column. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @param string $newColumnName New Column Name + * @return void + */ + public function renameColumn($tableName, $columnName, $newColumnName); + + /** + * Change a table column type. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @param Column $newColumn New Column + * @return Table + */ + public function changeColumn($tableName, $columnName, Column $newColumn); + + /** + * Drops the specified column. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @return void + */ + public function dropColumn($tableName, $columnName); + + /** + * Checks to see if an index exists. + * + * @param string $tableName Table Name + * @param mixed $columns Column(s) + * @return boolean + */ + public function hasIndex($tableName, $columns); + + /** + * Adds the specified index to a database table. + * + * @param Table $table Table + * @param Index $index Index + * @return void + */ + public function addIndex(Table $table, Index $index); + + /** + * Drops the specified index from a database table. + * + * @param string $tableName + * @param mixed $columns Column(s) + * @return void + */ + public function dropIndex($tableName, $columns); + + /** + * Drops the index specified by name from a database table. + * + * @param string $tableName + * @param string $indexName + * @return void + */ + public function dropIndexByName($tableName, $indexName); + + /** + * Checks to see if a foreign key exists. + * + * @param string $tableName + * @param string[] $columns Column(s) + * @param string $constraint Constraint name + * @return boolean + */ + public function hasForeignKey($tableName, $columns, $constraint = null); + + /** + * Adds the specified foreign key to a database table. + * + * @param Table $table + * @param ForeignKey $foreignKey + * @return void + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey); + + /** + * Drops the specified foreign key from a database table. + * + * @param string $tableName + * @param string[] $columns Column(s) + * @param string $constraint Constraint name + * @return void + */ + public function dropForeignKey($tableName, $columns, $constraint = null); + + /** + * Returns an array of the supported Phinx column types. + * + * @return array + */ + public function getColumnTypes(); + + /** + * Checks that the given column is of a supported type. + * + * @param Column $column + * @return boolean + */ + public function isValidColumnType(Column $column); + + /** + * Converts the Phinx logical type to the adapter's SQL type. + * + * @param string $type + * @param integer $limit + * @return string + */ + public function getSqlType($type, $limit = null); + + /** + * Creates a new database. + * + * @param string $name Database Name + * @param array $options Options + * @return void + */ + public function createDatabase($name, $options = array()); + + /** + * Checks to see if a database exists. + * + * @param string $name Database Name + * @return boolean + */ + public function hasDatabase($name); + + /** + * Drops the specified database. + * + * @param string $name Database Name + * @return void + */ + public function dropDatabase($name); +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php new file mode 100644 index 00000000..cd1a5e63 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php @@ -0,0 +1,448 @@ + + */ +abstract class AdapterWrapper implements AdapterInterface, WrapperInterface +{ + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * {@inheritdoc} + */ + public function __construct(AdapterInterface $adapter) + { + $this->setAdapter($adapter); + } + + /** + * {@inheritdoc} + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->adapter->setOptions($options); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->adapter->getOptions(); + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return $this->adapter->hasOption($name); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + return $this->adapter->getOption($name); + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->adapter->setOutput($output); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->adapter->getOutput(); + } + + /** + * {@inheritdoc} + */ + public function connect() + { + return $this->getAdapter()->connect(); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + return $this->getAdapter()->disconnect(); + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getAdapter()->execute($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getAdapter()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $columns, $data) + { + return $this->getAdapter()->insert($table, $columns, $data); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + return $this->getAdapter()->fetchRow($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + return $this->getAdapter()->fetchAll($sql); + } + + /** + * {@inheritdoc} + */ + public function getVersions() + { + return $this->getAdapter()->getVersions(); + } + + /** + * {@inheritdoc} + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime) + { + $this->getAdapter()->migrated($migration, $direction, $startTime, $endTime); + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasSchemaTable() + { + return $this->getAdapter()->hasSchemaTable(); + } + + /** + * {@inheritdoc} + */ + public function createSchemaTable() + { + return $this->getAdapter()->createSchemaTable(); + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return $this->getAdapter()->getColumnTypes(); + } + + /** + * {@inheritdoc} + */ + public function isValidColumnType(Column $column) + { + return $this->getAdapter()->isValidColumnType($column); + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return $this->getAdapter()->hasTransactions(); + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + return $this->getAdapter()->beginTransaction(); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + return $this->getAdapter()->commitTransaction(); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + return $this->getAdapter()->rollbackTransaction(); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return $this->getAdapter()->quoteTableName($tableName); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return $this->getAdapter()->quoteColumnName($columnName); + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + return $this->getAdapter()->hasTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + return $this->getAdapter()->createTable($table); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + return $this->getAdapter()->renameTable($tableName, $newTableName); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + return $this->getAdapter()->dropTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + return $this->getAdapter()->getColumns($tableName); + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + return $this->getAdapter()->hasColumn($tableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + return $this->getAdapter()->addColumn($table, $column); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + return $this->getAdapter()->renameColumn($tableName, $columnName, $newColumnName); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + return $this->getAdapter()->changeColumn($tableName, $columnName, $newColumn); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + return $this->getAdapter()->dropColumn($tableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + return $this->getAdapter()->hasIndex($tableName, $columns); + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + return $this->getAdapter()->addIndex($table, $index); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns, $options = array()) + { + return $this->getAdapter()->dropIndex($tableName, $columns, $options); + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + return $this->getAdapter()->dropIndexByName($tableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + return $this->getAdapter()->hasForeignKey($tableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + return $this->getAdapter()->addForeignKey($table, $foreignKey); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + return $this->getAdapter()->dropForeignKey($tableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + return $this->getAdapter()->getSqlType($type, $limit); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + return $this->getAdapter()->createDatabase($name, $options); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + return $this->getAdapter()->hasDatabase($name); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + return $this->getAdapter()->dropDatabase($name); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php new file mode 100644 index 00000000..38a256df --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php @@ -0,0 +1,1110 @@ + + */ +class MysqlAdapter extends PdoAdapter implements AdapterInterface +{ + + protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true, 'boolean' => true); + + const TEXT_TINY = 255; + const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */ + const TEXT_REGULAR = 65535; + const TEXT_MEDIUM = 16777215; + const TEXT_LONG = 4294967295; + + // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT + const BLOB_TINY = 255; + const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */ + const BLOB_REGULAR = 65535; + const BLOB_MEDIUM = 16777215; + const BLOB_LONG = 4294967295; + + const INT_TINY = 255; + const INT_SMALL = 65535; + const INT_MEDIUM = 16777215; + const INT_REGULAR = 4294967295; + const INT_BIG = 18446744073709551615; + + /** + * {@inheritdoc} + */ + public function connect() + { + if (null === $this->connection) { + if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $db = null; + $options = $this->getOptions(); + + $dsn = 'mysql:'; + + if (!empty($options['unix_socket'])) { + // use socket connection + $dsn .= 'unix_socket=' . $options['unix_socket']; + } else { + // use network connection + $dsn .= 'host=' . $options['host']; + if (!empty($options['port'])) { + $dsn .= ';port=' . $options['port']; + } + } + + $dsn .= ';dbname=' . $options['name']; + + // charset support + if (!empty($options['charset'])) { + $dsn .= ';charset=' . $options['charset']; + } + + $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); + + // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO + // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants + foreach ($options as $key => $option) { + if (strpos($key, 'mysql_attr_') === 0) { + $driverOptions[constant('\PDO::' . strtoupper($key))] = $option; + } + } + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('START TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK'); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return str_replace('.', '`.`', $this->quoteColumnName($tableName)); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '`' . str_replace('`', '``', $columnName) . '`'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $options = $this->getOptions(); + + $exists = $this->fetchRow(sprintf( + "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'", + $options['name'], $tableName + )); + + return !empty($exists); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + + // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html + $defaultOptions = array( + 'engine' => 'InnoDB', + 'collation' => 'utf8_general_ci' + ); + $options = array_merge($defaultOptions, $table->getOptions()); + + // Add the default primary key + $columns = $table->getPendingColumns(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = 'id'; + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = $options['id']; + } + + // TODO - process table options like collation etc + + // process table engine (default to InnoDB) + $optionsStr = 'ENGINE = InnoDB'; + if (isset($options['engine'])) { + $optionsStr = sprintf('ENGINE = %s', $options['engine']); + } + + // process table collation + if (isset($options['collation'])) { + $charset = explode('_', $options['collation']); + $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]); + $optionsStr .= sprintf(' COLLATE %s', $options['collation']); + } + + // set the table comment + if (isset($options['comment'])) { + $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment'])); + } + + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + foreach ($columns as $column) { + $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $sql = rtrim($sql); + $sql .= ' PRIMARY KEY ('; + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $sql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the + // anonymous function, but for now just hard-code the adapter quotes + $sql .= implode( + ',', + array_map( + function ($v) { + return '`' . $v . '`'; + }, + $options['primary_key'] + ) + ); + } + $sql .= ')'; + } else { + $sql = substr(rtrim($sql), 0, -1); // no primary keys + } + + // set the indexes + $indexes = $table->getIndexes(); + if (!empty($indexes)) { + foreach ($indexes as $index) { + $sql .= ', ' . $this->getIndexSqlDefinition($index); + } + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey); + } + } + + $sql .= ') ' . $optionsStr; + $sql = rtrim($sql) . ';'; + + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $this->execute(sprintf('RENAME TABLE %s TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName))); + foreach ($rows as $columnInfo) { + + $phinxType = $this->getPhinxType($columnInfo['Type']); + + $column = new Column(); + $column->setName($columnInfo['Field']) + ->setNull($columnInfo['Null'] != 'NO') + ->setDefault($columnInfo['Default']) + ->setType($phinxType['name']) + ->setLimit($phinxType['limit']); + + if ($columnInfo['Extra'] == 'auto_increment') { + $column->setIdentity(true); + } + + $columns[] = $column; + } + + return $columns; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName))); + foreach ($rows as $column) { + if (strcasecmp($column['Field'], $columnName) === 0) { + return true; + } + } + + return false; + } + + /** + * Get the defintion for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = (int) $default; + } + return isset($default) ? ' DEFAULT ' . $default : ''; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + $sql = sprintf( + 'ALTER TABLE %s ADD %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + if ($column->getAfter()) { + $sql .= ' AFTER ' . $this->quoteColumnName($column->getAfter()); + } + + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->startCommandTimer(); + $rows = $this->fetchAll(sprintf('DESCRIBE %s', $this->quoteTableName($tableName))); + foreach ($rows as $row) { + if (strcasecmp($row['Field'], $columnName) === 0) { + $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL'; + $extra = ' ' . strtoupper($row['Extra']); + if (!is_null($row['Default'])) { + $extra .= $this->getDefaultValueDefinition($row['Default']); + } + $definition = $row['Type'] . ' ' . $null . $extra; + + $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s CHANGE COLUMN %s %s %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumnName), + $definition + ) + ); + $this->endCommandTimer(); + return; + } + } + + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' + . $columnName + )); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : ''; + $this->execute( + sprintf( + 'ALTER TABLE %s CHANGE %s %s %s%s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumn->getName()), + $this->getColumnSqlDefinition($newColumn), + $after + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s DROP COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + $this->endCommandTimer(); + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getIndexes($tableName) + { + $indexes = array(); + $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName))); + foreach ($rows as $row) { + if (!isset($indexes[$row['Key_name']])) { + $indexes[$row['Key_name']] = array('columns' => array()); + } + $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']); + } + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $this->execute( + sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getIndexSqlDefinition($index) + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $indexName => $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP INDEX %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + //$a = array_diff($columns, $index['columns']); + if ($name === $indexName) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP INDEX %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + if ($constraint) { + if (isset($foreignKeys[$constraint])) { + return !empty($foreignKeys[$constraint]); + } + return false; + } else { + foreach ($foreignKeys as $key) { + $a = array_diff($columns, $key['columns']); + if (empty($a)) { + return true; + } + } + return false; + } + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll(sprintf( + "SELECT + CONSTRAINT_NAME, + TABLE_NAME, + COLUMN_NAME, + REFERENCED_TABLE_NAME, + REFERENCED_COLUMN_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE REFERENCED_TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = '%s' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", + $tableName + )); + foreach ($rows as $row) { + $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME']; + } + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $this->execute( + sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getForeignKeySqlDefinition($foreignKey) + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + if ($constraint) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP FOREIGN KEY %s', + $this->quoteTableName($tableName), + $constraint + ) + ); + $this->endCommandTimer(); + return; + } else { + foreach ($columns as $column) { + $rows = $this->fetchAll(sprintf( + "SELECT + CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE REFERENCED_TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = '%s' + AND COLUMN_NAME = '%s' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", + $tableName, + $column + )); + foreach ($rows as $row) { + $this->dropForeignKey($tableName, $columns, $row['CONSTRAINT_NAME']); + } + } + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_STRING: + return array('name' => 'varchar', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_CHAR: + return array('name' => 'char', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_TEXT: + if ($limit) { + $sizes = array( + // Order matters! Size must always be tested from longest to shortest! + 'longtext' => static::TEXT_LONG, + 'mediumtext' => static::TEXT_MEDIUM, + 'text' => static::TEXT_REGULAR, + 'tinytext' => static::TEXT_SMALL, + ); + foreach ($sizes as $name => $length) { + if ($limit >= $length) { + return array('name' => $name); + } + } + } + return array('name' => 'text'); + break; + case static::PHINX_TYPE_BINARY: + if ($limit) { + $sizes = array( + // Order matters! Size must always be tested from longest to shortest! + 'longblob' => static::BLOB_LONG, + 'mediumblob' => static::BLOB_MEDIUM, + 'blob' => static::BLOB_REGULAR, + 'tinyblob' => static::BLOB_SMALL, + ); + foreach ($sizes as $name => $length) { + if ($limit >= $length) { + return array('name' => $name); + } + } + } + return array('name' => 'blob'); + break; + case static::PHINX_TYPE_INTEGER: + if ($limit && $limit >= static::INT_TINY) { + $sizes = array( + // Order matters! Size must always be tested from longest to shortest! + 'bigint' => static::INT_BIG, + 'int' => static::INT_REGULAR, + 'mediumint' => static::INT_MEDIUM, + 'smallint' => static::INT_SMALL, + 'tinyint' => static::INT_TINY, + ); + $limits = array( + 'int' => 11, + 'bigint' => 20, + ); + foreach ($sizes as $name => $length) { + if ($limit >= $length) { + $def = array('name' => $name); + if (isset($limits[$name])) { + $def['limit'] = $limits[$name]; + } + return $def; + } + } + } elseif (!$limit) { + $limit = 11; + } + return array('name' => 'int', 'limit' => $limit); + break; + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint', 'limit' => 20); + break; + case static::PHINX_TYPE_FLOAT: + return array('name' => 'float'); + break; + case static::PHINX_TYPE_DECIMAL: + return array('name' => 'decimal'); + break; + case static::PHINX_TYPE_DATETIME: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'timestamp'); + break; + case static::PHINX_TYPE_TIME: + return array('name' => 'time'); + break; + case static::PHINX_TYPE_DATE: + return array('name' => 'date'); + break; + case static::PHINX_TYPE_BOOLEAN: + return array('name' => 'tinyint', 'limit' => 1); + break; + case static::PHINX_TYPE_UUID: + return array('name' => 'char', 'limit' => 36); + // Geospatial database types + case static::PHINX_TYPE_GEOMETRY: + case static::PHINX_TYPE_POINT: + case static::PHINX_TYPE_LINESTRING: + case static::PHINX_TYPE_POLYGON: + return array('name' => $type); + case static::PHINX_TYPE_ENUM: + return array('name' => 'enum'); + break; + case static::PHINX_TYPE_SET: + return array('name' => 'set'); + break; + default: + throw new \RuntimeException('The type: "' . $type . '" is not supported.'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param string $sqlTypeDef + * @throws \RuntimeException + * @internal param string $sqlType SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlTypeDef) + { + if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) { + throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported'); + } else { + $limit = null; + $precision = null; + $type = $matches[1]; + if (count($matches) > 2) { + $limit = $matches[3] ? (int) $matches[3] : null; + } + if (count($matches) > 4) { + $precision = (int) $matches[5]; + } + if ($type === 'tinyint' && $limit === 1) { + $type = static::PHINX_TYPE_BOOLEAN; + $limit = null; + } + switch ($type) { + case 'varchar': + $type = static::PHINX_TYPE_STRING; + if ($limit === 255) { + $limit = null; + } + break; + case 'char': + $type = static::PHINX_TYPE_CHAR; + if ($limit === 255) { + $limit = null; + } + if ($limit === 36) { + $type = static::PHINX_TYPE_UUID; + } + break; + case 'tinyint': + $type = static::PHINX_TYPE_INTEGER; + $limit = static::INT_TINY; + break; + case 'smallint': + $type = static::PHINX_TYPE_INTEGER; + $limit = static::INT_SMALL; + break; + case 'mediumint': + $type = static::PHINX_TYPE_INTEGER; + $limit = static::INT_MEDIUM; + break; + case 'int': + $type = static::PHINX_TYPE_INTEGER; + if ($limit === 11) { + $limit = null; + } + break; + case 'bigint': + if ($limit === 20) { + $limit = null; + } + $type = static::PHINX_TYPE_BIG_INTEGER; + break; + case 'blob': + $type = static::PHINX_TYPE_BINARY; + break; + case 'tinyblob': + $type = static::PHINX_TYPE_BINARY; + $limit = static::BLOB_TINY; + break; + case 'mediumblob': + $type = static::PHINX_TYPE_BINARY; + $limit = static::BLOB_MEDIUM; + break; + case 'longblob': + $type = static::PHINX_TYPE_BINARY; + $limit = static::BLOB_LONG; + break; + case 'tinytext': + $type = static::PHINX_TYPE_TEXT; + $limit = static::TEXT_TINY; + break; + case 'mediumtext': + $type = static::PHINX_TYPE_TEXT; + $limit = static::TEXT_MEDIUM; + break; + case 'longtext': + $type = static::PHINX_TYPE_TEXT; + $limit = static::TEXT_LONG; + break; + } + + $this->getSqlType($type, $limit); + + return array( + 'name' => $type, + 'limit' => $limit, + 'precision' => $precision + ); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + $charset = isset($options['charset']) ? $options['charset'] : 'utf8'; + + if (isset($options['collation'])) { + $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation'])); + } else { + $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset)); + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + $rows = $this->fetchAll( + sprintf( + 'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'', + $name + ) + ); + + foreach ($rows as $row) { + if (!empty($row)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name)); + $this->endCommandTimer(); + } + + /** + * Gets the MySQL Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column) + { + $sqlType = $this->getSqlType($column->getType(), $column->getLimit()); + + $def = ''; + $def .= strtoupper($sqlType['name']); + if ($column->getPrecision() && $column->getScale()) { + $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; + } elseif (isset($sqlType['limit'])) { + $def .= '(' . $sqlType['limit'] . ')'; + } + if (($values = $column->getValues()) && is_array($values)) { + $def .= "('" . implode("', '", $values) . "')"; + } + $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '' ; + $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL'; + $def .= ($column->isIdentity()) ? ' AUTO_INCREMENT' : ''; + $def .= $this->getDefaultValueDefinition($column->getDefault()); + + if ($column->getComment()) { + $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment()); + } + + if ($column->getUpdate()) { + $def .= ' ON UPDATE ' . $column->getUpdate(); + } + + return $def; + } + + /** + * Gets the MySQL Index Definition for an Index object. + * + * @param Index $index Index + * @return string + */ + protected function getIndexSqlDefinition(Index $index) + { + $def = ''; + + if ($index->getType() == Index::UNIQUE) { + $def .= ' UNIQUE'; + } + + $def .= ' KEY'; + + if (is_string($index->getName())) { + $def .= ' `' . $index->getName() . '`'; + } + + $def .= ' (`' . implode('`,`', $index->getColumns()) . '`)'; + + return $def; + } + + /** + * Gets the MySQL Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey) + { + $def = ''; + if ($foreignKey->getConstraint()) { + $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint()); + } + $columnNames = array(); + foreach ($foreignKey->getColumns() as $column) { + $columnNames[] = $this->quoteColumnName($column); + } + $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; + $refColumnNames = array(); + foreach ($foreignKey->getReferencedColumns() as $column) { + $refColumnNames[] = $this->quoteColumnName($column); + } + $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')'; + if ($foreignKey->getOnDelete()) { + $def .= ' ON DELETE ' . $foreignKey->getOnDelete(); + } + if ($foreignKey->getOnUpdate()) { + $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate(); + } + return $def; + } + + /** + * Describes a database table. This is a MySQL adapter specific method. + * + * @param string $tableName Table name + * @return array + */ + public function describeTable($tableName) + { + $options = $this->getOptions(); + + // mysql specific + $sql = sprintf( + "SELECT * + FROM information_schema.tables + WHERE table_schema = '%s' + AND table_name = '%s'", + $options['name'], + $tableName + ); + + return $this->fetchRow($sql); + } + + /** + * Returns MySQL column types (inherited and MySQL specified). + * @return array + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array ('enum', 'set')); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php new file mode 100644 index 00000000..cf85094c --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php @@ -0,0 +1,484 @@ + + */ +abstract class PdoAdapter implements AdapterInterface +{ + /** + * @var array + */ + protected $options = array(); + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var string + */ + protected $schemaTableName = 'phinxlog'; + + /** + * @var \PDO + */ + protected $connection; + + /** + * @var float + */ + protected $commandStartTime; + + /** + * Class Constructor. + * + * @param array $options Options + * @param OutputInterface $output Output Interface + */ + public function __construct(array $options, OutputInterface $output = null) + { + $this->setOptions($options); + if (null !== $output) { + $this->setOutput($output); + } + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->options = $options; + + if (isset($options['default_migration_table'])) { + $this->setSchemaTableName($options['default_migration_table']); + } + + if (isset($options['connection'])) { + $this->setConnection($options['connection']); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + return null; + } + return $this->options[$name]; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + if (null == $this->output) { + $output = new NullOutput(); + $this->setOutput($output); + } + return $this->output; + } + + /** + * Sets the schema table name. + * + * @param string $schemaTableName Schema Table Name + * @return PdoAdapter + */ + public function setSchemaTableName($schemaTableName) + { + $this->schemaTableName = $schemaTableName; + return $this; + } + + /** + * Gets the schema table name. + * + * @return string + */ + public function getSchemaTableName() + { + return $this->schemaTableName; + } + + /** + * Sets the database connection. + * + * @param \PDO $connection Connection + * @return AdapterInterface + */ + public function setConnection(\PDO $connection) + { + $this->connection = $connection; + + // Create the schema table if it doesn't already exist + if (!$this->hasSchemaTable()) { + $this->createSchemaTable(); + } + + return $this; + } + + /** + * Gets the database connection + * + * @return \PDO + */ + public function getConnection() + { + if (null === $this->connection) { + $this->connect(); + } + return $this->connection; + } + + /** + * Sets the command start time + * + * @param int $time + * @return AdapterInterface + */ + public function setCommandStartTime($time) + { + $this->commandStartTime = $time; + return $this; + } + + /** + * Gets the command start time + * + * @return int + */ + public function getCommandStartTime() + { + return $this->commandStartTime; + } + + /** + * Start timing a command. + * + * @return void + */ + public function startCommandTimer() + { + $this->setCommandStartTime(microtime(true)); + } + + /** + * Stop timing the current command and write the elapsed time to the + * output. + * + * @return void + */ + public function endCommandTimer() + { + $end = microtime(true); + if (OutputInterface::VERBOSITY_VERBOSE === $this->getOutput()->getVerbosity()) { + $this->getOutput()->writeln(' -> ' . sprintf('%.4fs', $end - $this->getCommandStartTime())); + } + } + + /** + * Write a Phinx command to the output. + * + * @param string $command Command Name + * @param array $args Command Args + * @return void + */ + public function writeCommand($command, $args = array()) + { + if (OutputInterface::VERBOSITY_VERBOSE === $this->getOutput()->getVerbosity()) { + if (count($args)) { + $outArr = array(); + foreach ($args as $arg) { + if (is_array($arg)) { + $arg = array_map(function ($value) { + return '\'' . $value . '\''; + }, $arg); + $outArr[] = '[' . implode(', ', $arg) . ']'; + continue; + } + + $outArr[] = '\'' . $arg . '\''; + } + $this->getOutput()->writeln(' -- ' . $command . '(' . implode(', ', $outArr) . ')'); + return; + } + $this->getOutput()->writeln(' -- ' . $command); + } + } + + /** + * {@inheritdoc} + */ + public function connect() + { + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getConnection()->exec($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getConnection()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + $result = $this->query($sql); + return $result->fetch(); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + $rows = array(); + $result = $this->query($sql); + while ($row = $result->fetch()) { + $rows[] = $row; + } + return $rows; + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $columns, $data) + { + $this->startCommandTimer(); + + $sql = sprintf( + "INSERT INTO %s ", + $this->quoteTableName($table->getName()) + ); + + $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")"; + $sql .= " VALUES (" . implode(', ', array_fill(0, count($columns), '?')) . ")"; + + $stmt = $this->getConnection()->prepare($sql); + + foreach ($data as $row) { + $stmt->execute($row); + } + + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getVersions() + { + $rows = $this->fetchAll(sprintf('SELECT * FROM %s ORDER BY version ASC', $this->getSchemaTableName())); + return array_map( + function ($v) { + return $v['version']; + }, + $rows + ); + } + + /** + * {@inheritdoc} + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime) + { + if (strcasecmp($direction, MigrationInterface::UP) === 0) { + // up + $sql = sprintf( + 'INSERT INTO %s (' + . 'version, start_time, end_time' + . ') VALUES (' + . '\'%s\',' + . '\'%s\',' + . '\'%s\'' + . ');', + $this->getSchemaTableName(), + $migration->getVersion(), + $startTime, + $endTime + ); + + $this->query($sql); + } else { + // down + $sql = sprintf( + "DELETE FROM %s WHERE version = '%s'", + $this->getSchemaTableName(), + $migration->getVersion() + ); + + $this->query($sql); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasSchemaTable() + { + return $this->hasTable($this->getSchemaTableName()); + } + + /** + * {@inheritdoc} + */ + public function createSchemaTable() + { + try { + $options = array( + 'id' => false, + 'primary_key' => 'version' + ); + + $table = new Table($this->getSchemaTableName(), $options, $this); + + if ($this->getConnection()->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'mysql' + && version_compare($this->getConnection()->getAttribute(\PDO::ATTR_SERVER_VERSION), '5.6.0', '>=')) { + $table->addColumn('version', 'biginteger', array('limit' => 14)) + ->addColumn('start_time', 'timestamp', array('default' => 'CURRENT_TIMESTAMP')) + ->addColumn('end_time', 'timestamp', array('default' => 'CURRENT_TIMESTAMP')) + ->save(); + } else { + $table->addColumn('version', 'biginteger') + ->addColumn('start_time', 'timestamp') + ->addColumn('end_time', 'timestamp') + ->save(); + } + } catch (\Exception $exception) { + throw new \InvalidArgumentException('There was a problem creating the schema table: ' . $exception->getMessage()); + } + } + + /** + * {@inheritdoc} + */ + public function getAdapterType() + { + return $this->getOption('adapter'); + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array( + 'string', + 'char', + 'text', + 'integer', + 'biginteger', + 'float', + 'decimal', + 'datetime', + 'timestamp', + 'time', + 'date', + 'binary', + 'boolean', + 'uuid', + // Geospatial data types + 'geometry', + 'point', + 'linestring', + 'polygon', + ); + } + + /** + * {@inheritdoc} + */ + public function isValidColumnType(Column $column) { + return in_array($column->getType(), $this->getColumnTypes()); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php new file mode 100644 index 00000000..93ef16fc --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php @@ -0,0 +1,1175 @@ +connection) { + if (!class_exists('PDO') || !in_array('pgsql', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_Pgsql extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $db = null; + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the PostgreSQL default + if (isset($options['port'])) { + $dsn = 'pgsql:host=' . $options['host'] . ';port=' . $options['port'] . ';dbname=' . $options['name']; + } else { + $dsn = 'pgsql:host=' . $options['host'] . ';dbname=' . $options['name']; + } + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION)); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: ' + . $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('BEGIN'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK'); + } + + /** + * Quotes a schema name for use in a query. + * + * @param string $schemaName Schema Name + * @return string + */ + public function quoteSchemaName($schemaName) + { + return $this->quoteColumnName($schemaName); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return $this->quoteSchemaName($this->getSchemaName()) . '.' . $this->quoteColumnName($tableName); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '"'. $columnName . '"'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $result = $this->getConnection()->query( + sprintf( + 'SELECT * + FROM information_schema.tables + WHERE table_schema = %s + AND lower(table_name) = lower(%s)', + $this->getConnection()->quote($this->getSchemaName()), + $this->getConnection()->quote($tableName) + ) + ); + + return $result->rowCount() === 1; + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + $options = $table->getOptions(); + + // Add the default primary key + $columns = $table->getPendingColumns(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = 'id'; + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = $options['id']; + } + + // TODO - process table options like collation etc + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + + $this->columnsWithComments = array(); + foreach ($columns as $column) { + $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; + + // set column comments, if needed + if ($column->getComment()) { + $this->columnsWithComments[] = $column; + } + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $sql = rtrim($sql); + $sql .= sprintf(' CONSTRAINT %s_pkey PRIMARY KEY (', $table->getName()); + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $sql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function, + // but for now just hard-code the adapter quotes + $sql .= implode( + ',', + array_map( + function ($v) { + return '"' . $v . '"'; + }, + $options['primary_key'] + ) + ); + } + $sql .= ')'; + } else { + $sql = substr(rtrim($sql), 0, -1); // no primary keys + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName()); + } + } + + $sql .= ');'; + + // process column comments + if (!empty($this->columnsWithComments)) { + foreach ($this->columnsWithComments as $column) { + $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName()); + } + } + + + // set the indexes + $indexes = $table->getIndexes(); + if (!empty($indexes)) { + foreach ($indexes as $index) { + $sql .= $this->getIndexSqlDefinition($index, $table->getName()); + } + } + + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $sql = sprintf( + 'ALTER TABLE %s RENAME TO %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($newTableName) + ); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $sql = sprintf( + "SELECT column_name, data_type, is_identity, is_nullable, + column_default, character_maximum_length, numeric_precision, numeric_scale + FROM information_schema.columns + WHERE table_name ='%s'", + $tableName + ); + $columnsInfo = $this->fetchAll($sql); + + foreach ($columnsInfo as $columnInfo) { + $column = new Column(); + $column->setName($columnInfo['column_name']) + ->setType($this->getPhinxType($columnInfo['data_type'])) + ->setNull($columnInfo['is_nullable'] == 'YES') + ->setDefault($columnInfo['column_default']) + ->setIdentity($columnInfo['is_identity'] == 'YES') + ->setPrecision($columnInfo['numeric_precision']) + ->setScale($columnInfo['numeric_scale']); + + if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) { + $column->setTimezone(true); + } + + if (isset($columnInfo['character_maximum_length'])) { + $column->setLimit($columnInfo['character_maximum_length']); + } + $columns[] = $column; + } + return $columns; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName, $options = array()) + { + $sql = sprintf("SELECT count(*) + FROM information_schema.columns + WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'", + $this->getSchemaName(), + $tableName, + $columnName + ); + + $result = $this->fetchRow($sql); + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $sql = sprintf( + 'ALTER TABLE %s ADD %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->startCommandTimer(); + $sql = sprintf( + "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists + FROM information_schema.columns + WHERE table_name ='%s' AND column_name = '%s'", + $tableName, + $columnName + ); + $result = $this->fetchRow($sql); + if (!(bool) $result['column_exists']) { + throw new \InvalidArgumentException("The specified column does not exist: $columnName"); + } + $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s RENAME COLUMN %s TO %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $newColumnName + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + // TODO - is it possible to merge these 3 queries into less? + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + // change data type + $sql = sprintf( + 'ALTER TABLE %s ALTER COLUMN %s TYPE %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->getColumnSqlDefinition($newColumn) + ); + //NULL and DEFAULT cannot be set while changing column type + $sql = preg_replace('/ NOT NULL/', '', $sql); + $sql = preg_replace('/ NULL/', '', $sql); + //If it is set, DEFAULT is the last definition + $sql = preg_replace('/DEFAULT .*/', '', $sql); + $this->execute($sql); + // process null + $sql = sprintf( + 'ALTER TABLE %s ALTER COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ); + if ($newColumn->isNull()) { + $sql .= ' DROP NOT NULL'; + } else { + $sql .= ' SET NOT NULL'; + } + $this->execute($sql); + if (!is_null($newColumn->getDefault())) { + //change default + $this->execute( + sprintf( + 'ALTER TABLE %s ALTER COLUMN %s SET %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->getDefaultValueDefinition($newColumn->getDefault()) + ) + ); + } + else { + //drop default + $this->execute( + sprintf( + 'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + } + // rename column + if ($columnName !== $newColumn->getName()) { + $this->execute( + sprintf( + 'ALTER TABLE %s RENAME COLUMN %s TO %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumn->getName()) + ) + ); + } + + // change column comment if needed + if ($newColumn->getComment()) { + $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName); + $this->execute($sql); + } + + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s DROP COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + $this->endCommandTimer(); + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getIndexes($tableName) + { + $indexes = array(); + $sql = "SELECT + i.relname AS index_name, + a.attname AS column_name + FROM + pg_class t, + pg_class i, + pg_index ix, + pg_attribute a + WHERE + t.oid = ix.indrelid + AND i.oid = ix.indexrelid + AND a.attrelid = t.oid + AND a.attnum = ANY(ix.indkey) + AND t.relkind = 'r' + AND t.relname = '$tableName' + ORDER BY + t.relname, + i.relname;"; + $rows = $this->fetchAll($sql); + foreach ($rows as $row) { + if (!isset($indexes[$row['index_name']])) { + $indexes[$row['index_name']] = array('columns' => array()); + } + $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']); + } + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); + } + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + foreach ($indexes as $index) { + if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) { + return true; + } + } + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $sql = $this->getIndexSqlDefinition($index, $table->getName()); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $indexName => $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'DROP INDEX IF EXISTS %s', + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $sql = sprintf( + 'DROP INDEX IF EXISTS %s', + $indexName + ); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + if ($constraint) { + if (isset($foreignKeys[$constraint])) { + return !empty($foreignKeys[$constraint]); + } + return false; + } else { + foreach ($foreignKeys as $key) { + $a = array_diff($columns, $key['columns']); + if (empty($a)) { + return true; + } + } + return false; + } + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll(sprintf( + "SELECT + tc.constraint_name, + tc.table_name, kcu.column_name, + ccu.table_name AS referenced_table_name, + ccu.column_name AS referenced_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' + ORDER BY kcu.position_in_unique_constraint", + $tableName + )); + foreach ($rows as $row) { + $foreignKeys[$row['constraint_name']]['table'] = $row['table_name']; + $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name']; + $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name']; + $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name']; + } + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $sql = sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getForeignKeySqlDefinition($foreignKey, $table->getName()) + ); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + if ($constraint) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP CONSTRAINT %s', + $this->quoteTableName($tableName), + $constraint + ) + ); + } else { + foreach ($columns as $column) { + $rows = $this->fetchAll(sprintf( + "SELECT CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA = CURRENT_SCHEMA() + AND TABLE_NAME IS NOT NULL + AND TABLE_NAME = '%s' + AND COLUMN_NAME = '%s' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", + $tableName, + $column + )); + + foreach ($rows as $row) { + $this->dropForeignKey($tableName, $columns, $row['constraint_name']); + } + } + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_INTEGER: + if ($limit && $limit == static::INT_SMALL) { + return array( + 'name' => 'smallint', + 'limit' => static::INT_SMALL + ); + } + return array('name' => $type); + case static::PHINX_TYPE_TEXT: + case static::PHINX_TYPE_TIME: + case static::PHINX_TYPE_DATE: + case static::PHINX_TYPE_BOOLEAN: + case static::PHINX_TYPE_JSON: + case static::PHINX_TYPE_JSONB: + case static::PHINX_TYPE_UUID: + return array('name' => $type); + case static::PHINX_TYPE_DECIMAL: + return array('name' => $type, 'precision' => 18, 'scale' => 0); + case static::PHINX_TYPE_STRING: + return array('name' => 'character varying', 'limit' => 255); + case static::PHINX_TYPE_CHAR: + return array('name' => 'character', 'limit' => 255); + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint'); + case static::PHINX_TYPE_FLOAT: + return array('name' => 'real'); + case static::PHINX_TYPE_DATETIME: + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'timestamp'); + case static::PHINX_TYPE_BINARY: + return array('name' => 'bytea'); + // Geospatial database types + // Spatial storage in Postgres is done via the PostGIS extension, + // which enables the use of the "geography" type in combination + // with SRID 4326. + case static::PHINX_TYPE_GEOMETRY: + return array('name' => 'geography', 'geometry', 4326); + break; + case static::PHINX_TYPE_POINT: + return array('name' => 'geography', 'point', 4326); + break; + case static::PHINX_TYPE_LINESTRING: + return array('name' => 'geography', 'linestring', 4326); + break; + case static::PHINX_TYPE_POLYGON: + return array('name' => 'geography', 'polygon', 4326); + break; + default: + if ($this->isArrayType($type)) { + return array('name' => $type); + } + // Return array type + throw new \RuntimeException('The type: "' . $type . '" is not supported'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param string $sqlType SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlType) + { + switch ($sqlType) { + case 'character varying': + case 'varchar': + return static::PHINX_TYPE_STRING; + case 'character': + case 'char': + return static::PHINX_TYPE_CHAR; + case 'text': + return static::PHINX_TYPE_TEXT; + case 'json': + return static::PHINX_TYPE_JSON; + case 'jsonb': + return static::PHINX_TYPE_JSONB; + case 'smallint': + return array( + 'name' => 'smallint', + 'limit' => static::INT_SMALL + ); + case 'int': + case 'int4': + case 'integer': + return static::PHINX_TYPE_INTEGER; + case 'decimal': + case 'numeric': + return static::PHINX_TYPE_DECIMAL; + case 'bigint': + case 'int8': + return static::PHINX_TYPE_BIG_INTEGER; + case 'real': + case 'float4': + return static::PHINX_TYPE_FLOAT; + case 'bytea': + return static::PHINX_TYPE_BINARY; + break; + case 'time': + case 'timetz': + case 'time with time zone': + case 'time without time zone': + return static::PHINX_TYPE_TIME; + case 'date': + return static::PHINX_TYPE_DATE; + case 'timestamp': + case 'timestamptz': + case 'timestamp with time zone': + case 'timestamp without time zone': + return static::PHINX_TYPE_DATETIME; + case 'bool': + case 'boolean': + return static::PHINX_TYPE_BOOLEAN; + case 'uuid': + return static::PHINX_TYPE_UUID; + default: + throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported'); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + $charset = isset($options['charset']) ? $options['charset'] : 'utf8'; + $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($databaseName) + { + $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName); + $result = $this->fetchRow($sql); + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + $this->disconnect(); + $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name)); + $this->connect(); + $this->endCommandTimer(); + } + + /** + * Get the defintion for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = $default ? 'TRUE' : 'FALSE'; + } + return isset($default) ? 'DEFAULT ' . $default : ''; + } + + /** + * Gets the PostgreSQL Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column) + { + $buffer = array(); + if ($column->isIdentity()) { + $buffer[] = 'SERIAL'; + } else { + $sqlType = $this->getSqlType($column->getType(), $column->getLimit()); + $buffer[] = strtoupper($sqlType['name']); + // integers cant have limits in postgres + if (static::PHINX_TYPE_DECIMAL == $sqlType['name'] && ($column->getPrecision() || $column->getScale())) { + $buffer[] = sprintf( + '(%s, %s)', + $column->getPrecision() ? $column->getPrecision() : $sqlType['precision'], + $column->getScale() ? $column->getScale() : $sqlType['scale'] + ); + } elseif (!in_array($sqlType['name'], array('integer', 'smallint'))) { + if ($column->getLimit() || isset($sqlType['limit'])) { + $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']); + } + } + + $timeTypes = array( + 'time', + 'timestamp', + ); + if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) { + $buffer[] = strtoupper('with time zone'); + } + } + + $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL'; + + if (!is_null($column->getDefault())) { + $buffer[] = $this->getDefaultValueDefinition($column->getDefault()); + } + + return implode(' ', $buffer); + } + + /** + * Gets the PostgreSQL Column Comment Defininition for a column object. + * + * @param Column $column Column + * @param string $tableName Table name + * @return string + */ + protected function getColumnCommentSqlDefinition(Column $column, $tableName) + { + // passing 'null' is to remove column comment + $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) + ? $this->getConnection()->quote($column->getComment()) + : 'NULL'; + + return sprintf( + 'COMMENT ON COLUMN %s.%s IS %s;', + $tableName, + $column->getName(), + $comment + ); + } + + /** + * Gets the PostgreSQL Index Definition for an Index object. + * + * @param Index $index Index + * @param string $tableName Table name + * @return string + */ + protected function getIndexSqlDefinition(Index $index, $tableName) + { + if (is_string($index->getName())) { + $indexName = $index->getName(); + } else { + $columnNames = $index->getColumns(); + if (is_string($columnNames)) { + $columnNames = array($columnNames); + } + $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames)); + } + $def = sprintf( + "CREATE %s INDEX %s ON %s (%s);", + ($index->getType() == Index::UNIQUE ? 'UNIQUE' : ''), + $indexName, + $this->quoteTableName($tableName), + implode(',', $index->getColumns()) + ); + return $def; + } + + /** + * Gets the MySQL Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @param string $tableName Table name + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName) + { + $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns()); + $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")'; + $def .= " REFERENCES {$foreignKey->getReferencedTable()->getName()} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")'; + if ($foreignKey->getOnDelete()) { + $def .= " ON DELETE {$foreignKey->getOnDelete()}"; + } + if ($foreignKey->getOnUpdate()) { + $def .= " ON UPDATE {$foreignKey->getOnUpdate()}"; + } + return $def; + } + + /** + * {@inheritdoc} + */ + public function createSchemaTable() + { + // Create the public/custom schema if it doesn't already exist + if (false === $this->hasSchema($this->getSchemaName())) { + $this->createSchema($this->getSchemaName()); + } + + $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName())); + + return parent::createSchemaTable(); + } + + /** + * {@inheritdoc} + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime) + { + if (strcasecmp($direction, MigrationInterface::UP) === 0) { + // up + $sql = sprintf( + "INSERT INTO %s (version, start_time, end_time) VALUES ('%s', '%s', '%s');", + $this->getSchemaTableName(), + $migration->getVersion(), + $startTime, + $endTime + ); + + $this->query($sql); + } else { + // down + $sql = sprintf( + "DELETE FROM %s WHERE version = '%s'", + $this->getSchemaTableName(), + $migration->getVersion() + ); + + $this->query($sql); + } + return $this; + } + + /** + * Creates the specified schema. + * + * @param string $schemaName Schema Name + * @return void + */ + public function createSchema($schemaName = 'public') + { + $this->startCommandTimer(); + $this->writeCommand('addSchema', array($schemaName)); + $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name" + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * Checks to see if a schema exists. + * + * @param string $schemaName Schema Name + * @return boolean + */ + public function hasSchema($schemaName) + { + $sql = sprintf( + "SELECT count(*) + FROM pg_namespace + WHERE nspname = '%s'", + $schemaName + ); + $result = $this->fetchRow($sql); + return $result['count'] > 0; + } + + /** + * Drops the specified schema table. + * + * @param string $schemaName Schema name + * @return void + */ + public function dropSchema($schemaName) + { + $this->startCommandTimer(); + $this->writeCommand('dropSchema', array($schemaName)); + $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName)); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * Drops all schemas. + * + * @return void + */ + public function dropAllSchemas() + { + $this->startCommandTimer(); + $this->writeCommand('dropAllSchemas'); + foreach ($this->getAllSchemas() as $schema) { + $this->dropSchema($schema); + } + $this->endCommandTimer(); + } + + /** + * Returns schemas. + * + * @return array + */ + public function getAllSchemas() + { + $sql = "SELECT schema_name + FROM information_schema.schemata + WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'"; + $items = $this->fetchAll($sql); + $schemaNames = array(); + foreach ($items as $item) { + $schemaNames[] = $item['schema_name']; + } + return $schemaNames; + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('json', 'jsonb')); + } + + /** + * {@inheritdoc} + */ + public function isValidColumnType(Column $column) + { + // If not a standard column type, maybe it is array type? + return (parent::isValidColumnType($column) || $this->isArrayType($column->getType())); + } + + /** + * Check if the given column is an array of a valid type. + * + * @param string $columnType + * @return bool + */ + protected function isArrayType($columnType) + { + if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) { + return false; + } + + $baseType = $matches[1]; + return in_array($baseType, $this->getColumnTypes()); + } + + /** + * Gets the schema name. + * + * @return string + */ + private function getSchemaName() + { + $options = $this->getOptions(); + return empty($options['schema']) ? 'public' : $options['schema']; + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php new file mode 100644 index 00000000..12360318 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php @@ -0,0 +1,325 @@ + + */ +class ProxyAdapter extends AdapterWrapper +{ + /** + * @var array + */ + protected $commands; + + /** + * {@inheritdoc} + */ + public function getAdapterType() + { + return 'ProxyAdapter'; + } + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->recordCommand('createTable', array($table->getName())); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->recordCommand('renameTable', array($tableName, $newTableName)); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->recordCommand('dropTable', array($tableName)); + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->recordCommand('addColumn', array($table, $column)); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->recordCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $this->recordCommand('changeColumn', array($tableName, $columnName, $newColumn)); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->recordCommand('dropColumn', array($tableName, $columnName)); + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->recordCommand('addIndex', array($table, $index)); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns, $options = array()) + { + $this->recordCommand('dropIndex', array($tableName, $columns, $options)); + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->recordCommand('dropIndexByName', array($tableName, $indexName)); + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->recordCommand('addForeignKey', array($table, $foreignKey)); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->recordCommand('dropForeignKey', array($columns, $constraint)); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->recordCommand('createDatabase', array($name, $options)); + } + + /** + * Record a command for execution later. + * + * @param string $name Command Name + * @param array $arguments Command Arguments + * @return void + */ + public function recordCommand($name, $arguments) + { + $this->commands[] = array( + 'name' => $name, + 'arguments' => $arguments + ); + } + + /** + * Sets an array of recorded commands. + * + * @param array $commands Commands + * @return ProxyAdapter + */ + public function setCommands($commands) + { + $this->commands = $commands; + return $this; + } + + /** + * Gets an array of the recorded commands. + * + * @return array + */ + public function getCommands() + { + return $this->commands; + } + + /** + * Gets an array of the recorded commands in reverse. + * + * @throws IrreversibleMigrationException if a command cannot be reversed. + * @return array + */ + public function getInvertedCommands() + { + if (null === $this->getCommands()) { + return array(); + } + + $invCommands = array(); + $supportedCommands = array( + 'createTable', 'renameTable', 'addColumn', + 'renameColumn', 'addIndex', 'addForeignKey' + ); + foreach (array_reverse($this->getCommands()) as $command) { + if (!in_array($command['name'], $supportedCommands)) { + throw new IrreversibleMigrationException(sprintf( + 'Cannot reverse a "%s" command', + $command['name'] + )); + } + $invertMethod = 'invert' . ucfirst($command['name']); + $invertedCommand = $this->$invertMethod($command['arguments']); + $invCommands[] = array( + 'name' => $invertedCommand['name'], + 'arguments' => $invertedCommand['arguments'] + ); + } + + return $invCommands; + } + + /** + * Execute the recorded commands. + * + * @return void + */ + public function executeCommands() + { + $commands = $this->getCommands(); + foreach ($commands as $command) { + call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']); + } + } + + /** + * Execute the recorded commands in reverse. + * + * @return void + */ + public function executeInvertedCommands() + { + $commands = $this->getInvertedCommands(); + foreach ($commands as $command) { + call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']); + } + } + + /** + * Returns the reverse of a createTable command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertCreateTable($args) + { + return array('name' => 'dropTable', 'arguments' => array($args[0])); + } + + /** + * Returns the reverse of a renameTable command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertRenameTable($args) + { + return array('name' => 'renameTable', 'arguments' => array($args[1], $args[0])); + } + + /** + * Returns the reverse of a addColumn command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertAddColumn($args) + { + return array('name' => 'dropColumn', 'arguments' => array($args[0]->getName(), $args[1]->getName())); + } + + /** + * Returns the reverse of a renameColumn command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertRenameColumn($args) + { + return array('name' => 'renameColumn', 'arguments' => array($args[0], $args[2], $args[1])); + } + + /** + * Returns the reverse of a addIndex command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertAddIndex($args) + { + return array('name' => 'dropIndex', 'arguments' => array($args[0]->getName(), $args[1]->getColumns())); + } + + /** + * Returns the reverse of a addForeignKey command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertAddForeignKey($args) + { + return array('name' => 'dropForeignKey', 'arguments' => array($args[0]->getName(), $args[1]->getColumns())); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php new file mode 100644 index 00000000..6cd68358 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php @@ -0,0 +1,1106 @@ + + * @author Richard McIntyre + */ +class SQLiteAdapter extends PdoAdapter implements AdapterInterface +{ + protected $definitionsWithLimits = array( + 'CHARACTER', + 'VARCHAR', + 'VARYING CHARACTER', + 'NCHAR', + 'NATIVE CHARACTER', + 'NVARCHAR' + ); + + /** + * {@inheritdoc} + */ + public function connect() + { + if (null === $this->connection) { + if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $db = null; + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the MySQL default + if (isset($options['memory'])) { + $dsn = 'sqlite::memory:'; + } else { + $dsn = 'sqlite:' . $options['name']; + if (file_exists($options['name'] . '.sqlite3')) { + $dsn = 'sqlite:' . $options['name'] . '.sqlite3'; + } + } + + try { + $db = new \PDO($dsn); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('BEGIN TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK'); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return str_replace('.', '`.`', $this->quoteColumnName($tableName)); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '`' . str_replace('`', '``', $columnName) . '`'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $tables = array(); + $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName)); + foreach ($rows as $row) { + $tables[] = strtolower($row[0]); + } + + return in_array(strtolower($tableName), $tables); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + + // Add the default primary key + $columns = $table->getPendingColumns(); + $options = $table->getOptions(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + } + + + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + foreach ($columns as $column) { + $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $sql = rtrim($sql); + $sql .= ' PRIMARY KEY ('; + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $sql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function, + // but for now just hard-code the adapter quotes + $sql .= implode( + ',', + array_map( + function ($v) { + return '`' . $v . '`'; + }, + $options['primary_key'] + ) + ); + } + $sql .= ')'; + } else { + $sql = substr(rtrim($sql), 0, -1); // no primary keys + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey); + } + } + + $sql = rtrim($sql) . ');'; + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + + foreach ($table->getIndexes() as $index) { + $this->addIndex($table, $index); + } + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + + foreach ($rows as $columnInfo) { + $column = new Column(); + $type = strtolower($columnInfo['type']); + $column->setName($columnInfo['name']) + ->setNull($columnInfo['notnull'] != '1') + ->setDefault($columnInfo['dflt_value']); + + $phinxType = $this->getPhinxType($type); + $column->setType($phinxType['name']) + ->setLimit($phinxType['limit']); + + if ($columnInfo['pk'] == 1) { + $column->setIdentity(true); + } + + $columns[] = $column; + } + + return $columns; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + foreach ($rows as $column) { + if (strcasecmp($column['name'], $columnName) === 0) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + + $sql = sprintf( + 'ALTER TABLE %s ADD COLUMN %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] == $tableName) { + $sql = $table['sql']; + } + } + + $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $selectColumns = array(); + $writeColumns = array(); + foreach ($columns as $column) { + $selectName = $column['name']; + $writeName = ($selectName == $columnName)? $newColumnName : $selectName; + $selectColumns[] = $this->quoteColumnName($selectName); + $writeColumns[] = $this->quoteColumnName($writeName); + } + + if (!in_array($this->quoteColumnName($columnName), $selectColumns)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' . $columnName + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName)); + + $sql = str_replace( + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumnName), + $sql + ); + $this->execute($sql); + + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $writeColumns), + implode(', ', $selectColumns), + $tmpTableName + ); + + $this->execute($sql); + + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + + // TODO: DRY this up.... + + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] == $tableName) { + $sql = $table['sql']; + } + } + + $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $selectColumns = array(); + $writeColumns = array(); + foreach ($columns as $column) { + $selectName = $column['name']; + $writeName = ($selectName == $columnName)? $newColumn->getName() : $selectName; + $selectColumns[] = $this->quoteColumnName($selectName); + $writeColumns[] = $this->quoteColumnName($writeName); + } + + if (!in_array($this->quoteColumnName($columnName), $selectColumns)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' . $columnName + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName)); + + $val = end($columns); + $replacement = ($val['name'] === $columnName) ? "%s %s" : "%s %s,"; + $sql = preg_replace( + sprintf("/%s[^,]*[^\)]/", $this->quoteColumnName($columnName)), + sprintf($replacement, $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)), + $sql + ); + + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $writeColumns), + implode(', ', $selectColumns), + $tmpTableName + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + // TODO: DRY this up.... + + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] == $tableName) { + $sql = $table['sql']; + } + } + + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $columns = array(); + $columnType = null; + foreach ($rows as $row) { + if ($row['name'] != $columnName) { + $columns[] = $row['name']; + } else { + $found = true; + $columnType = $row['type']; + } + } + + if (!isset($found)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' . $columnName + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName)); + + $sql = preg_replace( + sprintf("/%s\s%s[^,)]*(,\s|\))/", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)), + "", + $sql + ); + + if (substr($sql, -2) === ', ') { + $sql = substr($sql, 0, -2) . ')'; + } + + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $columns), + implode(', ', $columns), + $tmpTableName + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getIndexes($tableName) + { + $indexes = array(); + $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName)); + + foreach ($rows as $row) { + $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name'])); + if (!isset($indexes[$tableName])) { + $indexes[$tableName] = array('index' => $row['name'], 'columns' => array()); + } + foreach ($indexData as $indexItem) { + $indexes[$tableName]['columns'][] = strtolower($indexItem['name']); + } + } + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $indexColumnArray = array(); + foreach ($index->getColumns() as $column) { + $indexColumnArray []= sprintf('`%s` ASC', $column); + } + $indexColumns = implode(',', $indexColumnArray); + $this->execute( + sprintf( + 'CREATE %s ON %s (%s)', + $this->getIndexSqlDefinition($table, $index), + $this->quoteTableName($table->getName()), + $indexColumns + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'DROP INDEX %s', + $this->quoteColumnName($index['index']) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + if ($indexName == $index['index']) { + $this->execute( + sprintf( + 'DROP INDEX %s', + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + + $a = array_diff($columns, $foreignKeys); + if (empty($a)) { + return true; + } + return false; + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll( + "SELECT sql, tbl_name + FROM ( + SELECT sql sql, type type, tbl_name tbl_name, name name + FROM sqlite_master + UNION ALL + SELECT sql, type, tbl_name, name + FROM sqlite_temp_master + ) + WHERE type != 'meta' + AND sql NOTNULL + AND name NOT LIKE 'sqlite_%' + ORDER BY substr(type, 2, 1), name" + ); + + foreach ($rows as $row) { + if ($row['tbl_name'] == $tableName) { + + if (strpos($row['sql'], 'REFERENCES') !== false) { + preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches); + foreach ($matches[1] as $match) { + $foreignKeys[] = $match; + } + } + } + } + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + // TODO: DRY this up.... + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $this->execute('pragma foreign_keys = ON'); + + $tmpTableName = 'tmp_' . $table->getName(); + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $row) { + if ($row['tbl_name'] == $table->getName()) { + $sql = $row['sql']; + } + } + + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName()))); + $columns = array(); + foreach ($rows as $column) { + $columns[] = $this->quoteColumnName($column['name']); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName)); + + $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')'; + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $this->quoteTableName($table->getName()), + implode(', ', $columns), + implode(', ', $columns), + $this->quoteTableName($tmpTableName) + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + // TODO: DRY this up.... + + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] == $tableName) { + $sql = $table['sql']; + } + } + + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $replaceColumns = array(); + foreach ($rows as $row) { + if (!in_array($row['name'], $columns)) { + $replaceColumns[] = $row['name']; + } else { + $found = true; + } + } + + if (!isset($found)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName)); + + foreach ($columns as $columnName) { + $sql = preg_replace(sprintf("/,[^,]*\(%s\) REFERENCES[^,]*\([^\)]*\)/", $this->quoteColumnName($columnName)), '', $sql, 1); + } + + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $columns), + implode(', ', $columns), + $tmpTableName + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $columns, $data) + { + $this->startCommandTimer(); + + foreach($data as $row) { + $sql = sprintf( + "INSERT INTO %s ", + $this->quoteTableName($table->getName()) + ); + + $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")"; + $sql .= " VALUES "; + + $sql .= "(" . implode(', ', array_map(function ($value) { + if (is_numeric($value)) { + return $value; + } + return "'{$value}'"; + }, $row)) . ")"; + + $this->execute($sql); + } + + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_STRING: + return array('name' => 'varchar', 'limit' => 255); + break; + case static::PHINX_TYPE_CHAR: + return array('name' => 'char', 'limit' => 255); + break; + case static::PHINX_TYPE_TEXT: + return array('name' => 'text'); + break; + case static::PHINX_TYPE_INTEGER: + return array('name' => 'integer'); + break; + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint'); + break; + case static::PHINX_TYPE_FLOAT: + return array('name' => 'float'); + break; + case static::PHINX_TYPE_DECIMAL: + return array('name' => 'decimal'); + break; + case static::PHINX_TYPE_DATETIME: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIME: + return array('name' => 'time'); + break; + case static::PHINX_TYPE_DATE: + return array('name' => 'date'); + break; + case static::PHINX_TYPE_BINARY: + return array('name' => 'blob'); + break; + case static::PHINX_TYPE_BOOLEAN: + return array('name' => 'boolean'); + break; + case static::PHINX_TYPE_UUID: + return array('name' => 'char', 'limit' => 36); + // Geospatial database types + // No specific data types exist in SQLite, instead all geospatial + // functionality is handled in the client. See also: SpatiaLite. + case static::PHINX_TYPE_GEOMETRY: + case static::PHINX_TYPE_POLYGON: + return array('name' => 'text'); + return; + case static::PHINX_TYPE_LINESTRING: + return array('name' => 'varchar', 'limit' => 255); + break; + case static::PHINX_TYPE_POINT: + return array('name' => 'float'); + default: + throw new \RuntimeException('The type: "' . $type . '" is not supported.'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param string $sqlTypeDef SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlTypeDef) + { + if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) { + throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported'); + } else { + $limit = null; + $precision = null; + $type = $matches[1]; + if (count($matches) > 2) { + $limit = $matches[3] ? $matches[3] : null; + } + if (count($matches) > 4) { + $precision = $matches[5]; + } + switch ($matches[1]) { + case 'varchar': + $type = static::PHINX_TYPE_STRING; + if ($limit == 255) { + $limit = null; + } + break; + case 'char': + $type = static::PHINX_TYPE_CHAR; + if ($limit == 255) { + $limit = null; + } + if ($limit === 36) { + $type = static::PHINX_TYPE_UUID; + } + break; + case 'int': + $type = static::PHINX_TYPE_INTEGER; + if ($limit == 11) { + $limit = null; + } + break; + case 'bigint': + if ($limit == 11) { + $limit = null; + } + $type = static::PHINX_TYPE_BIG_INTEGER; + break; + case 'blob': + $type = static::PHINX_TYPE_BINARY; + break; + } + if ($type == 'tinyint') { + if ($matches[3] == 1) { + $type = static::PHINX_TYPE_BOOLEAN; + $limit = null; + } + } + + $this->getSqlType($type); + + return array( + 'name' => $type, + 'limit' => $limit, + 'precision' => $precision + ); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + touch($name . '.sqlite3'); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + return is_file($name . '.sqlite3'); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + if (file_exists($name . '.sqlite3')) { + unlink($name . '.sqlite3'); + } + $this->endCommandTimer(); + } + + /** + * Get the definition for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = (int) $default; + } + return isset($default) ? ' DEFAULT ' . $default : ''; + } + + /** + * Gets the SQLite Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column) + { + $sqlType = $this->getSqlType($column->getType()); + $def = ''; + $def .= strtoupper($sqlType['name']); + if ($column->getPrecision() && $column->getScale()) { + $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; + } + $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits); + if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) { + $def .= '(' . ($column->getLimit() ? $column->getLimit() : $sqlType['limit']) . ')'; + } + + $default = $column->getDefault(); + + $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL'; + $def .= $this->getDefaultValueDefinition($default); + $def .= ($column->isIdentity()) ? ' PRIMARY KEY AUTOINCREMENT' : ''; + + if ($column->getUpdate()) { + $def .= ' ON UPDATE ' . $column->getUpdate(); + } + + $def .= $this->getCommentDefinition($column); + + return $def; + } + + /** + * Gets the comment Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getCommentDefinition(Column $column) + { + if ($column->getComment()) { + return ' /* ' . $column->getComment() . ' */ '; + } + return ''; + } + + /** + * Gets the SQLite Index Definition for an Index object. + * + * @param Index $index Index + * @return string + */ + protected function getIndexSqlDefinition(Table $table, Index $index) + { + if ($index->getType() == Index::UNIQUE) { + $def = 'UNIQUE INDEX'; + } else { + $def = 'INDEX'; + } + if (is_string($index->getName())) { + $indexName = $index->getName(); + } else { + $indexName = $table->getName() . '_'; + foreach ($index->getColumns() as $column) { + $indexName .= $column . '_'; + } + $indexName .= 'index'; + } + $def .= ' `' . $indexName . '`'; + return $def; + } + + /** + * Gets the SQLite Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey) + { + $def = ''; + if ($foreignKey->getConstraint()) { + $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint()); + } else { + $columnNames = array(); + foreach ($foreignKey->getColumns() as $column) { + $columnNames[] = $this->quoteColumnName($column); + } + $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; + $refColumnNames = array(); + foreach ($foreignKey->getReferencedColumns() as $column) { + $refColumnNames[] = $this->quoteColumnName($column); + } + $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')'; + if ($foreignKey->getOnDelete()) { + $def .= ' ON DELETE ' . $foreignKey->getOnDelete(); + } + if ($foreignKey->getOnUpdate()) { + $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate(); + } + } + return $def; + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php new file mode 100644 index 00000000..36cea347 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php @@ -0,0 +1,1179 @@ + + */ +class SqlServerAdapter extends PdoAdapter implements AdapterInterface +{ + protected $schema = 'dbo'; + + protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true); + + /** + * {@inheritdoc} + */ + public function connect() + { + if (null === $this->connection) { + if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) { + // try our connection via freetds (Mac/Linux) + return $this->connectDblib(); + } + + $db = null; + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the SqlServer default + if (empty($options['port'])) { + $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name']; + } else { + $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name']; + } + $dsn .= ';MultipleActiveResultSets=false'; + + $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); + + // charset support + if (isset($options['charset'])) { + $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset']; + } + + // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO + // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants + foreach ($options as $key => $option) { + if (strpos($key, 'sqlsrv_attr_') === 0) { + $driverOptions[constant('\PDO::' . strtoupper($key))] = $option; + } + } + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * Connect to MSSQL using dblib/freetds. + * + * The "sqlsrv" driver is not available on Unix machines. + * + * @throws \InvalidArgumentException + */ + protected function connectDblib() + { + if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the SqlServer default + if (empty($options['port'])) { + $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name']; + } else { + $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name']; + } + + $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); + + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('BEGIN TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return str_replace('.', '].[', $this->quoteColumnName($tableName)); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '[' . str_replace(']', '\]', $columnName) . ']'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName)); + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + + $options = $table->getOptions(); + + // Add the default primary key + $columns = $table->getPendingColumns(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = 'id'; + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = $options['id']; + } + + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + $sqlBuffer = array(); + $columnsWithComments = array(); + foreach ($columns as $column) { + $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column); + + // set column comments, if needed + if ($column->getComment()) { + $columnsWithComments[] = $column; + } + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName()); + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $pkSql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function, + // but for now just hard-code the adapter quotes + $pkSql .= implode( + ',', + array_map( + function ($v) { + return '[' . $v . ']'; + }, + $options['primary_key'] + ) + ); + } + $pkSql .= ')'; + $sqlBuffer[] = $pkSql; + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName()); + } + } + + $sql .= implode(', ', $sqlBuffer); + $sql .= ');'; + + // process column comments + if (!empty($columnsWithComments)) { + foreach ($columnsWithComments as $column) { + $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName()); + } + } + + // set the indexes + $indexes = $table->getIndexes(); + if (!empty($indexes)) { + foreach ($indexes as $index) { + $sql .= $this->getIndexSqlDefinition($index, $table->getName()); + } + } + + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * Gets the SqlServer Column Comment Defininition for a column object. + * + * @param Column $column Column + * @param string $tableName Table name + * + * @return string + */ + protected function getColumnCommentSqlDefinition(Column $column, $tableName) + { + // passing 'null' is to remove column comment + $currentComment = $this->getColumnComment($tableName, $column->getName()); + + $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\''; + $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty'; + return sprintf( + "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';", + $command, + $comment, + $this->schema, + $tableName, + $column->getName() + ); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + public function getColumnComment($tableName, $columnName) + { + $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment + FROM sys.schemas + INNER JOIN sys.tables + ON schemas.schema_id = tables.schema_id + INNER JOIN sys.columns + ON tables.object_id = columns.object_id + INNER JOIN sys.extended_properties + ON tables.object_id = extended_properties.major_id + AND columns.column_id = extended_properties.minor_id + AND extended_properties.name = 'MS_Description' + WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName); + $row = $this->fetchRow($sql); + + if ($row) { + return $row['comment']; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $sql = sprintf( + "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type], + IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default], + CHARACTER_MAXIMUM_LENGTH AS [char_length], + NUMERIC_PRECISION AS [precision], + NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position], + COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity] + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = '%s' + ORDER BY ordinal_position", + $tableName + ); + $rows = $this->fetchAll($sql); + foreach ($rows as $columnInfo) { + $column = new Column(); + $column->setName($columnInfo['name']) + ->setType($this->getPhinxType($columnInfo['type'])) + ->setNull($columnInfo['null'] != 'NO') + ->setDefault($this->parseDefault($columnInfo['default'])) + ->setIdentity($columnInfo['identity'] === '1') + ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name'])); + + if (!empty($columnInfo['char_length'])) { + $column->setLimit($columnInfo['char_length']); + } + + $columns[$columnInfo['name']] = $column; + } + + return $columns; + } + + protected function parseDefault($default) + { + $default = preg_replace(array("/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"), '$1', $default); + + if (strtoupper($default) === 'NULL') { + $default = null; + } elseif (is_numeric($default)) { + $default = (int) $default; + } + + return $default; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName, $options = array()) + { + $sql = sprintf( + "SELECT count(*) as [count] + FROM information_schema.columns + WHERE table_name = '%s' AND column_name = '%s'", + $tableName, + $columnName + ); + $result = $this->fetchRow($sql); + + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + $sql = sprintf( + 'ALTER TABLE %s ADD %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->startCommandTimer(); + + if (!$this->hasColumn($tableName, $columnName)) { + throw new \InvalidArgumentException("The specified column does not exist: $columnName"); + } + $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + $this->renameDefault($tableName, $columnName, $newColumnName); + $this->execute( + sprintf( + "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ", + $tableName, + $columnName, + $newColumnName + ) + ); + $this->endCommandTimer(); + } + + protected function renameDefault($tableName, $columnName, $newColumnName) + { + $oldConstraintName = "DF_{$tableName}_{$columnName}"; + $newConstraintName = "DF_{$tableName}_{$newColumnName}"; + $sql = <<execute(sprintf( + $sql, + $oldConstraintName, + $newConstraintName + )); + } + + public function changeDefault($tableName, Column $newColumn) + { + $constraintName = "DF_{$tableName}_{$newColumn->getName()}"; + $default = $newColumn->getDefault(); + + if ($default === null) { + $default = 'DEFAULT NULL'; + } else { + $default = $this->getDefaultValueDefinition($default); + } + + if (empty($default)) { + return; + } + + $this->execute(sprintf( + 'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s', + $this->quoteTableName($tableName), + $constraintName, + $default, + $this->quoteColumnName($newColumn->getName()) + )); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + $columns = $this->getColumns($tableName); + $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType(); + if ($columnName != $newColumn->getName()) { + $this->renameColumn($tableName, $columnName, $newColumn->getName()); + } + + if ($changeDefault) { + $this->dropDefaultConstraint($tableName, $newColumn->getName()); + } + + $this->execute( + sprintf( + 'ALTER TABLE %s ALTER COLUMN %s %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($newColumn->getName()), + $this->getColumnSqlDefinition($newColumn, false) + ) + ); + // change column comment if needed + if ($newColumn->getComment()) { + $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName); + $this->execute($sql); + } + + if ($changeDefault) { + $this->changeDefault($tableName, $newColumn); + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + $this->dropDefaultConstraint($tableName, $columnName); + + $this->execute( + sprintf( + 'ALTER TABLE %s DROP COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + $this->endCommandTimer(); + } + + protected function dropDefaultConstraint($tableName, $columnName) + { + $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName); + + if (!$defaultConstraint) { + return; + } + + $this->dropForeignKey($tableName, $columnName, $defaultConstraint); + } + + protected function getDefaultConstraint($tableName, $columnName) + { + $sql = "SELECT + default_constraints.name +FROM + sys.all_columns + + INNER JOIN + sys.tables + ON all_columns.object_id = tables.object_id + + INNER JOIN + sys.schemas + ON tables.schema_id = schemas.schema_id + + INNER JOIN + sys.default_constraints + ON all_columns.default_object_id = default_constraints.object_id + +WHERE + schemas.name = 'dbo' + AND tables.name = '{$tableName}' + AND all_columns.name = '{$columnName}'"; + + $rows = $this->fetchAll($sql); + return empty($rows) ? false : $rows[0]['name']; + } + + protected function getIndexColums($tableId, $indexId) + { + $sql = "SELECT AC.[name] AS [column_name] +FROM sys.[index_columns] IC + INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id] +WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId} AND IC.[object_id] = {$tableId} +ORDER BY IC.[key_ordinal];"; + + $rows = $this->fetchAll($sql); + $columns = array(); + foreach($rows as $row) { + $columns[] = strtolower($row['column_name']); + } + return $columns; + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + public function getIndexes($tableName) + { + $indexes = array(); + $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id] +FROM sys.[tables] AS T + INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] +WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP' AND T.[name] = '{$tableName}' +ORDER BY T.[name], I.[index_id];"; + + $rows = $this->fetchAll($sql); + foreach ($rows as $row) { + $columns = $this->getIndexColums($row['table_id'], $row['index_id']); + $indexes[$row['index_name']] = array('columns' => $columns); + } + + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + + if (empty($a)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $sql = $this->getIndexSqlDefinition($index, $table->getName()); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $indexName => $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'DROP INDEX %s ON %s', + $this->quoteColumnName($indexName), + $this->quoteTableName($tableName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + if ($name === $indexName) { + $this->execute( + sprintf( + 'DROP INDEX %s ON %s', + $this->quoteColumnName($indexName), + $this->quoteTableName($tableName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + if ($constraint) { + if (isset($foreignKeys[$constraint])) { + return !empty($foreignKeys[$constraint]); + } + return false; + } else { + foreach ($foreignKeys as $key) { + $a = array_diff($columns, $key['columns']); + if (empty($a)) { + return true; + } + } + return false; + } + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll(sprintf( + "SELECT + tc.constraint_name, + tc.table_name, kcu.column_name, + ccu.table_name AS referenced_table_name, + ccu.column_name AS referenced_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' + ORDER BY kcu.ordinal_position", + $tableName + )); + foreach ($rows as $row) { + $foreignKeys[$row['constraint_name']]['table'] = $row['table_name']; + $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name']; + $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name']; + $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name']; + } + + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $this->execute( + sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getForeignKeySqlDefinition($foreignKey, $table->getName()) + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + if ($constraint) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP CONSTRAINT %s', + $this->quoteTableName($tableName), + $constraint + ) + ); + $this->endCommandTimer(); + return; + } else { + foreach ($columns as $column) { + $rows = $this->fetchAll(sprintf( + "SELECT + tc.constraint_name, + tc.table_name, kcu.column_name, + ccu.table_name AS referenced_table_name, + ccu.column_name AS referenced_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s' + ORDER BY kcu.ordinal_position", + $tableName, + $column + )); + foreach ($rows as $row) { + $this->dropForeignKey($tableName, $columns, $row['constraint_name']); + } + } + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_STRING: + return array('name' => 'nvarchar', 'limit' => 255); + break; + case static::PHINX_TYPE_CHAR: + return array('name' => 'nchar', 'limit' => 255); + break; + case static::PHINX_TYPE_TEXT: + return array('name' => 'ntext'); + break; + case static::PHINX_TYPE_INTEGER: + return array('name' => 'int'); + break; + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint'); + break; + case static::PHINX_TYPE_FLOAT: + return array('name' => 'float'); + break; + case static::PHINX_TYPE_DECIMAL: + return array('name' => 'decimal'); + break; + case static::PHINX_TYPE_DATETIME: + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIME: + return array('name' => 'time'); + break; + case static::PHINX_TYPE_DATE: + return array('name' => 'date'); + break; + case static::PHINX_TYPE_BINARY: + return array('name' => 'varbinary'); + break; + case static::PHINX_TYPE_BOOLEAN: + return array('name' => 'bit'); + break; + case static::PHINX_TYPE_UUID: + return array('name' => 'uniqueidentifier'); + case static::PHINX_TYPE_FILESTREAM: + return array('name' => 'varbinary', 'limit' => 'max'); + // Geospatial database types + case static::PHINX_TYPE_GEOMETRY: + case static::PHINX_TYPE_POINT: + case static::PHINX_TYPE_LINESTRING: + case static::PHINX_TYPE_POLYGON: + // SQL Server stores all spatial data using a single data type. + // Specific types (point, polygon, etc) are set at insert time. + return array('name' => 'geography'); + break; + default: + throw new \RuntimeException('The type: "' . $type . '" is not supported.'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param $sqlTypeDef + * @throws \RuntimeException + * @internal param string $sqlType SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlType) + { + switch ($sqlType) { + case 'nvarchar': + case 'varchar': + return static::PHINX_TYPE_STRING; + case 'char': + case 'nchar': + return static::PHINX_TYPE_CHAR; + case 'text': + case 'ntext': + return static::PHINX_TYPE_TEXT; + case 'int': + case 'integer': + return static::PHINX_TYPE_INTEGER; + case 'decimal': + case 'numeric': + case 'money': + return static::PHINX_TYPE_DECIMAL; + case 'bigint': + return static::PHINX_TYPE_BIG_INTEGER; + case 'real': + case 'float': + return static::PHINX_TYPE_FLOAT; + case 'binary': + case 'image': + case 'varbinary': + return static::PHINX_TYPE_BINARY; + break; + case 'time': + return static::PHINX_TYPE_TIME; + case 'date': + return static::PHINX_TYPE_DATE; + case 'datetime': + case 'timestamp': + return static::PHINX_TYPE_DATETIME; + case 'bit': + return static::PHINX_TYPE_BOOLEAN; + case 'uniqueidentifier': + return static::PHINX_TYPE_UUID; + case 'filestream': + return static::PHINX_TYPE_FILESTREAM; + default: + throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported'); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + + if (isset($options['collation'])) { + $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation'])); + } else { + $this->execute(sprintf('CREATE DATABASE [%s]', $name)); + } + $this->execute(sprintf('USE [%s]', $name)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + $result = $this->fetchRow( + sprintf( + 'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'', + $name + ) + ); + + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + $sql = <<execute($sql); + $this->endCommandTimer(); + } + + /** + * Get the defintion for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = (int) $default; + } + return isset($default) ? ' DEFAULT ' . $default : ''; + } + + /** + * Gets the SqlServer Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column, $create = true) + { + $buffer = array(); + + $sqlType = $this->getSqlType($column->getType()); + $buffer[] = strtoupper($sqlType['name']); + // integers cant have limits in SQlServer + $noLimits = array( + 'bigint', + 'int', + 'tinyint' + ); + if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) { + $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']); + } + if ($column->getPrecision() && $column->getScale()) { + $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; + } + + $properties = $column->getProperties(); + $buffer[] = $column->getType() == 'filestream' ? 'FILESTREAM' : ''; + $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : ''; + + $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL'; + + if ($create === true) { + if ($column->getDefault() === null && $column->isNull()) { + $buffer[] = ' DEFAULT NULL'; + } else { + $buffer[] = $this->getDefaultValueDefinition($column->getDefault()); + } + } + + if ($column->isIdentity()) { + $buffer[] = 'IDENTITY(1, 1)'; + } + + return implode(' ', $buffer); + } + + /** + * Gets the SqlServer Index Definition for an Index object. + * + * @param Index $index Index + * @return string + */ + protected function getIndexSqlDefinition(Index $index, $tableName) + { + if (is_string($index->getName())) { + $indexName = $index->getName(); + } else { + $columnNames = $index->getColumns(); + if (is_string($columnNames)) { + $columnNames = array($columnNames); + } + $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames)); + } + $def = sprintf( + "CREATE %s INDEX %s ON %s (%s);", + ($index->getType() == Index::UNIQUE ? 'UNIQUE' : ''), + $indexName, + $this->quoteTableName($tableName), + '[' . implode('],[', $index->getColumns()) . ']' + ); + + return $def; + } + + /** + * Gets the SqlServer Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName) + { + $def = ' CONSTRAINT "'; + $def .= $tableName . '_' . implode('_', $foreignKey->getColumns()); + $def .= '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")'; + $def .= " REFERENCES {$foreignKey->getReferencedTable()->getName()} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")'; + if ($foreignKey->getOnDelete()) { + $def .= " ON DELETE {$foreignKey->getOnDelete()}"; + } + if ($foreignKey->getOnUpdate()) { + $def .= " ON UPDATE {$foreignKey->getOnUpdate()}"; + } + + return $def; + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('filestream')); + } + + /** + * {@inheritdoc} + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime) { + if (strcasecmp($direction, MigrationInterface::UP) === 0) { + // up + $sql = sprintf( + "INSERT INTO %s ([version], [start_time], [end_time]) VALUES ('%s', '%s', '%s');", + $this->getSchemaTableName(), + $migration->getVersion(), + $startTime, + $endTime + ); + + $this->query($sql); + } else { + // down + $sql = sprintf( + "DELETE FROM %s WHERE [version] = '%s'", + $this->getSchemaTableName(), + $migration->getVersion() + ); + + $this->query($sql); + } + + return $this; + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php new file mode 100644 index 00000000..c3d8b648 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php @@ -0,0 +1,253 @@ + + */ +class TablePrefixAdapter extends AdapterWrapper +{ + /** + * {@inheritdoc} + */ + public function getAdapterType() + { + return 'TablePrefixAdapter'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasTable($adapterTableName); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + + foreach ($adapterTable->getForeignKeys() as $fk) { + $adapterReferenceTable = $fk->getReferencedTable(); + $adapterReferenceTableName = $this->getAdapterTableName($adapterReferenceTable->getName()); + $adapterReferenceTable->setName($adapterReferenceTableName); + } + + return parent::createTable($adapterTable); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + $adapterNewTableName = $this->getAdapterTableName($newTableName); + return parent::renameTable($adapterTableName, $adapterNewTableName); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropTable($adapterTableName); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::getColumns($adapterTableName); + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasColumn($adapterTableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::addColumn($adapterTable, $column); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::renameColumn($adapterTableName, $columnName, $newColumnName); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::changeColumn($adapterTableName, $columnName, $newColumn); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropColumn($adapterTableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasIndex($adapterTableName, $columns); + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::addIndex($adapterTable, $index); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns, $options = array()) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropIndex($adapterTableName, $columns, $options); + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropIndexByName($adapterTableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasForeignKey($adapterTableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::addForeignKey($adapterTable, $foreignKey); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropForeignKey($adapterTableName, $columns, $constraint); + } + + /** + * Gets the table prefix. + * + * @return string + */ + public function getPrefix() + { + return (string) $this->getOption('table_prefix'); + } + + /** + * Gets the table suffix. + * + * @return string + */ + public function getSuffix() + { + return (string) $this->getOption('table_suffix'); + } + + /** + * Applies the prefix and suffix to the table name. + * + * @param string $tableName + * @return string + */ + public function getAdapterTableName($tableName) + { + return $this->getPrefix() . $tableName . $this->getSuffix(); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php new file mode 100644 index 00000000..b8181d47 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php @@ -0,0 +1,60 @@ + + */ +interface WrapperInterface +{ + /** + * Class constructor, must always wrap another adapter. + * + * @param AdapterInterface $adapter + */ + public function __construct(AdapterInterface $adapter); + + /** + * Sets the database adapter to proxy commands to. + * + * @param AdapterInterface $adapter + * @return AdapterInterface + */ + public function setAdapter(AdapterInterface $adapter); + + /** + * Gets the database adapter. + * + * @throws \RuntimeException if the adapter has not been set + * @return AdapterInterface + */ + public function getAdapter(); +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Table.php b/vendor/robmorgan/phinx/src/Phinx/Db/Table.php new file mode 100644 index 00000000..e8eda19e --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Table.php @@ -0,0 +1,656 @@ +setName($name); + $this->setOptions($options); + + if (null !== $adapter) { + $this->setAdapter($adapter); + } + } + + /** + * Sets the table name. + * + * @param string $name Table Name + * @return Table + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Gets the table name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the table options. + * + * @param array $options + * @return Table + */ + public function setOptions($options) + { + $this->options = $options; + return $this; + } + + /** + * Gets the table options. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter Database Adapter + * @return Table + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Does the table exist? + * + * @return boolean + */ + public function exists() + { + return $this->getAdapter()->hasTable($this->getName()); + } + + /** + * Drops the database table. + * + * @return void + */ + public function drop() + { + $this->getAdapter()->dropTable($this->getName()); + } + + /** + * Renames the database table. + * + * @param string $newTableName New Table Name + * @return Table + */ + public function rename($newTableName) + { + $this->getAdapter()->renameTable($this->getName(), $newTableName); + $this->setName($newTableName); + return $this; + } + + /** + * Sets an array of columns waiting to be committed. + * Use setPendingColumns + * + * @deprecated + * @param array $columns Columns + * @return Table + */ + public function setColumns($columns) + { + $this->setPendingColumns($columns); + } + + /** + * Gets an array of the table columns. + * + * @return Column[] + */ + public function getColumns() + { + return $this->getAdapter()->getColumns($this->getName()); + } + + /** + * Sets an array of columns waiting to be committed. + * + * @param array $columns Columns + * @return Table + */ + public function setPendingColumns($columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Gets an array of columns waiting to be committed. + * + * @return Column[] + */ + public function getPendingColumns() + { + return $this->columns; + } + + /** + * Sets an array of columns waiting to be indexed. + * + * @param array $indexes Indexes + * @return Table + */ + public function setIndexes($indexes) + { + $this->indexes = $indexes; + return $this; + } + + /** + * Gets an array of indexes waiting to be committed. + * + * @return array + */ + public function getIndexes() + { + return $this->indexes; + } + + /** + * Gets an array of foreign keys waiting to be commited. + * + * @param ForeignKey[] $foreignKeys foreign keys + * @return Table + */ + public function setForeignKeys($foreignKeys) + { + $this->foreignKeys = $foreignKeys; + return $this; + } + + /** + * Gets an array of foreign keys waiting to be commited. + * + * @return array|ForeignKey[] + */ + public function getForeignKeys() + { + return $this->foreignKeys; + } + + /** + * @param $data array of data to be inserted + * @return Table + */ + public function setData($data) + { + $this->data = $data; + return $this; + } + + /** + * Gets the data waiting to be inserted + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Resets all of the pending table changes. + * + * @return void + */ + public function reset() + { + $this->setPendingColumns(array()); + $this->setIndexes(array()); + $this->setForeignKeys(array()); + $this->setData(array()); + } + + /** + * Add a table column. + * + * Type can be: string, text, integer, float, decimal, datetime, timestamp, + * time, date, binary, boolean. + * + * Valid options can be: limit, default, null, precision or scale. + * + * @param string|Column $columnName Column Name + * @param string $type Column Type + * @param array $options Column Options + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return Table + */ + public function addColumn($columnName, $type = null, $options = array()) + { + // we need an adapter set to add a column + if (null === $this->getAdapter()) { + throw new \RuntimeException('An adapter must be specified to add a column.'); + } + + // create a new column object if only strings were supplied + if (!$columnName instanceof Column) { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); // map options to column methods + } else { + $column = $columnName; + } + + // Delegate to Adapters to check column type + if (!$this->getAdapter()->isValidColumnType($column)) { + throw new \InvalidArgumentException(sprintf( + 'An invalid column type "%s" was specified for column "%s".', + $column->getType(), + $column->getName() + )); + } + + $this->columns[] = $column; + return $this; + } + + /** + * Remove a table column. + * + * @param string $columnName Column Name + * @return Table + */ + public function removeColumn($columnName) + { + $this->getAdapter()->dropColumn($this->getName(), $columnName); + return $this; + } + + /** + * Rename a table column. + * + * @param string $oldName Old Column Name + * @param string $newName New Column Name + * @return Table + */ + public function renameColumn($oldName, $newName) + { + $this->getAdapter()->renameColumn($this->getName(), $oldName, $newName); + return $this; + } + + /** + * Change a table column type. + * + * @param string $columnName Column Name + * @param string|Column $newColumnType New Column Type + * @param array $options Options + * @return Table + */ + public function changeColumn($columnName, $newColumnType, $options = array()) + { + // create a column object if one wasn't supplied + if (!$newColumnType instanceof Column) { + $newColumn = new Column(); + $newColumn->setType($newColumnType); + $newColumn->setOptions($options); + } else { + $newColumn = $newColumnType; + } + + // if the name was omitted use the existing column name + if (null === $newColumn->getName() || strlen($newColumn->getName()) == 0) { + $newColumn->setName($columnName); + } + + $this->getAdapter()->changeColumn($this->getName(), $columnName, $newColumn); + return $this; + } + + /** + * Checks to see if a column exists. + * + * @param string $columnName Column Name + * @param array $options Options + * @return boolean + */ + public function hasColumn($columnName, $options = array()) + { + return $this->getAdapter()->hasColumn($this->getName(), $columnName, $options); + } + + /** + * Add an index to a database table. + * + * In $options you can specific unique = true/false or name (index name). + * + * @param string|array|Index $columns Table Column(s) + * @param array $options Index Options + * @return Table + */ + public function addIndex($columns, $options = array()) + { + // create a new index object if strings or an array of strings were supplied + if (!$columns instanceof Index) { + $index = new Index(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $index->setColumns($columns); + $index->setOptions($options); + } else { + $index = $columns; + } + + $this->indexes[] = $index; + return $this; + } + + /** + * Removes the given index from a table. + * + * @param array $columns Columns + * @param array $options Options + * @return Table + */ + public function removeIndex($columns, $options = array()) + { + $this->getAdapter()->dropIndex($this->getName(), $columns, $options); + return $this; + } + + /** + * Removes the given index identified by its name from a table. + * + * @param string $name Index name + * @return Table + */ + public function removeIndexByName($name) + { + $this->getAdapter()->dropIndexByName($this->getName(), $name); + return $this; + } + + /** + * Checks to see if an index exists. + * + * @param string|array $columns Columns + * @param array $options Options + * @return boolean + */ + public function hasIndex($columns, $options = array()) + { + return $this->getAdapter()->hasIndex($this->getName(), $columns, $options); + } + + /** + * Add a foreign key to a database table. + * + * In $options you can specify on_delete|on_delete = cascade|no_action .., + * on_update, constraint = constraint name. + * + * @param string|array $columns Columns + * @param string|Table $referencedTable Referenced Table + * @param string|array $referencedColumns Referenced Columns + * @param array $options Options + * @return Table + */ + public function addForeignKey($columns, $referencedTable, $referencedColumns = array('id'), $options = array()) + { + if (is_string($referencedColumns)) { + $referencedColumns = array($referencedColumns); // str to array + } + $fk = new ForeignKey(); + if ($referencedTable instanceof Table) { + $fk->setReferencedTable($referencedTable); + } else { + $fk->setReferencedTable(new Table($referencedTable, array(), $this->adapter)); + } + $fk->setColumns($columns) + ->setReferencedColumns($referencedColumns) + ->setOptions($options); + $this->foreignKeys[] = $fk; + + return $this; + } + + /** + * Removes the given foreign key from the table. + * + * @param string|array $columns Column(s) + * @param null|string $constraint Constraint names + * @return Table + */ + public function dropForeignKey($columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); + } + if ($constraint) { + $this->getAdapter()->dropForeignKey($this->getName(), array(), $constraint); + } else { + $this->getAdapter()->dropForeignKey($this->getName(), $columns); + } + + return $this; + } + + /** + * Checks to see if a foreign key exists. + * + * @param string|array $columns Column(s) + * @param null|string $constraint Constraint names + * @return boolean + */ + public function hasForeignKey($columns, $constraint = null) + { + return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint); + } + + /** + * Add timestamp columns created_at and updated_at to the table. + * + * @return Table + */ + public function addTimestamps() + { + $this->addColumn('created_at', 'timestamp', array( + 'default' => 'CURRENT_TIMESTAMP', + 'update' => '' + )) + ->addColumn('updated_at', 'timestamp', array( + 'null' => true, + 'default' => null + )); + + return $this; + } + + /** + * @param array $columns column names + * @param $data array of data in the form : + * array( + * array("value1", "anotherValue1"), + * array("value2", "anotherValue2"), + * ) + * + * @return Table + */ + public function insert($columns, $data) + { + $this->data[] = array("columns" => $columns, "data" => $data); + return $this; + } + + /** + * Creates a table from the object instance. + * + * @return void + */ + public function create() + { + $this->getAdapter()->createTable($this); + $this->saveData(); + $this->reset(); // reset pending changes + } + + /** + * Updates a table from the object instance. + * + * @throws \RuntimeException + * @return void + */ + public function update() + { + if (!$this->exists()) { + throw new \RuntimeException('Cannot update a table that doesn\'t exist!'); + } + + // update table + foreach ($this->getPendingColumns() as $column) { + $this->getAdapter()->addColumn($this, $column); + } + + foreach ($this->getIndexes() as $index) { + $this->getAdapter()->addIndex($this, $index); + } + + foreach ($this->getForeignKeys() as $foreignKey) { + $this->getAdapter()->addForeignKey($this, $foreignKey); + } + + $this->saveData(); + + $this->reset(); // reset pending changes + } + + /** + * Does the actual insertion of pending data + */ + public function saveData() + { + foreach ($this->getData() as $data) { + $this->getAdapter()->insert($this, $data["columns"], $data["data"]); + } + } + + /** + * Commits the table changes. + * + * If the table doesn't exist it is created otherwise it is updated. + * + * @return void + */ + public function save() + { + if ($this->exists()) { + $this->update(); // update the table + } else { + $this->create(); // create the table + } + + $this->reset(); // reset pending changes + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Table/Column.php b/vendor/robmorgan/phinx/src/Phinx/Db/Table/Column.php new file mode 100644 index 00000000..9598176c --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Table/Column.php @@ -0,0 +1,549 @@ +name = $name; + return $this; + } + + /** + * Gets the column name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the column type. + * + * @param string $type + * @return Column + */ + public function setType($type) + { + $this->type = $type; + return $this; + } + + /** + * Gets the column type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Sets the column limit. + * + * @param integer $limit + * @return Column + */ + public function setLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Gets the column limit. + * + * @return integer + */ + public function getLimit() + { + return $this->limit; + } + + /** + * Sets whether the column allows nulls. + * + * @param boolean $null + * @return Column + */ + public function setNull($null) + { + $this->null = (bool) $null; + return $this; + } + + /** + * Gets whether the column allows nulls. + * + * @return boolean + */ + public function getNull() + { + return $this->null; + } + + /** + * Does the column allow nulls? + * + * @return boolean + */ + public function isNull() + { + return $this->getNull(); + } + + /** + * Sets the default column value. + * + * @param mixed $default + * @return Column + */ + public function setDefault($default) + { + $this->default = $default; + return $this; + } + + /** + * Gets the default column value. + * + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * Sets whether or not the column is an identity column. + * + * @param boolean $identity + * @return Column + */ + public function setIdentity($identity) + { + $this->identity = $identity; + return $this; + } + + /** + * Gets whether or not the column is an identity column. + * + * @return boolean + */ + public function getIdentity() + { + return $this->identity; + } + + /** + * Is the column an identity column? + * + * @return boolean + */ + public function isIdentity() + { + return $this->getIdentity(); + } + + /** + * Sets the name of the column to add this column after. + * + * @param string $after After + * @return Column + */ + public function setAfter($after) + { + $this->after = $after; + return $this; + } + + /** + * Returns the name of the column to add this column after. + * + * @return string + */ + public function getAfter() + { + return $this->after; + } + + /** + * Sets the 'ON UPDATE' mysql column function. + * + * @param string $update On Update function + * @return Column + */ + public function setUpdate($update) + { + $this->update = $update; + return $this; + } + + /** + * Returns the value of the ON UPDATE column function. + * + * @return string + */ + public function getUpdate() + { + return $this->update; + } + + /** + * Sets the column precision for decimal. + * + * @param integer $precision + * @return Column + */ + public function setPrecision($precision) + { + $this->precision = $precision; + return $this; + } + + /** + * Gets the column precision for decimal. + * + * @return integer + */ + public function getPrecision() + { + return $this->precision; + } + + /** + * Sets the column scale for decimal. + * + * @param integer $scale + * @return Column + */ + public function setScale($scale) + { + $this->scale = $scale; + return $this; + } + + /** + * Gets the column scale for decimal. + * + * @return integer + */ + public function getScale() + { + return $this->scale; + } + + /** + * Sets the column comment. + * + * @param string $comment + * @return Column + */ + public function setComment($comment) + { + $this->comment = $comment; + return $this; + } + + /** + * Gets the column comment. + * + * @return string + */ + public function getComment() + { + return $this->comment; + } + + /** + * Sets whether field should be signed. + * + * @param bool $signed + * @return Column + */ + public function setSigned($signed) + { + $this->signed = (bool) $signed; + return $this; + } + + /** + * Gets whether field should be signed. + * + * @return string + */ + public function getSigned() + { + return $this->signed; + } + + /** + * Should the column be signed? + * + * @return boolean + */ + public function isSigned() + { + return $this->getSigned(); + } + + /** + * Sets whether the field should have a timezone identifier. + * Used for date/time columns only! + * + * @param bool $timezone + * @return Column + */ + public function setTimezone($timezone) + { + $this->timezone = (bool) $timezone; + return $this; + } + + /** + * Gets whether field has a timezone identifier. + * + * @return boolean + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * Should the column have a timezone? + * + * @return boolean + */ + public function isTimezone() + { + return $this->getTimezone(); + } + + /** + * Sets field properties. + * + * @param array $properties + * + * @return Column + */ + public function setProperties($properties) + { + $this->properties = $properties; + return $this; + } + + /** + * Gets field properties + * + * @return array + */ + public function getProperties() + { + return $this->properties; + } + + /** + * Sets field values. + * + * @param mixed (array|string) $values + * + * @return Column + */ + public function setValues($values) + { + if (!is_array($values)) { + $values = preg_split('/,\s*/', $values); + } + $this->values = $values; + return $this; + } + + /** + * Gets field values + * + * @return string + */ + public function getValues() + { + return $this->values; + } + + /** + * Gets all allowed options. Each option must have a corresponding `setFoo` method. + * + * @return array + */ + protected function getValidOptions() + { + return array( + 'limit', + 'default', + 'null', + 'identity', + 'precision', + 'scale', + 'after', + 'update', + 'comment', + 'signed', + 'timezone', + 'properties', + 'values', + ); + } + + /** + * Gets all aliased options. Each alias must reference a valid option. + * + * @return array + */ + protected function getAliasedOptions() + { + return array( + 'length' => 'limit', + ); + } + + /** + * Utility method that maps an array of column options to this objects methods. + * + * @param array $options Options + * @return Column + */ + public function setOptions($options) + { + $validOptions = $this->getValidOptions(); + $aliasOptions = $this->getAliasedOptions(); + + foreach ($options as $option => $value) { + if (isset($aliasOptions[$option])) { + // proxy alias -> option + $option = $aliasOptions[$option]; + } + + if (!in_array($option, $validOptions)) { + throw new \RuntimeException(sprintf('"%s" is not a valid column option.', $option)); + } + + $method = 'set' . ucfirst($option); + $this->$method($value); + } + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php b/vendor/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php new file mode 100644 index 00000000..886cf2ca --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php @@ -0,0 +1,252 @@ + + */ +namespace Phinx\Db\Table; + +use Phinx\Db\Table; + +class ForeignKey +{ + const CASCADE = 'CASCADE'; + const RESTRICT = 'RESTRICT'; + const SET_NULL = 'SET NULL'; + const NO_ACTION = 'NO ACTION'; + + /** + * @var array + */ + protected $columns = array(); + + /** + * @var Table + */ + protected $referencedTable; + + /** + * @var array + */ + protected $referencedColumns = array(); + + /** + * @var string + */ + protected $onDelete; + + /** + * @var string + */ + protected $onUpdate; + + /** + * @var string|boolean + */ + protected $constraint; + + /** + * Sets the foreign key columns. + * + * @param array|string $columns + * @return ForeignKey + */ + public function setColumns($columns) + { + if (is_string($columns)) { + $columns = array($columns); + } + $this->columns = $columns; + return $this; + } + + /** + * Gets the foreign key columns. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Sets the foreign key referenced table. + * + * @param Table $table + * @return ForeignKey + */ + public function setReferencedTable(Table $table) + { + $this->referencedTable = $table; + return $this; + } + + /** + * Gets the foreign key referenced table. + * + * @return Table + */ + public function getReferencedTable() + { + return $this->referencedTable; + } + + /** + * Sets the foreign key referenced columns. + * + * @param array $referencedColumns + * @return ForeignKey + */ + public function setReferencedColumns(array $referencedColumns) + { + $this->referencedColumns = $referencedColumns; + return $this; + } + + /** + * Gets the foreign key referenced columns. + * + * @return array + */ + public function getReferencedColumns() + { + return $this->referencedColumns; + } + + /** + * Sets ON DELETE action for the foreign key. + * + * @param string $onDelete + * @return ForeignKey + */ + public function setOnDelete($onDelete) + { + $this->onDelete = $this->normalizeAction($onDelete); + return $this; + } + + /** + * Gets ON DELETE action for the foreign key. + * + * @return string + */ + public function getOnDelete() + { + return $this->onDelete; + } + + /** + * Gets ON UPDATE action for the foreign key. + * + * @return string + */ + public function getOnUpdate() + { + return $this->onUpdate; + } + + /** + * Sets ON UPDATE action for the foreign key. + * + * @param string $onUpdate + * @return ForeignKey + */ + public function setOnUpdate($onUpdate) + { + $this->onUpdate = $this->normalizeAction($onUpdate); + return $this; + } + + /** + * Sets constraint for the foreign key. + * + * @param string $constraint + * @return ForeignKey + */ + public function setConstraint($constraint) + { + $this->constraint = $constraint; + return $this; + } + + /** + * Gets constraint name for the foreign key. + * + * @return string + */ + public function getConstraint() + { + return $this->constraint; + } + + /** + * Utility method that maps an array of index options to this objects methods. + * + * @param array $options Options + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return ForeignKey + */ + public function setOptions($options) + { + // Valid Options + $validOptions = array('delete', 'update', 'constraint'); + foreach ($options as $option => $value) { + if (!in_array($option, $validOptions)) { + throw new \RuntimeException('\'' . $option . '\' is not a valid foreign key option.'); + } + + // handle $options['delete'] as $options['update'] + if ('delete' == $option) { + $this->setOnDelete($value); + } elseif ('update' == $option) { + $this->setOnUpdate($value); + } else { + $method = 'set' . ucfirst($option); + $this->$method($value); + } + } + + return $this; + } + + /** + * From passed value checks if it's correct and fixes if needed + * + * @param string $action + * @throws \InvalidArgumentException + * @return string + */ + protected function normalizeAction($action) + { + $constantName = 'static::' . str_replace(' ', '_', strtoupper(trim($action))); + if (!defined($constantName)) { + throw new \InvalidArgumentException('Unknown action passed: ' . $action); + } + return constant($constantName); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Db/Table/Index.php b/vendor/robmorgan/phinx/src/Phinx/Db/Table/Index.php new file mode 100644 index 00000000..4aca0a49 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Db/Table/Index.php @@ -0,0 +1,141 @@ +columns = $columns; + return $this; + } + + /** + * Gets the index columns. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Sets the index type. + * + * @param string $type + * @return Index + */ + public function setType($type) + { + $this->type = $type; + return $this; + } + + /** + * Gets the index type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getName() + { + return $this->name; + } + + /** + * Utility method that maps an array of index options to this objects methods. + * + * @param array $options Options + * @throws \RuntimeException + * @return Index + */ + public function setOptions($options) + { + // Valid Options + $validOptions = array('type', 'unique', 'name'); + foreach ($options as $option => $value) { + if (!in_array($option, $validOptions)) { + throw new \RuntimeException('\'' . $option . '\' is not a valid index option.'); + } + + // handle $options['unique'] + if (strcasecmp($option, self::UNIQUE) === 0) { + if ((bool) $value) { + $this->setType(self::UNIQUE); + } + continue; + } + + $method = 'set' . ucfirst($option); + $this->$method($value); + } + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php b/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php new file mode 100644 index 00000000..504eaf84 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php @@ -0,0 +1,230 @@ + + */ +abstract class AbstractMigration implements MigrationInterface +{ + /** + * @var float + */ + protected $version; + + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * @var OutputInterface + */ + protected $output; + + + /** + * Class Constructor. + * + * @param int $version Migration Version + */ + final public function __construct($version) + { + $this->version = $version; + $this->init(); + } + + /** + * Initialize method. + * + * @return void + */ + protected function init() + { + } + + /** + * {@inheritdoc} + */ + public function up() + { + } + + /** + * {@inheritdoc} + */ + public function down() + { + } + + /** + * {@inheritdoc} + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->output; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return get_class($this); + } + + /** + * {@inheritdoc} + */ + public function setVersion($version) + { + $this->version = $version; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getVersion() + { + return $this->version; + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getAdapter()->execute($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getAdapter()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + return $this->getAdapter()->fetchRow($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + return $this->getAdapter()->fetchAll($sql); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options) + { + $this->getAdapter()->createDatabase($name, $options); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->getAdapter()->dropDatabase($name); + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + return $this->getAdapter()->hasTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function table($tableName, $options = array()) + { + return new Table($tableName, $options, $this->getAdapter()); + } + + /** + * A short-hand method to drop the given database table. + * + * @param string $tableName Table Name + * @return void + */ + public function dropTable($tableName) + { + $this->table($tableName)->drop(); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Migration/CreationInterface.php b/vendor/robmorgan/phinx/src/Phinx/Migration/CreationInterface.php new file mode 100644 index 00000000..3c6b2ac7 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Migration/CreationInterface.php @@ -0,0 +1,59 @@ + + */ +interface CreationInterface +{ + /** + * Get the migration template. + * + * This will be the content that Phinx will amend to generate the migration file. + * + * @return string The content of the template for Phinx to amend. + */ + public function getMigrationTemplate(); + + /** + * Post Migration Creation. + * + * Once the migration file has been created, this method will be called, allowing any additional + * processing, specific to the template to be performed. + * + * @param string $migrationFilename The name of the newly created migration. + * @param string $className The class name. + * @param string $baseClassName The name of the base class. + * @return void + */ + public function postMigrationCreation($migrationFilename, $className, $baseClassName); +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php b/vendor/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php new file mode 100644 index 00000000..9c2a8d21 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php @@ -0,0 +1,39 @@ + + */ +class IrreversibleMigrationException extends \Exception +{ +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php b/vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php new file mode 100644 index 00000000..b185acd7 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php @@ -0,0 +1,506 @@ +setConfig($config); + $this->setOutput($output); + } + + /** + * Prints the specified environment's migration status. + * + * @param string $environment + * @param null $format + * @return void + */ + public function printStatus($environment, $format = null) + { + $output = $this->getOutput(); + $migrations = array(); + if (count($this->getMigrations())) { + $output->writeln(''); + $output->writeln(' Status Migration ID Migration Name '); + $output->writeln('-----------------------------------------'); + + $env = $this->getEnvironment($environment); + $versions = $env->getVersions(); + + foreach ($this->getMigrations() as $migration) { + if (in_array($migration->getVersion(), $versions)) { + $status = ' up '; + unset($versions[array_search($migration->getVersion(), $versions)]); + } else { + $status = ' down '; + } + + $output->writeln( + $status + . sprintf(' %14.0f ', $migration->getVersion()) + . ' ' . $migration->getName() . '' + ); + $migrations[] = array('migration_status' => trim(strip_tags($status)), 'migration_id' => sprintf('%14.0f', $migration->getVersion()), 'migration_name' => $migration->getName()); + } + + foreach ($versions as $missing) { + $output->writeln( + ' up ' + . sprintf(' %14.0f ', $missing) + . ' ** MISSING **' + ); + } + } else { + // there are no migrations + $output->writeln(''); + $output->writeln('There are no available migrations. Try creating one using the create command.'); + } + + // write an empty line + $output->writeln(''); + if ($format != null) { + switch ($format) { + case 'json': + $output->writeln(json_encode($migrations)); + break; + default: + $output->writeln('Unsupported format: '.$format.''); + break; + } + } + + } + + /** + * Migrate to the version of the database on a given date. + * + * @param string $environment Environment + * @param \DateTime $dateTime Date to migrate to + * + * @return void + */ + public function migrateToDateTime($environment, \DateTime $dateTime) + { + $env = $this->getEnvironment($environment); + $versions = array_keys($this->getMigrations()); + $dateString = $dateTime->format('Ymdhis'); + $earlierVersion = null; + foreach ($versions as $version) { + if ($version > $dateString) { + if (!is_null($earlierVersion)) { + $this->getOutput()->writeln( + 'Migrating to version ' . $earlierVersion + ); + } + return $this->migrate($environment, $earlierVersion); + } + $earlierVersion = $version; + } + //If the date is greater than the latest version, migrate + //to the latest version. + $this->getOutput()->writeln( + 'Migrating to version ' . $earlierVersion + ); + return $this->migrate($environment, $earlierVersion); + } + + /** + * Roll back to the version of the database on a given date. + * + * @param string $environment Environment + * @param \DateTime $dateTime Date to roll back to + * + * @return void + */ + public function rollbackToDateTime($environment, \DateTime $dateTime) + { + $env = $this->getEnvironment($environment); + $versions = $env->getVersions(); + $dateString = $dateTime->format('Ymdhis'); + sort($versions); + $laterVersion = null; + foreach (array_reverse($versions) as $version) { + if ($version < $dateString) { + if (!is_null($laterVersion)) { + $this->getOutput()->writeln('Rolling back to version '.$version); + } + return $this->rollback($environment, $version); + } + $laterVersion = $version; + } + $this->getOutput()->writeln('Rolling back to version ' . $laterVersion); + return $this->rollback($environment, $laterVersion); + } + + /** + * Migrate an environment to the specified version. + * + * @param string $environment Environment + * @param int $version + * @return void + */ + public function migrate($environment, $version = null) + { + $migrations = $this->getMigrations(); + $env = $this->getEnvironment($environment); + $versions = $env->getVersions(); + $current = $env->getCurrentVersion(); + + if (empty($versions) && empty($migrations)) { + return; + } + + if (null === $version) { + $version = max(array_merge($versions, array_keys($migrations))); + } else { + if (0 != $version && !isset($migrations[$version])) { + $this->output->writeln(sprintf( + 'warning %s is not a valid version', + $version + )); + return; + } + } + + // are we migrating up or down? + $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN; + + if ($direction == MigrationInterface::DOWN) { + // run downs first + krsort($migrations); + foreach ($migrations as $migration) { + if ($migration->getVersion() <= $version) { + break; + } + + if (in_array($migration->getVersion(), $versions)) { + $this->executeMigration($environment, $migration, MigrationInterface::DOWN); + } + } + } + + ksort($migrations); + foreach ($migrations as $migration) { + if ($migration->getVersion() > $version) { + break; + } + + if (!in_array($migration->getVersion(), $versions)) { + $this->executeMigration($environment, $migration, MigrationInterface::UP); + } + } + } + + /** + * Execute a migration against the specified Environment. + * + * @param string $name Environment Name + * @param MigrationInterface $migration Migration + * @param string $direction Direction + * @return void + */ + public function executeMigration($name, MigrationInterface $migration, $direction = MigrationInterface::UP) + { + $this->getOutput()->writeln(''); + $this->getOutput()->writeln( + ' ==' + . ' ' . $migration->getVersion() . ' ' . $migration->getName() . ':' + . ' ' . ($direction == 'up' ? 'migrating' : 'reverting') . '' + ); + + // Execute the migration and log the time elapsed. + $start = microtime(true); + $this->getEnvironment($name)->executeMigration($migration, $direction); + $end = microtime(true); + + $this->getOutput()->writeln( + ' ==' + . ' ' . $migration->getVersion() . ' ' . $migration->getName() . ':' + . ' ' . ($direction == 'up' ? 'migrated' : 'reverted') + . ' ' . sprintf('%.4fs', $end - $start) . '' + ); + } + + /** + * Rollback an environment to the specified version. + * + * @param string $environment Environment + * @param int $version + * @return void + */ + public function rollback($environment, $version = null) + { + $migrations = $this->getMigrations(); + $env = $this->getEnvironment($environment); + $versions = $env->getVersions(); + + ksort($migrations); + sort($versions); + + // Check we have at least 1 migration to revert + if (empty($versions) || $version == end($versions)) { + $this->getOutput()->writeln('No migrations to rollback'); + return; + } + + // If no target version was supplied, revert the last migration + if (null === $version) { + // Get the migration before the last run migration + $prev = count($versions) - 2; + $version = $prev >= 0 ? $versions[$prev] : 0; + } else { + // Get the first migration number + $first = reset($versions); + + // If the target version is before the first migration, revert all migrations + if ($version < $first) { + $version = 0; + } + } + + // Check the target version exists + if (0 !== $version && !isset($migrations[$version])) { + $this->getOutput()->writeln("Target version ($version) not found"); + return; + } + + // Revert the migration(s) + krsort($migrations); + foreach ($migrations as $migration) { + if ($migration->getVersion() <= $version) { + break; + } + + if (in_array($migration->getVersion(), $versions)) { + $this->executeMigration($environment, $migration, MigrationInterface::DOWN); + } + } + } + + /** + * Sets the environments. + * + * @param array $environments Environments + * @return Manager + */ + public function setEnvironments($environments = array()) + { + $this->environments = $environments; + return $this; + } + + /** + * Gets the manager class for the given environment. + * + * @param string $name Environment Name + * @throws \InvalidArgumentException + * @return Environment + */ + public function getEnvironment($name) + { + if (isset($this->environments[$name])) { + return $this->environments[$name]; + } + + // check the environment exists + if (!$this->getConfig()->hasEnvironment($name)) { + throw new \InvalidArgumentException(sprintf( + 'The environment "%s" does not exist', + $name + )); + } + + // create an environment instance and cache it + $environment = new Environment($name, $this->getConfig()->getEnvironment($name)); + $this->environments[$name] = $environment; + $environment->setOutput($this->getOutput()); + + return $environment; + } + + /** + * Sets the console output. + * + * @param OutputInterface $output Output + * @return Manager + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * Gets the console output. + * + * @return OutputInterface + */ + public function getOutput() + { + return $this->output; + } + + /** + * Sets the database migrations. + * + * @param array $migrations Migrations + * @return Manager + */ + public function setMigrations(array $migrations) + { + $this->migrations = $migrations; + return $this; + } + + /** + * Gets an array of the database migrations. + * + * @throws \InvalidArgumentException + * @return AbstractMigration[] + */ + public function getMigrations() + { + if (null === $this->migrations) { + $config = $this->getConfig(); + $phpFiles = glob($config->getMigrationPath() . DIRECTORY_SEPARATOR . '*.php'); + + // filter the files to only get the ones that match our naming scheme + $fileNames = array(); + /** @var AbstractMigration[] $versions */ + $versions = array(); + + foreach ($phpFiles as $filePath) { + if (Util::isValidMigrationFileName(basename($filePath))) { + $version = Util::getVersionFromFileName(basename($filePath)); + + if (isset($versions[$version])) { + throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion())); + } + + // convert the filename to a class name + $class = Util::mapFileNameToClassName(basename($filePath)); + + if (isset($fileNames[$class])) { + throw new \InvalidArgumentException(sprintf( + 'Migration "%s" has the same name as "%s"', + basename($filePath), + $fileNames[$class] + )); + } + + $fileNames[$class] = basename($filePath); + + // load the migration file + /** @noinspection PhpIncludeInspection */ + require_once $filePath; + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf( + 'Could not find class "%s" in file "%s"', + $class, + $filePath + )); + } + + // instantiate it + $migration = new $class($version); + + if (!($migration instanceof AbstractMigration)) { + throw new \InvalidArgumentException(sprintf( + 'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration', + $class, + $filePath + )); + } + + $migration->setOutput($this->getOutput()); + $versions[$version] = $migration; + } + } + + ksort($versions); + $this->setMigrations($versions); + } + + return $this->migrations; + } + + /** + * Sets the config. + * + * @param ConfigInterface $config Configuration Object + * @return Manager + */ + public function setConfig(ConfigInterface $config) + { + $this->config = $config; + return $this; + } + + /** + * Gets the config. + * + * @return ConfigInterface + */ + public function getConfig() + { + return $this->config; + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php b/vendor/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php new file mode 100644 index 00000000..8bf26730 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php @@ -0,0 +1,314 @@ +name = $name; + $this->options = $options; + } + + /** + * Executes the specified migration on this environment. + * + * @param MigrationInterface $migration Migration + * @param string $direction Direction + * @return void + */ + public function executeMigration(MigrationInterface $migration, $direction = MigrationInterface::UP) + { + $startTime = time(); + $direction = ($direction == MigrationInterface::UP) ? MigrationInterface::UP : MigrationInterface::DOWN; + $migration->setAdapter($this->getAdapter()); + + // begin the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->beginTransaction(); + } + + // Run the migration + if (method_exists($migration, MigrationInterface::CHANGE)) { + if ($direction == MigrationInterface::DOWN) { + // Create an instance of the ProxyAdapter so we can record all + // of the migration commands for reverse playback + $proxyAdapter = AdapterFactory::instance() + ->getWrapper('proxy', $this->getAdapter()); + $migration->setAdapter($proxyAdapter); + /** @noinspection PhpUndefinedMethodInspection */ + $migration->change(); + $proxyAdapter->executeInvertedCommands(); + $migration->setAdapter($this->getAdapter()); + } else { + /** @noinspection PhpUndefinedMethodInspection */ + $migration->change(); + } + } else { + $migration->{$direction}(); + } + + // commit the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->commitTransaction(); + } + + // Record it in the database + $this->getAdapter()->migrated($migration, $direction, date('Y-m-d H:i:s', $startTime), date('Y-m-d H:i:s', time())); + } + + /** + * Sets the environment's name. + * + * @param string $name Environment Name + * @return Environment + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Gets the environment name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the environment's options. + * + * @param array $options Environment Options + * @return Environment + */ + public function setOptions($options) + { + $this->options = $options; + return $this; + } + + /** + * Gets the environment's options. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the console output. + * + * @param OutputInterface $output Output + * @return Environment + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * Gets the console output. + * + * @return OutputInterface + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets all migrated version numbers. + * + * @return array + */ + public function getVersions() + { + return $this->getAdapter()->getVersions(); + } + + /** + * Sets the current version of the environment. + * + * @param int $version Environment Version + * @return Environment + */ + public function setCurrentVersion($version) + { + $this->currentVersion = $version; + return $this; + } + + /** + * Gets the current version of the environment. + * + * @return int + */ + public function getCurrentVersion() + { + // We don't cache this code as the current version is pretty volatile. + // TODO - that means they're no point in a setter then? + // maybe we should cache and call a reset() method everytime a migration is run + $versions = $this->getVersions(); + $version = 0; + + if (!empty($versions)) { + $version = end($versions); + } + + $this->setCurrentVersion($version); + return $this->currentVersion; + } + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter Database Adapter + * @return Environment + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter() + { + if (isset($this->adapter)) { + return $this->adapter; + } + if (isset($this->options['connection'])) { + if (!($this->options['connection'] instanceof \PDO)) { + throw new \RuntimeException('The specified connection is not a PDO instance'); + } + + $this->options['adapter'] = $this->options['connection']->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + if (!isset($this->options['adapter'])) { + throw new \RuntimeException('No adapter was specified for environment: ' . $this->getName()); + } + + $adapter = AdapterFactory::instance() + ->getAdapter($this->options['adapter'], $this->options); + + if (isset($this->options['wrapper'])) { + $adapter = AdapterFactory::instance() + ->getWrapper($this->options['wrapper'], $adapter); + } + + if ($this->getOutput()) { + $adapter->setOutput($this->getOutput()); + } + + // Use the TablePrefixAdapter if table prefix/suffixes are in use + if ($adapter->hasOption('table_prefix') || $adapter->hasOption('table_suffix')) { + $adapter = AdapterFactory::instance() + ->getWrapper('prefix', $adapter); + } + + $this->setAdapter($adapter); + + return $adapter; + } + + /** + * Sets the schema table name. + * + * @param string $schemaTableName Schema Table Name + * @return Environment + */ + public function setSchemaTableName($schemaTableName) + { + $this->schemaTableName = $schemaTableName; + return $this; + } + + /** + * Gets the schema table name. + * + * @return string + */ + public function getSchemaTableName() + { + return $this->schemaTableName; + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Migration/Migration.template.php.dist b/vendor/robmorgan/phinx/src/Phinx/Migration/Migration.template.php.dist new file mode 100644 index 00000000..80ac0c17 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Migration/Migration.template.php.dist @@ -0,0 +1,32 @@ + + */ +interface MigrationInterface +{ + /** + * @var string + */ + const CHANGE = 'change'; + + /** + * @var string + */ + const UP = 'up'; + + /** + * @var string + */ + const DOWN = 'down'; + + /** + * Migrate Up + * + * @return void + */ + public function up(); + + /** + * Migrate Down + * + * @return void + */ + public function down(); + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter Database Adapter + * @return MigrationInterface + */ + public function setAdapter(AdapterInterface $adapter); + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter(); + + /** + * Sets the output object to be used in migration object + * + * @param OutputInterface $output + * @return MigrationInterface + */ + public function setOutput(OutputInterface $output); + + /** + * Gets the output object to be used in migration object + * + * @return OutputInterface + */ + public function getOutput(); + + /** + * Gets the name. + * + * @return string + */ + public function getName(); + + /** + * Sets the migration version number. + * + * @param float $version Version + * @return MigrationInterface + */ + public function setVersion($version); + + /** + * Gets the migration version number. + * + * @return float + */ + public function getVersion(); + + /** + * Executes a SQL statement and returns the number of affected rows. + * + * @param string $sql SQL + * @return int + */ + public function execute($sql); + + /** + * Executes a SQL statement and returns the result as an array. + * + * @param string $sql SQL + * @return array + */ + public function query($sql); + + /** + * Executes a query and returns only one row as an array. + * + * @param string $sql SQL + * @return array + */ + public function fetchRow($sql); + + /** + * Executes a query and returns an array of rows. + * + * @param string $sql SQL + * @return array + */ + public function fetchAll($sql); + + /** + * Create a new database. + * + * @param string $name Database Name + * @param array $options Options + * @return void + */ + public function createDatabase($name, $options); + + /** + * Drop a database. + * + * @param string $name Database Name + * @return void + */ + public function dropDatabase($name); + + /** + * Checks to see if a table exists. + * + * @param string $tableName Table Name + * @return boolean + */ + public function hasTable($tableName); + + /** + * Returns an instance of the \Table class. + * + * You can use this class to create and manipulate tables. + * + * @param string $tableName Table Name + * @param array $options Options + * @return Table + */ + public function table($tableName, $options); +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Migration/Util.php b/vendor/robmorgan/phinx/src/Phinx/Migration/Util.php new file mode 100644 index 00000000..a4555cfa --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Migration/Util.php @@ -0,0 +1,172 @@ +format(static::DATE_FORMAT); + } + + /** + * Gets an array of all the existing migration class names. + * + * @return string + */ + public static function getExistingMigrationClassNames($path) + { + $classNames = array(); + + if (!is_dir($path)) { + return $classNames; + } + + // filter the files to only get the ones that match our naming scheme + $phpFiles = glob($path . DIRECTORY_SEPARATOR . '*.php'); + + foreach ($phpFiles as $filePath) { + if (preg_match('/([0-9]+)_([_a-z0-9]*).php/', basename($filePath))) { + $classNames[] = static::mapFileNameToClassName(basename($filePath)); + } + } + + return $classNames; + } + + /** + * Get the version from the beginning of a file name. + * + * @param string $fileName File Name + * @return string + */ + public static function getVersionFromFileName($fileName) + { + $matches = array(); + preg_match('/^[0-9]+/', basename($fileName), $matches); + return $matches[0]; + } + + /** + * Turn migration names like 'CreateUserTable' into file names like + * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into + * '12345678901234_limit_resource_names_to_30_chars.php'. + * + * @param string $className Class Name + * @return string + */ + public static function mapClassNameToFileName($className) + { + $arr = preg_split('/(?=[A-Z])/', $className); + unset($arr[0]); // remove the first element ('') + $fileName = static::getCurrentTimestamp() . '_' . strtolower(implode($arr, '_')) . '.php'; + return $fileName; + } + + /** + * Turn file names like '12345678901234_create_user_table.php' into class names + * like 'CreateUserTable'. + * + * @param string $fileName File Name + * @return string + */ + public static function mapFileNameToClassName($fileName) + { + $matches = array(); + if (preg_match(static::FILE_NAME_PATTERN, $fileName, $matches)) { + $fileName = $matches[1]; + } + + return str_replace(' ', '', ucwords(str_replace('_', ' ', $fileName))); + } + + /** + * Check if a migration class name is unique regardless of the timestamp. + * + * This method takes a class name and a path to a migrations directory. + * + * Migration class names must be in CamelCase format. + * e.g: CreateUserTable or AddIndexToPostsTable. + * + * Single words are not allowed on their own. + * + * @param string $className Class Name + * @param string $path Path + * @return boolean + */ + public static function isUniqueMigrationClassName($className, $path) + { + $existingClassNames = static::getExistingMigrationClassNames($path); + return !(in_array($className, $existingClassNames)); + } + + /** + * Check if a migration class name is valid. + * + * Migration class names must be in CamelCase format. + * e.g: CreateUserTable or AddIndexToPostsTable. + * + * Single words are not allowed on their own. + * + * @param string $className Class Name + * @return boolean + */ + public static function isValidMigrationClassName($className) + { + return (bool) preg_match('/^([A-Z][a-z0-9]+)+$/', $className); + } + + /** + * Check if a migration file name is valid. + * + * @param string $fileName File Name + * @return boolean + */ + public static function isValidMigrationFileName($fileName) + { + $matches = array(); + return preg_match(static::FILE_NAME_PATTERN, $fileName, $matches); + } +} diff --git a/vendor/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php b/vendor/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php new file mode 100644 index 00000000..4cabbe11 --- /dev/null +++ b/vendor/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php @@ -0,0 +1,199 @@ + + */ +class TextWrapper +{ + /** + * @var PhinxApplication + */ + private $app; + + /** + * @var array + */ + private $options = array(); + + /** + * @var integer + */ + private $exit_code; + + /** + * @param PhinxApplication $app + * @param array $options + */ + public function __construct(PhinxApplication $app, array $options = array()) + { + $options += array( + 'environment' => 'development', + ); + + $this->app = $app; + $this->options = $options; + } + + /** + * Get the application instance. + * + * @return PhinxApplication + */ + public function getApp() + { + return $this->app; + } + + /** + * Returns the exit code from the last run command. + * @return integer + */ + public function getExitCode() + { + return $this->exit_code; + } + + + /** + * Returns the output from running the "status" command. + * @param string $env environment name (optional) + * @return string + */ + public function getStatus($env = null) + { + $command = array( + 'status', + '-e' => $env ?: $this->getOption('environment'), + '-c' => $this->getOption('configuration'), + '-p' => $this->getOption('parser') + ); + return $this->executeRun($command); + } + + /** + * Returns the output from running the "migrate" command. + * @param string $env environment name (optional) + * @param string $target target version (optional) + * @return string + */ + public function getMigrate($env = null, $target = null) + { + $command = array( + 'migrate', + '-e' => $env ?: $this->getOption('environment'), + '-c' => $this->getOption('configuration'), + '-p' => $this->getOption('parser') + ); + if ($target) { + $command += array('-t' => $target); + } + return $this->executeRun($command); + } + + /** + * Returns the output from running the "rollback" command. + * @param string $env environment name (optional) + * @param mixed $target target version, or 0 (zero) fully revert (optional) + * @return string + */ + public function getRollback($env = null, $target = null) + { + $command = array( + 'rollback', + '-e' => $env ?: $this->getOption('environment'), + '-c' => $this->getOption('configuration'), + '-p' => $this->getOption('parser') + ); + if (isset($target)) { + // Need to use isset() with rollback, because -t0 is a valid option! + // See http://docs.phinx.org/en/latest/commands.html#the-rollback-command + $command += array('-t' => $target); + } + return $this->executeRun($command); + } + + /** + * Get option from options array + * + * @param string $key + * @return string + */ + protected function getOption($key) + { + if (!isset($this->options[$key])) { + return null; + } + return $this->options[$key]; + } + + /** + * Set option in options array + * + * @param string $key + * @param string $value + * @return object + */ + public function setOption($key, $value) + { + $this->options[$key] = $value; + return $this; + } + + /** + * Execute a command, capturing output and storing the exit code. + * + * @param array $command + * @return string + */ + protected function executeRun(array $command) + { + // Output will be written to a temporary stream, so that it can be + // collected after running the command. + $stream = fopen('php://temp', 'w+'); + + // Execute the command, capturing the output in the temporary stream + // and storing the exit code for debugging purposes. + $this->exit_code = $this->app->doRun(new ArrayInput($command), new StreamOutput($stream)); + + // Get the output of the command and close the stream, which will + // destroy the temporary file. + $result = stream_get_contents($stream, -1, 0); + fclose($stream); + + return $result; + } +} diff --git a/vendor/robmorgan/phinx/src/composer_autoloader.php b/vendor/robmorgan/phinx/src/composer_autoloader.php new file mode 100644 index 00000000..93f8eee8 --- /dev/null +++ b/vendor/robmorgan/phinx/src/composer_autoloader.php @@ -0,0 +1,46 @@ + array( + 'paths' => array( + 'migrations' => '%%PHINX_CONFIG_PATH%%/testmigrations2', + 'schema' => '%%PHINX_CONFIG_PATH%%/testmigrations2/schema.sql', + ) + ), + 'paths' => array( + 'migrations' => $this->getMigrationPath(), + ), + 'templates' => array( + 'file' => '%%PHINX_CONFIG_PATH%%/tpl/testtemplate.txt', + 'class' => '%%PHINX_CONFIG_PATH%%/tpl/testtemplate.php' + ), + 'environments' => array( + 'default_migration_table' => 'phinxlog', + 'default_database' => 'testing', + 'testing' => array( + 'adapter' => 'sqllite', + 'wrapper' => 'testwrapper', + 'path' => '%%PHINX_CONFIG_PATH%%/testdb/test.db' + ), + 'production' => array( + 'adapter' => 'mysql' + ) + ) + ); + } + + /** + * Generate dummy migration path + * @return string + */ + protected function getMigrationPath() + { + if (null === $this->migrationPath) { + $this->migrationPath = uniqid('phinx', true); + } + return $this->migrationPath; + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigDefaultEnvironmentTest.php b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigDefaultEnvironmentTest.php new file mode 100644 index 00000000..e2d0c039 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigDefaultEnvironmentTest.php @@ -0,0 +1,75 @@ +getConfigArray(); + $config = new Config($configArray); + $this->assertEquals('testing', $config->getDefaultEnvironment()); + } + + public function testConfigReplacesTokensWithEnvVariables() + { + $_SERVER['PHINX_DBHOST'] = 'localhost'; + $_SERVER['PHINX_DBNAME'] = 'productionapp'; + $_SERVER['PHINX_DBUSER'] = 'root'; + $_SERVER['PHINX_DBPASS'] = 'ds6xhj1'; + $_SERVER['PHINX_DBPORT'] = '1234'; + $path = __DIR__ . '/_files'; + $config = Config::fromYaml($path . '/external_variables.yml'); + $env = $config->getEnvironment($config->getDefaultEnvironment()); + $this->assertEquals('localhost', $env['host']); + $this->assertEquals('productionapp', $env['name']); + $this->assertEquals('root', $env['user']); + $this->assertEquals('ds6xhj1', $env['pass']); + $this->assertEquals('1234', $env['port']); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage The environment configuration (read from $PHINX_ENVIRONMENT) for 'conf-test' is missing + */ + public function testGetDefaultEnvironmentOverridenByEnvButNotSet() + { + // set dummy + $dummyEnv = 'conf-test'; + putenv('PHINX_ENVIRONMENT=' . $dummyEnv); + + try { + $config = new Config(array()); + $config->getDefaultEnvironment(); + } + catch (\Exception $e) { + // reset back to normal + putenv('PHINX_ENVIRONMENT='); + + // throw again in order to finish test + throw $e; + } + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Could not find a default environment + */ + public function testGetDefaultEnvironmentOverridenFailedToFind() + { + // set empty env var + putenv('PHINX_ENVIRONMENT='); + + $config = new Config(array()); + $config->getDefaultEnvironment(); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigFileTest.php b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigFileTest.php new file mode 100644 index 00000000..4c48f489 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigFileTest.php @@ -0,0 +1,131 @@ +previousDir = getcwd(); + $this->baseDir = realpath(__DIR__ . '/_rootDirectories'); + } + + public function tearDown() + { + chdir($this->previousDir); + } + + /** + * Test workingContext + * + * @dataProvider workingProvider + * + * @param $input + * @param $dir + * @param $expectedFile + */ + public function testWorkingGetConfigFile($input, $dir, $expectedFile) + { + $foundPath = $this->runLocateFile($input, $dir); + $expectedPath = $this->baseDir . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . $expectedFile; + + $this->assertEquals($foundPath, $expectedPath); + } + + /** + * Test workingContext + * + * @dataProvider notWorkingProvider + * + * @param $input + * @param $dir + * @expectedException \InvalidArgumentException + */ + public function testNotWorkingGetConfigFile($input, $dir) + { + $this->runLocateFile($input, $dir); + } + + /** + * Do the locateFile Action + * + * @param $arg + * @param $dir + * @return string + */ + protected function runLocateFile($arg, $dir) + { + chdir($this->baseDir . '/' . $dir); + $definition = new InputDefinition(array(new InputOption('configuration'))); + $input = new ArgvInput(array(), $definition); + if ($arg) { + $input->setOption('configuration', $arg); + } + $command = new VoidCommand('void'); + return $command->locateConfigFile($input); + } + + /** + * Working cases + * + * + * @return array + */ + public function workingProvider() + { + return array( + //explicit yaml + array('phinx.yml', 'OnlyYaml', 'phinx.yml'), + //implicit with all choice + array(null, 'all', 'phinx.php'), + //implicit with no php choice + array(null, 'noPhp', 'phinx.json'), + //implicit with only yaml choice + array(null, 'OnlyYaml', 'phinx.yml'), + //explicit Php + array('phinx.php', 'all', 'phinx.php'), + //explicit json + array('phinx.json', 'all', 'phinx.json'), + ); + } + + /** + * Not working cases + * + * @return array + */ + public function notWorkingProvider() + { + return array( + //no valid file available + array(null, 'NoValidFile'), + //called file not available + array('phinx.yml', 'noYaml'), + array('phinx.json', 'OnlyYaml'), + array('phinx.php', 'OnlyYaml'), + ); + } +} + +/** + * Class VoidCommand : used to expose locateConfigFile To testing + * + * @package Test\Phinx\Config + */ +class VoidCommand extends AbstractCommand +{ + public function locateConfigFile(InputInterface $input) + { + return parent::locateConfigFile($input); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigJsonTest.php b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigJsonTest.php new file mode 100644 index 00000000..8834e8e8 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigJsonTest.php @@ -0,0 +1,33 @@ +assertEquals('dev', $config->getDefaultEnvironment()); + } + + /** + * @covers \Phinx\Config\Config::fromJson + * @expectedException \RuntimeException + */ + public function testFromJSONInvalidJson() + { + $path = __DIR__ . '/_files'; + Config::fromJson($path . '/invalid.json'); + } +} \ No newline at end of file diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigMigrationPathTest.php b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigMigrationPathTest.php new file mode 100644 index 00000000..81948252 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigMigrationPathTest.php @@ -0,0 +1,32 @@ +getMigrationPath(); + } + + /** + * Normal behavior + */ + public function testGetMigrationPath() + { + $config = new Config($this->getConfigArray()); + $this->assertEquals($this->getMigrationPath(), $config->getMigrationPath()); + } +} \ No newline at end of file diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigPhpTest.php b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigPhpTest.php new file mode 100644 index 00000000..fddbd7c6 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigPhpTest.php @@ -0,0 +1,48 @@ +assertEquals('dev', $config->getDefaultEnvironment()); + } + + /** + * @covers \Phinx\Config\Config::fromPhp + * @covers \Phinx\Config\Config::getDefaultEnvironment + * @expectedException \RuntimeException + */ + public function testFromPHPMethodWithoutArray() + { + $path = __DIR__ . '/_files'; + $config = Config::fromPhp($path . '/config_without_array.php'); + $this->assertEquals('dev', $config->getDefaultEnvironment()); + } + + /** + * @covers \Phinx\Config\Config::fromPhp + * @covers \Phinx\Config\Config::getDefaultEnvironment + * @expectedException \RuntimeException + */ + public function testFromJSONMethodWithoutJSON() + { + $path = __DIR__ . '/_files'; + $config = Config::fromPhp($path . '/empty.json'); + $this->assertEquals('dev', $config->getDefaultEnvironment()); + } +} \ No newline at end of file diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigReplaceTokensTest.php b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigReplaceTokensTest.php new file mode 100644 index 00000000..27dec549 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigReplaceTokensTest.php @@ -0,0 +1,101 @@ + 'some-value', + 'NON_PHINX_TEST_VAR_1' => 'some-other-value', + 'PHINX_TEST_VAR_2' => 213456, + ); + + /** + * Pass vars to $_SERVER + */ + public function setUp() + { + foreach (static::$server as $name => $value) { + $_SERVER[$name] = $value; + } + } + + /** + * Clean-up + */ + public function tearDown() + { + foreach (static::$server as $name => $value) { + unset($_SERVER[$name]); + } + } + + /** + * @covers \Phinx\Config\Config::replaceTokens + * @covers \Phinx\Config\Config::recurseArrayForTokens + */ + public function testReplaceTokens() + { + $config = new Config(array( + 'some-var-1' => 'includes/%%PHINX_TEST_VAR_1%%', + 'some-var-2' => 'includes/%%NON_PHINX_TEST_VAR_1%%', + 'some-var-3' => 'includes/%%PHINX_TEST_VAR_2%%', + 'some-var-4' => 123456, + )); + + $this->assertContains( + static::$server['PHINX_TEST_VAR_1'].'', // force convert to string + $config->offsetGet('some-var-1') + ); + $this->assertNotContains( + static::$server['NON_PHINX_TEST_VAR_1'].'', // force convert to string + $config->offsetGet('some-var-2') + ); + $this->assertContains( + static::$server['PHINX_TEST_VAR_2'].'', // force convert to string + $config->offsetGet('some-var-3') + ); + } + + /** + * @covers \Phinx\Config\Config::replaceTokens + * @covers \Phinx\Config\Config::recurseArrayForTokens + */ + public function testReplaceTokensRecursive() + { + $config = new Config(array( + 'folding' => array( + 'some-var-1' => 'includes/%%PHINX_TEST_VAR_1%%', + 'some-var-2' => 'includes/%%NON_PHINX_TEST_VAR_1%%', + 'some-var-3' => 'includes/%%PHINX_TEST_VAR_2%%', + 'some-var-4' => 123456, + ) + )); + + $folding = $config->offsetGet('folding'); + + $this->assertContains( + static::$server['PHINX_TEST_VAR_1'].'', // force convert to string + $folding['some-var-1'] + ); + $this->assertNotContains( + static::$server['NON_PHINX_TEST_VAR_1'].'', // force convert to string + $folding['some-var-2'] + ); + $this->assertContains( + static::$server['PHINX_TEST_VAR_2'].'', // force convert to string + $folding['some-var-3'] + ); + } +} \ No newline at end of file diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigTest.php b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigTest.php new file mode 100644 index 00000000..3bd3ce14 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigTest.php @@ -0,0 +1,194 @@ +assertAttributeEmpty('values', $config); + $this->assertAttributeEquals(null, 'configFilePath', $config); + $this->assertNull($config->getConfigFilePath()); + } + + /** + * @covers \Phinx\Config\Config::__construct + * @covers \Phinx\Config\Config::getConfigFilePath + */ + public function testConstructByArray() + { + $config = new Config($this->getConfigArray()); + $this->assertAttributeNotEmpty('values', $config); + $this->assertAttributeEquals(null, 'configFilePath', $config); + $this->assertNull($config->getConfigFilePath()); + } + + /** + * @covers \Phinx\Config\Config::getEnvironments + */ + public function testGetEnvironmentsMethod() + { + $config = new Config($this->getConfigArray()); + $this->assertEquals(2, count($config->getEnvironments())); + $this->assertArrayHasKey('testing', $config->getEnvironments()); + $this->assertArrayHasKey('production', $config->getEnvironments()); + } + + /** + * @covers \Phinx\Config\Config::hasEnvironment + */ + public function testHasEnvironmentDoesntHave() + { + $config = new Config(array()); + $this->assertFalse($config->hasEnvironment('dummy')); + } + + /** + * @covers \Phinx\Config\Config::hasEnvironment + */ + public function testHasEnvironmentHasOne() + { + $config = new Config($this->getConfigArray()); + $this->assertTrue($config->hasEnvironment('testing')); + } + + /** + * @covers \Phinx\Config\Config::getEnvironments + */ + public function testGetEnvironmentsNotSet() + { + $config = new Config(array()); + $this->assertNull($config->getEnvironments()); + } + + /** + * @covers \Phinx\Config\Config::getEnvironment + */ + public function testGetEnvironmentMethod() + { + $config = new Config($this->getConfigArray()); + $db = $config->getEnvironment('testing'); + $this->assertEquals('sqllite', $db['adapter']); + } + + /** + * @covers \Phinx\Config\Config::getEnvironment + */ + public function testHasEnvironmentMethod() + { + $configArray = $this->getConfigArray(); + $config = new Config($configArray); + $this->assertTrue($config->hasEnvironment('testing')); + $this->assertFalse($config->hasEnvironment('fakeenvironment')); + } + + /** + * @covers \Phinx\Config\Config::offsetGet + * @covers \Phinx\Config\Config::offsetSet + * @covers \Phinx\Config\Config::offsetExists + * @covers \Phinx\Config\Config::offsetUnset + */ + public function testArrayAccessMethods() + { + $config = new Config(array()); + $config['foo'] = 'bar'; + $this->assertEquals('bar', $config['foo']); + $this->assertTrue(isset($config['foo'])); + unset($config['foo']); + $this->assertFalse(isset($config['foo'])); + } + + /** + * @covers \Phinx\Config\Config::offsetGet + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testUndefinedArrayAccess() + { + $config = new Config(array()); + $config['foo']; + } + + /** + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameGetsDefaultBaseClass() + { + $config = new Config(array()); + $this->assertEquals('AbstractMigration', $config->getMigrationBaseClassName()); + } + + /** + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameGetsDefaultBaseClassWithNamespace() + { + $config = new Config(array()); + $this->assertEquals('Phinx\Migration\AbstractMigration', $config->getMigrationBaseClassName(false)); + } + + /** + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameGetsAlternativeBaseClass() + { + $config = new Config(array('migration_base_class' => 'Phinx\Migration\AlternativeAbstractMigration')); + $this->assertEquals('AlternativeAbstractMigration', $config->getMigrationBaseClassName()); + } + + /** + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameGetsAlternativeBaseClassWithNamespace() + { + $config = new Config(array('migration_base_class' => 'Phinx\Migration\AlternativeAbstractMigration')); + $this->assertEquals('Phinx\Migration\AlternativeAbstractMigration', $config->getMigrationBaseClassName(false)); + } + + /** + * @covers \Phinx\Config\Config::getTemplateFile(); + * @covers \Phinx\Config\Config::getTemplateClass(); + */ + public function testGetTemplateValuesFalseOnEmpty() + { + $config = new \Phinx\Config\Config(array()); + $this->assertFalse($config->getTemplateFile()); + $this->assertFalse($config->getTemplateClass()); + } + + public function testGetAliasNoAliasesEntry() + { + $config = new \Phinx\Config\Config(array()); + $this->assertNull($config->getAlias('Short')); + } + + public function testGetAliasEmptyAliasesEntry() + { + $config = new \Phinx\Config\Config(array('aliases'=> array())); + $this->assertNull($config->getAlias('Short')); + } + + public function testGetAliasInvalidAliasRequest() + { + $config = new \Phinx\Config\Config(array('aliases'=> array('Medium' => 'Some\Long\Classname'))); + $this->assertNull($config->getAlias('Short')); + } + + public function testGetAliasValidAliasRequest() + { + $config = new \Phinx\Config\Config(array('aliases'=> array('Short' => 'Some\Long\Classname'))); + $this->assertEquals('Some\Long\Classname', $config->getAlias('Short')); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigYamlTest.php b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigYamlTest.php new file mode 100644 index 00000000..15448186 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Config/ConfigYamlTest.php @@ -0,0 +1,61 @@ +getDefaultEnvironment(); + } + + /** + * @covers \Phinx\Config\Config::fromYaml + * @covers \Phinx\Config\Config::getDefaultEnvironment + * @expectedException \RuntimeException + * @expectedExceptionMessage The environment configuration for 'staging' is missing + */ + public function testGetDefaultEnvironmentWithAMissingEnvironmentEntry() + { + // test using a Yaml file with a 'default_database' key, but without a + // corresponding entry + $path = __DIR__ . '/_files'; + $config = Config::fromYaml($path . '/missing_environment_entry.yml'); + $config->getDefaultEnvironment(); + } + + /** + * @covers \Phinx\Config\Config::getDefaultEnvironment + */ + public function testGetDefaultEnvironmentMethod() + { + $path = __DIR__ . '/_files'; + + // test using a Yaml file without the 'default_database' key. + // (it should default to the first one). + $config = Config::fromYaml($path . '/no_default_database_key.yml'); + $this->assertEquals('production', $config->getDefaultEnvironment()); + + // test using environment variable PHINX_ENVIRONMENT + // (it should return the configuration specified in the environment) + putenv('PHINX_ENVIRONMENT=externally-specified-environment'); + $config = Config::fromYaml($path . '/no_default_database_key.yml'); + $this->assertEquals('externally-specified-environment', $config->getDefaultEnvironment()); + putenv('PHINX_ENVIRONMENT='); + } +} \ No newline at end of file diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_files/config_without_array.php b/vendor/robmorgan/phinx/tests/Phinx/Config/_files/config_without_array.php new file mode 100644 index 00000000..dd58d4ac --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Config/_files/config_without_array.php @@ -0,0 +1,2 @@ + array( + 'migrations' => 'application/migrations' + ), + 'environments' => array( + 'default_migration_table' => 'phinxlog', + 'default_database' => 'dev', + 'dev' => array( + 'adapter' => 'mysql', + 'wrapper' => 'testwrapper', + 'host' => 'localhost', + 'name' => 'testing', + 'user' => 'root', + 'pass' => '', + 'port' => 3306 + ) + ) +); diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/NoValidFile/NotAConfig.php b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/NoValidFile/NotAConfig.php new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/OnlyPhp/phinx.php b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/OnlyPhp/phinx.php new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/OnlyYaml/phinx.yml b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/OnlyYaml/phinx.yml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/all/phinx.json b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/all/phinx.json new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/all/phinx.php b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/all/phinx.php new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/all/phinx.yml b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/all/phinx.yml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/noPhp/phinx.json b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/noPhp/phinx.json new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/noPhp/phinx.yml b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/noPhp/phinx.yml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/noYaml/phinx.json b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/noYaml/phinx.json new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/noYaml/phinx.php b/vendor/robmorgan/phinx/tests/Phinx/Config/_rootDirectories/noYaml/phinx.php new file mode 100644 index 00000000..e69de29b diff --git a/vendor/robmorgan/phinx/tests/Phinx/Console/Command/CreateTest.php b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/CreateTest.php new file mode 100644 index 00000000..6923deb7 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/CreateTest.php @@ -0,0 +1,60 @@ +config = new Config(array( + 'paths' => array( + 'migrations' => sys_get_temp_dir(), + ), + 'environments' => array( + 'default_migration_table' => 'phinxlog', + 'default_database' => 'development', + 'development' => array( + 'adapter' => 'mysql', + 'host' => 'fakehost', + 'name' => 'development', + 'user' => '', + 'pass' => '', + 'port' => 3006, + ) + ) + )); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The migration class name "MyDuplicateMigration" already exists + */ + public function testExecuteWithDuplicateMigrationNames() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Create()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('create'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName(), 'name' => 'MyDuplicateMigration')); + sleep(1.01); // need at least a second due to file naming scheme + $commandTester->execute(array('command' => $command->getName(), 'name' => 'MyDuplicateMigration')); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Console/Command/InitTest.php b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/InitTest.php new file mode 100644 index 00000000..f0a9c475 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/InitTest.php @@ -0,0 +1,68 @@ +add(new Init()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('init'); + + $commandTester = new CommandTester($command); + $commandTester->execute(array( + 'command' => $command->getName(), + 'path' => sys_get_temp_dir() + )); + + $this->assertRegExp( + '/created (.*)phinx.yml(.*)/', + $commandTester->getDisplay() + ); + + $this->assertFileExists( + sys_get_temp_dir() . '/phinx.yml', + 'Phinx configuration not existent' + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /The file "(.*)" already exists/ + */ + public function testThrowsExceptionWhenConfigFilePresent() + { + touch(sys_get_temp_dir() . '/phinx.yml'); + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Init()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('init'); + + $commandTester = new CommandTester($command); + $commandTester->execute(array( + 'command' => $command->getName(), + 'path' => sys_get_temp_dir() + )); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Console/Command/MigrateTest.php b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/MigrateTest.php new file mode 100644 index 00000000..260dc497 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/MigrateTest.php @@ -0,0 +1,104 @@ +config = new Config(array( + 'paths' => array( + 'migrations' => __FILE__, + ), + 'environments' => array( + 'default_migration_table' => 'phinxlog', + 'default_database' => 'development', + 'development' => array( + 'adapter' => 'mysql', + 'host' => 'fakehost', + 'name' => 'development', + 'user' => '', + 'pass' => '', + 'port' => 3006, + ) + ) + )); + } + + public function testExecute() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Migrate()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('migrate'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + $managerStub->expects($this->once()) + ->method('migrate'); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName())); + + $this->assertRegExp('/no environment specified/', $commandTester->getDisplay()); + } + + public function testExecuteWithEnvironmentOption() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Migrate()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('migrate'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + $managerStub->expects($this->once()) + ->method('migrate'); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName(), '--environment' => 'fakeenv')); + $this->assertRegExp('/using environment fakeenv/', $commandTester->getDisplay()); + } + + public function testDatabaseNameSpecified() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Migrate()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('migrate'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + $managerStub->expects($this->once()) + ->method('migrate'); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName())); + $this->assertRegExp('/using database development/', $commandTester->getDisplay()); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Console/Command/RollbackTest.php b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/RollbackTest.php new file mode 100644 index 00000000..41a8a0ce --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/RollbackTest.php @@ -0,0 +1,104 @@ +config = new Config(array( + 'paths' => array( + 'migrations' => __FILE__, + ), + 'environments' => array( + 'default_migration_table' => 'phinxlog', + 'default_database' => 'development', + 'development' => array( + 'adapter' => 'mysql', + 'host' => 'fakehost', + 'name' => 'development', + 'user' => '', + 'pass' => '', + 'port' => 3006, + ) + ) + )); + } + + public function testExecute() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Rollback()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('rollback'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + $managerStub->expects($this->once()) + ->method('rollback'); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName())); + + $this->assertRegExp('/no environment specified/', $commandTester->getDisplay()); + } + + public function testExecuteWithEnvironmentOption() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Rollback()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('rollback'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + $managerStub->expects($this->once()) + ->method('rollback'); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName(), '--environment' => 'fakeenv')); + $this->assertRegExp('/using environment fakeenv/', $commandTester->getDisplay()); + } + + public function testDatabaseNameSpecified() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Rollback()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('rollback'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + $managerStub->expects($this->once()) + ->method('rollback'); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName())); + $this->assertRegExp('/using database development/', $commandTester->getDisplay()); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Console/Command/StatusTest.php b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/StatusTest.php new file mode 100644 index 00000000..385e8410 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Console/Command/StatusTest.php @@ -0,0 +1,104 @@ +config = new Config(array( + 'paths' => array( + 'migrations' => __FILE__, + ), + 'environments' => array( + 'default_migration_table' => 'phinxlog', + 'default_database' => 'development', + 'development' => array( + 'adapter' => 'pgsql', + 'host' => 'fakehost', + 'name' => 'development', + 'user' => '', + 'pass' => '', + 'port' => 5433, + ) + ) + )); + } + + public function testExecute() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Status()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('status'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + $managerStub->expects($this->once()) + ->method('printStatus'); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName())); + + $this->assertRegExp('/no environment specified/', $commandTester->getDisplay()); + } + + public function testExecuteWithEnvironmentOption() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Status()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('status'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + $managerStub->expects($this->once()) + ->method('printStatus'); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName(), '--environment' => 'fakeenv')); + $this->assertRegExp('/using environment fakeenv/', $commandTester->getDisplay()); + } + + public function testFormatSpecified() + { + $application = new \Phinx\Console\PhinxApplication('testing'); + $application->add(new Status()); + + // setup dependencies + $output = new StreamOutput(fopen('php://memory', 'a', false)); + + $command = $application->find('status'); + + // mock the manager class + $managerStub = $this->getMock('\Phinx\Migration\Manager', array(), array($this->config, $output)); + $managerStub->expects($this->once()) + ->method('printStatus'); + + $command->setConfig($this->config); + $command->setManager($managerStub); + + $commandTester = new CommandTester($command); + $commandTester->execute(array('command' => $command->getName(), '--format' => 'json')); + $this->assertRegExp('/using format json/', $commandTester->getDisplay()); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Console/PhinxApplicationTest.php b/vendor/robmorgan/phinx/tests/Phinx/Console/PhinxApplicationTest.php new file mode 100644 index 00000000..d4875a90 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Console/PhinxApplicationTest.php @@ -0,0 +1,33 @@ +setAutoExit(false); // Set autoExit to false when testing + $app->setCatchExceptions(false); + + $appTester = new ApplicationTester($app); + $appTester->run(array('command' => $command)); + $stream = $appTester->getOutput()->getStream(); + rewind($stream); + + $this->assertRegExp($result, stream_get_contents($stream)); + } + + public function provider() + { + return array( + array('help', '/help \[options\] \[--\] \[\]/') + ); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/AdapterFactoryTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/AdapterFactoryTest.php new file mode 100644 index 00000000..6e5596d2 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/AdapterFactoryTest.php @@ -0,0 +1,111 @@ +factory = AdapterFactory::instance(); + } + + public function tearDown() + { + unset($this->factory); + } + + public function testInstanceIsFactory() + { + $this->assertInstanceOf('Phinx\Db\Adapter\AdapterFactory', $this->factory); + } + + public function testRegisterAdapter() + { + // AdapterFactory::getClass is protected, work around it to avoid + // creating unnecessary instances and making the test more complex. + $method = new \ReflectionMethod(get_class($this->factory), 'getClass'); + $method->setAccessible(true); + + $adapter = $method->invoke($this->factory, 'mysql'); + $this->factory->registerAdapter('test', $adapter); + + $this->assertEquals($adapter, $method->invoke($this->factory, 'test')); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Adapter class "Test\Phinx\Db\Adapter\AdapterFactoryTest" must be implement Phinx\Db\Adapter\AdapterInterface + */ + public function testRegisterAdapterFailure() + { + $adapter = get_class($this); + $this->factory->registerAdapter('test', $adapter); + } + + public function testGetAdapter() + { + $adapter = $this->factory->getAdapter('mysql', array()); + + $this->assertInstanceOf('Phinx\Db\Adapter\MysqlAdapter', $adapter); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Adapter "bad" has not been registered + */ + public function testGetAdapterFailure() + { + $this->factory->getAdapter('bad', array()); + } + + public function testRegisterWrapper() + { + // WrapperFactory::getClass is protected, work around it to avoid + // creating unnecessary instances and making the test more complex. + $method = new \ReflectionMethod(get_class($this->factory), 'getWrapperClass'); + $method->setAccessible(true); + + $wrapper = $method->invoke($this->factory, 'proxy'); + $this->factory->registerWrapper('test', $wrapper); + + $this->assertEquals($wrapper, $method->invoke($this->factory, 'test')); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Wrapper class "Test\Phinx\Db\Adapter\AdapterFactoryTest" must be implement Phinx\Db\Adapter\WrapperInterface + */ + public function testRegisterWrapperFailure() + { + $wrapper = get_class($this); + $this->factory->registerWrapper('test', $wrapper); + } + + private function getAdapterMock() + { + return $this->getMock('Phinx\Db\Adapter\AdapterInterface', array()); + } + + public function testGetWrapper() + { + $wrapper = $this->factory->getWrapper('prefix', $this->getAdapterMock()); + + $this->assertInstanceOf('Phinx\Db\Adapter\TablePrefixAdapter', $wrapper); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Wrapper "nope" has not been registered + */ + public function testGetWrapperFailure() + { + $this->factory->getWrapper('nope', $this->getAdapterMock()); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/MysqlAdapterTest.php new file mode 100644 index 00000000..68cf895a --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -0,0 +1,952 @@ +markTestSkipped('Mysql tests disabled. See TESTS_PHINX_DB_ADAPTER_MYSQL_ENABLED constant.'); + } + + $options = array( + 'host' => TESTS_PHINX_DB_ADAPTER_MYSQL_HOST, + 'name' => TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE, + 'user' => TESTS_PHINX_DB_ADAPTER_MYSQL_USERNAME, + 'pass' => TESTS_PHINX_DB_ADAPTER_MYSQL_PASSWORD, + 'port' => TESTS_PHINX_DB_ADAPTER_MYSQL_PORT + ); + $this->adapter = new MysqlAdapter($options, new NullOutput()); + + // ensure the database is empty for each test + $this->adapter->dropDatabase($options['name']); + $this->adapter->createDatabase($options['name']); + + // leave the adapter in a disconnected state for each test + $this->adapter->disconnect(); + } + + public function tearDown() + { + unset($this->adapter); + } + + public function testConnection() + { + $this->assertTrue($this->adapter->getConnection() instanceof \PDO); + } + + public function testConnectionWithoutPort() + { + $options = $this->adapter->getOptions(); + unset($options['port']); + $this->adapter->setOptions($options); + $this->assertTrue($this->adapter->getConnection() instanceof \PDO); + } + + public function testConnectionWithInvalidCredentials() + { + $options = array( + 'host' => TESTS_PHINX_DB_ADAPTER_MYSQL_HOST, + 'name' => TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE, + 'port' => TESTS_PHINX_DB_ADAPTER_MYSQL_PORT, + 'user' => 'invaliduser', + 'pass' => 'invalidpass' + ); + + try { + $adapter = new MysqlAdapter($options, new NullOutput()); + $adapter->connect(); + $this->fail('Expected the adapter to throw an exception'); + } catch (\InvalidArgumentException $e) { + $this->assertInstanceOf( + 'InvalidArgumentException', + $e, + 'Expected exception of type InvalidArgumentException, got ' . get_class($e) + ); + $this->assertRegExp('/There was a problem connecting to the database/', $e->getMessage()); + } + } + + public function testConnectionWithSocketConnection() + { + if (!TESTS_PHINX_DB_ADAPTER_MYSQL_UNIX_SOCKET) { + $this->markTestSkipped('MySQL socket connection skipped. See TESTS_PHINX_DB_ADAPTER_MYSQL_UNIX_SOCKET constant.'); + } + + $options = array( + 'name' => TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE, + 'user' => TESTS_PHINX_DB_ADAPTER_MYSQL_USERNAME, + 'pass' => TESTS_PHINX_DB_ADAPTER_MYSQL_PASSWORD, + 'unix_socket' => TESTS_PHINX_DB_ADAPTER_MYSQL_UNIX_SOCKET, + ); + + $adapter = new MysqlAdapter($options, new NullOutput()); + $adapter->connect(); + + $this->assertInstanceOf('\PDO', $this->adapter->getConnection()); + } + + public function testCreatingTheSchemaTableOnConnect() + { + $this->adapter->connect(); + $this->assertTrue($this->adapter->hasTable($this->adapter->getSchemaTableName())); + $this->adapter->dropTable($this->adapter->getSchemaTableName()); + $this->assertFalse($this->adapter->hasTable($this->adapter->getSchemaTableName())); + $this->adapter->disconnect(); + $this->adapter->connect(); + $this->assertTrue($this->adapter->hasTable($this->adapter->getSchemaTableName())); + } + + public function testSchemaTableIsCreatedWithPrimaryKey() + { + $this->adapter->connect(); + $table = new \Phinx\Db\Table($this->adapter->getSchemaTableName(), array(), $this->adapter); + $this->assertTrue($this->adapter->hasIndex($this->adapter->getSchemaTableName(), array('version'))); + } + + public function testQuoteTableName() + { + $this->assertEquals('`test_table`', $this->adapter->quoteTableName('test_table')); + } + + public function testQuoteColumnName() + { + $this->assertEquals('`test_column`', $this->adapter->quoteColumnName('test_column')); + } + + public function testCreateTable() + { + $table = new \Phinx\Db\Table('ntable', array(), $this->adapter); + $table->addColumn('realname', 'string') + ->addColumn('email', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'email')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + } + + public function testCreateTableWithComment() + { + $table = new \Phinx\Db\Table('ntable', array('comment'=>$tableComment = 'Table comment'), $this->adapter); + $table->addColumn('realname', 'string') + ->save(); + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + + $rows = $this->adapter->fetchAll(sprintf( + "SELECT table_comment FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='%s' AND table_name='ntable'", + TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE)); + $comment = $rows[0]; + + $this->assertEquals($tableComment, $comment['table_comment'], 'Dont set table comment correctly'); + } + + public function testCreateTableWithForeignKeys() + { + + $tag_table = new \Phinx\Db\Table('ntable_tag', array(), $this->adapter); + $tag_table->addColumn('realname', 'string') + ->save(); + + $table = new \Phinx\Db\Table('ntable', array(), $this->adapter); + $table->addColumn('realname', 'string') + ->addColumn('tag_id', 'integer') + ->addForeignKey('tag_id', 'ntable_tag', 'id', array('delete'=> 'NO_ACTION', 'update'=> 'NO_ACTION')) + ->save(); + + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + + $rows = $this->adapter->fetchAll(sprintf( + "SELECT table_name, column_name, referenced_table_name, referenced_column_name + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE + WHERE table_schema='%s' AND REFERENCED_TABLE_NAME='ntable_tag'", + TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE)); + $foreignKey = $rows[0]; + + $this->assertEquals($foreignKey['table_name'], 'ntable'); + $this->assertEquals($foreignKey['column_name'], 'tag_id'); + $this->assertEquals($foreignKey['referenced_table_name'], 'ntable_tag'); + $this->assertEquals($foreignKey['referenced_column_name'], 'id'); + } + + public function testCreateTableCustomIdColumn() + { + $table = new \Phinx\Db\Table('ntable', array('id' => 'custom_id'), $this->adapter); + $table->addColumn('realname', 'string') + ->addColumn('email', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'custom_id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'email')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + } + + public function testCreateTableWithNoOptions() + { + $this->markTestIncomplete(); + //$this->adapter->createTable('ntable', ) + } + + public function testCreateTableWithNoPrimaryKey() + { + $options = array( + 'id' => false + ); + $table = new \Phinx\Db\Table('atable', $options, $this->adapter); + $table->addColumn('user_id', 'integer') + ->save(); + $this->assertFalse($this->adapter->hasColumn('atable', 'id')); + } + + public function testCreateTableWithMultiplePrimaryKeys() + { + $options = array( + 'id' => false, + 'primary_key' => array('user_id', 'tag_id') + ); + $table = new \Phinx\Db\Table('table1', $options, $this->adapter); + $table->addColumn('user_id', 'integer') + ->addColumn('tag_id', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('user_id', 'tag_id'))); + $this->assertTrue($this->adapter->hasIndex('table1', array('tag_id', 'USER_ID'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('tag_id', 'user_email'))); + } + + public function testCreateTableWithMultipleIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addColumn('name', 'string') + ->addIndex('email') + ->addIndex('name') + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertTrue($this->adapter->hasIndex('table1', array('name'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_name'))); + } + + public function testCreateTableWithUniqueIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('unique' => true)) + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + } + + public function testCreateTableWithNamedIndex() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('name' => 'myemailindex')) + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + } + + public function testCreateTableWithMultiplePKsAndUniqueIndexes() + { + $this->markTestIncomplete(); + } + + public function testCreateTableWithMyISAMEngine() + { + $table = new \Phinx\Db\Table('ntable', array('engine' => 'MyISAM'), $this->adapter); + $table->addColumn('realname', 'string') + ->save(); + $this->assertTrue($this->adapter->hasTable('ntable')); + $row = $this->adapter->fetchRow(sprintf("SHOW TABLE STATUS WHERE Name = '%s'", 'ntable')); + $this->assertEquals('MyISAM', $row['Engine']); + } + + public function testCreateTableWithLatin1Collate() + { + $table = new \Phinx\Db\Table('latin1_table', array('collation' => 'latin1_general_ci'), $this->adapter); + $table->addColumn('name', 'string') + ->save(); + $this->assertTrue($this->adapter->hasTable('latin1_table')); + $row = $this->adapter->fetchRow(sprintf("SHOW TABLE STATUS WHERE Name = '%s'", 'latin1_table')); + $this->assertEquals('latin1_general_ci', $row['Collation']); + } + + public function testRenameTable() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertTrue($this->adapter->hasTable('table1')); + $this->assertFalse($this->adapter->hasTable('table2')); + $this->adapter->renameTable('table1', 'table2'); + $this->assertFalse($this->adapter->hasTable('table1')); + $this->assertTrue($this->adapter->hasTable('table2')); + } + + public function testAddColumn() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('email')); + $table->addColumn('email', 'string') + ->save(); + $this->assertTrue($table->hasColumn('email')); + $table->addColumn('realname', 'string', array('after' => 'id')) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals('realname', $rows[1]['Field']); + } + + public function testAddColumnWithDefaultValue() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_zero', 'string', array('default' => 'test')) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals("test", $rows[1]['Default']); + } + + public function testAddColumnWithDefaultZero() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_zero', 'integer', array('default' => 0)) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertNotNull($rows[1]['Default']); + $this->assertEquals("0", $rows[1]['Default']); + } + + public function testAddColumnWithDefaultEmptyString() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_empty', 'string', array('default' => '')) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals('', $rows[1]['Default']); + } + + public function testAddColumnWithDefaultBoolean() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_true', 'boolean', array('default' => true)) + ->addColumn('default_false', 'boolean', array('default' => false)) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals('1', $rows[1]['Default']); + $this->assertEquals('0', $rows[2]['Default']); + } + + public function testAddIntegerColumnWithDefaultSigned() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('user_id')); + $table->addColumn('user_id', 'integer') + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals('int(11)', $rows[1]['Type']); + } + + public function testAddIntegerColumnWithSignedEqualsFalse() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('user_id')); + $table->addColumn('user_id', 'integer', array('signed' => false)) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals('int(11) unsigned', $rows[1]['Type']); + } + + public function testAddBooleanColumnWithSignedEqualsFalse() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('test_boolean')); + $table->addColumn('test_boolean', 'boolean', array('signed' => false)) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals('tinyint(1) unsigned', $rows[1]['Type']); + } + + public function testAddStringColumnWithSignedEqualsFalse() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('user_id')); + $table->addColumn('user_id', 'string', array('signed' => false)) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals('varchar(255)', $rows[1]['Type']); + } + + public function testRenameColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $this->assertFalse($this->adapter->hasColumn('t', 'column2')); + $this->adapter->renameColumn('t', 'column1', 'column2'); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + $this->assertTrue($this->adapter->hasColumn('t', 'column2')); + } + + public function testRenamingANonExistentColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + + try { + $this->adapter->renameColumn('t', 'column2', 'column1'); + $this->fail('Expected the adapter to throw an exception'); + } catch (\InvalidArgumentException $e) { + $this->assertInstanceOf( + 'InvalidArgumentException', + $e, + 'Expected exception of type InvalidArgumentException, got ' . get_class($e) + ); + $this->assertEquals('The specified column doesn\'t exist: column2', $e->getMessage()); + } + } + + public function testChangeColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setType('string'); + $table->changeColumn('column1', $newColumn1); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $newColumn2 = new \Phinx\Db\Table\Column(); + $newColumn2->setName('column2') + ->setType('string'); + $table->changeColumn('column1', $newColumn2); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + $this->assertTrue($this->adapter->hasColumn('t', 'column2')); + } + + public function testChangeColumnDefaultValue() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string', array('default' => 'test')) + ->save(); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setDefault('test1') + ->setType('string'); + $table->changeColumn('column1', $newColumn1); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM t'); + $this->assertNotNull($rows[1]['Default']); + $this->assertEquals("test1", $rows[1]['Default']); + } + + + public function testChangeColumnDefaultToZero() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'integer') + ->save(); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setDefault(0) + ->setType('integer'); + $table->changeColumn('column1', $newColumn1); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM t'); + $this->assertNotNull($rows[1]['Default']); + $this->assertEquals("0", $rows[1]['Default']); + } + + public function testChangeColumnDefaultToNull() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string', array('default' => 'test')) + ->save(); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setDefault(null) + ->setType('string'); + $table->changeColumn('column1', $newColumn1); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM t'); + $this->assertNull($rows[1]['Default']); + } + + public function testLongTextColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'text', array('limit' => MysqlAdapter::TEXT_LONG)) + ->save(); + $columns = $table->getColumns('t'); + $sqlType = $this->adapter->getSqlType($columns[1]->getType(), $columns[1]->getLimit()); + $this->assertEquals('longtext', $sqlType['name']); + } + + public function testMediumTextColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'text', array('limit' => MysqlAdapter::TEXT_MEDIUM)) + ->save(); + $columns = $table->getColumns('t'); + $sqlType = $this->adapter->getSqlType($columns[1]->getType(), $columns[1]->getLimit()); + $this->assertEquals('mediumtext', $sqlType['name']); + } + + public function testTinyTextColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'text', array('limit' => MysqlAdapter::TEXT_TINY)) + ->save(); + $columns = $table->getColumns('t'); + $sqlType = $this->adapter->getSqlType($columns[1]->getType(), $columns[1]->getLimit()); + $this->assertEquals('tinytext', $sqlType['name']); + } + + public function testBigIntegerColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'integer', array('limit' => MysqlAdapter::INT_BIG)) + ->save(); + $columns = $table->getColumns('t'); + $sqlType = $this->adapter->getSqlType($columns[1]->getType(), $columns[1]->getLimit()); + $this->assertEquals('bigint', $sqlType['name']); + } + + public function testMediumIntegerColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'integer', array('limit' => MysqlAdapter::INT_MEDIUM)) + ->save(); + $columns = $table->getColumns('t'); + $sqlType = $this->adapter->getSqlType($columns[1]->getType(), $columns[1]->getLimit()); + $this->assertEquals('mediumint', $sqlType['name']); + } + + public function testSmallIntegerColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'integer', array('limit' => MysqlAdapter::INT_SMALL)) + ->save(); + $columns = $table->getColumns('t'); + $sqlType = $this->adapter->getSqlType($columns[1]->getType(), $columns[1]->getLimit()); + $this->assertEquals('smallint', $sqlType['name']); + } + + public function testTinyIntegerColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'integer', array('limit' => MysqlAdapter::INT_TINY)) + ->save(); + $columns = $table->getColumns('t'); + $sqlType = $this->adapter->getSqlType($columns[1]->getType(), $columns[1]->getLimit()); + $this->assertEquals('tinyint', $sqlType['name']); + } + + public function testIntegerColumnLimit() + { + $limit = 8; + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'integer', array('limit' => $limit)) + ->save(); + $columns = $table->getColumns('t'); + $sqlType = $this->adapter->getSqlType($columns[1]->getType(), $columns[1]->getLimit()); + $this->assertEquals($limit, $sqlType['limit']); + } + + public function testDropColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $this->adapter->dropColumn('t', 'column1'); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + } + + public function testGetColumns() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'integer') + ->addColumn('column3', 'biginteger') + ->addColumn('column4', 'text') + ->addColumn('column5', 'float') + ->addColumn('column6', 'decimal') + ->addColumn('column7', 'datetime') + ->addColumn('column8', 'time') + ->addColumn('column9', 'timestamp') + ->addColumn('column10', 'date') + ->addColumn('column11', 'binary') + ->addColumn('column12', 'boolean') + ->addColumn('column13', 'string', array('limit' => 10)) + ->addColumn('column15', 'integer', array('limit' => 10)) + ->addColumn('column16', 'geometry') + ->addColumn('column17', 'point') + ->addColumn('column18', 'linestring') + ->addColumn('column19', 'polygon') + ->addColumn('column20', 'uuid') + ->addColumn('column21', 'set', array('values' => "one, two")) + ->addColumn('column22', 'enum', array('values' => array('three', 'four'))); + $pendingColumns = $table->getPendingColumns(); + $table->save(); + $columns = $this->adapter->getColumns('t'); + $this->assertCount(count($pendingColumns) + 1, $columns); + for ($i = 0; $i++; $i < count($pendingColumns)) { + $this->assertEquals($pendingColumns[$i], $columns[$i+1]); + } + } + + public function testDescribeTable() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string'); + $table->save(); + + $described = $this->adapter->describeTable('t'); + + $this->assertTrue(in_array($described['TABLE_TYPE'], array('VIEW','BASE TABLE'))); + $this->assertEquals($described['TABLE_NAME'], 't'); + $this->assertEquals($described['TABLE_SCHEMA'], TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE); + $this->assertEquals($described['TABLE_ROWS'], 0); + } + + public function testGetColumnsReservedTableName() + { + $table = new \Phinx\Db\Table('group', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'integer') + ->addColumn('column3', 'biginteger') + ->addColumn('column4', 'text') + ->addColumn('column5', 'float') + ->addColumn('column6', 'decimal') + ->addColumn('column7', 'datetime') + ->addColumn('column8', 'time') + ->addColumn('column9', 'timestamp') + ->addColumn('column10', 'date') + ->addColumn('column11', 'binary') + ->addColumn('column12', 'boolean') + ->addColumn('column13', 'string', array('limit' => 10)) + ->addColumn('column15', 'integer', array('limit' => 10)) + ->addColumn('column16', 'geometry') + ->addColumn('column17', 'point') + ->addColumn('column18', 'linestring') + ->addColumn('column19', 'polygon') + ->addColumn('column20', 'uuid') + ->addColumn('column21', 'set', array('values' => "one, two")) + ->addColumn('column22', 'enum', array('values' => array('three', 'four'))); + $pendingColumns = $table->getPendingColumns(); + $table->save(); + $columns = $this->adapter->getColumns('group'); + $this->assertCount(count($pendingColumns) + 1, $columns); + for ($i = 0; $i++; $i < count($pendingColumns)) { + $this->assertEquals($pendingColumns[$i], $columns[$i+1]); + } + } + + + public function testAddIndex() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->save(); + $this->assertFalse($table->hasIndex('email')); + $table->addIndex('email') + ->save(); + $this->assertTrue($table->hasIndex('email')); + } + + public function testDropIndex() + { + // single column index + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email') + ->save(); + $this->assertTrue($table->hasIndex('email')); + $this->adapter->dropIndex($table->getName(), 'email'); + $this->assertFalse($table->hasIndex('email')); + + // multiple column index + $table2 = new \Phinx\Db\Table('table2', array(), $this->adapter); + $table2->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname')) + ->save(); + $this->assertTrue($table2->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndex($table2->getName(), array('fname', 'lname')); + $this->assertFalse($table2->hasIndex(array('fname', 'lname'))); + + // index with name specified, but dropping it by column name + $table3 = new \Phinx\Db\Table('table3', array(), $this->adapter); + $table3->addColumn('email', 'string') + ->addIndex('email', array('name' => 'someindexname')) + ->save(); + $this->assertTrue($table3->hasIndex('email')); + $this->adapter->dropIndex($table3->getName(), 'email'); + $this->assertFalse($table3->hasIndex('email')); + + // multiple column index with name specified + $table4 = new \Phinx\Db\Table('table4', array(), $this->adapter); + $table4->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname'), array('name' => 'multiname')) + ->save(); + $this->assertTrue($table4->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndex($table4->getName(), array('fname', 'lname')); + $this->assertFalse($table4->hasIndex(array('fname', 'lname'))); + } + + public function testDropIndexByName() + { + // single column index + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('name' => 'myemailindex')) + ->save(); + $this->assertTrue($table->hasIndex('email')); + $this->adapter->dropIndexByName($table->getName(), 'myemailindex'); + $this->assertFalse($table->hasIndex('email')); + + // multiple column index + $table2 = new \Phinx\Db\Table('table2', array(), $this->adapter); + $table2->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname'), array('name' => 'twocolumnindex')) + ->save(); + $this->assertTrue($table2->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndexByName($table2->getName(), 'twocolumnindex'); + $this->assertFalse($table2->hasIndex(array('fname', 'lname'))); + } + + public function testAddForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testDropForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->adapter->dropForeignKey($table->getName(), array('ref_table_id')); + $this->assertFalse($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testDropForeignKeyAsString() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->adapter->dropForeignKey($table->getName(), 'ref_table_id'); + $this->assertFalse($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testHasForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + $this->assertFalse($this->adapter->hasForeignKey($table->getName(), array('ref_table_id2'))); + } + + public function testHasForeignKeyAsString() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), 'ref_table_id')); + $this->assertFalse($this->adapter->hasForeignKey($table->getName(), 'ref_table_id2')); + } + + public function testHasForeignKeyWithConstraint() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setConstraint("my_constraint") + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'), 'my_constraint')); + $this->assertFalse($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'), 'my_constraint2')); + } + + public function testHasDatabase() + { + $this->assertFalse($this->adapter->hasDatabase('fake_database_name')); + $this->assertTrue($this->adapter->hasDatabase(TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE)); + } + + public function testDropDatabase() + { + $this->assertFalse($this->adapter->hasDatabase('phinx_temp_database')); + $this->adapter->createDatabase('phinx_temp_database'); + $this->assertTrue($this->adapter->hasDatabase('phinx_temp_database')); + $this->adapter->dropDatabase('phinx_temp_database'); + } + + public function testAddColumnWithComment() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('column1', 'string', array('comment' => $comment = 'Comments from "column1"')) + ->save(); + + $rows = $this->adapter->fetchAll(sprintf( + "SELECT column_name, column_comment FROM information_schema.columns WHERE table_schema='%s' AND table_name='table1'", + TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE)); + $columnWithComment = $rows[1]; + + $this->assertEquals($comment, $columnWithComment['column_comment'], 'Dont set column comment correctly'); + } + + public function testAddGeoSpatialColumns() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('geo_geom')); + $table->addColumn('geo_geom', 'geometry') + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals('geometry', $rows[1]['Type']); + } + + public function testAddSetColumn() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('set_column')); + $table->addColumn('set_column', 'set', array('values' => array('one', 'two'))) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals("set('one','two')", $rows[1]['Type']); + } + + public function testAddEnumColumn() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('enum_column')); + $table->addColumn('enum_column', 'enum', array('values' => array('one', 'two'))) + ->save(); + $rows = $this->adapter->fetchAll('SHOW COLUMNS FROM table1'); + $this->assertEquals("enum('one','two')", $rows[1]['Type']); + } + + public function testHasColumn() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + + $this->assertFalse($table->hasColumn('column2')); + $this->assertTrue($table->hasColumn('column1')); + } + + public function testHasColumnReservedName() + { + $tableQuoted = new \Phinx\Db\Table('group', array(), $this->adapter); + $tableQuoted->addColumn('value', 'string') + ->save(); + + $this->assertFalse($tableQuoted->hasColumn('column2')); + $this->assertTrue($tableQuoted->hasColumn('value')); + } + + public function testInsertData() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'integer') + ->insert( + array("column1", "column2"), + array( + array('value1', 1), + array('value2', 2) + ) + ) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals(1, $rows[0]['column2']); + $this->assertEquals(2, $rows[1]['column2']); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/MysqlAdapterUnitTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/MysqlAdapterUnitTest.php new file mode 100644 index 00000000..3524de41 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/MysqlAdapterUnitTest.php @@ -0,0 +1,1415 @@ +connection = $connection; + } + + public function getConnection() + { + return $this->connection; + } + + // change visibility for testing + public function getDefaultValueDefinition($default) + { + return parent::getDefaultValueDefinition($default); + } + + public function getColumnSqlDefinition(Column $column) + { + return parent::getColumnSqlDefinition($column); + } + + public function getIndexSqlDefinition(Index $index) + { + return parent::getIndexSqlDefinition($index); + } + + public function getIndexes($tableName) + { + return parent::getIndexes($tableName); + } + + public function getForeignKeys($tableName) + { + return parent::getForeignKeys($tableName); + } +} + +class MysqlAdapterUnitTest extends \PHPUnit_Framework_TestCase +{ + + public function setUp() + { + if (!TESTS_PHINX_DB_ADAPTER_MYSQL_ENABLED) { + $this->markTestSkipped('Mysql tests disabled. See TESTS_PHINX_DB_ADAPTER_MYSQL_ENABLED constant.'); + } + + $this->adapter = new MysqlAdapterTester(array(), new NullOutput()); + + $this->conn = $this->getMockBuilder('PDOMock') + ->disableOriginalConstructor() + ->setMethods(array( 'query', 'exec', 'quote' )) + ->getMock(); + $this->result = $this->getMockBuilder('stdclass') + ->disableOriginalConstructor() + ->setMethods(array( 'fetch' )) + ->getMock(); + $this->adapter->setMockConnection($this->conn); + } + + // helper methods for easy mocking + private function assertExecuteSql($expected_sql) + { + $this->conn->expects($this->once()) + ->method('exec') + ->with($this->equalTo($expected_sql)); + } + + private function assertQuerySql($expectedSql, $returnValue = null) + { + $expect = $this->conn->expects($this->once()) + ->method('query') + ->with($this->equalTo($expectedSql)); + if (!is_null($returnValue)) { + $expect->will($this->returnValue($returnValue)); + } + } + + private function assertFetchRowSql($expectedSql, $returnValue) + { + $this->result->expects($this->once()) + ->method('fetch') + ->will($this->returnValue($returnValue)); + $this->assertQuerySql($expectedSql, $this->result); + } + + + public function testDisconnect() + { + $this->assertNotNull($this->adapter->getConnection()); + $this->adapter->disconnect(); + $this->assertNull($this->adapter->getConnection()); + } + + + // database related tests + + public function testHasDatabaseExists() + { + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue(array('SCHEMA_NAME' => 'database_name'))); + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'database_name'", $this->result); + + $this->assertTrue($this->adapter->hasDatabase('database_name')); + } + + public function testHasDatabaseNotExists() + { + $this->result->expects($this->once()) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'database_name2'", $this->result); + + $this->assertFalse($this->adapter->hasDatabase('database_name2')); + } + + public function testDropDatabase() + { + $this->assertExecuteSql("DROP DATABASE IF EXISTS `database_name`"); + $this->adapter->dropDatabase('database_name'); + } + + public function testCreateDatabase() + { + $this->assertExecuteSql("CREATE DATABASE `database_name` DEFAULT CHARACTER SET `utf8`"); + $this->adapter->createDatabase('database_name'); + } + + public function testCreateDatabaseWithCharset() + { + $this->assertExecuteSql("CREATE DATABASE `database_name` DEFAULT CHARACTER SET `latin1`"); + $this->adapter->createDatabase('database_name', array('charset' => 'latin1')); + } + + public function testCreateDatabaseWithCharsetAndCollation() + { + $this->assertExecuteSql("CREATE DATABASE `database_name` DEFAULT CHARACTER SET `latin1` COLLATE `latin1_swedish_ci`"); + $this->adapter->createDatabase('database_name', array('charset' => 'latin1', 'collation'=>'latin1_swedish_ci')); + } + + public function testHasTransactions() + { + $this->assertTrue($this->adapter->hasTransactions()); + } + + public function testBeginTransaction() + { + $this->assertExecuteSql("START TRANSACTION"); + $this->adapter->beginTransaction(); + } + + public function testCommitTransaction() + { + $this->assertExecuteSql("COMMIT"); + $this->adapter->commitTransaction(); + } + + public function testRollbackTransaction() + { + $this->assertExecuteSql("ROLLBACK"); + $this->adapter->rollbackTransaction(); + } + + // table related tests + + public function testDescribeTable() + { + $this->adapter->setOptions(array('name'=>'database_name')); + + $expectedSql = "SELECT * + FROM information_schema.tables + WHERE table_schema = 'database_name' + AND table_name = 'table_name'"; + + $returnValue = array('TABLE_TYPE' => 'BASE_TABLE', + 'TABLE_NAME' => 'table_name', + 'TABLE_SCHEMA' => 'database_name', + 'TABLE_ROWS' => 0); + $this->assertFetchRowSql($expectedSql, $returnValue); + + $described = $this->adapter->describeTable('table_name'); + $this->assertEquals($returnValue, $described); + } + + public function testRenameTable() + { + $this->assertExecuteSql("RENAME TABLE `old_table_name` TO `new_table_name`"); + $this->adapter->renameTable('old_table_name', 'new_table_name'); + } + + public function testDropTable() + { + $this->assertExecuteSql("DROP TABLE `table_name`"); + $this->adapter->dropTable("table_name"); + } + + public function testHasTableExists() + { + $this->adapter->setOptions(array('name'=>'database_name')); + $this->result->expects($this->once()) + ->method('fetch') + ->will($this->returnValue(array('somecontent'))); + $expectedSql = 'SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = \'database_name\' AND TABLE_NAME = \'table_name\''; + $this->assertQuerySql($expectedSql, $this->result); + $this->assertTrue($this->adapter->hasTable("table_name")); + } + + public function testHasTableNotExists() + { + $this->adapter->setOptions(array('name'=>'database_name')); + $this->result->expects($this->once()) + ->method('fetch') + ->will($this->returnValue(array())); + $expectedSql = 'SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = \'database_name\' AND TABLE_NAME = \'table_name\''; + $this->assertQuerySql($expectedSql, $this->result); + $this->assertFalse($this->adapter->hasTable("table_name")); + } + + public function testCreateTableBasic() + { + $column1 = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit', 'setLimit')) + ->getMock(); + + $column1->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column1->expects($this->any())->method('getType')->will($this->returnValue('string')); + $column1->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column1->expects($this->at(0))->method('getLimit')->will($this->returnValue('64')); + + $column2 = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit', 'setLimit')) + ->getMock(); + + $column2->expects($this->any())->method('getName')->will($this->returnValue('column_name2')); + $column2->expects($this->any())->method('getType')->will($this->returnValue('integer')); + $column2->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column2->expects($this->at(0))->method('getLimit')->will($this->returnValue('4')); + + $table = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName', 'getOptions', 'getPendingColumns', 'getIndexes', 'getForeignKeys')) + ->getMock(); + + $table->expects($this->any())->method('getPendingColumns')->will($this->returnValue(array($column1,$column2))); + $table->expects($this->any())->method('getName')->will($this->returnValue('table_name')); + $table->expects($this->any())->method('getOptions')->will($this->returnValue(array())); + $table->expects($this->any())->method('getIndexes')->will($this->returnValue(array())); + $table->expects($this->any())->method('getForeignKeys')->will($this->returnValue(array())); + + $expectedSql = 'CREATE TABLE `table_name` (`id` INT(11) NOT NULL AUTO_INCREMENT, `column_name` VARCHAR(255) NOT NULL, `column_name2` INT(11) NOT NULL, PRIMARY KEY (`id`)) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;'; + $this->assertExecuteSql($expectedSql); + $this->adapter->createTable($table); + } + + public function testCreateTablePrimaryKey() + { + $column1 = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit', 'setLimit')) + ->getMock(); + + $column1->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column1->expects($this->any())->method('getType')->will($this->returnValue('string')); + $column1->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column1->expects($this->at(0))->method('getLimit')->will($this->returnValue('64')); + + $column2 = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit', 'setLimit')) + ->getMock(); + + $column2->expects($this->any())->method('getName')->will($this->returnValue('column_name2')); + $column2->expects($this->any())->method('getType')->will($this->returnValue('integer')); + $column2->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column2->expects($this->at(0))->method('getLimit')->will($this->returnValue('4')); + + $table = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName', 'getOptions', 'getPendingColumns', 'getIndexes', 'getForeignKeys')) + ->getMock(); + + $tableOptions = array('id' => 'column_name2'); + $table->expects($this->any())->method('getPendingColumns')->will($this->returnValue(array($column1,$column2))); + $table->expects($this->any())->method('getName')->will($this->returnValue('table_name')); + $table->expects($this->any())->method('getOptions')->will($this->returnValue($tableOptions)); + $table->expects($this->any())->method('getIndexes')->will($this->returnValue(array())); + $table->expects($this->any())->method('getForeignKeys')->will($this->returnValue(array())); + + $expectedSql = 'CREATE TABLE `table_name` (`column_name2` INT(11) NOT NULL AUTO_INCREMENT, `column_name` VARCHAR(255) NOT NULL, `column_name2` INT(11) NOT NULL, PRIMARY KEY (`column_name2`)) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;'; + $this->assertExecuteSql($expectedSql); + $this->adapter->createTable($table); + } + + + public function testCreateTableAdvanced() + { + $refTable = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName', 'getOptions', 'getPendingColumns', 'getIndexes', 'getForeignKeys')) + ->getMock(); + $refTable->expects($this->any())->method('getName')->will($this->returnValue('other_table')); + + + $table = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName', 'getOptions', 'getPendingColumns', 'getIndexes', 'getForeignKeys')) + ->getMock(); + + $tableOptions = array('collation' => 'latin1_swedish_ci', + 'engine' => 'MyISAM', + 'id' => array('ref_id','other_table_id'), + 'primary_key' => array('ref_id','other_table_id'), + 'comment' => "Table Comment"); + $this->conn->expects($this->any())->method('quote')->with('Table Comment')->will($this->returnValue('`Table Comment`')); + + $column1 = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit', 'setLimit')) + ->getMock(); + + $column1->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column1->expects($this->any())->method('getType')->will($this->returnValue('string')); + $column1->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column1->expects($this->at(0))->method('getLimit')->will($this->returnValue('64')); + + $column2 = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit', 'setLimit')) + ->getMock(); + + $column2->expects($this->any())->method('getName')->will($this->returnValue('other_table_id')); + $column2->expects($this->any())->method('getType')->will($this->returnValue('integer')); + $column2->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column2->expects($this->at(0))->method('getLimit')->will($this->returnValue('4')); + + $column3 = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit', 'setLimit')) + ->getMock(); + + $column3->expects($this->any())->method('getName')->will($this->returnValue('ref_id')); + $column3->expects($this->any())->method('getType')->will($this->returnValue('integer')); + $column3->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column3->expects($this->at(0))->method('getLimit')->will($this->returnValue('11')); + + $index = $this->getMockBuilder('Phinx\Db\Table\Index') + ->disableOriginalConstructor() + ->setMethods(array( 'getColumns')) + ->getMock(); + + $index->expects($this->any())->method('getColumns')->will($this->returnValue(array('column_name'))); + + $foreignkey = $this->getMockBuilder('Phinx\Db\Table\ForeignKey') + ->disableOriginalConstructor() + ->setMethods(array( 'getColumns', + 'getConstraint', + 'getReferencedColumns', + 'getOnDelete', + 'getOnUpdate', + 'getReferencedTable')) + ->getMock(); + + $foreignkey->expects($this->any())->method('getColumns')->will($this->returnValue(array('other_table_id'))); + $foreignkey->expects($this->any())->method('getConstraint')->will($this->returnValue('fk1')); + $foreignkey->expects($this->any())->method('getReferencedColumns')->will($this->returnValue(array('id'))); + $foreignkey->expects($this->any())->method('getReferencedTable')->will($this->returnValue($refTable)); + $foreignkey->expects($this->any())->method('onDelete')->will($this->returnValue(null)); + $foreignkey->expects($this->any())->method('onUpdate')->will($this->returnValue(null)); + + $table->expects($this->any())->method('getPendingColumns')->will($this->returnValue(array($column1, $column2, $column3))); + $table->expects($this->any())->method('getName')->will($this->returnValue('table_name')); + $table->expects($this->any())->method('getOptions')->will($this->returnValue($tableOptions)); + $table->expects($this->any())->method('getIndexes')->will($this->returnValue(array($index))); + $table->expects($this->any())->method('getForeignKeys')->will($this->returnValue(array($foreignkey))); + + $expectedSql ='CREATE TABLE `table_name` (`column_name` VARCHAR(255) NOT NULL, `other_table_id` INT(11) NOT NULL, `ref_id` INT(11) NOT NULL, PRIMARY KEY (`ref_id`,`other_table_id`), KEY (`column_name`), CONSTRAINT `fk1` FOREIGN KEY (`other_table_id`) REFERENCES `other_table` (`id`)) ENGINE = MyISAM CHARACTER SET latin1 COLLATE latin1_swedish_ci COMMENT=`Table Comment`;'; + $this->assertExecuteSql($expectedSql); + $this->adapter->createTable($table); + } + + /** + * @todo not real unit, Column class is not mocked, improve dependency of Column removing new. Could be done calling protected newColumn() and override newColumn() in tester class + * + */ + public function testGetColumns() + { + $column1 = array('Field' => 'column1', + 'Type' => 'int(15)', + 'Null' => 'NO', + 'Default' => '', + 'Key' => 'PRI', + 'Extra' => 'auto_increment'); + + $column2 = array('Field' => 'column2', + 'Type' => 'varchar(32)', + 'Null' => '', + 'Default' => 'NULL', + 'Key' => '', + 'Extra' => ''); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($column1)); + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue($column2)); + $this->result->expects($this->at(2)) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql("SHOW COLUMNS FROM `table_name`", $this->result); + + $columns = $this->adapter->getColumns("table_name"); + + $this->assertTrue(is_array($columns)); + $this->assertEquals(2, count($columns)); + + $this->assertEquals('column1', $columns[0]->getName()); + $this->assertInstanceOf('Phinx\Db\Table\Column', $columns[0]); + $this->assertEquals('15', $columns[0]->getLimit()); + $this->assertFalse($columns[0]->getNull()); + $this->assertEquals('', $columns[0]->getDefault()); + $this->assertTrue($columns[0]->getIdentity()); + + $this->assertEquals('column2', $columns[1]->getName()); + $this->assertInstanceOf('Phinx\Db\Table\Column', $columns[1]); + $this->assertEquals('32', $columns[1]->getLimit()); + $this->assertTrue($columns[1]->getNull()); + $this->assertEquals('NULL', $columns[1]->getDefault()); + $this->assertFalse($columns[1]->getIdentity()); + } + + + // column related tests + + public function testHasColumnExists() + { + $column1 = array('Field' => 'column1', + 'Type' => 'int(15)', + 'Null' => 'NO', + 'Default' => '', + 'Extra' => 'auto_increment'); + + $column2 = array('Field' => 'column2', + 'Type' => 'varchar(32)', + 'Null' => '', + 'Default' => 'NULL', + 'Extra' => ''); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($column1)); + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue($column2)); + $this->result->expects($this->at(2)) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql('SHOW COLUMNS FROM `table_name`', $this->result); + + $this->assertTrue($this->adapter->hasColumn('table_name', 'column1')); + } + + public function testGetColumnSqlDefinitionInteger() + { + $column = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit')) + ->getMock(); + + $column->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column->expects($this->any())->method('getLimit')->will($this->returnValue('11')); + $column->expects($this->any())->method('getType')->will($this->returnValue('integer')); + + $this->assertEquals("INT(11) NOT NULL", + $this->adapter->getColumnSqlDefinition($column)); + } + + public function testGetColumnSqlDefinitionFloat() + { + $column = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit', 'setLimit', 'getScale', 'getPrecision')) + ->getMock(); + + $column->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column->expects($this->any())->method('getType')->will($this->returnValue('float')); + $column->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column->expects($this->any())->method('getPrecision')->will($this->returnValue('8')); + $column->expects($this->any())->method('getScale')->will($this->returnValue('3')); + + $this->assertEquals("FLOAT(8,3) NOT NULL", + $this->adapter->getColumnSqlDefinition($column)); + } + + /** + * @todo must enter in code that removes limit + */ + public function testGetColumnSqlDefinitionTextWithLimit() + { + $column = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit', 'setLimit')) + ->getMock(); + + $column->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column->expects($this->any())->method('getType')->will($this->returnValue('text')); + $column->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column->expects($this->at(0))->method('getLimit')->will($this->returnValue('2048')); + $column->expects($this->at(1))->method('getLimit')->will($this->returnValue(null)); + + $this->assertEquals("TEXT NOT NULL", + $this->adapter->getColumnSqlDefinition($column)); + } + + public function testGetColumnSqlDefinitionComplete() + { + $this->conn->expects($this->once()) + ->method('quote') + ->with($this->equalTo('Custom Comment')) + ->will($this->returnValue("`Custom Comment`")); + + $column = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', + 'getAfter', + 'getType', + 'getLimit', + 'getScale', + 'getPrecision', + 'getComment', + 'isIdentity', + 'getUpdate')) + ->getMock(); + + $column->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column->expects($this->any())->method('isIdentity')->will($this->returnValue(true)); + $column->expects($this->any())->method('getComment')->will($this->returnValue('Custom Comment')); + $column->expects($this->any())->method('getUpdate')->will($this->returnValue('CASCADE')); + $column->expects($this->any())->method('getLimit')->will($this->returnValue('')); + $column->expects($this->any())->method('getScale')->will($this->returnValue('2')); + $column->expects($this->any())->method('getPrecision')->will($this->returnValue('8')); + $column->expects($this->any())->method('getType')->will($this->returnValue('float')); + + $this->assertEquals("FLOAT(8,2) NOT NULL AUTO_INCREMENT COMMENT `Custom Comment` ON UPDATE CASCADE", + $this->adapter->getColumnSqlDefinition($column)); + } + + public function testHasColumnExistsCaseInsensitive() + { + $column1 = array('Field' => 'column1', + 'Type' => 'int(15)', + 'Null' => 'NO', + 'Default' => '', + 'Extra' => 'auto_increment'); + + $column2 = array('Field' => 'column2', + 'Type' => 'varchar(32)', + 'Null' => '', + 'Default' => 'NULL', + 'Extra' => ''); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($column1)); + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue($column2)); + $this->result->expects($this->at(2)) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql('SHOW COLUMNS FROM `table_name`', $this->result); + + $this->assertTrue($this->adapter->hasColumn('table_name', 'CoLumN1')); + } + + public function testHasColumnNotExists() + { + $column1 = array('Field' => 'column1', + 'Type' => 'int(15)', + 'Null' => 'NO', + 'Default' => '', + 'Extra' => 'auto_increment'); + + $column2 = array('Field' => 'column2', + 'Type' => 'varchar(32)', + 'Null' => '', + 'Default' => 'NULL', + 'Extra' => ''); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($column1)); + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue($column2)); + $this->result->expects($this->at(2)) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql('SHOW COLUMNS FROM `table_name`', $this->result); + + $this->assertFalse($this->adapter->hasColumn('table_name', 'column3')); + } + + public function testDropColumn() + { + $this->assertExecuteSql("ALTER TABLE `table_name` DROP COLUMN `column1`"); + $this->adapter->dropColumn('table_name', 'column1'); + } + + + public function testAddColumn() + { + $table = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName')) + ->getMock(); + $table->expects($this->any())->method('getName')->will($this->returnValue('table_name')); + + + $column = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit')) + ->getMock(); + + $column->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column->expects($this->any())->method('getAfter')->will($this->returnValue(null)); + $column->expects($this->any())->method('getLimit')->will($this->returnValue('11')); + $column->expects($this->any())->method('getType')->will($this->returnValue('integer')); + + $this->assertExecuteSql('ALTER TABLE `table_name` ADD `column_name` INT(11) NOT NULL'); + $this->adapter->addColumn($table, $column); + } + + public function testAddColumnWithAfter() + { + $table = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName')) + ->getMock(); + $table->expects($this->any())->method('getName')->will($this->returnValue('table_name')); + + + $column = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit')) + ->getMock(); + + $column->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column->expects($this->any())->method('getAfter')->will($this->returnValue('column_name2')); + $column->expects($this->any())->method('getLimit')->will($this->returnValue('11')); + $column->expects($this->any())->method('getType')->will($this->returnValue('integer')); + + $this->assertExecuteSql('ALTER TABLE `table_name` ADD `column_name` INT(11) NOT NULL AFTER `column_name2`'); + $this->adapter->addColumn($table, $column); + } + + public function testChangeColumn() + { + $column = $this->getMockBuilder('Phinx\Db\Table\Column') + ->disableOriginalConstructor() + ->setMethods(array( 'getName', 'getAfter', 'getType', 'getLimit')) + ->getMock(); + + $column->expects($this->any())->method('getName')->will($this->returnValue('column_name')); + $column->expects($this->any())->method('getLimit')->will($this->returnValue('11')); + $column->expects($this->any())->method('getType')->will($this->returnValue('integer')); + + $this->assertExecuteSql('ALTER TABLE `table_name` CHANGE `column1` `column_name` INT(11) NOT NULL'); + $this->adapter->changeColumn('table_name', 'column1', $column); + } + + public function testRenameColumnExists() + { + $column1 = array('Field' => 'column_old', + 'Type' => 'int(15)', + 'Null' => 'NO', + 'Default' => '', + 'Extra' => 'auto_increment'); + + $column2 = array('Field' => 'column2', + 'Type' => 'varchar(32)', + 'Null' => '', + 'Default' => 'NULL', + 'Extra' => ''); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($column1)); + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue($column2)); + $this->result->expects($this->at(2)) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql("DESCRIBE `table_name`", $this->result); + + + $this->assertExecuteSql('ALTER TABLE `table_name` CHANGE COLUMN `column_old` `column_new` int(15) NOT NULL AUTO_INCREMENT'); + $this->adapter->renameColumn('table_name', 'column_old', 'column_new'); + } + + public function testRenameColumnNotExists() + { + $column1 = array('Field' => 'column1', + 'Type' => 'int(15)', + 'Null' => 'NO', + 'Default' => '', + 'Extra' => 'auto_increment'); + + $column2 = array('Field' => 'column2', + 'Type' => 'varchar(32)', + 'Null' => '', + 'Default' => 'NULL', + 'Extra' => ''); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($column1)); + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue($column2)); + $this->result->expects($this->at(2)) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql("DESCRIBE `table_name`", $this->result); + + + $this->setExpectedException('\InvalidArgumentException', 'The specified column doesn\'t exist: column_old'); + $this->adapter->renameColumn('table_name', 'column_old', 'column_new'); + } + + public function testGetDefaultValueDefinitionEmpty() + { + $this->assertEquals('', $this->adapter->getDefaultValueDefinition(null)); + $this->assertEquals('', $this->adapter->getDefaultValueDefinition('NULL')); + } + + public function testGetDefaultValueDefinitionBoolean() + { + $this->assertEquals(' DEFAULT 1', + $this->adapter->getDefaultValueDefinition(true)); + } + + public function testGetDefaultValueDefinitionInteger() + { + $this->assertEquals(' DEFAULT 5', + $this->adapter->getDefaultValueDefinition(5)); + } + + public function testGetDefaultValueDefinitionCurrentTimestamp() + { + $this->assertEquals(' DEFAULT CURRENT_TIMESTAMP', + $this->adapter->getDefaultValueDefinition('CURRENT_TIMESTAMP')); + } + + public function testGetDefaultValueDefinitionString() + { + $this->conn->expects($this->once()) + ->method('quote') + ->with($this->equalTo('str')) + ->will($this->returnValue("`str`")); + $this->assertEquals(' DEFAULT `str`', $this->adapter->getDefaultValueDefinition('str')); + } + + + public function testGetSqlTypeExists() + { + $this->assertEquals(array('name' => 'varchar', 'limit' => 255), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_STRING)); + $this->assertEquals(array('name' => 'char', 'limit' => 255), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_CHAR, 255)); + + //text combinations + $this->assertEquals(array('name' => 'text'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TEXT)); + $this->assertEquals(array('name' => 'tinytext'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TEXT, MysqlAdapter::TEXT_TINY)); + $this->assertEquals(array('name' => 'tinytext'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TEXT, MysqlAdapter::TEXT_TINY+1)); + $this->assertEquals(array('name' => 'text'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TEXT, MysqlAdapter::TEXT_REGULAR)); + $this->assertEquals(array('name' => 'text'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TEXT, MysqlAdapter::TEXT_REGULAR+1)); + $this->assertEquals(array('name' => 'mediumtext'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TEXT, MysqlAdapter::TEXT_MEDIUM)); + $this->assertEquals(array('name' => 'mediumtext'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TEXT, MysqlAdapter::TEXT_MEDIUM+1)); + $this->assertEquals(array('name' => 'longtext'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TEXT, MysqlAdapter::TEXT_LONG)); + $this->assertEquals(array('name' => 'longtext'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TEXT, MysqlAdapter::TEXT_LONG+1)); + + //blob combinations + $this->assertEquals(array('name' => 'blob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY)); + $this->assertEquals(array('name' => 'tinyblob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY, MysqlAdapter::BLOB_TINY)); + $this->assertEquals(array('name' => 'tinyblob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY, MysqlAdapter::BLOB_TINY+1)); + $this->assertEquals(array('name' => 'blob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY, MysqlAdapter::BLOB_REGULAR)); + $this->assertEquals(array('name' => 'blob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY, MysqlAdapter::BLOB_REGULAR+1)); + $this->assertEquals(array('name' => 'mediumblob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY, MysqlAdapter::BLOB_MEDIUM)); + $this->assertEquals(array('name' => 'mediumblob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY, MysqlAdapter::BLOB_MEDIUM+1)); + $this->assertEquals(array('name' => 'longblob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY, MysqlAdapter::BLOB_LONG)); + $this->assertEquals(array('name' => 'longblob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY, MysqlAdapter::BLOB_LONG+1)); + + //int combinations + $this->assertEquals(array('name' => 'int', 'limit' => 11), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER)); + $this->assertEquals(array('name' => 'bigint', 'limit' => 20), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BIG_INTEGER)); + $this->assertEquals(array('name' => 'tinyint'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_TINY)); + $this->assertEquals(array('name' => 'tinyint'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_TINY+1)); + $this->assertEquals(array('name' => 'smallint'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_SMALL)); + $this->assertEquals(array('name' => 'smallint'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_SMALL+1)); + $this->assertEquals(array('name' => 'mediumint'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_MEDIUM)); + $this->assertEquals(array('name' => 'mediumint'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_MEDIUM+1)); + $this->assertEquals(array('name' => 'int', 'limit' => 11), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_REGULAR)); + $this->assertEquals(array('name' => 'int', 'limit' => 11), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_REGULAR+1)); + $this->assertEquals(array('name' => 'bigint', 'limit'=> 20), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_BIG)); + $this->assertEquals(array('name' => 'bigint', 'limit'=> 20), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_INTEGER, MysqlAdapter::INT_BIG+1)); + + $this->assertEquals(array('name' => 'float'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_FLOAT)); + $this->assertEquals(array('name' => 'decimal'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_DECIMAL)); + $this->assertEquals(array('name' => 'datetime'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_DATETIME)); + $this->assertEquals(array('name' => 'timestamp'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TIMESTAMP)); + $this->assertEquals(array('name' => 'date'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_DATE)); + $this->assertEquals(array('name' => 'time'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_TIME)); + $this->assertEquals(array('name' => 'blob'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BINARY)); + $this->assertEquals(array('name' => 'tinyint', 'limit' => 1), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_BOOLEAN)); + $this->assertEquals(array('name' => 'geometry'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_GEOMETRY)); + $this->assertEquals(array('name' => 'linestring'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_LINESTRING)); + $this->assertEquals(array('name' => 'point'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_POINT)); + $this->assertEquals(array('name' => 'polygon'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_POLYGON)); + $this->assertEquals(array('name' => 'enum'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_ENUM)); + $this->assertEquals(array('name' => 'set'), + $this->adapter->getSqlType(MysqlAdapter::PHINX_TYPE_SET)); + } + + public function testGetSqlTypeNotExists() + { + $this->setExpectedException('\RuntimeException', 'The type: "fake" is not supported.'); + $this->adapter->getSqlType('fake'); + } + + public function testPhinxTypeExistsWithoutLimit() + { + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_STRING, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('varchar')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_CHAR, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('char')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_INTEGER, 'limit' => MysqlAdapter::INT_TINY, 'precision' => null), + $this->adapter->getPhinxType('tinyint')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_INTEGER, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('int')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_INTEGER, 'limit' => MysqlAdapter::INT_SMALL, 'precision' => null), + $this->adapter->getPhinxType('smallint')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_INTEGER, 'limit' => MysqlAdapter::INT_MEDIUM, 'precision' => null), + $this->adapter->getPhinxType('mediumint')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BIG_INTEGER, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('bigint')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BINARY, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('blob')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_FLOAT, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('float')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_DECIMAL, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('decimal')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_DATETIME, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('datetime')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_TIMESTAMP, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('timestamp')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_DATE, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('date')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_TIME, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('time')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_TEXT, 'limit' => MysqlAdapter::TEXT_TINY, 'precision' => null), + $this->adapter->getPhinxType('tinytext')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_TEXT, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('text')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_TEXT, 'limit' => MysqlAdapter::TEXT_MEDIUM, 'precision' => null), + $this->adapter->getPhinxType('mediumtext')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_TEXT, 'limit' => MysqlAdapter::TEXT_LONG, 'precision' => null), + $this->adapter->getPhinxType('longtext')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BINARY, 'limit' => MysqlAdapter::BLOB_TINY, 'precision' => null), + $this->adapter->getPhinxType('tinyblob')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BINARY, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('blob')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BINARY, 'limit' => MysqlAdapter::BLOB_MEDIUM, 'precision' => null), + $this->adapter->getPhinxType('mediumblob')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BINARY, 'limit' => MysqlAdapter::BLOB_LONG, 'precision' => null), + $this->adapter->getPhinxType('longblob')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_POINT, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('point')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_GEOMETRY, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('geometry')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_LINESTRING, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('linestring')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_POLYGON, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('polygon')); + } + + public function testPhinxTypeExistsWithLimit() + { + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_STRING, 'limit' => 32, 'precision' => null), + $this->adapter->getPhinxType('varchar(32)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_CHAR, 'limit' => 32, 'precision' => null), + $this->adapter->getPhinxType('char(32)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_INTEGER, 'limit' => 12, 'precision' => null), + $this->adapter->getPhinxType('int(12)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BIG_INTEGER, 'limit' => 21, 'precision' => null), + $this->adapter->getPhinxType('bigint(21)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BINARY, 'limit' => 1024, 'precision' => null), + $this->adapter->getPhinxType('blob(1024)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_FLOAT, 'limit' => 8, 'precision' => 2), + $this->adapter->getPhinxType('float(8,2)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_DECIMAL, 'limit' => 8, 'precision' => 2), + $this->adapter->getPhinxType('decimal(8,2)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_TEXT, 'limit' => 1024, 'precision' => null), + $this->adapter->getPhinxType('text(1024)')); + } + + + public function testPhinxTypeExistsWithLimitNull() + { + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_STRING, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('varchar(255)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_CHAR, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('char(255)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_INTEGER, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('int(11)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BIG_INTEGER, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('bigint(20)')); + $this->assertEquals(array('name' => MysqlAdapter::PHINX_TYPE_BOOLEAN, 'limit' => null, 'precision' => null), + $this->adapter->getPhinxType('tinyint(1)')); + } + + public function testPhinxTypeNotValidType() + { + $this->setExpectedException('\RuntimeException', 'The type: "fake" is not supported.'); + $this->adapter->getPhinxType('fake'); + } + + public function testPhinxTypeNotValidTypeRegex() + { + $this->setExpectedException('\RuntimeException', 'Column type ?int? is not supported'); + $this->adapter->getPhinxType('?int?'); + } + + //index related tests + + public function testGetIndexSqlDefinitionRegular() + { + $index = $this->getMockBuilder('Phinx\Db\Table\Index') + ->disableOriginalConstructor() + ->setMethods(array( 'getColumns', 'getName', 'getType')) + ->getMock(); + + $index->expects($this->any())->method('getColumns')->will($this->returnValue(array('column_name'))); + $index->expects($this->any())->method('getName')->will($this->returnValue('index_name')); + $index->expects($this->any())->method('getType')->will($this->returnValue(\Phinx\Db\Table\Index::INDEX)); + $this->assertEquals(' KEY `index_name` (`column_name`)', $this->adapter->getIndexSqlDefinition($index)); + } + + public function testGetIndexSqlDefinitionUnique() + { + $index = $this->getMockBuilder('Phinx\Db\Table\Index') + ->disableOriginalConstructor() + ->setMethods(array( 'getColumns', 'getName', 'getType')) + ->getMock(); + + $index->expects($this->any())->method('getColumns')->will($this->returnValue(array('column_name'))); + $index->expects($this->any())->method('getName')->will($this->returnValue('index_name')); + $index->expects($this->any())->method('getType')->will($this->returnValue(\Phinx\Db\Table\Index::UNIQUE)); + $this->assertEquals(' UNIQUE KEY `index_name` (`column_name`)', $this->adapter->getIndexSqlDefinition($index)); + } + + public function testGetIndexesEmpty() + { + $this->result->expects($this->once()) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql("SHOW INDEXES FROM `table_name`", $this->result); + + $indexes = $this->adapter->getIndexes("table_name"); + + $this->assertEquals(array(), $indexes); + } + + private function prepareCaseIndexes() + { + $index1 = array('Table' => 'table_name', + 'Non_unique' => '0', + 'Key_name' => 'PRIMARY', + 'Seq_in_index' => '1', + 'Column_name' => 'id', + 'Collation' => 'A', + 'Cardinality' => '0', + 'Sub_part' => 'NULL', + 'Packed' => 'NULL', + 'Null' => '', + 'Index_type' => 'BTREE', + 'Comment' => '', + 'Index_comment' => ''); + + $index2 = array('Table' => 'table_name', + 'Non_unique' => '0', + 'Key_name' => 'index_name', + 'Seq_in_index' => '1', + 'Column_name' => 'column_name', + 'Collation' => 'A', + 'Cardinality' => '0', + 'Sub_part' => 'NULL', + 'Packed' => 'NULL', + 'Null' => '', + 'Index_type' => 'BTREE', + 'Comment' => '', + 'Index_comment' => ''); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($index1)); + + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue($index2)); + + $this->result->expects($this->at(2)) + ->method('fetch') + ->will($this->returnValue(null)); + + $this->assertQuerySql("SHOW INDEXES FROM `table_name`", $this->result); + return array($index1, $index2); + } + + public function testGetIndexes() + { + list($index1, $index2) = $this->prepareCaseIndexes(); + $indexes = $this->adapter->getIndexes("table_name"); + + $this->assertTrue(is_array($indexes)); + $this->assertEquals(2, count($indexes)); + $this->assertEquals(array('columns' => array($index1['Column_name'])), $indexes[$index1['Key_name']]); + $this->assertEquals(array('columns' => array($index2['Column_name'])), $indexes[$index2['Key_name']]); + } + + + public function testHasIndexExistsAsString() + { + $this->prepareCaseIndexes(); + $this->assertTrue($this->adapter->hasIndex("table_name", "column_name")); + } + + public function testHasIndexNotExistsAsString() + { + $this->prepareCaseIndexes(); + $this->assertFalse($this->adapter->hasIndex("table_name", "column_name_not_exists")); + } + + public function testHasIndexExistsAsArray() + { + $this->prepareCaseIndexes(); + $this->assertTrue($this->adapter->hasIndex("table_name", array("column_name"))); + } + + public function testHasIndexNotExistsAsArray() + { + $this->prepareCaseIndexes(); + $this->assertFalse($this->adapter->hasIndex("table_name", array("column_name_not_exists"))); + } + + public function testAddIndex() + { + $table = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName')) + ->getMock(); + $table->expects($this->any())->method('getName')->will($this->returnValue('table_name')); + + + $index = $this->getMockBuilder('Phinx\Db\Table\Index') + ->disableOriginalConstructor() + ->setMethods(array( 'getColumns')) + ->getMock(); + + $index->expects($this->any())->method('getColumns')->will($this->returnValue(array('column_name'))); + + $this->assertExecuteSql('ALTER TABLE `table_name` ADD KEY (`column_name`)'); + $this->adapter->addIndex($table, $index); + } + + public function testDropIndexAsString() + { + $this->prepareCaseIndexes(); + $this->assertExecuteSql('ALTER TABLE `table_name` DROP INDEX `index_name`'); + $this->adapter->dropIndex('table_name', 'column_name'); + } + + public function testDropIndexAsArray() + { + $this->prepareCaseIndexes(); + $this->assertExecuteSql('ALTER TABLE `table_name` DROP INDEX `index_name`'); + $this->adapter->dropIndex('table_name', array('column_name')); + } + + public function testDropIndexByName() + { + $this->prepareCaseIndexes(); + $this->assertExecuteSql('ALTER TABLE `table_name` DROP INDEX `index_name`'); + $this->adapter->dropIndexByName('table_name', 'index_name'); + } + + //foregnkey related tests + + private function prepareCaseForeignKeys() + { + $fk = array('CONSTRAINT_NAME' => 'fk1', + 'TABLE_NAME' => 'table_name', + 'COLUMN_NAME' => 'other_table_id', + 'REFERENCED_TABLE_NAME' => 'other_table', + 'REFERENCED_COLUMN_NAME' => 'id'); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($fk)); + + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue(null)); + + $expectedSql = 'SELECT + CONSTRAINT_NAME, + TABLE_NAME, + COLUMN_NAME, + REFERENCED_TABLE_NAME, + REFERENCED_COLUMN_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE REFERENCED_TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = \'table_name\' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT'; + $this->assertQuerySql($expectedSql, $this->result); + return array($fk); + } + + public function testGetForeignKeys() + { + list($fk) = $this->prepareCaseForeignKeys(); + $foreignkeys = $this->adapter->getForeignKeys("table_name"); + + $this->assertTrue(is_array($foreignkeys)); + $this->assertEquals(1, count($foreignkeys)); + $this->assertEquals('table_name', $foreignkeys['fk1']['table']); + $this->assertEquals(array('other_table_id'), $foreignkeys['fk1']['columns']); + $this->assertEquals('other_table', $foreignkeys['fk1']['referenced_table']); + $this->assertEquals(array('id'), $foreignkeys['fk1']['referenced_columns']); + } + + public function testHasForeignKeyExistsAsString() + { + $this->prepareCaseForeignKeys(); + $this->assertTrue($this->adapter->hasForeignKey("table_name", "other_table_id")); + } + + public function testHasForeignKeyExistsAsStringAndConstraint() + { + $this->prepareCaseForeignKeys(); + $this->assertTrue($this->adapter->hasForeignKey("table_name", "other_table_id", 'fk1')); + } + + public function testHasForeignKeyNotExistsAsString() + { + $this->prepareCaseForeignKeys(); + $this->assertFalse($this->adapter->hasForeignKey("table_name", "not_table_id")); + } + + public function testHasForeignKeyNotExistsAsStringAndConstraint() + { + $this->prepareCaseForeignKeys(); + $this->assertFalse($this->adapter->hasForeignKey("table_name", "not_table_id"), 'fk2'); + } + + public function testHasForeignKeyExistsAsArray() + { + $this->prepareCaseForeignKeys(); + $this->assertTrue($this->adapter->hasForeignKey("table_name", array("other_table_id"))); + } + + public function testHasForeignKeyExistsAsArrayAndConstraint() + { + $this->prepareCaseForeignKeys(); + $this->assertTrue($this->adapter->hasForeignKey("table_name", array("other_table_id"), 'fk1')); + } + + public function testHasForeignKeyNotExistsAsArray() + { + $this->prepareCaseForeignKeys(); + $this->assertFalse($this->adapter->hasForeignKey("table_name", array("not_table_id"))); + } + + public function testHasForeignKeyNotExistsAsArrayAndConstraint() + { + $this->prepareCaseForeignKeys(); + $this->assertFalse($this->adapter->hasForeignKey("table_name", array("not_table_id"), 'fk2')); + } + + public function testAddForeignKeyBasic() + { + $table = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName')) + ->getMock(); + $table->expects($this->any())->method('getName')->will($this->returnValue('table_name')); + + $refTable = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName')) + ->getMock(); + $refTable->expects($this->any())->method('getName')->will($this->returnValue('other_table')); + + $foreignkey = $this->getMockBuilder('Phinx\Db\Table\ForeignKey') + ->disableOriginalConstructor() + ->setMethods(array( 'getColumns', + 'getConstraint', + 'getReferencedColumns', + 'getOnDelete', + 'getOnUpdate', + 'getReferencedTable')) + ->getMock(); + + $foreignkey->expects($this->any())->method('getColumns')->will($this->returnValue(array('other_table_id'))); + $foreignkey->expects($this->any())->method('getConstraint')->will($this->returnValue('fk1')); + $foreignkey->expects($this->any())->method('getReferencedColumns')->will($this->returnValue(array('id'))); + $foreignkey->expects($this->any())->method('getReferencedTable')->will($this->returnValue($refTable)); + $foreignkey->expects($this->any())->method('onDelete')->will($this->returnValue(null)); + $foreignkey->expects($this->any())->method('onUpdate')->will($this->returnValue(null)); + + $this->assertExecuteSql('ALTER TABLE `table_name` ADD CONSTRAINT `fk1` FOREIGN KEY (`other_table_id`) REFERENCES `other_table` (`id`)'); + $this->adapter->addForeignKey($table, $foreignkey); + } + + + public function testAddForeignKeyComplete() + { + $table = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName')) + ->getMock(); + $table->expects($this->any())->method('getName')->will($this->returnValue('table_name')); + + $refTable = $this->getMockBuilder('Phinx\Db\Table') + ->disableOriginalConstructor() + ->setMethods(array('getName')) + ->getMock(); + $refTable->expects($this->any())->method('getName')->will($this->returnValue('other_table')); + + $foreignkey = $this->getMockBuilder('Phinx\Db\Table\ForeignKey') + ->disableOriginalConstructor() + ->setMethods(array( 'getColumns', + 'getConstraint', + 'getReferencedColumns', + 'getOnDelete', + 'getOnUpdate', + 'getReferencedTable')) + ->getMock(); + + $foreignkey->expects($this->any())->method('getColumns')->will($this->returnValue(array('other_table_id'))); + $foreignkey->expects($this->any())->method('getConstraint')->will($this->returnValue('fk1')); + $foreignkey->expects($this->any())->method('getReferencedColumns')->will($this->returnValue(array('id'))); + $foreignkey->expects($this->any())->method('getReferencedTable')->will($this->returnValue($refTable)); + $foreignkey->expects($this->any())->method('getOnDelete')->will($this->returnValue('CASCADE')); + $foreignkey->expects($this->any())->method('getOnUpdate')->will($this->returnValue('CASCADE')); + + $this->assertExecuteSql('ALTER TABLE `table_name` ADD CONSTRAINT `fk1` FOREIGN KEY (`other_table_id`) REFERENCES `other_table` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'); + $this->adapter->addForeignKey($table, $foreignkey); + } + + public function testDropForeignKeyAsString() + { + $fk = array('CONSTRAINT_NAME' => 'fk1', + 'TABLE_NAME' => 'table_name', + 'COLUMN_NAME' => 'other_table_id', + 'REFERENCED_TABLE_NAME' => 'other_table', + 'REFERENCED_COLUMN_NAME' => 'id'); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($fk)); + + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue(null)); + + $expectedSql = 'SELECT + CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE REFERENCED_TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = \'table_name\' + AND COLUMN_NAME = \'column_name\' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT'; + $this->assertQuerySql($expectedSql, $this->result); + + $this->assertExecuteSql('ALTER TABLE `table_name` DROP FOREIGN KEY fk1'); + $this->adapter->dropForeignKey('table_name', 'column_name'); + } + + public function _testDropForeignKeyAsArray() + { + $fk = array('CONSTRAINT_NAME' => 'fk1', + 'TABLE_NAME' => 'table_name', + 'COLUMN_NAME' => 'other_table_id', + 'REFERENCED_TABLE_NAME' => 'other_table', + 'REFERENCED_COLUMN_NAME' => 'id'); + + $this->result->expects($this->at(0)) + ->method('fetch') + ->will($this->returnValue($fk)); + + $this->result->expects($this->at(1)) + ->method('fetch') + ->will($this->returnValue(null)); + + $expectedSql = 'SELECT + CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE REFERENCED_TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = \'table_name\' + AND COLUMN_NAME = \'column_name\' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT'; + $this->assertQuerySql($expectedSql, $this->result); + + $this->assertExecuteSql('ALTER TABLE `table_name` DROP FOREIGN KEY fk1'); + $this->adapter->dropForeignKey('table_name', array('column_name')); + } + + public function testDropForeignKeyAsStringByConstraint() + { + $this->assertExecuteSql('ALTER TABLE `table_name` DROP FOREIGN KEY fk1'); + $this->adapter->dropForeignKey('table_name', 'column_name', 'fk1'); + } + + public function _testDropForeignKeyAsArrayByConstraint() + { + $this->assertExecuteSql('ALTER TABLE `table_name` DROP FOREIGN KEY fk1'); + $this->adapter->dropForeignKey('table_name', array('column_name'), 'fk1'); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/PdoAdapterTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/PdoAdapterTest.php new file mode 100644 index 00000000..d899d4e3 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/PdoAdapterTest.php @@ -0,0 +1,33 @@ +adapter = $this->getMockForAbstractClass('\Phinx\Db\Adapter\PdoAdapter', array(array('foo' => 'bar'))); + } + + public function tearDown() + { + unset($this->adapter); + } + + public function testOptions() + { + $options = $this->adapter->getOptions(); + $this->assertArrayHasKey('foo', $options); + $this->assertEquals('bar', $options['foo']); + } + + public function testSchemaTableName() + { + $this->adapter->setSchemaTableName('schema_table_test'); + $this->assertEquals('schema_table_test', $this->adapter->getSchemaTableName()); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/PostgresAdapterTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/PostgresAdapterTest.php new file mode 100644 index 00000000..88fe09ed --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/PostgresAdapterTest.php @@ -0,0 +1,873 @@ +markTestSkipped('Postgres tests disabled. See TESTS_PHINX_DB_ADAPTER_POSTGRES_ENABLED constant.'); + } + + $options = array( + 'host' => TESTS_PHINX_DB_ADAPTER_POSTGRES_HOST, + 'name' => TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE, + 'user' => TESTS_PHINX_DB_ADAPTER_POSTGRES_USERNAME, + 'pass' => TESTS_PHINX_DB_ADAPTER_POSTGRES_PASSWORD, + 'port' => TESTS_PHINX_DB_ADAPTER_POSTGRES_PORT, + 'schema' => TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE_SCHEMA + ); + $this->adapter = new PostgresAdapter($options, new NullOutput()); + + $this->adapter->dropAllSchemas(); + $this->adapter->createSchema($options['schema']); + + // leave the adapter in a disconnected state for each test + $this->adapter->disconnect(); + } + + public function tearDown() + { + if ($this->adapter) { + $this->adapter->dropAllSchemas(); + unset($this->adapter); + } + } + + public function testConnection() + { + $this->assertTrue($this->adapter->getConnection() instanceof \PDO); + } + + public function testConnectionWithoutPort() + { + $options = $this->adapter->getOptions(); + unset($options['port']); + $this->adapter->setOptions($options); + $this->assertTrue($this->adapter->getConnection() instanceof \PDO); + } + + public function testConnectionWithInvalidCredentials() + { + $options = array( + 'host' => TESTS_PHINX_DB_ADAPTER_POSTGRES_HOST, + 'name' => TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE, + 'port' => TESTS_PHINX_DB_ADAPTER_POSTGRES_PORT, + 'user' => 'invaliduser', + 'pass' => 'invalidpass' + ); + + try { + $adapter = new PostgresAdapter($options, new NullOutput()); + $adapter->connect(); + $this->fail('Expected the adapter to throw an exception'); + } catch (\InvalidArgumentException $e) { + $this->assertInstanceOf( + 'InvalidArgumentException', + $e, + 'Expected exception of type InvalidArgumentException, got ' . get_class($e) + ); + $this->assertRegExp('/There was a problem connecting to the database/', $e->getMessage()); + } + } + + public function testCreatingTheSchemaTableOnConnect() + { + $this->adapter->connect(); + $this->assertTrue($this->adapter->hasTable($this->adapter->getSchemaTableName())); + $this->adapter->dropTable($this->adapter->getSchemaTableName()); + $this->assertFalse($this->adapter->hasTable($this->adapter->getSchemaTableName())); + $this->adapter->disconnect(); + $this->adapter->connect(); + $this->assertTrue($this->adapter->hasTable($this->adapter->getSchemaTableName())); + } + + public function testSchemaTableIsCreatedWithPrimaryKey() + { + $this->adapter->connect(); + $table = new \Phinx\Db\Table($this->adapter->getSchemaTableName(), array(), $this->adapter); + $this->assertTrue($this->adapter->hasIndex($this->adapter->getSchemaTableName(), array('version'))); + } + + public function testQuoteSchemaName() + { + $this->assertEquals('"schema"', $this->adapter->quoteSchemaName('schema')); + $this->assertEquals('"schema.schema"', $this->adapter->quoteSchemaName('schema.schema')); + } + + public function testQuoteTableName() + { + $this->assertEquals('"public"."table"', $this->adapter->quoteTableName('table')); + $this->assertEquals('"public"."table.table"', $this->adapter->quoteTableName('table.table')); + } + + public function testQuoteColumnName() + { + $this->assertEquals('"string"', $this->adapter->quoteColumnName('string')); + $this->assertEquals('"string.string"', $this->adapter->quoteColumnName('string.string')); + } + + public function testCreateTable() + { + $table = new \Phinx\Db\Table('ntable', array(), $this->adapter); + $table->addColumn('realname', 'string') + ->addColumn('email', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'email')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + } + + public function testCreateTableCustomIdColumn() + { + $table = new \Phinx\Db\Table('ntable', array('id' => 'custom_id'), $this->adapter); + $table->addColumn('realname', 'string') + ->addColumn('email', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'custom_id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'email')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + } + + public function testCreateTableWithNoPrimaryKey() + { + $options = array( + 'id' => false + ); + $table = new \Phinx\Db\Table('atable', $options, $this->adapter); + $table->addColumn('user_id', 'integer') + ->save(); + $this->assertFalse($this->adapter->hasColumn('atable', 'id')); + } + + public function testCreateTableWithMultiplePrimaryKeys() + { + $options = array( + 'id' => false, + 'primary_key' => array('user_id', 'tag_id') + ); + $table = new \Phinx\Db\Table('table1', $options, $this->adapter); + $table->addColumn('user_id', 'integer') + ->addColumn('tag_id', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('user_id', 'tag_id'))); + $this->assertTrue($this->adapter->hasIndex('table1', array('tag_id', 'USER_ID'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('tag_id', 'user_email'))); + } + + public function testCreateTableWithMultipleIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addColumn('name', 'string') + ->addIndex('email') + ->addIndex('name') + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertTrue($this->adapter->hasIndex('table1', array('name'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_name'))); + } + + public function testCreateTableWithUniqueIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('unique' => true)) + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + } + + public function testCreateTableWithNamedIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('name' => 'myemailindex')) + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + } + + public function testRenameTable() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertTrue($this->adapter->hasTable('table1')); + $this->assertFalse($this->adapter->hasTable('table2')); + $this->adapter->renameTable('table1', 'table2'); + $this->assertFalse($this->adapter->hasTable('table1')); + $this->assertTrue($this->adapter->hasTable('table2')); + } + + public function testAddColumn() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('email')); + $table->addColumn('email', 'string') + ->save(); + $this->assertTrue($table->hasColumn('email')); + } + + public function testAddColumnWithDefaultValue() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_zero', 'string', array('default' => 'test')) + ->save(); + $columns = $this->adapter->getColumns('table1'); + foreach ($columns as $column) { + if ($column->getName() == 'default_zero') { + $this->assertEquals("'test'::character varying", $column->getDefault()); + } + } + } + + public function testAddColumnWithDefaultZero() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_zero', 'integer', array('default' => 0)) + ->save(); + $columns = $this->adapter->getColumns('table1'); + foreach ($columns as $column) { + if ($column->getName() == 'default_zero') { + $this->assertNotNull($column->getDefault()); + $this->assertEquals('0', $column->getDefault()); + } + } + } + + public function testAddColumnWithDefaultBoolean() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_true', 'boolean', array('default' => true)) + ->addColumn('default_false', 'boolean', array('default' => false)) + ->save(); + $columns = $this->adapter->getColumns('table1'); + foreach ($columns as $column) { + if ($column->getName() == 'default_true') { + $this->assertNotNull($column->getDefault()); + $this->assertEquals('true', $column->getDefault()); + } + if ($column->getName() == 'default_false') { + $this->assertNotNull($column->getDefault()); + $this->assertEquals('false', $column->getDefault()); + } + } + } + + public function testAddDecimalWithPrecisionAndScale() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('number', 'decimal', array('precision' => 10, 'scale' => 2)) + ->addColumn('number2', 'decimal', array('limit' => 12)) + ->addColumn('number3', 'decimal') + ->save(); + $columns = $this->adapter->getColumns('table1'); + foreach ($columns as $column) { + if ($column->getName() == 'number') { + $this->assertEquals("10", $column->getPrecision()); + $this->assertEquals("2", $column->getScale()); + } + + if ($column->getName() == 'number2') { + $this->assertEquals("12", $column->getPrecision()); + $this->assertEquals("0", $column->getScale()); + } + } + } + + public function providerArrayType() + { + return array( + array('array_text', 'text[]'), + array('array_char', 'char[]'), + array('array_integer', 'integer[]'), + array('array_float', 'float[]'), + array('array_decimal', 'decimal[]'), + array('array_timestamp', 'timestamp[]'), + array('array_time', 'time[]'), + array('array_date', 'date[]'), + array('array_boolean', 'boolean[]'), + array('array_json', 'json[]'), + array('array_json2d', 'json[][]'), + array('array_json3d', 'json[][][]'), + array('array_uuid', 'uuid[]'), + ); + } + + /** + * @dataProvider providerArrayType + */ + public function testAddColumnArrayType($column_name, $column_type) + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn($column_name)); + $table->addColumn($column_name, $column_type) + ->save(); + $this->assertTrue($table->hasColumn($column_name)); + } + + public function testRenameColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $this->assertFalse($this->adapter->hasColumn('t', 'column2')); + $this->adapter->renameColumn('t', 'column1', 'column2'); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + $this->assertTrue($this->adapter->hasColumn('t', 'column2')); + } + + public function testRenamingANonExistentColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + + try { + $this->adapter->renameColumn('t', 'column2', 'column1'); + $this->fail('Expected the adapter to throw an exception'); + } catch (\InvalidArgumentException $e) { + $this->assertInstanceOf( + 'InvalidArgumentException', + $e, + 'Expected exception of type InvalidArgumentException, got ' . get_class($e) + ); + $this->assertEquals('The specified column does not exist: column2', $e->getMessage()); + } + } + + public function testChangeColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setType('string'); + $table->changeColumn('column1', $newColumn1); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $newColumn2 = new \Phinx\Db\Table\Column(); + $newColumn2->setName('column2') + ->setType('string') + ->setNull(true); + $table->changeColumn('column1', $newColumn2); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + $this->assertTrue($this->adapter->hasColumn('t', 'column2')); + $columns = $this->adapter->getColumns('t'); + foreach ($columns as $column) { + if ($column->getName() == 'column2') { + $this->assertTrue($column->isNull()); + } + } + } + + public function testChangeColumnWithDefault() { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setName('column1') + ->setType('string') + ->setNull(true); + + $newColumn1->setDefault('Test'); + $table->changeColumn('column1', $newColumn1); + + $columns = $this->adapter->getColumns('t'); + foreach ($columns as $column) { + if ($column->getName() === 'column1') { + $this->assertTrue($column->isNull()); + $this->assertRegExp('/Test/', $column->getDefault()); + } + } + } + + public function testChangeColumnWithDropDefault() { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string', array('default' => 'Test')) + ->save(); + + $columns = $this->adapter->getColumns('t'); + foreach ($columns as $column) { + if ($column->getName() === 'column1') { + $this->assertRegExp('/Test/', $column->getDefault()); + } + } + + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setName('column1') + ->setType('string'); + + $table->changeColumn('column1', $newColumn1); + + $columns = $this->adapter->getColumns('t'); + foreach ($columns as $column) { + if ($column->getName() === 'column1') { + $this->assertNull($column->getDefault()); + } + } + } + + public function testDropColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $this->adapter->dropColumn('t', 'column1'); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + } + + public function testGetColumns() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'integer', array('limit' => PostgresAdapter::INT_SMALL)) + ->addColumn('column3', 'integer') + ->addColumn('column4', 'biginteger') + ->addColumn('column5', 'text') + ->addColumn('column6', 'float') + ->addColumn('column7', 'decimal') + ->addColumn('column8', 'time') + ->addColumn('column9', 'timestamp') + ->addColumn('column10', 'date') + ->addColumn('column11', 'boolean') + ->addColumn('column12', 'datetime') + ->addColumn('column13', 'binary') + ->addColumn('column14', 'string', array('limit' => 10)); + $pendingColumns = $table->getPendingColumns(); + $table->save(); + $columns = $this->adapter->getColumns('t'); + $this->assertCount(count($pendingColumns) + 1, $columns); + for ($i = 0; $i++; $i < count($pendingColumns)) { + $this->assertEquals($pendingColumns[$i], $columns[$i+1]); + } + } + + public function testAddIndex() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->save(); + $this->assertFalse($table->hasIndex('email')); + $table->addIndex('email') + ->save(); + $this->assertTrue($table->hasIndex('email')); + } + + public function testDropIndex() + { + // single column index + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email') + ->save(); + $this->assertTrue($table->hasIndex('email')); + $this->adapter->dropIndex($table->getName(), 'email'); + $this->assertFalse($table->hasIndex('email')); + + // multiple column index + $table2 = new \Phinx\Db\Table('table2', array(), $this->adapter); + $table2->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname')) + ->save(); + $this->assertTrue($table2->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndex($table2->getName(), array('fname', 'lname')); + $this->assertFalse($table2->hasIndex(array('fname', 'lname'))); + + // index with name specified, but dropping it by column name + $table3 = new \Phinx\Db\Table('table3', array(), $this->adapter); + $table3->addColumn('email', 'string') + ->addIndex('email', array('name' => 'someindexname')) + ->save(); + $this->assertTrue($table3->hasIndex('email')); + $this->adapter->dropIndex($table3->getName(), 'email'); + $this->assertFalse($table3->hasIndex('email')); + + // multiple column index with name specified + $table4 = new \Phinx\Db\Table('table4', array(), $this->adapter); + $table4->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname'), array('name' => 'multiname')) + ->save(); + $this->assertTrue($table4->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndex($table4->getName(), array('fname', 'lname')); + $this->assertFalse($table4->hasIndex(array('fname', 'lname'))); + } + + public function testDropIndexByName() + { + // single column index + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('name' => 'myemailindex')) + ->save(); + $this->assertTrue($table->hasIndex('email')); + $this->adapter->dropIndexByName($table->getName(), 'myemailindex'); + $this->assertFalse($table->hasIndex('email')); + + // multiple column index + $table2 = new \Phinx\Db\Table('table2', array(), $this->adapter); + $table2->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname'), + array('name' => 'twocolumnuniqueindex', 'unique' => true)) + ->save(); + $this->assertTrue($table2->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndexByName($table2->getName(), 'twocolumnuniqueindex'); + $this->assertFalse($table2->hasIndex(array('fname', 'lname'))); + } + + public function testAddForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testDropForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + $this->adapter->dropForeignKey($table->getName(), array('ref_table_id')); + $this->assertFalse($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testHasDatabase() + { + $this->assertFalse($this->adapter->hasDatabase('fake_database_name')); + $this->assertTrue($this->adapter->hasDatabase(TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE)); + } + + public function testDropDatabase() + { + $this->assertFalse($this->adapter->hasDatabase('phinx_temp_database')); + $this->adapter->createDatabase('phinx_temp_database'); + $this->assertTrue($this->adapter->hasDatabase('phinx_temp_database')); + $this->adapter->dropDatabase('phinx_temp_database'); + } + + public function testCreateSchema() + { + $this->adapter->createSchema('foo'); + $this->assertTrue($this->adapter->hasSchema('foo')); + } + + public function testDropSchema() + { + $this->adapter->createSchema('foo'); + $this->assertTrue($this->adapter->hasSchema('foo')); + $this->adapter->dropSchema('foo'); + $this->assertFalse($this->adapter->hasSchema('foo')); + } + + public function testDropAllSchemas() + { + $this->adapter->createSchema('foo'); + $this->adapter->createSchema('bar'); + + $this->assertTrue($this->adapter->hasSchema('foo')); + $this->assertTrue($this->adapter->hasSchema('bar')); + $this->adapter->dropAllSchemas(); + $this->assertFalse($this->adapter->hasSchema('foo')); + $this->assertFalse($this->adapter->hasSchema('bar')); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage The type: "idontexist" is not supported + */ + public function testInvalidSqlType() + { + $this->adapter->getSqlType('idontexist'); + } + + public function testGetPhinxType() + { + $this->assertEquals('integer', $this->adapter->getPhinxType('int')); + $this->assertEquals('integer', $this->adapter->getPhinxType('int4')); + $this->assertEquals('integer', $this->adapter->getPhinxType('integer')); + + $this->assertEquals('biginteger', $this->adapter->getPhinxType('bigint')); + $this->assertEquals('biginteger', $this->adapter->getPhinxType('int8')); + + $this->assertEquals('decimal', $this->adapter->getPhinxType('decimal')); + $this->assertEquals('decimal', $this->adapter->getPhinxType('numeric')); + + $this->assertEquals('float', $this->adapter->getPhinxType('real')); + $this->assertEquals('float', $this->adapter->getPhinxType('float4')); + + $this->assertEquals('boolean', $this->adapter->getPhinxType('bool')); + $this->assertEquals('boolean', $this->adapter->getPhinxType('boolean')); + + $this->assertEquals('string', $this->adapter->getPhinxType('character varying')); + $this->assertEquals('string', $this->adapter->getPhinxType('varchar')); + + $this->assertEquals('text', $this->adapter->getPhinxType('text')); + + $this->assertEquals('time', $this->adapter->getPhinxType('time')); + $this->assertEquals('time', $this->adapter->getPhinxType('timetz')); + $this->assertEquals('time', $this->adapter->getPhinxType('time with time zone')); + $this->assertEquals('time', $this->adapter->getPhinxType('time without time zone')); + + $this->assertEquals('datetime', $this->adapter->getPhinxType('timestamp')); + $this->assertEquals('datetime', $this->adapter->getPhinxType('timestamptz')); + $this->assertEquals('datetime', $this->adapter->getPhinxType('timestamp with time zone')); + $this->assertEquals('datetime', $this->adapter->getPhinxType('timestamp without time zone')); + + $this->assertEquals('uuid', $this->adapter->getPhinxType('uuid')); + + } + + public function testCanAddColumnComment() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('field1', 'string', array('comment' => $comment = 'Comments from column "field1"')) + ->save(); + + $row = $this->adapter->fetchRow( + 'SELECT + (select pg_catalog.col_description(oid,cols.ordinal_position::int) + from pg_catalog.pg_class c + where c.relname=cols.table_name ) as column_comment + FROM information_schema.columns cols + WHERE cols.table_catalog=\''. TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE .'\' + AND cols.table_name=\'table1\' + AND cols.column_name = \'field1\'' + ); + + $this->assertEquals($comment, $row['column_comment'], 'Dont set column comment correctly'); + } + + /** + * @depends testCanAddColumnComment + */ + public function testCanChangeColumnComment() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('field1', 'string', array('comment' => 'Comments from column "field1"')) + ->save(); + + $table->changeColumn('field1', 'string', array('comment' => $comment = 'New Comments from column "field1"')) + ->save(); + + $row = $this->adapter->fetchRow( + 'SELECT + (select pg_catalog.col_description(oid,cols.ordinal_position::int) + from pg_catalog.pg_class c + where c.relname=cols.table_name ) as column_comment + FROM information_schema.columns cols + WHERE cols.table_catalog=\''. TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE .'\' + AND cols.table_name=\'table1\' + AND cols.column_name = \'field1\'' + ); + + $this->assertEquals($comment, $row['column_comment'], 'Dont change column comment correctly'); + } + + /** + * @depends testCanAddColumnComment + */ + public function testCanRemoveColumnComment() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('field1', 'string', array('comment' => 'Comments from column "field1"')) + ->save(); + + $table->changeColumn('field1', 'string', array('comment' => 'null')) + ->save(); + + $row = $this->adapter->fetchRow( + 'SELECT + (select pg_catalog.col_description(oid,cols.ordinal_position::int) + from pg_catalog.pg_class c + where c.relname=cols.table_name ) as column_comment + FROM information_schema.columns cols + WHERE cols.table_catalog=\''. TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE .'\' + AND cols.table_name=\'table1\' + AND cols.column_name = \'field1\'' + ); + + $this->assertEmpty($row['column_comment'], 'Dont remove column comment correctly'); + } + + /** + * @depends testCanAddColumnComment + */ + public function testCanAddMultipleCommentsToOneTable() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('comment1', 'string', array( + 'comment' => $comment1 = 'first comment' + )) + ->addColumn('comment2', 'string', array( + 'comment' => $comment2 = 'second comment' + )) + ->save(); + + $row = $this->adapter->fetchRow( + 'SELECT + (select pg_catalog.col_description(oid,cols.ordinal_position::int) + from pg_catalog.pg_class c + where c.relname=cols.table_name ) as column_comment + FROM information_schema.columns cols + WHERE cols.table_catalog=\''. TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE .'\' + AND cols.table_name=\'table1\' + AND cols.column_name = \'comment1\'' + ); + + $this->assertEquals($comment1, $row['column_comment'], 'Could not create first column comment'); + + $row = $this->adapter->fetchRow( + 'SELECT + (select pg_catalog.col_description(oid,cols.ordinal_position::int) + from pg_catalog.pg_class c + where c.relname=cols.table_name ) as column_comment + FROM information_schema.columns cols + WHERE cols.table_catalog=\''. TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE .'\' + AND cols.table_name=\'table1\' + AND cols.column_name = \'comment2\'' + ); + + $this->assertEquals($comment2, $row['column_comment'], 'Could not create second column comment'); + } + + /** + * @depends testCanAddColumnComment + */ + public function testColumnsAreResetBetweenTables() + { + $table = new \Phinx\Db\Table('widgets', array(), $this->adapter); + $table->addColumn('transport', 'string', array( + 'comment' => $comment = 'One of: car, boat, truck, plane, train' + )) + ->save(); + + $table = new \Phinx\Db\Table('things', array(), $this->adapter); + $table->addColumn('speed', 'integer') + ->save(); + + $row = $this->adapter->fetchRow( + 'SELECT + (select pg_catalog.col_description(oid,cols.ordinal_position::int) + from pg_catalog.pg_class c + where c.relname=cols.table_name ) as column_comment + FROM information_schema.columns cols + WHERE cols.table_catalog=\''. TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE .'\' + AND cols.table_name=\'widgets\' + AND cols.column_name = \'transport\'' + ); + + $this->assertEquals($comment, $row['column_comment'], 'Could not create column comment'); + } + + /** + * Test that column names are properly escaped when creating Foreign Keys + */ + public function testForignKeysArePropertlyEscaped() + { + $userId = 'user'; + $sessionId = 'session'; + + $local = new \Phinx\Db\Table('users', array('primary_key' => $userId, 'id' => $userId), $this->adapter); + $local->create(); + + $foreign = new \Phinx\Db\Table('sessions', array('primary_key' => $sessionId, 'id' => $sessionId), $this->adapter); + $foreign->addColumn('user', 'integer') + ->addForeignKey('user', 'users', $userId) + ->create(); + + $this->assertTrue($foreign->hasForeignKey('user')); + } + + public function testTimestampWithTimezone() + { + $table = new \Phinx\Db\Table('tztable', array('id' => false), $this->adapter); + $table + ->addColumn('timestamp_tz', 'timestamp', array('timezone' => true)) + ->addColumn('time_tz', 'time', array('timezone' => true)) + ->addColumn('date_notz', 'date', array('timezone' => true)) /* date columns cannot have timestamp */ + ->addColumn('time_notz', 'timestamp') /* default for timezone option is false */ + ->save(); + + $this->assertTrue($this->adapter->hasColumn('tztable', 'timestamp_tz')); + $this->assertTrue($this->adapter->hasColumn('tztable', 'time_tz')); + $this->assertTrue($this->adapter->hasColumn('tztable', 'date_notz')); + $this->assertTrue($this->adapter->hasColumn('tztable', 'time_notz')); + + $columns = $this->adapter->getColumns('tztable'); + foreach ($columns as $column) { + if (substr($column->getName(), -4) === 'notz') { + $this->assertFalse($column->isTimezone(), 'column: ' . $column->getName()); + } else { + $this->assertTrue($column->isTimezone(), 'column: ' . $column->getName()); + } + } + } + + public function testInsertData() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'integer') + ->insert( + array("column1", "column2"), + array( + array('value1', 1), + array('value2', 2) + ) + ) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals(1, $rows[0]['column2']); + $this->assertEquals(2, $rows[1]['column2']); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/ProxyAdapterTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/ProxyAdapterTest.php new file mode 100644 index 00000000..24299aab --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/ProxyAdapterTest.php @@ -0,0 +1,117 @@ +getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + + $this->adapter = new ProxyAdapter($stub); + } + + public function tearDown() + { + unset($this->adapter); + } + + public function testProxyAdapterCanInvertCreateTable() + { + $table = new \Phinx\Db\Table('atable'); + $this->adapter->createTable($table); + + $commands = $this->adapter->getInvertedCommands(); + $this->assertEquals('dropTable', $commands[0]['name']); + $this->assertEquals('atable', $commands[0]['arguments'][0]); + } + + public function testProxyAdapterCanInvertRenameTable() + { + $this->adapter->renameTable('oldname', 'newname'); + + $commands = $this->adapter->getInvertedCommands(); + $this->assertEquals('renameTable', $commands[0]['name']); + $this->assertEquals('newname', $commands[0]['arguments'][0]); + $this->assertEquals('oldname', $commands[0]['arguments'][1]); + } + + public function testProxyAdapterCanInvertAddColumn() + { + $table = new \Phinx\Db\Table('atable'); + $column = new \Phinx\Db\Table\Column(); + $column->setName('acolumn'); + + $this->adapter->addColumn($table, $column); + + $commands = $this->adapter->getInvertedCommands(); + $this->assertEquals('dropColumn', $commands[0]['name']); + $this->assertEquals('atable', $commands[0]['arguments'][0]); + $this->assertContains('acolumn', $commands[0]['arguments'][1]); + } + + public function testProxyAdapterCanInvertRenameColumn() + { + $this->adapter->renameColumn('atable', 'oldname', 'newname'); + + $commands = $this->adapter->getInvertedCommands(); + $this->assertEquals('renameColumn', $commands[0]['name']); + $this->assertEquals('atable', $commands[0]['arguments'][0]); + $this->assertEquals('newname', $commands[0]['arguments'][1]); + $this->assertEquals('oldname', $commands[0]['arguments'][2]); + } + + public function testProxyAdapterCanInvertAddIndex() + { + $table = new \Phinx\Db\Table('atable'); + $index = new \Phinx\Db\Table\Index(); + $index->setType(\Phinx\Db\Table\Index::INDEX) + ->setColumns(array('email')); + + $this->adapter->addIndex($table, $index); + + $commands = $this->adapter->getInvertedCommands(); + $this->assertEquals('dropIndex', $commands[0]['name']); + $this->assertEquals('atable', $commands[0]['arguments'][0]); + $this->assertContains('email', $commands[0]['arguments'][1]); + } + + public function testProxyAdapterCanInvertAddForeignKey() + { + $table = new \Phinx\Db\Table('atable'); + $refTable = new \Phinx\Db\Table('refTable'); + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + + $commands = $this->adapter->getInvertedCommands(); + $this->assertEquals('dropForeignKey', $commands[0]['name']); + $this->assertEquals('atable', $commands[0]['arguments'][0]); + $this->assertContains('ref_table_id', $commands[0]['arguments'][1]); + } + + /** + * @expectedException \Phinx\Migration\IrreversibleMigrationException + * @expectedExceptionMessage Cannot reverse a "createDatabase" command + */ + public function testGetInvertedCommandsThrowsExceptionForIrreversibleCommand() + { + $this->adapter->recordCommand('createDatabase', array('testdb')); + $this->adapter->getInvertedCommands(); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/SQLiteAdapterTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/SQLiteAdapterTest.php new file mode 100644 index 00000000..8271d319 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/SQLiteAdapterTest.php @@ -0,0 +1,646 @@ +markTestSkipped('SQLite tests disabled. See TESTS_PHINX_DB_ADAPTER_SQLITE_ENABLED constant.'); + } + + $options = array( + 'name' => TESTS_PHINX_DB_ADAPTER_SQLITE_DATABASE + ); + $this->adapter = new SQLiteAdapter($options, new NullOutput()); + + // ensure the database is empty for each test + $this->adapter->dropDatabase($options['name']); + $this->adapter->createDatabase($options['name']); + + // leave the adapter in a disconnected state for each test + $this->adapter->disconnect(); + } + + public function tearDown() + { + unset($this->adapter); + } + + public function testConnection() + { + $this->assertTrue($this->adapter->getConnection() instanceof \PDO); + } + + public function testBeginTransaction() + { + $this->adapter->getConnection() + ->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->adapter->beginTransaction(); + + $this->assertTrue(true, 'Transaction query succeeded'); + } + + public function testCreatingTheSchemaTableOnConnect() + { + $this->adapter->connect(); + $this->assertTrue($this->adapter->hasTable($this->adapter->getSchemaTableName())); + $this->adapter->dropTable($this->adapter->getSchemaTableName()); + $this->assertFalse($this->adapter->hasTable($this->adapter->getSchemaTableName())); + $this->adapter->disconnect(); + $this->adapter->connect(); + $this->assertTrue($this->adapter->hasTable($this->adapter->getSchemaTableName())); + } + + public function testSchemaTableIsCreatedWithPrimaryKey() + { + $this->adapter->connect(); + $table = new \Phinx\Db\Table($this->adapter->getSchemaTableName(), array(), $this->adapter); + $this->assertTrue($this->adapter->hasIndex($this->adapter->getSchemaTableName(), array('version'))); + } + + public function testQuoteTableName() + { + $this->assertEquals('`test_table`', $this->adapter->quoteTableName('test_table')); + } + + public function testQuoteColumnName() + { + $this->assertEquals('`test_column`', $this->adapter->quoteColumnName('test_column')); + } + + public function testCreateTable() + { + $table = new \Phinx\Db\Table('ntable', array(), $this->adapter); + $table->addColumn('realname', 'string') + ->addColumn('email', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'email')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + } + + public function testCreateTableCustomIdColumn() + { + $table = new \Phinx\Db\Table('ntable', array('id' => 'custom_id'), $this->adapter); + $table->addColumn('realname', 'string') + ->addColumn('email', 'integer') + ->save(); + + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'custom_id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'email')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + } + + public function testCreateTableWithNoOptions() + { + $this->markTestIncomplete(); + //$this->adapter->createTable('ntable', ) + } + + public function testCreateTableWithNoPrimaryKey() + { + $options = array( + 'id' => false + ); + $table = new \Phinx\Db\Table('atable', $options, $this->adapter); + $table->addColumn('user_id', 'integer') + ->save(); + $this->assertFalse($this->adapter->hasColumn('atable', 'id')); + } + + public function testCreateTableWithMultiplePrimaryKeys() + { + $options = array( + 'id' => false, + 'primary_key' => array('user_id', 'tag_id') + ); + $table = new \Phinx\Db\Table('table1', $options, $this->adapter); + $table->addColumn('user_id', 'integer') + ->addColumn('tag_id', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('user_id', 'tag_id'))); + $this->assertTrue($this->adapter->hasIndex('table1', array('tag_id', 'USER_ID'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('tag_id', 'user_email'))); + } + + public function testCreateTableWithMultipleIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addColumn('name', 'string') + ->addIndex('email') + ->addIndex('name') + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertTrue($this->adapter->hasIndex('table1', array('name'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_name'))); + } + + public function testCreateTableWithUniqueIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('unique' => true)) + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + } + + public function testCreateTableWithNamedIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('name' => 'myemailindex')) + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + } + + public function testCreateTableWithMultiplePKsAndUniqueIndexes() + { + $this->markTestIncomplete(); + } + + public function testCreateTableWithForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer'); + $table->addForeignKey('ref_table_id', 'ref_table', 'id'); + $table->save(); + + $this->assertTrue($this->adapter->hasTable($table->getName())); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testRenameTable() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertTrue($this->adapter->hasTable('table1')); + $this->assertFalse($this->adapter->hasTable('table2')); + $this->adapter->renameTable('table1', 'table2'); + $this->assertFalse($this->adapter->hasTable('table1')); + $this->assertTrue($this->adapter->hasTable('table2')); + } + + public function testAddColumn() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('email')); + $table->addColumn('email', 'string') + ->save(); + $this->assertTrue($table->hasColumn('email')); + + // In SQLite it is not possible to dictate order of added columns. + // $table->addColumn('realname', 'string', array('after' => 'id')) + // ->save(); + // $this->assertEquals('realname', $rows[1]['Field']); + } + + public function testAddColumnWithDefaultValue() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_zero', 'string', array('default' => 'test')) + ->save(); + $rows = $this->adapter->fetchAll(sprintf('pragma table_info(%s)', 'table1')); + $this->assertEquals("'test'", $rows[1]['dflt_value']); + } + + public function testAddColumnWithDefaultZero() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_zero', 'integer', array('default' => 0)) + ->save(); + $rows = $this->adapter->fetchAll(sprintf('pragma table_info(%s)', 'table1')); + $this->assertNotNull($rows[1]['dflt_value']); + $this->assertEquals("0", $rows[1]['dflt_value']); + } + + public function testAddColumnWithDefaultEmptyString() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_empty', 'string', array('default' => '')) + ->save(); + $rows = $this->adapter->fetchAll(sprintf('pragma table_info(%s)', 'table1')); + $this->assertEquals("''", $rows[1]['dflt_value']); + } + + public function testRenameColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $this->adapter->renameColumn('t', 'column1', 'column2'); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + $this->assertTrue($this->adapter->hasColumn('t', 'column2')); + } + + public function testRenamingANonExistentColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + + try { + $this->adapter->renameColumn('t', 'column2', 'column1'); + $this->fail('Expected the adapter to throw an exception'); + } catch (\InvalidArgumentException $e) { + $this->assertInstanceOf( + 'InvalidArgumentException', + $e, + 'Expected exception of type InvalidArgumentException, got ' . get_class($e) + ); + $this->assertEquals('The specified column doesn\'t exist: column2', $e->getMessage()); + } + } + + public function testChangeColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setType('string'); + $table->changeColumn('column1', $newColumn1); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $newColumn2 = new \Phinx\Db\Table\Column(); + $newColumn2->setName('column2') + ->setType('string'); + $table->changeColumn('column1', $newColumn2); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + $this->assertTrue($this->adapter->hasColumn('t', 'column2')); + } + + public function testChangeColumnDefaultValue() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string', array('default' => 'test')) + ->save(); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setDefault('test1') + ->setType('string'); + $table->changeColumn('column1', $newColumn1); + $rows = $this->adapter->fetchAll('pragma table_info(t)'); + + $this->assertEquals("'test1'", $rows[1]['dflt_value']); + } + + + public function testChangeColumnDefaultToZero() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'integer') + ->save(); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setDefault(0) + ->setType('integer'); + $table->changeColumn('column1', $newColumn1); + $rows = $this->adapter->fetchAll('pragma table_info(t)'); + $this->assertEquals("0", $rows[1]['dflt_value']); + } + + public function testChangeColumnDefaultToNull() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string', array('default' => 'test')) + ->save(); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setDefault(null) + ->setType('string'); + $table->changeColumn('column1', $newColumn1); + $rows = $this->adapter->fetchAll('pragma table_info(t)'); + $this->assertNull($rows[1]['dflt_value']); + } + + public function testDropColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $this->adapter->dropColumn('t', 'column1'); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + } + + public function testGetColumns() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'integer') + ->addColumn('column3', 'biginteger') + ->addColumn('column4', 'text') + ->addColumn('column5', 'float') + ->addColumn('column6', 'decimal') + ->addColumn('column7', 'datetime') + ->addColumn('column8', 'time') + ->addColumn('column9', 'timestamp') + ->addColumn('column10', 'date') + ->addColumn('column11', 'binary') + ->addColumn('column12', 'boolean') + ->addColumn('column13', 'string', array('limit' => 10)) + ->addColumn('column15', 'integer', array('limit' => 10)); + $pendingColumns = $table->getPendingColumns(); + $table->save(); + $columns = $this->adapter->getColumns('t'); + $this->assertCount(count($pendingColumns) + 1, $columns); + for ($i = 0; $i++; $i < count($pendingColumns)) { + $this->assertEquals($pendingColumns[$i], $columns[$i+1]); + } + } + + public function testAddIndex() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->save(); + $this->assertFalse($table->hasIndex('email')); + $table->addIndex('email') + ->save(); + $this->assertTrue($table->hasIndex('email')); + } + + public function testDropIndex() + { + // single column index + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email') + ->save(); + $this->assertTrue($table->hasIndex('email')); + $this->adapter->dropIndex($table->getName(), 'email'); + $this->assertFalse($table->hasIndex('email')); + + // multiple column index + $table2 = new \Phinx\Db\Table('table2', array(), $this->adapter); + $table2->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname')) + ->save(); + $this->assertTrue($table2->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndex($table2->getName(), array('fname', 'lname')); + $this->assertFalse($table2->hasIndex(array('fname', 'lname'))); + + // single column index with name specified + $table3 = new \Phinx\Db\Table('table3', array(), $this->adapter); + $table3->addColumn('email', 'string') + ->addIndex('email', array('name' => 'someindexname')) + ->save(); + $this->assertTrue($table3->hasIndex('email')); + $this->adapter->dropIndex($table3->getName(), 'email'); + $this->assertFalse($table3->hasIndex('email')); + + // multiple column index with name specified + $table4 = new \Phinx\Db\Table('table4', array(), $this->adapter); + $table4->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname'), array('name' => 'multiname')) + ->save(); + $this->assertTrue($table4->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndex($table4->getName(), array('fname', 'lname')); + $this->assertFalse($table4->hasIndex(array('fname', 'lname'))); + } + + public function testDropIndexByName() + { + // single column index + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('name' => 'myemailindex')) + ->save(); + $this->assertTrue($table->hasIndex('email')); + $this->adapter->dropIndexByName($table->getName(), 'myemailindex'); + $this->assertFalse($table->hasIndex('email')); + + // multiple column index + $table2 = new \Phinx\Db\Table('table2', array(), $this->adapter); + $table2->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname'), array('name' => 'twocolumnindex')) + ->save(); + $this->assertTrue($table2->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndexByName($table2->getName(), 'twocolumnindex'); + $this->assertFalse($table2->hasIndex(array('fname', 'lname'))); + } + + public function testAddForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testAddForeignKeyWithPdoExceptionErrorMode() + { + $this->adapter->getConnection() + ->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testDropForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + + $this->adapter->dropForeignKey($table->getName(), array('ref_table_id')); + $this->assertFalse($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + + $this->adapter->dropForeignKey($table->getName(), array('ref_table_id')); + $this->assertFalse($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testHasDatabase() + { + $this->assertFalse($this->adapter->hasDatabase('fake_database_name')); + $this->assertTrue($this->adapter->hasDatabase(TESTS_PHINX_DB_ADAPTER_SQLITE_DATABASE)); + } + + public function testDropDatabase() + { + $this->assertFalse($this->adapter->hasDatabase('phinx_temp_database')); + $this->adapter->createDatabase('phinx_temp_database'); + $this->assertTrue($this->adapter->hasDatabase('phinx_temp_database')); + $this->adapter->dropDatabase('phinx_temp_database'); + } + + public function testAddColumnWithComment() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('column1', 'string', array('comment' => $comment = 'Comments from "column1"')) + ->save(); + + $rows = $this->adapter->fetchAll('select * from sqlite_master where `type` = \'table\''); + + foreach ($rows as $row) { + if ($row['tbl_name'] == 'table1') { + $sql = $row['sql']; + } + } + + $this->assertRegExp('/\/\* Comments from "column1" \*\//', $sql); + } + + public function testPhinxTypeNotValidType() + { + $this->setExpectedException('\RuntimeException','The type: "fake" is not supported.'); + $this->adapter->getPhinxType('fake'); + } + + public function testPhinxTypeNotValidTypeRegex() + { + $this->setExpectedException('\RuntimeException','Column type ?int? is not supported'); + $this->adapter->getPhinxType('?int?'); + } + + + public function testAddIndexTwoTablesSameIndex() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->save(); + $table2 = new \Phinx\Db\Table('table2', array(), $this->adapter); + $table2->addColumn('email', 'string') + ->save(); + + $this->assertFalse($table->hasIndex('email')); + $this->assertFalse($table2->hasIndex('email')); + + $table->addIndex('email') + ->save(); + $table2->addIndex('email') + ->save(); + + $this->assertTrue($table->hasIndex('email')); + $this->assertTrue($table2->hasIndex('email')); + } + + public function testInsertData() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'integer') + ->insert( + array("column1", "column2"), + array( + array('value1', 1), + array('value2', 2) + ) + ) + ->insert( + array("column1", "column2"), + array( + array('value3', 3), + ) + ) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals('value3', $rows[2]['column1']); + $this->assertEquals(1, $rows[0]['column2']); + $this->assertEquals(2, $rows[1]['column2']); + $this->assertEquals(3, $rows[2]['column2']); + } + + public function testNullWithoutDefaultValue() + { + $this->markTestSkipped('Skipping for now. See Github Issue #265.'); + + // construct table with default/null combinations + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn("aa", "string", array("null" => true)) // no default value + ->addColumn("bb", "string", array("null" => false)) // no default value + ->addColumn("cc", "string", array("null" => true, "default" => "some1")) + ->addColumn("dd", "string", array("null" => false, "default" => "some2")) + ->save(); + + // load table info + $columns = $this->adapter->getColumns("table1"); + + $this->assertEquals(count($columns), 5); + + $aa = $columns[1]; + $bb = $columns[2]; + $cc = $columns[3]; + $dd = $columns[4]; + + $this->assertEquals("aa", $aa->getName()); + $this->assertEquals(true, $aa->isNull()); + $this->assertEquals(null, $aa->getDefault()); + + $this->assertEquals("bb", $bb->getName()); + $this->assertEquals(false, $bb->isNull()); + $this->assertEquals(null, $bb->getDefault()); + + $this->assertEquals("cc", $cc->getName()); + $this->assertEquals(true, $cc->isNull()); + $this->assertEquals("some1", $cc->getDefault()); + + $this->assertEquals("dd", $dd->getName()); + $this->assertEquals(false, $dd->isNull()); + $this->assertEquals("some2", $dd->getDefault()); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php new file mode 100644 index 00000000..90a6d95c --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php @@ -0,0 +1,666 @@ +markTestSkipped('SqlServer tests disabled. See TESTS_PHINX_DB_ADAPTER_SQLSRV_ENABLED constant.'); + } + + $options = array( + 'host' => TESTS_PHINX_DB_ADAPTER_SQLSRV_HOST, + 'name' => TESTS_PHINX_DB_ADAPTER_SQLSRV_DATABASE, + 'user' => TESTS_PHINX_DB_ADAPTER_SQLSRV_USERNAME, + 'pass' => TESTS_PHINX_DB_ADAPTER_SQLSRV_PASSWORD, + 'port' => TESTS_PHINX_DB_ADAPTER_SQLSRV_PORT + ); + $this->adapter = new SqlServerAdapter($options, new NullOutput()); + + // ensure the database is empty for each test + $this->adapter->dropDatabase($options['name']); + $this->adapter->createDatabase($options['name']); + + // leave the adapter in a disconnected state for each test + $this->adapter->disconnect(); + } + + public function tearDown() + { + unset($this->adapter); + } + + public function testConnection() + { + $this->assertTrue($this->adapter->getConnection() instanceof \PDO); + } + + public function testConnectionWithoutPort() + { + $options = $this->adapter->getOptions(); + unset($options['port']); + $this->adapter->setOptions($options); + $this->assertTrue($this->adapter->getConnection() instanceof \PDO); + } + + public function testConnectionWithInvalidCredentials() + { + $options = array( + 'host' => TESTS_PHINX_DB_ADAPTER_SQLSRV_HOST, + 'name' => TESTS_PHINX_DB_ADAPTER_SQLSRV_DATABASE, + 'port' => TESTS_PHINX_DB_ADAPTER_SQLSRV_PORT, + 'user' => 'invaliduser', + 'pass' => 'invalidpass' + ); + + try { + $adapter = new SqlServerAdapter($options, new NullOutput()); + $adapter->connect(); + $this->fail('Expected the adapter to throw an exception'); + } catch (\InvalidArgumentException $e) { + $this->assertInstanceOf( + 'InvalidArgumentException', + $e, + 'Expected exception of type InvalidArgumentException, got ' . get_class($e) + ); + $this->assertRegExp('/There was a problem connecting to the database/', $e->getMessage()); + } + } + + public function testCreatingTheSchemaTableOnConnect() + { + $this->adapter->connect(); + $this->assertTrue($this->adapter->hasTable($this->adapter->getSchemaTableName())); + $this->adapter->dropTable($this->adapter->getSchemaTableName()); + $this->assertFalse($this->adapter->hasTable($this->adapter->getSchemaTableName())); + $this->adapter->disconnect(); + $this->adapter->connect(); + $this->assertTrue($this->adapter->hasTable($this->adapter->getSchemaTableName())); + } + + public function testSchemaTableIsCreatedWithPrimaryKey() + { + $this->adapter->connect(); + $table = new \Phinx\Db\Table($this->adapter->getSchemaTableName(), array(), $this->adapter); + $this->assertTrue($this->adapter->hasIndex($this->adapter->getSchemaTableName(), array('version'))); + } + + public function testQuoteTableName() + { + $this->assertEquals('[test_table]', $this->adapter->quoteTableName('test_table')); + } + + public function testQuoteColumnName() + { + $this->assertEquals('[test_column]', $this->adapter->quoteColumnName('test_column')); + } + + public function testCreateTable() + { + $table = new \Phinx\Db\Table('ntable', array(), $this->adapter); + $table->addColumn('realname', 'string') + ->addColumn('email', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'email')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + } + + public function testCreateTableCustomIdColumn() + { + $table = new \Phinx\Db\Table('ntable', array('id' => 'custom_id'), $this->adapter); + $table->addColumn('realname', 'string') + ->addColumn('email', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasTable('ntable')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'custom_id')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'realname')); + $this->assertTrue($this->adapter->hasColumn('ntable', 'email')); + $this->assertFalse($this->adapter->hasColumn('ntable', 'address')); + } + + public function testCreateTableWithNoPrimaryKey() + { + $options = array( + 'id' => false + ); + $table = new \Phinx\Db\Table('atable', $options, $this->adapter); + $table->addColumn('user_id', 'integer') + ->save(); + $this->assertFalse($this->adapter->hasColumn('atable', 'id')); + } + + public function testCreateTableWithMultiplePrimaryKeys() + { + $options = array( + 'id' => false, + 'primary_key' => array('user_id', 'tag_id') + ); + $table = new \Phinx\Db\Table('table1', $options, $this->adapter); + $table->addColumn('user_id', 'integer') + ->addColumn('tag_id', 'integer') + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('user_id', 'tag_id'))); + $this->assertTrue($this->adapter->hasIndex('table1', array('tag_id', 'USER_ID'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('tag_id', 'user_email'))); + } + + public function testCreateTableWithMultipleIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addColumn('name', 'string') + ->addIndex('email') + ->addIndex('name') + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertTrue($this->adapter->hasIndex('table1', array('name'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_name'))); + } + + public function testCreateTableWithUniqueIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('unique' => true)) + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + } + + public function testCreateTableWithNamedIndexes() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('name' => 'myemailindex')) + ->save(); + $this->assertTrue($this->adapter->hasIndex('table1', array('email'))); + $this->assertFalse($this->adapter->hasIndex('table1', array('email', 'user_email'))); + } + + public function testRenameTable() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertTrue($this->adapter->hasTable('table1')); + $this->assertFalse($this->adapter->hasTable('table2')); + $this->adapter->renameTable('table1', 'table2'); + $this->assertFalse($this->adapter->hasTable('table1')); + $this->assertTrue($this->adapter->hasTable('table2')); + } + + public function testAddColumn() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $this->assertFalse($table->hasColumn('email')); + $table->addColumn('email', 'string') + ->save(); + $this->assertTrue($table->hasColumn('email')); + } + + public function testAddColumnWithDefaultValue() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_zero', 'string', array('default' => 'test')) + ->save(); + $columns = $this->adapter->getColumns('table1'); + foreach ($columns as $column) { + if ($column->getName() == 'default_zero') { + $this->assertEquals("test", $column->getDefault()); + } + } + } + + public function testAddColumnWithDefaultZero() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_zero', 'integer', array('default' => 0)) + ->save(); + $columns = $this->adapter->getColumns('table1'); + foreach ($columns as $column) { + if ($column->getName() == 'default_zero') { + $this->assertNotNull($column->getDefault()); + $this->assertEquals('0', $column->getDefault()); + } + } + } + + public function testAddColumnWithDefaultNull() { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table->addColumn('default_null', 'string', array('null' => true, 'default' => null)) + ->save(); + $columns = $this->adapter->getColumns('table1'); + foreach ($columns as $column) { + if ($column->getName() == 'default_null') { + $this->assertNull($column->getDefault()); + } + } + } + + public function testAddColumnWithDefaultBool() { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->save(); + $table + ->addColumn('default_false', 'integer', array('default' => false)) + ->addColumn('default_true', 'integer', array('default' => true)) + ->save(); + $columns = $this->adapter->getColumns('table1'); + foreach ($columns as $column) { + if ($column->getName() == 'default_false') { + $this->assertSame(0, $column->getDefault()); + } + if ($column->getName() == 'default_true') { + $this->assertSame(1, $column->getDefault()); + } + } + } + + public function testRenameColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $this->assertFalse($this->adapter->hasColumn('t', 'column2')); + $this->adapter->renameColumn('t', 'column1', 'column2'); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + $this->assertTrue($this->adapter->hasColumn('t', 'column2')); + } + + public function testRenamingANonExistentColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + + try { + $this->adapter->renameColumn('t', 'column2', 'column1'); + $this->fail('Expected the adapter to throw an exception'); + } catch (\InvalidArgumentException $e) { + $this->assertInstanceOf( + 'InvalidArgumentException', + $e, + 'Expected exception of type InvalidArgumentException, got ' . get_class($e) + ); + $this->assertEquals('The specified column does not exist: column2', $e->getMessage()); + } + } + + public function testChangeColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1->setType('string'); + $table->changeColumn('column1', $newColumn1); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $newColumn2 = new \Phinx\Db\Table\Column(); + $newColumn2->setName('column2') + ->setType('string') + ->setNull(true); + $table->changeColumn('column1', $newColumn2); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + $this->assertTrue($this->adapter->hasColumn('t', 'column2')); + $columns = $this->adapter->getColumns('t'); + foreach ($columns as $column) { + if ($column->getName() == 'column2') { + $this->assertTrue($column->isNull()); + } + } + } + + public function testChangeColumnDefaults() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string', array('default' => 'test')) + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + + $columns = $this->adapter->getColumns('t'); + $this->assertSame('test', $columns['column1']->getDefault()); + + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1 + ->setType('string') + ->setDefault('another test'); + $table->changeColumn('column1', $newColumn1); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + + $columns = $this->adapter->getColumns('t'); + $this->assertSame('another test', $columns['column1']->getDefault()); + } + + public function testChangeColumnDefaultToNull() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string', array('null' => true, 'default' => 'test')) + ->save(); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1 + ->setType('string') + ->setDefault(null); + $table->changeColumn('column1', $newColumn1); + $columns = $this->adapter->getColumns('t'); + $this->assertNull($columns['column1']->getDefault()); + } + + public function testChangeColumnDefaultToZero() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'integer') + ->save(); + $newColumn1 = new \Phinx\Db\Table\Column(); + $newColumn1 + ->setType('string') + ->setDefault(0); + $table->changeColumn('column1', $newColumn1); + $columns = $this->adapter->getColumns('t'); + $this->assertSame(0, $columns['column1']->getDefault()); + } + + public function testDropColumn() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string') + ->save(); + $this->assertTrue($this->adapter->hasColumn('t', 'column1')); + $this->adapter->dropColumn('t', 'column1'); + $this->assertFalse($this->adapter->hasColumn('t', 'column1')); + } + + public function testGetColumns() + { + $table = new \Phinx\Db\Table('t', array(), $this->adapter); + $table->addColumn('column1', 'string', array('null' => true, 'default' => null)) + ->addColumn('column2', 'integer', array('default' => 0)) + ->addColumn('column3', 'biginteger', array('default' => 5)) + ->addColumn('column4', 'text', array('default' => 'text')) + ->addColumn('column5', 'float') + ->addColumn('column6', 'decimal') + ->addColumn('column7', 'time') + ->addColumn('column8', 'timestamp') + ->addColumn('column9', 'date') + ->addColumn('column10', 'boolean') + ->addColumn('column11', 'datetime') + ->addColumn('column12', 'binary') + ->addColumn('column13', 'string', array('limit' => 10)); + $pendingColumns = $table->getPendingColumns(); + $table->save(); + $columns = $this->adapter->getColumns('t'); + $this->assertCount(count($pendingColumns) + 1, $columns); + for ($i = 0; $i++; $i < count($pendingColumns)) { + $this->assertEquals($pendingColumns[$i], $columns[$i + 1]); + } + + $this->assertNull($columns['column1']->getDefault()); + $this->assertSame(0, $columns['column2']->getDefault()); + $this->assertSame(5, $columns['column3']->getDefault()); + $this->assertSame('text', $columns['column4']->getDefault()); + } + + public function testAddIndex() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->save(); + $this->assertFalse($table->hasIndex('email')); + $table->addIndex('email') + ->save(); + $this->assertTrue($table->hasIndex('email')); + } + + public function testGetIndexes() + { + // single column index + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addColumn('username', 'string') + ->addIndex('email') + ->addIndex(array('email', 'username'), array('unique' => true, 'name' => 'email_username')) + ->save(); + + $indexes = $this->adapter->getIndexes('table1'); + $this->assertArrayHasKey('PK_table1', $indexes); + $this->assertArrayHasKey('table1_email', $indexes); + $this->assertArrayHasKey('email_username', $indexes); + + $this->assertEquals(array('id'), $indexes['PK_table1']['columns']); + $this->assertEquals(array('email'), $indexes['table1_email']['columns']); + $this->assertEquals(array('email', 'username'), $indexes['email_username']['columns']); + } + + public function testDropIndex() + { + // single column index + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email') + ->save(); + $this->assertTrue($table->hasIndex('email')); + $this->adapter->dropIndex($table->getName(), 'email'); + $this->assertFalse($table->hasIndex('email')); + return; + // multiple column index + $table2 = new \Phinx\Db\Table('table2', array(), $this->adapter); + $table2->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname')) + ->save(); + $this->assertTrue($table2->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndex($table2->getName(), array('fname', 'lname')); + $this->assertFalse($table2->hasIndex(array('fname', 'lname'))); + + // index with name specified, but dropping it by column name + $table3 = new \Phinx\Db\Table('table3', array(), $this->adapter); + $table3->addColumn('email', 'string') + ->addIndex('email', array('name' => 'someindexname')) + ->save(); + $this->assertTrue($table3->hasIndex('email')); + $this->adapter->dropIndex($table3->getName(), 'email'); + $this->assertFalse($table3->hasIndex('email')); + + // multiple column index with name specified + $table4 = new \Phinx\Db\Table('table4', array(), $this->adapter); + $table4->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname'), array('name' => 'multiname')) + ->save(); + $this->assertTrue($table4->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndex($table4->getName(), array('fname', 'lname')); + $this->assertFalse($table4->hasIndex(array('fname', 'lname'))); + } + + public function testDropIndexByName() + { + // single column index + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('email', 'string') + ->addIndex('email', array('name' => 'myemailindex')) + ->save(); + $this->assertTrue($table->hasIndex('email')); + $this->adapter->dropIndexByName($table->getName(), 'myemailindex'); + $this->assertFalse($table->hasIndex('email')); + + // multiple column index + $table2 = new \Phinx\Db\Table('table2', array(), $this->adapter); + $table2->addColumn('fname', 'string') + ->addColumn('lname', 'string') + ->addIndex(array('fname', 'lname'), + array('name' => 'twocolumnuniqueindex', 'unique' => true)) + ->save(); + $this->assertTrue($table2->hasIndex(array('fname', 'lname'))); + $this->adapter->dropIndexByName($table2->getName(), 'twocolumnuniqueindex'); + $this->assertFalse($table2->hasIndex(array('fname', 'lname'))); + } + + public function testAddForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function dropForeignKey() + { + $refTable = new \Phinx\Db\Table('ref_table', array(), $this->adapter); + $refTable->addColumn('field1', 'string')->save(); + + $table = new \Phinx\Db\Table('table', array(), $this->adapter); + $table->addColumn('ref_table_id', 'integer')->save(); + + $fk = new \Phinx\Db\Table\ForeignKey(); + $fk->setReferencedTable($refTable) + ->setColumns(array('ref_table_id')) + ->setReferencedColumns(array('id')); + + $this->adapter->addForeignKey($table, $fk); + $this->assertTrue($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + $this->adapter->dropForeignKey($table->getName(), array('ref_table_id')); + $this->assertFalse($this->adapter->hasForeignKey($table->getName(), array('ref_table_id'))); + } + + public function testHasDatabase() + { + $this->assertFalse($this->adapter->hasDatabase('fake_database_name')); + $this->assertTrue($this->adapter->hasDatabase(TESTS_PHINX_DB_ADAPTER_SQLSRV_DATABASE)); + } + + public function testDropDatabase() + { + $this->assertFalse($this->adapter->hasDatabase('phinx_temp_database')); + $this->adapter->createDatabase('phinx_temp_database'); + $this->assertTrue($this->adapter->hasDatabase('phinx_temp_database')); + $this->adapter->dropDatabase('phinx_temp_database'); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage The type: "idontexist" is not supported + */ + public function testInvalidSqlType() + { + $this->adapter->getSqlType('idontexist'); + } + + public function testGetPhinxType() + { + $this->assertEquals('integer', $this->adapter->getPhinxType('int')); + $this->assertEquals('integer', $this->adapter->getPhinxType('integer')); + + $this->assertEquals('biginteger', $this->adapter->getPhinxType('bigint')); + + $this->assertEquals('decimal', $this->adapter->getPhinxType('decimal')); + $this->assertEquals('decimal', $this->adapter->getPhinxType('numeric')); + + $this->assertEquals('float', $this->adapter->getPhinxType('real')); + + $this->assertEquals('boolean', $this->adapter->getPhinxType('bit')); + + $this->assertEquals('string', $this->adapter->getPhinxType('varchar')); + $this->assertEquals('string', $this->adapter->getPhinxType('nvarchar')); + $this->assertEquals('char', $this->adapter->getPhinxType('char')); + $this->assertEquals('char', $this->adapter->getPhinxType('nchar')); + + $this->assertEquals('text', $this->adapter->getPhinxType('text')); + + $this->assertEquals('datetime', $this->adapter->getPhinxType('timestamp')); + + $this->assertEquals('date', $this->adapter->getPhinxType('date')); + + $this->assertEquals('datetime', $this->adapter->getPhinxType('datetime')); + + } + + public function testAddColumnComment() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('field1', 'string', array('comment' => $comment = 'Comments from column "field1"')) + ->save(); + + $resultComment = $this->adapter->getColumnComment('table1', 'field1'); + + $this->assertEquals($comment, $resultComment, 'Dont set column comment correctly'); + } + + /** + * @dependss testAddColumnComment + */ + public function testChangeColumnComment() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('field1', 'string', array('comment' => 'Comments from column "field1"')) + ->save(); + + $table->changeColumn('field1', 'string', array('comment' => $comment = 'New Comments from column "field1"')) + ->save(); + + $resultComment = $this->adapter->getColumnComment('table1', 'field1'); + + $this->assertEquals($comment, $resultComment, 'Dont change column comment correctly'); + } + + /** + * @depends testAddColumnComment + */ + public function testRemoveColumnComment() + { + $table = new \Phinx\Db\Table('table1', array(), $this->adapter); + $table->addColumn('field1', 'string', array('comment' => 'Comments from column "field1"')) + ->save(); + + $table->changeColumn('field1', 'string', array('comment' => 'null')) + ->save(); + + $resultComment = $this->adapter->getColumnComment('table1', 'field1'); + + $this->assertEmpty($resultComment, 'Dont remove column comment correctly'); + } + + /** + * Test that column names are properly escaped when creating Foreign Keys + */ + public function testForignKeysArePropertlyEscaped() + { + $userId = 'user'; + $sessionId = 'session'; + + $local = new \Phinx\Db\Table('users', array('primary_key' => $userId, 'id' => $userId), $this->adapter); + $local->create(); + + $foreign = new \Phinx\Db\Table('sessions', array('primary_key' => $sessionId, 'id' => $sessionId), $this->adapter); + $foreign->addColumn('user', 'integer') + ->addForeignKey('user', 'users', $userId) + ->create(); + + $this->assertTrue($foreign->hasForeignKey('user')); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/TablePrefixAdapterTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/TablePrefixAdapterTest.php new file mode 100644 index 00000000..dea17b90 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Adapter/TablePrefixAdapterTest.php @@ -0,0 +1,337 @@ + 'pre_', + 'table_suffix' => '_suf', + ); + + $this->mock = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + + $this->mock + ->expects($this->any()) + ->method('getOption') + ->with($this->logicalOr( + $this->equalTo('table_prefix'), + $this->equalTo('table_suffix') + )) + ->will($this->returnCallback(function ($option) use ($options) { + return $options[$option]; + })); + + $this->adapter = new TablePrefixAdapter($this->mock); + } + + public function tearDown() + { + unset($this->adapter); + unset($this->mock); + } + + public function testGetAdapterTableName() + { + $tableName = $this->adapter->getAdapterTableName('table'); + $this->assertEquals('pre_table_suf', $tableName); + } + + public function testHasTable() + { + $this->mock + ->expects($this->once()) + ->method('hasTable') + ->with($this->equalTo('pre_table_suf')); + + $this->adapter->hasTable('table'); + } + + public function testCreateTable() + { + $table = new Table('table'); + + $this->mock + ->expects($this->once()) + ->method('createTable') + ->with($this->callback( + function ($table) { + return $table->getName() == 'pre_table_suf'; + } + )); + + $this->adapter->createTable($table); + } + + public function testRenameTable() + { + $this->mock + ->expects($this->once()) + ->method('renameTable') + ->with( + $this->equalTo('pre_old_suf'), + $this->equalTo('pre_new_suf') + ); + + $this->adapter->renameTable('old', 'new'); + } + + public function testDropTable() + { + $this->mock + ->expects($this->once()) + ->method('dropTable') + ->with($this->equalTo('pre_table_suf')); + + $this->adapter->dropTable('table'); + } + + public function testGetColumns() + { + $this->mock + ->expects($this->once()) + ->method('getColumns') + ->with($this->equalTo('pre_table_suf')); + + $this->adapter->getColumns('table'); + } + + public function testHasColumn() + { + $this->mock + ->expects($this->once()) + ->method('hasColumn') + ->with( + $this->equalTo('pre_table_suf'), + $this->equalTo('column') + ); + + $this->adapter->hasColumn('table', 'column'); + } + + public function testAddColumn() + { + $table = new Table('table'); + $column = new Column(); + + $this->mock + ->expects($this->once()) + ->method('addColumn') + ->with($this->callback( + function ($table) { + return $table->getName() == 'pre_table_suf'; + }, + $this->equalTo($column) + )); + + $this->adapter->addColumn($table, $column); + } + + public function testRenameColumn() + { + $this->mock + ->expects($this->once()) + ->method('renameColumn') + ->with( + $this->equalTo('pre_table_suf'), + $this->equalTo('column'), + $this->equalTo('new_column') + ); + + $this->adapter->renameColumn('table', 'column', 'new_column'); + } + + public function testChangeColumn() + { + $newColumn = new Column(); + + $this->mock + ->expects($this->once()) + ->method('changeColumn') + ->with( + $this->equalTo('pre_table_suf'), + $this->equalTo('column'), + $this->equalTo($newColumn) + ); + + $this->adapter->changeColumn('table', 'column', $newColumn); + } + + public function testDropColumn() + { + $this->mock + ->expects($this->once()) + ->method('dropColumn') + ->with( + $this->equalTo('pre_table_suf'), + $this->equalTo('column') + ); + + $this->adapter->dropColumn('table', 'column'); + } + + public function testHasIndex() + { + $columns = array(); + + $this->mock + ->expects($this->once()) + ->method('hasIndex') + ->with( + $this->equalTo('pre_table_suf'), + $this->equalTo($columns) + ); + + $this->adapter->hasIndex('table', $columns); + } + + public function testDropIndex() + { + $columns = array(); + $options = null; + + $this->mock + ->expects($this->once()) + ->method('dropIndex') + ->with( + $this->equalTo('pre_table_suf'), + $this->equalTo($columns), + $this->equalTo($options) + ); + + $this->adapter->dropIndex('table', $columns, $options); + } + + public function testDropIndexByName() + { + $this->mock + ->expects($this->once()) + ->method('dropIndexByName') + ->with( + $this->equalTo('pre_table_suf'), + $this->equalTo('index') + ); + + $this->adapter->dropIndexByName('table', 'index'); + } + + public function testHasForeignKey() + { + $columns = array(); + $constraint = null; + + $this->mock + ->expects($this->once()) + ->method('hasForeignKey') + ->with( + $this->equalTo('pre_table_suf'), + $this->equalTo($columns), + $this->equalTo($constraint) + ); + + $this->adapter->hasForeignKey('table', $columns, $constraint); + } + + public function testAddForeignKey() + { + $table = new Table('table'); + $foreignKey = new ForeignKey(); + + $this->mock + ->expects($this->once()) + ->method('addForeignKey') + ->with($this->callback( + function ($table) { + return $table->getName() == 'pre_table_suf'; + }, + $this->equalTo($foreignKey) + )); + + $this->adapter->addForeignKey($table, $foreignKey); + } + + public function testDropForeignKey() + { + $columns = array(); + $constraint = null; + + $this->mock + ->expects($this->once()) + ->method('dropForeignKey') + ->with( + $this->equalTo('pre_table_suf'), + $this->equalTo($columns), + $this->equalTo($constraint) + ); + + $this->adapter->dropForeignKey('table', $columns, $constraint); + } + + public function testAddTableWithForeignKey() + { + $this->mock + ->expects($this->any()) + ->method('isValidColumnType') + ->with($this->callback( + function ($column) { + return in_array($column->getType(), array('string', 'integer')); + } + )) + ->will($this->returnValue(true)); + + $table = new Table('table', array(), $this->adapter); + $table + ->addColumn('bar', 'string') + ->addColumn('relation', 'integer') + ->addForeignKey('relation', 'target_table', array('id')); + + $this->mock + ->expects($this->once()) + ->method('createTable') + ->with($this->callback( + function ($table) { + if ($table->getName() !== 'pre_table_suf') { + throw new \Exception(sprintf( + 'Table::getName was not prefixed/suffixed properly: "%s"', + $table->getName() + )); + } + $fks = $table->getForeignKeys(); + if (count($fks) !== 1) { + throw new \Exception(sprintf( + 'Table::getForeignKeys count was incorrect: %d', + count($fks) + )); + } + foreach ($fks as $fk) { + if ($fk->getReferencedTable()->getName() !== 'pre_target_table_suf') { + throw new \Exception(sprintf( + 'ForeignKey::getReferencedTable was not prefixed/suffixed properly: "%s"', + $fk->getReferencedTable->getName() + )); + } + } + return true; + } + )); + + $table->create(); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/Table/ForeignKeyTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/Table/ForeignKeyTest.php new file mode 100644 index 00000000..34271ba2 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/Table/ForeignKeyTest.php @@ -0,0 +1,90 @@ +fk = new ForeignKey(); + } + + public function testOnDeleteSetNullCanBeSetThroughOptions() + { + $this->assertEquals( + ForeignKey::SET_NULL, + $this->fk->setOptions(array('delete' => ForeignKey::SET_NULL))->getOnDelete() + ); + } + + public function testInitiallyActionsEmpty() + { + $this->assertNull($this->fk->getOnDelete()); + $this->assertNull($this->fk->getOnUpdate()); + } + + /** + * @param string $dirtyValue + * @param string $valueOfConstant + * @dataProvider actionsProvider + */ + public function testBothActionsCanBeSetThroughSetters($dirtyValue, $valueOfConstant) + { + $this->fk->setOnDelete($dirtyValue)->setOnUpdate($dirtyValue); + $this->assertEquals($valueOfConstant, $this->fk->getOnDelete()); + $this->assertEquals($valueOfConstant, $this->fk->getOnUpdate()); + } + + /** + * @param string $dirtyValue + * @param string $valueOfConstant + * @dataProvider actionsProvider + */ + public function testBothActionsCanBeSetThroughOptions($dirtyValue, $valueOfConstant) + { + $this->fk->setOptions(array( + 'delete' => $dirtyValue, + 'update' => $dirtyValue, + )); + $this->assertEquals($valueOfConstant, $this->fk->getOnDelete()); + $this->assertEquals($valueOfConstant, $this->fk->getOnUpdate()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUnknownActionsNotAlowedThroughSetter() + { + $this->fk->setOnDelete('i m dump'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUnknownActionsNotAlowedThroughOptions() + { + $this->fk->setOptions(array('update' => 'no yu a dumb')); + } + + public function actionsProvider() + { + return array( + array(ForeignKey::CASCADE, ForeignKey::CASCADE), + array(ForeignKey::RESTRICT, ForeignKey::RESTRICT), + array(ForeignKey::NO_ACTION, ForeignKey::NO_ACTION), + array(ForeignKey::SET_NULL, ForeignKey::SET_NULL), + array('no Action ', ForeignKey::NO_ACTION), + array('Set nuLL', ForeignKey::SET_NULL), + array('no_Action', ForeignKey::NO_ACTION), + array('Set_nuLL', ForeignKey::SET_NULL), + ); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Db/TableTest.php b/vendor/robmorgan/phinx/tests/Phinx/Db/TableTest.php new file mode 100644 index 00000000..05aa5695 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Db/TableTest.php @@ -0,0 +1,249 @@ +addColumn('realname', 'string'); + $this->fail('Expected the table object to throw an exception'); + } catch (\RuntimeException $e) { + $this->assertInstanceOf( + 'RuntimeException', + $e, + 'Expected exception of type RuntimeException, got ' . get_class($e) + ); + $this->assertRegExp('/An adapter must be specified to add a column./', $e->getMessage()); + } + } + + public function testAddColumnWithColumnObject() + { + $adapter = new MysqlAdapter(array()); + $column = new \Phinx\Db\Table\Column(); + $column->setName('email') + ->setType('integer'); + $table = new \Phinx\Db\Table('ntable', array(), $adapter); + $table->addColumn($column); + $columns = $table->getPendingColumns(); + $this->assertEquals('email', $columns[0]->getName()); + $this->assertEquals('integer', $columns[0]->getType()); + } + + public function testAddColumnWithAnInvalidColumnType() + { + try { + $adapter = new MysqlAdapter(array()); + $column = new \Phinx\Db\Table\Column(); + $column->setType('badtype'); + $table = new \Phinx\Db\Table('ntable', array(), $adapter); + $table->addColumn($column); + } catch (\InvalidArgumentException $e) { + $this->assertInstanceOf( + 'InvalidArgumentException', + $e, + 'Expected exception of type InvalidArgumentException, got ' . get_class($e) + ); + $this->assertRegExp('/^An invalid column type /', $e->getMessage()); + } + } + + public function testRemoveColumn() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('dropColumn'); + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $table->removeColumn('test'); + } + + public function testRenameColumn() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('renameColumn'); + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $table->renameColumn('test1', 'test2'); + } + + public function testChangeColumn() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('changeColumn'); + $newColumn = new \Phinx\Db\Table\Column(); + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $table->changeColumn('test1', $newColumn); + } + + public function testChangeColumnWithoutAColumnObject() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('changeColumn'); + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $table->changeColumn('test1', 'text', array('null' => false)); + } + + public function testGetColumns() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('getColumns'); + + $table = new \Phinx\Db\Table('table1', array(), $adapterStub); + $table->getColumns(); + } + + public function testAddIndex() + { + $adapter = new MysqlAdapter(array()); + $table = new \Phinx\Db\Table('ntable', array(), $adapter); + $table->addIndex(array('email'), array('unique' => true, 'name' => 'myemailindex')); + $indexes = $table->getIndexes(); + $this->assertEquals(\Phinx\Db\Table\Index::UNIQUE, $indexes[0]->getType()); + $this->assertEquals('myemailindex', $indexes[0]->getName()); + $this->assertContains('email', $indexes[0]->getColumns()); + } + + public function testAddIndexWithoutType() + { + $adapter = new MysqlAdapter(array()); + $table = new \Phinx\Db\Table('ntable', array(), $adapter); + $table->addIndex(array('email')); + $indexes = $table->getIndexes(); + $this->assertEquals(\Phinx\Db\Table\Index::INDEX, $indexes[0]->getType()); + $this->assertContains('email', $indexes[0]->getColumns()); + } + + public function testAddIndexWithIndexObject() + { + $adapter = new MysqlAdapter(array()); + $index = new \Phinx\Db\Table\Index(); + $index->setType(\Phinx\Db\Table\Index::INDEX) + ->setColumns(array('email')); + $table = new \Phinx\Db\Table('ntable', array(), $adapter); + $table->addIndex($index); + $indexes = $table->getIndexes(); + $this->assertEquals(\Phinx\Db\Table\Index::INDEX, $indexes[0]->getType()); + $this->assertContains('email', $indexes[0]->getColumns()); + } + + public function testRemoveIndex() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('dropIndex'); + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $table->removeIndex(array('email')); + } + + public function testRemoveIndexByName() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('dropIndexByName'); + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $table->removeIndexByName('emailindex'); + } + + public function testAddForeignKey() + { + $adapter = new MysqlAdapter(array()); + $table = new \Phinx\Db\Table('ntable', array(), $adapter); + $table->addForeignKey('test', 'testTable', 'testRef'); + $fks = $table->getForeignKeys(); + $this->assertCount(1, $fks); + $this->assertContains('test', $fks[0]->getColumns()); + $this->assertContains('testRef', $fks[0]->getReferencedColumns()); + $this->assertEquals('testTable', $fks[0]->getReferencedTable()->getName()); + } + + public function testDropForeignKey() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('dropForeignKey'); + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $table->dropForeignKey('test'); + } + + public function testAddTimestamps() + { + $adapter = new MysqlAdapter(array()); + $table = new \Phinx\Db\Table('ntable', array(), $adapter); + $table->addTimestamps(); + + $columns = $table->getPendingColumns(); + + $this->assertEquals('created_at', $columns[0]->getName()); + $this->assertEquals('timestamp', $columns[0]->getType()); + $this->assertEquals('CURRENT_TIMESTAMP', $columns[0]->getDefault()); + $this->assertEquals('', $columns[0]->getUpdate()); + + $this->assertEquals('updated_at', $columns[1]->getName()); + $this->assertEquals('timestamp', $columns[1]->getType()); + $this->assertTrue($columns[1]->isNull()); + $this->assertNull($columns[1]->getDefault()); + } + + public function testInsert() + { + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $columns = array("column1", "column2"); + $data = array( array("value1", "value2") ); + $table->insert($columns, $data); + $expectedData = array( + array("columns" => $columns, "data" => $data) + ); + $this->assertEquals($expectedData, $table->getData()); + } + + public function testInsertSaveData() + { + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $columns = array("column1"); + $data = array( + array("value1"), + array("value2") + ); + $moreData = array( + array("value3"), + array("value4") + ); + + $adapterStub->expects($this->exactly(2)) + ->method('insert') + ->with($table, $columns, $this->logicalOr($data, $moreData)); + + $table->insert($columns, $data) + ->insert($columns, $moreData) + ->save(); + } + + public function testResetAfterAddingData() + { + $adapterStub = $this->getMock('\Phinx\Db\Adapter\MysqlAdapter', array(), array(array())); + $table = new \Phinx\Db\Table('ntable', array(), $adapterStub); + $columns = array("column1"); + $data = array(array("value1")); + $table->insert($columns, $data)->save(); + $this->assertEquals(array(), $table->getData()); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Migration/AbstractMigrationTest.php b/vendor/robmorgan/phinx/tests/Phinx/Migration/AbstractMigrationTest.php new file mode 100644 index 00000000..88d84e06 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Migration/AbstractMigrationTest.php @@ -0,0 +1,196 @@ +getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + $this->assertNull($migrationStub->up()); + } + + public function testDown() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + $this->assertNull($migrationStub->down()); + } + + public function testAdapterMethods() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + + // test methods + $this->assertNull($migrationStub->getAdapter()); + $migrationStub->setAdapter($adapterStub); + $this->assertTrue($migrationStub->getAdapter() instanceof AdapterInterface); + } + + public function testSetOutputMethods() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub output + $outputStub = $this->getMock('\Symfony\Component\Console\Output\OutputInterface', array(), array(array())); + + // test methods + $this->assertNull($migrationStub->getOutput()); + $migrationStub->setOutput($outputStub); + $this->assertInstanceOf('\Symfony\Component\Console\Output\OutputInterface', $migrationStub->getOutput()); + } + + public function testGetName() + { + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + $this->assertFalse(!(strpos($migrationStub->getName(), 'AbstractMigration'))); + } + + public function testVersionMethods() + { + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(20120103080000)); + $this->assertEquals(20120103080000, $migrationStub->getVersion()); + $migrationStub->setVersion(20120915093312); + $this->assertEquals(20120915093312, $migrationStub->getVersion()); + } + + public function testExecute() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('execute') + ->will($this->returnValue(2)); + + $migrationStub->setAdapter($adapterStub); + $this->assertEquals(2, $migrationStub->execute('SELECT FOO FROM BAR')); + } + + public function testQuery() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('query') + ->will($this->returnValue(array(array('0' => 'bar', 'foo' => 'bar')))); + + $migrationStub->setAdapter($adapterStub); + $this->assertEquals(array(array('0' => 'bar', 'foo' => 'bar')), $migrationStub->query('SELECT FOO FROM BAR')); + } + + public function testFetchRow() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('fetchRow') + ->will($this->returnValue(array('0' => 'bar', 'foo' => 'bar'))); + + $migrationStub->setAdapter($adapterStub); + $this->assertEquals(array('0' => 'bar', 'foo' => 'bar'), $migrationStub->fetchRow('SELECT FOO FROM BAR')); + } + + public function testFetchAll() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('fetchAll') + ->will($this->returnValue(array(array('0' => 'bar', 'foo' => 'bar')))); + + $migrationStub->setAdapter($adapterStub); + $this->assertEquals(array(array('0' => 'bar', 'foo' => 'bar')), $migrationStub->fetchAll('SELECT FOO FROM BAR')); + } + + public function testCreateDatabase() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('createDatabase') + ->will($this->returnValue(array(array('0' => 'bar', 'foo' => 'bar')))); + + $migrationStub->setAdapter($adapterStub); + $migrationStub->createDatabase('testdb', array()); + } + + public function testDropDatabase() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('dropDatabase') + ->will($this->returnValue(array(array('0' => 'bar', 'foo' => 'bar')))); + + $migrationStub->setAdapter($adapterStub); + $migrationStub->dropDatabase('testdb'); + } + + public function testHasTable() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('hasTable') + ->will($this->returnValue(true)); + + $migrationStub->setAdapter($adapterStub); + $this->assertTrue($migrationStub->hasTable('test_table')); + } + + public function testTableMethod() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $migrationStub->setAdapter($adapterStub); + + $this->assertTrue($migrationStub->table('test_table') instanceof Table); + } + + public function testDropTableMethod() + { + // stub migration + $migrationStub = $this->getMockForAbstractClass('\Phinx\Migration\AbstractMigration', array(0)); + + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('dropTable'); + + $migrationStub->setAdapter($adapterStub); + $migrationStub->dropTable('test_table'); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Migration/Manager/EnvironmentTest.php b/vendor/robmorgan/phinx/tests/Phinx/Migration/Manager/EnvironmentTest.php new file mode 100644 index 00000000..01a3f990 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Migration/Manager/EnvironmentTest.php @@ -0,0 +1,214 @@ +environment = new Environment('test', array()); + } + + public function testConstructorWorksAsExpected() + { + $env = new Environment('testenv', array('foo' => 'bar')); + $this->assertEquals('testenv', $env->getName()); + $this->assertArrayHasKey('foo', $env->getOptions()); + } + + public function testSettingTheName() + { + $this->environment->setName('prod123'); + $this->assertEquals('prod123', $this->environment->getName()); + } + + public function testSettingOptions() + { + $this->environment->setOptions(array('foo' => 'bar')); + $this->assertArrayHasKey('foo', $this->environment->getOptions()); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Adapter "fakeadapter" has not been registered + */ + public function testInvalidAdapter() + { + $this->environment->setOptions(array('adapter' => 'fakeadapter')); + $this->environment->getAdapter(); + } + + /** + * @expectedException \RuntimeException + */ + public function testNoAdapter() + { + $this->environment->getAdapter(); + } + + public function testGetAdapterWithExistingPdoInstance() + { + $adapter = $this->getMockForAbstractClass('\Phinx\Db\Adapter\PdoAdapter', array(array('foo' => 'bar'))); + AdapterFactory::instance()->registerAdapter('pdomock', $adapter); + $this->environment->setOptions(array('connection' => new PDOMock())); + $options = $this->environment->getAdapter()->getOptions(); + $this->assertEquals('pdomock', $options['adapter']); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage The specified connection is not a PDO instance + */ + public function testGetAdapterWithBadExistingPdoInstance() + { + $this->environment->setOptions(array('connection' => new \stdClass())); + $this->environment->getAdapter(); + } + + public function testTablePrefixAdapter() + { + $this->environment->setOptions(array('table_prefix' => 'tbl_', 'adapter' => 'mysql')); + $this->assertInstanceOf('Phinx\Db\Adapter\TablePrefixAdapter', $this->environment->getAdapter()); + + $tablePrefixAdapter = $this->environment->getAdapter(); + $this->assertInstanceOf('Phinx\Db\Adapter\MysqlAdapter', $tablePrefixAdapter->getAdapter()); + } + + public function testSchemaName() + { + $this->assertEquals('phinxlog', $this->environment->getSchemaTableName()); + + $this->environment->setSchemaTableName('changelog'); + $this->assertEquals('changelog', $this->environment->getSchemaTableName()); + } + + public function testCurrentVersion() + { + $stub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $stub->expects($this->any()) + ->method('getVersions') + ->will($this->returnValue(array('20110301080000'))); + + $this->environment->setAdapter($stub); + + $this->assertEquals('20110301080000', $this->environment->getCurrentVersion()); + } + + public function testExecutingAMigrationUp() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('migrated') + ->will($this->returnArgument(0)); + + $this->environment->setAdapter($adapterStub); + + // up + $upMigration = $this->getMock('\Phinx\Migration\AbstractMigration', array('up'), array('20110301080000')); + $upMigration->expects($this->once()) + ->method('up'); + + $this->environment->executeMigration($upMigration, MigrationInterface::UP); + } + + public function testExecutingAMigrationDown() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('migrated') + ->will($this->returnArgument(0)); + + $this->environment->setAdapter($adapterStub); + + // down + $downMigration = $this->getMock('\Phinx\Migration\AbstractMigration', array('down'), array('20110301080000')); + $downMigration->expects($this->once()) + ->method('down'); + + $this->environment->executeMigration($downMigration, MigrationInterface::DOWN); + } + + public function testExecutingAMigrationWithTransactions() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('beginTransaction'); + + $adapterStub->expects($this->once()) + ->method('commitTransaction'); + + $adapterStub->expects($this->exactly(2)) + ->method('hasTransactions') + ->will($this->returnValue(true)); + + $this->environment->setAdapter($adapterStub); + + // migrate + $migration = $this->getMock('\Phinx\Migration\AbstractMigration', array('up'), array('20110301080000')); + $migration->expects($this->once()) + ->method('up'); + + $this->environment->executeMigration($migration, MigrationInterface::UP); + } + + public function testExecutingAChangeMigrationUp() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('migrated') + ->will($this->returnArgument(0)); + + $this->environment->setAdapter($adapterStub); + + // migration + $migration = $this->getMock('\Phinx\Migration\AbstractMigration', array('change'), array('20130301080000')); + $migration->expects($this->once()) + ->method('change'); + + $this->environment->executeMigration($migration, MigrationInterface::UP); + } + + public function testExecutingAChangeMigrationDown() + { + // stub adapter + $adapterStub = $this->getMock('\Phinx\Db\Adapter\PdoAdapter', array(), array(array())); + $adapterStub->expects($this->once()) + ->method('migrated') + ->will($this->returnArgument(0)); + + $this->environment->setAdapter($adapterStub); + + // migration + $migration = $this->getMock('\Phinx\Migration\AbstractMigration', array('change'), array('20130301080000')); + $migration->expects($this->once()) + ->method('change'); + + $this->environment->executeMigration($migration, MigrationInterface::DOWN); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Migration/ManagerTest.php b/vendor/robmorgan/phinx/tests/Phinx/Migration/ManagerTest.php new file mode 100644 index 00000000..03a86f47 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Migration/ManagerTest.php @@ -0,0 +1,308 @@ +getConfigArray()); + $output = new StreamOutput(fopen('php://memory', 'a', false)); + $this->manager = new Manager($config, $output); + } + + protected function tearDown() + { + $this->manager = null; + } + + private function getCorrectedPath($path) + { + return str_replace('/', DIRECTORY_SEPARATOR, $path); + } + + /** + * Returns a sample configuration array for use with the unit tests. + * + * @return array + */ + public function getConfigArray() + { + return array( + 'paths' => array( + 'migrations' => $this->getCorrectedPath(__DIR__ . '/_files/migrations'), + ), + 'environments' => array( + 'default_migration_table' => 'phinxlog', + 'default_database' => 'production', + 'production' => array( + 'adapter' => 'mysql', + 'host' => TESTS_PHINX_DB_ADAPTER_MYSQL_HOST, + 'name' => TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE, + 'user' => TESTS_PHINX_DB_ADAPTER_MYSQL_USERNAME, + 'pass' => TESTS_PHINX_DB_ADAPTER_MYSQL_PASSWORD, + 'port' => TESTS_PHINX_DB_ADAPTER_MYSQL_PORT + ) + ) + ); + } + + public function testInstantiation() + { + $this->assertTrue($this->manager->getOutput() instanceof StreamOutput); + } + + public function testPrintStatusMethod() + { + // stub environment + $envStub = $this->getMock('\Phinx\Migration\Manager\Environment', array(), array('mockenv', array())); + $envStub->expects($this->once()) + ->method('getVersions') + ->will($this->returnValue(array('20120111235330', '20120116183504'))); + + $this->manager->setEnvironments(array('mockenv' => $envStub)); + $this->manager->printStatus('mockenv'); + + rewind($this->manager->getOutput()->getStream()); + $outputStr = stream_get_contents($this->manager->getOutput()->getStream()); + $this->assertRegExp('/up 20120111235330 TestMigration/', $outputStr); + $this->assertRegExp('/up 20120116183504 TestMigration2/', $outputStr); + } + + public function testPrintStatusMethodWithNoMigrations() + { + // stub environment + $envStub = $this->getMock('\Phinx\Migration\Manager\Environment', array(), array('mockenv', array())); + + // override the migrations directory to an empty one + $configArray = $this->getConfigArray(); + $configArray['paths']['migrations'] = $this->getCorrectedPath(__DIR__ . '/_files/nomigrations'); + $config = new Config($configArray); + + $this->manager->setConfig($config); + $this->manager->setEnvironments(array('mockenv' => $envStub)); + $this->manager->printStatus('mockenv'); + + rewind($this->manager->getOutput()->getStream()); + $outputStr = stream_get_contents($this->manager->getOutput()->getStream()); + $this->assertRegExp('/There are no available migrations. Try creating one using the create command./', $outputStr); + } + + public function testPrintStatusMethodWithMissingMigrations() + { + // stub environment + $envStub = $this->getMock('\Phinx\Migration\Manager\Environment', array(), array('mockenv', array())); + $envStub->expects($this->once()) + ->method('getVersions') + ->will($this->returnValue(array('20120103083300', '20120815145812'))); + + $this->manager->setEnvironments(array('mockenv' => $envStub)); + $this->manager->printStatus('mockenv'); + + rewind($this->manager->getOutput()->getStream()); + $outputStr = stream_get_contents($this->manager->getOutput()->getStream()); + $this->assertRegExp('/up 20120103083300 \*\* MISSING \*\*/', $outputStr); + $this->assertRegExp('/up 20120815145812 \*\* MISSING \*\*/', $outputStr); + } + + public function testGetMigrationsWithDuplicateMigrationVersions() + { + $this->setExpectedException( + 'InvalidArgumentException', + 'Duplicate migration - "' . $this->getCorrectedPath(__DIR__ . '/_files/duplicateversions/20120111235330_duplicate_migration_2.php') . '" has the same version as "20120111235330"' + ); + $config = new Config(array('paths' => array('migrations' => $this->getCorrectedPath(__DIR__ . '/_files/duplicateversions')))); + $output = new StreamOutput(fopen('php://memory', 'a', false)); + $manager = new Manager($config, $output); + $manager->getMigrations(); + } + + public function testGetMigrationsWithDuplicateMigrationNames() + { + $this->setExpectedException( + 'InvalidArgumentException', + 'Migration "20120111235331_duplicate_migration_name.php" has the same name as "20120111235330_duplicate_migration_name.php"' + ); + $config = new Config(array('paths' => array('migrations' => $this->getCorrectedPath(__DIR__ . '/_files/duplicatenames')))); + $output = new StreamOutput(fopen('php://memory', 'a', false)); + $manager = new Manager($config, $output); + $manager->getMigrations(); + } + + public function testGetMigrationsWithInvalidMigrationClassName() + { + $this->setExpectedException( + 'InvalidArgumentException', + 'Could not find class "InvalidClass" in file "' . $this->getCorrectedPath(__DIR__ . '/_files/invalidclassname/20120111235330_invalid_class.php') . '"' + ); + $config = new Config(array('paths' => array('migrations' => $this->getCorrectedPath(__DIR__ . '/_files/invalidclassname')))); + $output = new StreamOutput(fopen('php://memory', 'a', false)); + $manager = new Manager($config, $output); + $manager->getMigrations(); + } + + public function testGetMigrationsWithClassThatDoesntExtendAbstractMigration() + { + $this->setExpectedException( + 'InvalidArgumentException', + 'The class "InvalidSuperClass" in file "' . $this->getCorrectedPath(__DIR__ . '/_files/invalidsuperclass/20120111235330_invalid_super_class.php') . '" must extend \Phinx\Migration\AbstractMigration' + ); + $config = new Config(array('paths' => array('migrations' => $this->getCorrectedPath(__DIR__ . '/_files/invalidsuperclass')))); + $output = new StreamOutput(fopen('php://memory', 'a', false)); + $manager = new Manager($config, $output); + $manager->getMigrations(); + } + + public function testGettingAValidEnvironment() + { + $this->assertTrue($this->manager->getEnvironment('production') instanceof Environment); + } + + /** + * Test that migrating by date chooses the correct + * migration to point to. + * + * @dataProvider migrateDateDataProvider + */ + public function testMigrationsByDate($availableMigrations, $dateString, $expectedMigration) + { + // stub environment + $envStub = $this->getMock('\Phinx\Migration\Manager\Environment', array(), array('mockenv', array())); + $envStub->expects($this->once()) + ->method('getVersions') + ->will($this->returnValue($availableMigrations)); + + $this->manager->setEnvironments(array('mockenv' => $envStub)); + $this->manager->migrateToDateTime('mockenv', new \DateTime($dateString)); + rewind($this->manager->getOutput()->getStream()); + $output = stream_get_contents($this->manager->getOutput()->getStream()); + if (is_null($expectedMigration)) { + $this->assertEmpty($output); + } else { + $this->assertContains($expectedMigration, $output); + } + } + + /** + * Test that migrating by date chooses the correct + * migration to point to. + * + * @dataProvider rollbackDateDataProvider + */ + public function testRollbacksByDate($availableRollbacks, $dateString, $expectedRollback) + { + // stub environment + $envStub = $this->getMock('\Phinx\Migration\Manager\Environment', array(), array('mockenv', array())); + $envStub->expects($this->any()) + ->method('getVersions') + ->will($this->returnValue($availableRollbacks)); + + $this->manager->setEnvironments(array('mockenv' => $envStub)); + $this->manager->rollbackToDateTime('mockenv', new \DateTime($dateString)); + rewind($this->manager->getOutput()->getStream()); + $output = stream_get_contents($this->manager->getOutput()->getStream()); + if (is_null($expectedRollback)) { + $this->assertEmpty($output); + } else { + $this->assertContains($expectedRollback, $output); + } + } + + /** + * Migration lists, dates, and expected migrations to point to. + * + * @return array + */ + public function migrateDateDataProvider() + { + return array( + array(array('20120111235330', '20120116183504'), '20120118', '20120116183504'), + array(array('20120111235330', '20120116183504'), '20120115', '20120111235330'), + array(array('20120111235330', '20120116183504'), '20110115', null), + ); + } + + /** + * Migration lists, dates, and expected migrations to point to. + * + * @return array + */ + public function rollbackDateDataProvider() + { + return array( + array(array('20120111235330', '20120116183504', '20120120183504'), '20120118', '20120116183504'), + array(array('20120111235330', '20120116183504'), '20120115', '20120111235330'), + array(array('20120111235330', '20120116183504'), '20110115', '20120111235330'), + ); + } + + public function testGettingOutputObject() + { + $migrations = $this->manager->getMigrations(); + $outputObject = $this->manager->getOutput(); + $this->assertInstanceOf('\Symfony\Component\Console\Output\OutputInterface', $outputObject); + foreach ($migrations as $migration) { + $this->assertEquals($outputObject, $migration->getOutput()); + } + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The environment "invalidenv" does not exist + */ + public function testGettingAnInvalidEnvironment() + { + $this->manager->getEnvironment('invalidenv'); + } + + public function testReversibleMigrationsWorkAsExpected() + { + if (!TESTS_PHINX_DB_ADAPTER_MYSQL_ENABLED) { + $this->markTestSkipped('Mysql tests disabled. See TESTS_PHINX_DB_ADAPTER_MYSQL_ENABLED constant.'); + } + $configArray = $this->getConfigArray(); + $adapter = $this->manager->getEnvironment('production')->getAdapter(); + + // override the migrations directory to use the reversible migrations + $configArray['paths']['migrations'] = $this->getCorrectedPath(__DIR__ . '/_files/reversiblemigrations'); + $config = new Config($configArray); + + // ensure the database is empty + $adapter->dropDatabase(TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE); + $adapter->createDatabase(TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE); + $adapter->disconnect(); + + // migrate to the latest version + $this->manager->setConfig($config); + $this->manager->migrate('production'); + + // ensure up migrations worked + $this->assertFalse($adapter->hasTable('info')); + $this->assertTrue($adapter->hasTable('statuses')); + $this->assertTrue($adapter->hasTable('users')); + $this->assertTrue($adapter->hasTable('user_logins')); + $this->assertTrue($adapter->hasColumn('users', 'biography')); + $this->assertTrue($adapter->hasForeignKey('user_logins', array('user_id'))); + + // revert all changes to the first + $this->manager->rollback('production', '20121213232502'); + + // ensure reversed migrations worked + $this->assertTrue($adapter->hasTable('info')); + $this->assertFalse($adapter->hasTable('statuses')); + $this->assertFalse($adapter->hasTable('user_logins')); + $this->assertTrue($adapter->hasColumn('users', 'bio')); + $this->assertFalse($adapter->hasForeignKey('user_logins', array('user_id'))); + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Migration/UtilTest.php b/vendor/robmorgan/phinx/tests/Phinx/Migration/UtilTest.php new file mode 100644 index 00000000..c0521328 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Migration/UtilTest.php @@ -0,0 +1,87 @@ +getCorrectedPath(__DIR__ . '/_files/migrations')); + $this->assertCount(count($expectedResults), $existingClassNames); + foreach ($expectedResults as $expectedResult) { + $this->arrayHasKey($expectedResult, $existingClassNames); + } + } + + public function testGetExistingMigrationClassNamesWithFile() + { + $file = $this->getCorrectedPath(__DIR__ . '/_files/migrations/20120111235330_test_migration.php'); + $existingClassNames = Util::getExistingMigrationClassNames($file); + $this->assertCount(0, $existingClassNames); + } + + public function testGetCurrentTimestamp() + { + $dt = new \DateTime('now', new \DateTimeZone('UTC')); + $expected = $dt->format(Util::DATE_FORMAT); + + $current = Util::getCurrentTimestamp(); + + // Rather than using a strict equals, we use greater/lessthan checks to + // prevent false positives when the test hits the edge of a second. + $this->assertGreaterThanOrEqual($expected, $current); + // We limit the assertion time to 2 seconds, which should never fail. + $this->assertLessThanOrEqual($expected + 2, $current); + } + + public function testMapClassNameToFileName() + { + $expectedResults = array( + 'CamelCase87afterSomeBooze' => '/^\d{14}_camel_case87after_some_booze\.php$/', + 'CreateUserTable' => '/^\d{14}_create_user_table\.php$/', + 'LimitResourceNamesTo30Chars' => '/^\d{14}_limit_resource_names_to30_chars\.php$/', + ); + + foreach ($expectedResults as $input => $expectedResult) { + $this->assertRegExp($expectedResult, Util::mapClassNameToFileName($input)); + } + } + + public function testMapFileNameToClassName() + { + $expectedResults = array( + '20150902094024_create_user_table.php' => 'CreateUserTable', + '20150902102548_my_first_migration2.php' => 'MyFirstMigration2', + ); + + foreach ($expectedResults as $input => $expectedResult) { + $this->assertEquals($expectedResult, Util::mapFileNameToClassName($input)); + } + } + + public function testIsValidMigrationClassName() + { + $expectedResults = array( + 'CAmelCase' => false, + 'CreateUserTable' => true, + 'Test' => true, + 'test' => false + ); + + foreach ($expectedResults as $input => $expectedResult) { + $this->assertEquals($expectedResult, Util::isValidMigrationClassName($input)); + } + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/duplicatenames/20120111235330_duplicate_migration_name.php b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/duplicatenames/20120111235330_duplicate_migration_name.php new file mode 100644 index 00000000..98387694 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/duplicatenames/20120111235330_duplicate_migration_name.php @@ -0,0 +1,22 @@ +table('users'); + $users->addColumn('username', 'string', array('limit' => 20)) + ->addColumn('password', 'string', array('limit' => 40)) + ->addColumn('password_salt', 'string', array('limit' => 40)) + ->addColumn('email', 'string', array('limit' => 100)) + ->addColumn('first_name', 'string', array('limit' => 30)) + ->addColumn('last_name', 'string', array('limit' => 30)) + ->addColumn('bio', 'string', array('limit' => 160, 'null' => true, 'default' => null)) + ->addColumn('profile_image_url', 'string', array('limit' => 120, 'null' => true, 'default' => null)) + ->addColumn('twitter', 'string', array('limit' => 30, 'null' => true, 'default' => null)) + ->addColumn('role', 'string', array('limit' => 20)) + ->addColumn('confirmed', 'boolean', array('null' => true, 'default' => null)) + ->addColumn('confirmation_key', 'string', array('limit' => 40)) + ->addColumn('created', 'datetime') + ->addColumn('updated', 'datetime', array('default' => null)) + ->addIndex(array('username', 'email'), array('unique' => true)) + ->create(); + + // info table + $info = $this->table('info'); + $info->addColumn('username', 'string', array('limit' => 20)) + ->create(); + } + + /** + * Migrate Up. + */ + public function up() + { + + } + + /** + * Migrate Down. + */ + public function down() + { + + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121223011815_update_info_table.php b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121223011815_update_info_table.php new file mode 100644 index 00000000..5f58d6ad --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121223011815_update_info_table.php @@ -0,0 +1,33 @@ +table('info'); + $info->addColumn('password', 'string', array('limit' => 40)) + ->update(); + } + + /** + * Migrate Up. + */ + public function up() + { + + } + + /** + * Migrate Down. + */ + public function down() + { + + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121224200649_rename_info_table_to_statuses_table.php b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121224200649_rename_info_table_to_statuses_table.php new file mode 100644 index 00000000..ae94d0ff --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121224200649_rename_info_table_to_statuses_table.php @@ -0,0 +1,32 @@ +table('info'); + $table->rename('statuses'); + } + + /** + * Migrate Up. + */ + public function up() + { + + } + + /** + * Migrate Down. + */ + public function down() + { + + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121224200739_rename_bio_to_biography.php b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121224200739_rename_bio_to_biography.php new file mode 100644 index 00000000..bbe19a7c --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121224200739_rename_bio_to_biography.php @@ -0,0 +1,32 @@ +table('users'); + $table->renameColumn('bio', 'biography'); + } + + /** + * Migrate Up. + */ + public function up() + { + + } + + /** + * Migrate Down. + */ + public function down() + { + + } +} diff --git a/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121224200852_create_user_logins_table.php b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121224200852_create_user_logins_table.php new file mode 100644 index 00000000..99129dee --- /dev/null +++ b/vendor/robmorgan/phinx/tests/Phinx/Migration/_files/reversiblemigrations/20121224200852_create_user_logins_table.php @@ -0,0 +1,38 @@ +table('user_logins'); + $table->addColumn('user_id', 'integer') + ->addColumn('created', 'datetime') + ->create(); + + // add a foreign key back to the users table + $table->addForeignKey('user_id', 'users', array('id')) + ->update(); + } + + /** + * Migrate Up. + */ + public function up() + { + + } + + /** + * Migrate Down. + */ + public function down() + { + + } +} diff --git a/vendor/robmorgan/phinx/tests/TestConfiguration.php b/vendor/robmorgan/phinx/tests/TestConfiguration.php new file mode 100644 index 00000000..49385600 --- /dev/null +++ b/vendor/robmorgan/phinx/tests/TestConfiguration.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Use the notation: + * + * defined(...) || define(...); + * + * This ensures that, when a test is marked to run in a separate process, + * PHP will not complain of a constant already being defined. + */ + +/** + * Phinx_Db_Adapter_SqlServerAdapter + */ +defined('TESTS_PHINX_DB_ADAPTER_SQLSRV_ENABLED') || define('TESTS_PHINX_DB_ADAPTER_SQLSRV_ENABLED', getenv('TESTS_PHINX_DB_ADAPTER_SQLSRV_ENABLED')); +defined('TESTS_PHINX_DB_ADAPTER_SQLSRV_HOST') || define('TESTS_PHINX_DB_ADAPTER_SQLSRV_HOST', getenv('TESTS_PHINX_DB_ADAPTER_SQLSRV_HOST')); +defined('TESTS_PHINX_DB_ADAPTER_SQLSRV_USERNAME') || define('TESTS_PHINX_DB_ADAPTER_SQLSRV_USERNAME', getenv('TESTS_PHINX_DB_ADAPTER_SQLSRV_USERNAME')); +defined('TESTS_PHINX_DB_ADAPTER_SQLSRV_PASSWORD') || define('TESTS_PHINX_DB_ADAPTER_SQLSRV_PASSWORD', getenv('TESTS_PHINX_DB_ADAPTER_SQLSRV_PASSWORD')); +defined('TESTS_PHINX_DB_ADAPTER_SQLSRV_DATABASE') || define('TESTS_PHINX_DB_ADAPTER_SQLSRV_DATABASE', getenv('TESTS_PHINX_DB_ADAPTER_SQLSRV_DATABASE')); +defined('TESTS_PHINX_DB_ADAPTER_SQLSRV_PORT') || define('TESTS_PHINX_DB_ADAPTER_SQLSRV_PORT', getenv('TESTS_PHINX_DB_ADAPTER_SQLSRV_PORT')); + +/** + * Phinx_Db_Adapter_MysqlAdapter + */ +defined('TESTS_PHINX_DB_ADAPTER_MYSQL_ENABLED') || define('TESTS_PHINX_DB_ADAPTER_MYSQL_ENABLED', getenv('TESTS_PHINX_DB_ADAPTER_MYSQL_ENABLED')); +defined('TESTS_PHINX_DB_ADAPTER_MYSQL_HOST') || define('TESTS_PHINX_DB_ADAPTER_MYSQL_HOST', getenv('TESTS_PHINX_DB_ADAPTER_MYSQL_HOST')); +defined('TESTS_PHINX_DB_ADAPTER_MYSQL_USERNAME') || define('TESTS_PHINX_DB_ADAPTER_MYSQL_USERNAME', getenv('TESTS_PHINX_DB_ADAPTER_MYSQL_USERNAME')); +defined('TESTS_PHINX_DB_ADAPTER_MYSQL_PASSWORD') || define('TESTS_PHINX_DB_ADAPTER_MYSQL_PASSWORD', getenv('TESTS_PHINX_DB_ADAPTER_MYSQL_PASSWORD')); +defined('TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE') || define('TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE', getenv('TESTS_PHINX_DB_ADAPTER_MYSQL_DATABASE')); +defined('TESTS_PHINX_DB_ADAPTER_MYSQL_PORT') || define('TESTS_PHINX_DB_ADAPTER_MYSQL_PORT', getenv('TESTS_PHINX_DB_ADAPTER_MYSQL_PORT')); +defined('TESTS_PHINX_DB_ADAPTER_MYSQL_UNIX_SOCKET') || define('TESTS_PHINX_DB_ADAPTER_MYSQL_UNIX_SOCKET', getenv('TESTS_PHINX_DB_ADAPTER_MYSQL_UNIX_SOCKET')); + +/** + * Phinx_Db_Adapter_PostgresAdapter + */ +defined('TESTS_PHINX_DB_ADAPTER_POSTGRES_ENABLED') || define('TESTS_PHINX_DB_ADAPTER_POSTGRES_ENABLED', getenv('TESTS_PHINX_DB_ADAPTER_POSTGRES_ENABLED')); +defined('TESTS_PHINX_DB_ADAPTER_POSTGRES_HOST') || define('TESTS_PHINX_DB_ADAPTER_POSTGRES_HOST', getenv('TESTS_PHINX_DB_ADAPTER_POSTGRES_HOST')); +defined('TESTS_PHINX_DB_ADAPTER_POSTGRES_USERNAME') || define('TESTS_PHINX_DB_ADAPTER_POSTGRES_USERNAME', getenv('TESTS_PHINX_DB_ADAPTER_POSTGRES_USERNAME')); +defined('TESTS_PHINX_DB_ADAPTER_POSTGRES_PASSWORD') || define('TESTS_PHINX_DB_ADAPTER_POSTGRES_PASSWORD', getenv('TESTS_PHINX_DB_ADAPTER_POSTGRES_PASSWORD')); +defined('TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE') || define('TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE', getenv('TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE')); +defined('TESTS_PHINX_DB_ADAPTER_POSTGRES_PORT') || define('TESTS_PHINX_DB_ADAPTER_POSTGRES_PORT', getenv('TESTS_PHINX_DB_ADAPTER_POSTGRES_PORT')); +defined('TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE_SCHEMA') || define('TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE_SCHEMA', getenv('TESTS_PHINX_DB_ADAPTER_POSTGRES_DATABASE_SCHEMA')); + +/** + * Phinx_Db_Adapter_SQLiteAdapter + */ +defined('TESTS_PHINX_DB_ADAPTER_SQLITE_ENABLED') || define('TESTS_PHINX_DB_ADAPTER_SQLITE_ENABLED', getenv('TESTS_PHINX_DB_ADAPTER_SQLITE_ENABLED')); +defined('TESTS_PHINX_DB_ADAPTER_SQLITE_DATABASE') || define('TESTS_PHINX_DB_ADAPTER_SQLITE_DATABASE', getenv('TESTS_PHINX_DB_ADAPTER_SQLITE_DATABASE')); diff --git a/vendor/robmorgan/phinx/tests/phpunit-bootstrap.php b/vendor/robmorgan/phinx/tests/phpunit-bootstrap.php new file mode 100644 index 00000000..a5e3b4cd --- /dev/null +++ b/vendor/robmorgan/phinx/tests/phpunit-bootstrap.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + +$autoloader = require __DIR__ . '/../src/composer_autoloader.php'; + +if (!$autoloader()) { + die( + 'You need to set up the project dependencies using the following commands:' . PHP_EOL . + 'curl -s http://getcomposer.org/installer | php' . PHP_EOL . + 'php composer.phar install' . PHP_EOL + ); +} + +require __DIR__ . '/TestConfiguration.php'; diff --git a/vendor/symfony/config/.gitignore b/vendor/symfony/config/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/config/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/config/CHANGELOG.md b/vendor/symfony/config/CHANGELOG.md new file mode 100644 index 00000000..e1b19e6c --- /dev/null +++ b/vendor/symfony/config/CHANGELOG.md @@ -0,0 +1,27 @@ +CHANGELOG +========= + +2.7.0 +----- + + * added `ConfigCacheInterface`, `ConfigCacheFactoryInterface` and a basic `ConfigCacheFactory` + implementation to delegate creation of ConfigCache instances + +2.2.0 +----- + + * added ArrayNodeDefinition::canBeEnabled() and ArrayNodeDefinition::canBeDisabled() + to ease configuration when some sections are respectively disabled / enabled + by default. + * added a `normalizeKeys()` method for array nodes (to avoid key normalization) + * added numerical type handling for config definitions + * added convenience methods for optional configuration sections to ArrayNodeDefinition + * added a utils class for XML manipulations + +2.1.0 +----- + + * added a way to add documentation on configuration + * implemented `Serializable` on resources + * LoaderResolverInterface is now used instead of LoaderResolver for type + hinting diff --git a/vendor/symfony/config/ConfigCache.php b/vendor/symfony/config/ConfigCache.php new file mode 100644 index 00000000..cc99bc92 --- /dev/null +++ b/vendor/symfony/config/ConfigCache.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\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; + +/** + * ConfigCache manages PHP cache files. + * + * When debug is enabled, it knows when to flush the cache + * thanks to an array of ResourceInterface instances. + * + * @author Fabien Potencier + */ +class ConfigCache implements ConfigCacheInterface +{ + private $debug; + private $file; + + /** + * @param string $file The absolute cache path + * @param bool $debug Whether debugging is enabled or not + */ + public function __construct($file, $debug) + { + $this->file = $file; + $this->debug = (bool) $debug; + } + + /** + * Gets the cache file path. + * + * @return string The cache file path + * + * @deprecated since 2.7, to be removed in 3.0. Use getPath() instead. + */ + public function __toString() + { + @trigger_error('ConfigCache::__toString() is deprecated since version 2.7 and will be removed in 3.0. Use the getPath() method instead.', E_USER_DEPRECATED); + + return $this->file; + } + + /** + * Gets the cache file path. + * + * @return string The cache file path + */ + public function getPath() + { + return $this->file; + } + + /** + * Checks if the cache is still fresh. + * + * This method always returns true when debug is off and the + * cache file exists. + * + * @return bool true if the cache is fresh, false otherwise + */ + public function isFresh() + { + if (!is_file($this->file)) { + return false; + } + + if (!$this->debug) { + return true; + } + + $metadata = $this->getMetaFile(); + if (!is_file($metadata)) { + return false; + } + + $time = filemtime($this->file); + $meta = unserialize(file_get_contents($metadata)); + foreach ($meta as $resource) { + if (!$resource->isFresh($time)) { + return false; + } + } + + return true; + } + + /** + * Writes cache. + * + * @param string $content The content to write in the cache + * @param ResourceInterface[] $metadata An array of ResourceInterface instances + * + * @throws \RuntimeException When cache file can't be written + */ + public function write($content, array $metadata = null) + { + $mode = 0666; + $umask = umask(); + $filesystem = new Filesystem(); + $filesystem->dumpFile($this->file, $content, null); + try { + $filesystem->chmod($this->file, $mode, $umask); + } catch (IOException $e) { + // discard chmod failure (some filesystem may not support it) + } + + if (null !== $metadata && true === $this->debug) { + $filesystem->dumpFile($this->getMetaFile(), serialize($metadata), null); + try { + $filesystem->chmod($this->getMetaFile(), $mode, $umask); + } catch (IOException $e) { + // discard chmod failure (some filesystem may not support it) + } + } + } + + /** + * Gets the meta file path. + * + * @return string The meta file path + */ + private function getMetaFile() + { + return $this->file.'.meta'; + } +} diff --git a/vendor/symfony/config/ConfigCacheFactory.php b/vendor/symfony/config/ConfigCacheFactory.php new file mode 100644 index 00000000..5a8f4562 --- /dev/null +++ b/vendor/symfony/config/ConfigCacheFactory.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\Config; + +/** + * Basic implementation for ConfigCacheFactoryInterface + * that will simply create an instance of ConfigCache. + * + * @author Matthias Pigulla + */ +class ConfigCacheFactory implements ConfigCacheFactoryInterface +{ + /** + * @var bool Debug flag passed to the ConfigCache + */ + private $debug; + + /** + * @param bool $debug The debug flag to pass to ConfigCache + */ + public function __construct($debug) + { + $this->debug = $debug; + } + + /** + * {@inheritdoc} + */ + public function cache($file, $callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', gettype($callback))); + } + + $cache = new ConfigCache($file, $this->debug); + if (!$cache->isFresh()) { + call_user_func($callback, $cache); + } + + return $cache; + } +} diff --git a/vendor/symfony/config/ConfigCacheFactoryInterface.php b/vendor/symfony/config/ConfigCacheFactoryInterface.php new file mode 100644 index 00000000..bd614c4b --- /dev/null +++ b/vendor/symfony/config/ConfigCacheFactoryInterface.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\Config; + +/** + * Interface for a ConfigCache factory. This factory creates + * an instance of ConfigCacheInterface and initializes the + * cache if necessary. + * + * @author Matthias Pigulla + */ +interface ConfigCacheFactoryInterface +{ + /** + * Creates a cache instance and (re-)initializes it if necessary. + * + * @param string $file The absolute cache file path + * @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback + * + * @return ConfigCacheInterface $configCache The cache instance + */ + public function cache($file, $callable); +} diff --git a/vendor/symfony/config/ConfigCacheInterface.php b/vendor/symfony/config/ConfigCacheInterface.php new file mode 100644 index 00000000..e367ad18 --- /dev/null +++ b/vendor/symfony/config/ConfigCacheInterface.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\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Interface for ConfigCache. + * + * @author Matthias Pigulla + */ +interface ConfigCacheInterface +{ + /** + * Gets the cache file path. + * + * @return string The cache file path + */ + public function getPath(); + + /** + * Checks if the cache is still fresh. + * + * This check should take the metadata passed to the write() method into consideration. + * + * @return bool Whether the cache is still fresh. + */ + public function isFresh(); + + /** + * Writes the given content into the cache file. Metadata will be stored + * independently and can be used to check cache freshness at a later time. + * + * @param string $content The content to write into the cache + * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances + * + * @throws \RuntimeException When the cache file cannot be written + */ + public function write($content, array $metadata = null); +} diff --git a/vendor/symfony/config/Definition/ArrayNode.php b/vendor/symfony/config/Definition/ArrayNode.php new file mode 100644 index 00000000..05ae1fdc --- /dev/null +++ b/vendor/symfony/config/Definition/ArrayNode.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\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; + +/** + * Represents an Array node in the config tree. + * + * @author Johannes M. Schmitt + */ +class ArrayNode extends BaseNode implements PrototypeNodeInterface +{ + protected $xmlRemappings = array(); + protected $children = array(); + protected $allowFalse = false; + protected $allowNewKeys = true; + protected $addIfNotSet = false; + protected $performDeepMerging = true; + protected $ignoreExtraKeys = false; + protected $normalizeKeys = true; + + public function setNormalizeKeys($normalizeKeys) + { + $this->normalizeKeys = (bool) $normalizeKeys; + } + + /** + * Normalizes keys between the different configuration formats. + * + * Namely, you mostly have foo_bar in YAML while you have foo-bar in XML. + * After running this method, all keys are normalized to foo_bar. + * + * If you have a mixed key like foo-bar_moo, it will not be altered. + * The key will also not be altered if the target key already exists. + * + * @param mixed $value + * + * @return array The value with normalized keys + */ + protected function preNormalize($value) + { + if (!$this->normalizeKeys || !is_array($value)) { + return $value; + } + + foreach ($value as $k => $v) { + if (false !== strpos($k, '-') && false === strpos($k, '_') && !array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) { + $value[$normalizedKey] = $v; + unset($value[$k]); + } + } + + return $value; + } + + /** + * Retrieves the children of this node. + * + * @return array The children + */ + public function getChildren() + { + return $this->children; + } + + /** + * Sets the xml remappings that should be performed. + * + * @param array $remappings an array of the form array(array(string, string)) + */ + public function setXmlRemappings(array $remappings) + { + $this->xmlRemappings = $remappings; + } + + /** + * Gets the xml remappings that should be performed. + * + * @return array $remappings an array of the form array(array(string, string)) + */ + public function getXmlRemappings() + { + return $this->xmlRemappings; + } + + /** + * Sets whether to add default values for this array if it has not been + * defined in any of the configuration files. + * + * @param bool $boolean + */ + public function setAddIfNotSet($boolean) + { + $this->addIfNotSet = (bool) $boolean; + } + + /** + * Sets whether false is allowed as value indicating that the array should be unset. + * + * @param bool $allow + */ + public function setAllowFalse($allow) + { + $this->allowFalse = (bool) $allow; + } + + /** + * Sets whether new keys can be defined in subsequent configurations. + * + * @param bool $allow + */ + public function setAllowNewKeys($allow) + { + $this->allowNewKeys = (bool) $allow; + } + + /** + * Sets if deep merging should occur. + * + * @param bool $boolean + */ + public function setPerformDeepMerging($boolean) + { + $this->performDeepMerging = (bool) $boolean; + } + + /** + * Whether extra keys should just be ignore without an exception. + * + * @param bool $boolean To allow extra keys + */ + public function setIgnoreExtraKeys($boolean) + { + $this->ignoreExtraKeys = (bool) $boolean; + } + + /** + * Sets the node Name. + * + * @param string $name The node's name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Checks if the node has a default value. + * + * @return bool + */ + public function hasDefaultValue() + { + return $this->addIfNotSet; + } + + /** + * Retrieves the default value. + * + * @return array The default value + * + * @throws \RuntimeException if the node has no default value + */ + public function getDefaultValue() + { + if (!$this->hasDefaultValue()) { + throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath())); + } + + $defaults = array(); + foreach ($this->children as $name => $child) { + if ($child->hasDefaultValue()) { + $defaults[$name] = $child->getDefaultValue(); + } + } + + return $defaults; + } + + /** + * Adds a child node. + * + * @param NodeInterface $node The child node to add + * + * @throws \InvalidArgumentException when the child node has no name + * @throws \InvalidArgumentException when the child node's name is not unique + */ + public function addChild(NodeInterface $node) + { + $name = $node->getName(); + if (!strlen($name)) { + throw new \InvalidArgumentException('Child nodes must be named.'); + } + if (isset($this->children[$name])) { + throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name)); + } + + $this->children[$name] = $node; + } + + /** + * Finalizes the value of this node. + * + * @param mixed $value + * + * @return mixed The finalised value + * + * @throws UnsetKeyException + * @throws InvalidConfigurationException if the node doesn't have enough children + */ + protected function finalizeValue($value) + { + if (false === $value) { + $msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value)); + throw new UnsetKeyException($msg); + } + + foreach ($this->children as $name => $child) { + if (!array_key_exists($name, $value)) { + if ($child->isRequired()) { + $msg = sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath()); + $ex = new InvalidConfigurationException($msg); + $ex->setPath($this->getPath()); + + throw $ex; + } + + if ($child->hasDefaultValue()) { + $value[$name] = $child->getDefaultValue(); + } + + continue; + } + + try { + $value[$name] = $child->finalize($value[$name]); + } catch (UnsetKeyException $e) { + unset($value[$name]); + } + } + + return $value; + } + + /** + * Validates the type of the value. + * + * @param mixed $value + * + * @throws InvalidTypeException + */ + protected function validateType($value) + { + if (!is_array($value) && (!$this->allowFalse || false !== $value)) { + $ex = new InvalidTypeException(sprintf( + 'Invalid type for path "%s". Expected array, but got %s', + $this->getPath(), + gettype($value) + )); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + /** + * Normalizes the value. + * + * @param mixed $value The value to normalize + * + * @return mixed The normalized value + * + * @throws InvalidConfigurationException + */ + protected function normalizeValue($value) + { + if (false === $value) { + return $value; + } + + $value = $this->remapXml($value); + + $normalized = array(); + foreach ($value as $name => $val) { + if (isset($this->children[$name])) { + $normalized[$name] = $this->children[$name]->normalize($val); + unset($value[$name]); + } + } + + // if extra fields are present, throw exception + if (count($value) && !$this->ignoreExtraKeys) { + $msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath()); + $ex = new InvalidConfigurationException($msg); + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $normalized; + } + + /** + * Remaps multiple singular values to a single plural value. + * + * @param array $value The source values + * + * @return array The remapped values + */ + protected function remapXml($value) + { + foreach ($this->xmlRemappings as $transformation) { + list($singular, $plural) = $transformation; + + if (!isset($value[$singular])) { + continue; + } + + $value[$plural] = Processor::normalizeConfig($value, $singular, $plural); + unset($value[$singular]); + } + + return $value; + } + + /** + * Merges values together. + * + * @param mixed $leftSide The left side to merge. + * @param mixed $rightSide The right side to merge. + * + * @return mixed The merged values + * + * @throws InvalidConfigurationException + * @throws \RuntimeException + */ + protected function mergeValues($leftSide, $rightSide) + { + if (false === $rightSide) { + // if this is still false after the last config has been merged the + // finalization pass will take care of removing this key entirely + return false; + } + + if (false === $leftSide || !$this->performDeepMerging) { + return $rightSide; + } + + foreach ($rightSide as $k => $v) { + // no conflict + if (!array_key_exists($k, $leftSide)) { + if (!$this->allowNewKeys) { + $ex = new InvalidConfigurationException(sprintf( + 'You are not allowed to define new elements for path "%s". ' + .'Please define all elements for this path in one config file. ' + .'If you are trying to overwrite an element, make sure you redefine it ' + .'with the same name.', + $this->getPath() + )); + $ex->setPath($this->getPath()); + + throw $ex; + } + + $leftSide[$k] = $v; + continue; + } + + if (!isset($this->children[$k])) { + throw new \RuntimeException('merge() expects a normalized config array.'); + } + + $leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v); + } + + return $leftSide; + } +} diff --git a/vendor/symfony/config/Definition/BaseNode.php b/vendor/symfony/config/Definition/BaseNode.php new file mode 100644 index 00000000..fc3e0129 --- /dev/null +++ b/vendor/symfony/config/Definition/BaseNode.php @@ -0,0 +1,356 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * The base node class. + * + * @author Johannes M. Schmitt + */ +abstract class BaseNode implements NodeInterface +{ + protected $name; + protected $parent; + protected $normalizationClosures = array(); + protected $finalValidationClosures = array(); + protected $allowOverwrite = true; + protected $required = false; + protected $equivalentValues = array(); + protected $attributes = array(); + + /** + * Constructor. + * + * @param string $name The name of the node + * @param NodeInterface $parent The parent of this node + * + * @throws \InvalidArgumentException if the name contains a period. + */ + public function __construct($name, NodeInterface $parent = null) + { + if (false !== strpos($name, '.')) { + throw new \InvalidArgumentException('The name must not contain ".".'); + } + + $this->name = $name; + $this->parent = $parent; + } + + public function setAttribute($key, $value) + { + $this->attributes[$key] = $value; + } + + public function getAttribute($key, $default = null) + { + return isset($this->attributes[$key]) ? $this->attributes[$key] : $default; + } + + public function hasAttribute($key) + { + return isset($this->attributes[$key]); + } + + public function getAttributes() + { + return $this->attributes; + } + + public function setAttributes(array $attributes) + { + $this->attributes = $attributes; + } + + public function removeAttribute($key) + { + unset($this->attributes[$key]); + } + + /** + * Sets an info message. + * + * @param string $info + */ + public function setInfo($info) + { + $this->setAttribute('info', $info); + } + + /** + * Returns info message. + * + * @return string The info text + */ + public function getInfo() + { + return $this->getAttribute('info'); + } + + /** + * Sets the example configuration for this node. + * + * @param string|array $example + */ + public function setExample($example) + { + $this->setAttribute('example', $example); + } + + /** + * Retrieves the example configuration for this node. + * + * @return string|array The example + */ + public function getExample() + { + return $this->getAttribute('example'); + } + + /** + * Adds an equivalent value. + * + * @param mixed $originalValue + * @param mixed $equivalentValue + */ + public function addEquivalentValue($originalValue, $equivalentValue) + { + $this->equivalentValues[] = array($originalValue, $equivalentValue); + } + + /** + * Set this node as required. + * + * @param bool $boolean Required node + */ + public function setRequired($boolean) + { + $this->required = (bool) $boolean; + } + + /** + * Sets if this node can be overridden. + * + * @param bool $allow + */ + public function setAllowOverwrite($allow) + { + $this->allowOverwrite = (bool) $allow; + } + + /** + * Sets the closures used for normalization. + * + * @param \Closure[] $closures An array of Closures used for normalization + */ + public function setNormalizationClosures(array $closures) + { + $this->normalizationClosures = $closures; + } + + /** + * Sets the closures used for final validation. + * + * @param \Closure[] $closures An array of Closures used for final validation + */ + public function setFinalValidationClosures(array $closures) + { + $this->finalValidationClosures = $closures; + } + + /** + * Checks if this node is required. + * + * @return bool + */ + public function isRequired() + { + return $this->required; + } + + /** + * Returns the name of this node. + * + * @return string The Node's name. + */ + public function getName() + { + return $this->name; + } + + /** + * Retrieves the path of this node. + * + * @return string The Node's path + */ + public function getPath() + { + $path = $this->name; + + if (null !== $this->parent) { + $path = $this->parent->getPath().'.'.$path; + } + + return $path; + } + + /** + * Merges two values together. + * + * @param mixed $leftSide + * @param mixed $rightSide + * + * @return mixed The merged value + * + * @throws ForbiddenOverwriteException + */ + final public function merge($leftSide, $rightSide) + { + if (!$this->allowOverwrite) { + throw new ForbiddenOverwriteException(sprintf( + 'Configuration path "%s" cannot be overwritten. You have to ' + .'define all options for this path, and any of its sub-paths in ' + .'one configuration section.', + $this->getPath() + )); + } + + $this->validateType($leftSide); + $this->validateType($rightSide); + + return $this->mergeValues($leftSide, $rightSide); + } + + /** + * Normalizes a value, applying all normalization closures. + * + * @param mixed $value Value to normalize. + * + * @return mixed The normalized value. + */ + final public function normalize($value) + { + $value = $this->preNormalize($value); + + // run custom normalization closures + foreach ($this->normalizationClosures as $closure) { + $value = $closure($value); + } + + // replace value with their equivalent + foreach ($this->equivalentValues as $data) { + if ($data[0] === $value) { + $value = $data[1]; + } + } + + // validate type + $this->validateType($value); + + // normalize value + return $this->normalizeValue($value); + } + + /** + * Normalizes the value before any other normalization is applied. + * + * @param $value + * + * @return $value The normalized array value + */ + protected function preNormalize($value) + { + return $value; + } + + /** + * Returns parent node for this node. + * + * @return NodeInterface|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * Finalizes a value, applying all finalization closures. + * + * @param mixed $value The value to finalize + * + * @return mixed The finalized value + * + * @throws Exception + * @throws InvalidConfigurationException + */ + final public function finalize($value) + { + $this->validateType($value); + + $value = $this->finalizeValue($value); + + // Perform validation on the final value if a closure has been set. + // The closure is also allowed to return another value. + foreach ($this->finalValidationClosures as $closure) { + try { + $value = $closure($value); + } catch (Exception $e) { + throw $e; + } catch (\Exception $e) { + throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": %s', $this->getPath(), $e->getMessage()), $e->getCode(), $e); + } + } + + return $value; + } + + /** + * Validates the type of a Node. + * + * @param mixed $value The value to validate + * + * @throws InvalidTypeException when the value is invalid + */ + abstract protected function validateType($value); + + /** + * Normalizes the value. + * + * @param mixed $value The value to normalize. + * + * @return mixed The normalized value + */ + abstract protected function normalizeValue($value); + + /** + * Merges two values together. + * + * @param mixed $leftSide + * @param mixed $rightSide + * + * @return mixed The merged value + */ + abstract protected function mergeValues($leftSide, $rightSide); + + /** + * Finalizes a value. + * + * @param mixed $value The value to finalize + * + * @return mixed The finalized value + */ + abstract protected function finalizeValue($value); +} diff --git a/vendor/symfony/config/Definition/BooleanNode.php b/vendor/symfony/config/Definition/BooleanNode.php new file mode 100644 index 00000000..08e1a773 --- /dev/null +++ b/vendor/symfony/config/Definition/BooleanNode.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\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a Boolean value in the config tree. + * + * @author Johannes M. Schmitt + */ +class BooleanNode extends ScalarNode +{ + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + if (!is_bool($value)) { + $ex = new InvalidTypeException(sprintf( + 'Invalid type for path "%s". Expected boolean, but got %s.', + $this->getPath(), + gettype($value) + )); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + /** + * {@inheritdoc} + */ + protected function isValueEmpty($value) + { + // a boolean value cannot be empty + return false; + } +} diff --git a/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php new file mode 100644 index 00000000..c64b2ecf --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php @@ -0,0 +1,489 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\PrototypedArrayNode; +use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; + +/** + * This class provides a fluent interface for defining an array node. + * + * @author Johannes M. Schmitt + */ +class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface +{ + protected $performDeepMerging = true; + protected $ignoreExtraKeys = false; + protected $children = array(); + protected $prototype; + protected $atLeastOne = false; + protected $allowNewKeys = true; + protected $key; + protected $removeKeyItem; + protected $addDefaults = false; + protected $addDefaultChildren = false; + protected $nodeBuilder; + protected $normalizeKeys = true; + + /** + * {@inheritdoc} + */ + public function __construct($name, NodeParentInterface $parent = null) + { + parent::__construct($name, $parent); + + $this->nullEquivalent = array(); + $this->trueEquivalent = array(); + } + + /** + * Sets a custom children builder. + * + * @param NodeBuilder $builder A custom NodeBuilder + */ + public function setBuilder(NodeBuilder $builder) + { + $this->nodeBuilder = $builder; + } + + /** + * Returns a builder to add children nodes. + * + * @return NodeBuilder + */ + public function children() + { + return $this->getNodeBuilder(); + } + + /** + * Sets a prototype for child nodes. + * + * @param string $type the type of node + * + * @return NodeDefinition + */ + public function prototype($type) + { + return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this); + } + + /** + * Adds the default value if the node is not set in the configuration. + * + * This method is applicable to concrete nodes only (not to prototype nodes). + * If this function has been called and the node is not set during the finalization + * phase, it's default value will be derived from its children default values. + * + * @return ArrayNodeDefinition + */ + public function addDefaultsIfNotSet() + { + $this->addDefaults = true; + + return $this; + } + + /** + * Adds children with a default value when none are defined. + * + * @param int|string|array|null $children The number of children|The child name|The children names to be added + * + * This method is applicable to prototype nodes only. + * + * @return ArrayNodeDefinition + */ + public function addDefaultChildrenIfNoneSet($children = null) + { + $this->addDefaultChildren = $children; + + return $this; + } + + /** + * Requires the node to have at least one element. + * + * This method is applicable to prototype nodes only. + * + * @return ArrayNodeDefinition + */ + public function requiresAtLeastOneElement() + { + $this->atLeastOne = true; + + return $this; + } + + /** + * Disallows adding news keys in a subsequent configuration. + * + * If used all keys have to be defined in the same configuration file. + * + * @return ArrayNodeDefinition + */ + public function disallowNewKeysInSubsequentConfigs() + { + $this->allowNewKeys = false; + + return $this; + } + + /** + * Sets a normalization rule for XML configurations. + * + * @param string $singular The key to remap + * @param string $plural The plural of the key for irregular plurals + * + * @return ArrayNodeDefinition + */ + public function fixXmlConfig($singular, $plural = null) + { + $this->normalization()->remap($singular, $plural); + + return $this; + } + + /** + * Sets the attribute which value is to be used as key. + * + * This is useful when you have an indexed array that should be an + * associative array. You can select an item from within the array + * to be the key of the particular item. For example, if "id" is the + * "key", then: + * + * array( + * array('id' => 'my_name', 'foo' => 'bar'), + * ); + * + * becomes + * + * array( + * 'my_name' => array('foo' => 'bar'), + * ); + * + * If you'd like "'id' => 'my_name'" to still be present in the resulting + * array, then you can set the second argument of this method to false. + * + * This method is applicable to prototype nodes only. + * + * @param string $name The name of the key + * @param bool $removeKeyItem Whether or not the key item should be removed. + * + * @return ArrayNodeDefinition + */ + public function useAttributeAsKey($name, $removeKeyItem = true) + { + $this->key = $name; + $this->removeKeyItem = $removeKeyItem; + + return $this; + } + + /** + * Sets whether the node can be unset. + * + * @param bool $allow + * + * @return ArrayNodeDefinition + */ + public function canBeUnset($allow = true) + { + $this->merge()->allowUnset($allow); + + return $this; + } + + /** + * Adds an "enabled" boolean to enable the current section. + * + * By default, the section is disabled. If any configuration is specified then + * the node will be automatically enabled: + * + * enableableArrayNode: {enabled: true, ...} # The config is enabled & default values get overridden + * enableableArrayNode: ~ # The config is enabled & use the default values + * enableableArrayNode: true # The config is enabled & use the default values + * enableableArrayNode: {other: value, ...} # The config is enabled & default values get overridden + * enableableArrayNode: {enabled: false, ...} # The config is disabled + * enableableArrayNode: false # The config is disabled + * + * @return ArrayNodeDefinition + */ + public function canBeEnabled() + { + $this + ->addDefaultsIfNotSet() + ->treatFalseLike(array('enabled' => false)) + ->treatTrueLike(array('enabled' => true)) + ->treatNullLike(array('enabled' => true)) + ->beforeNormalization() + ->ifArray() + ->then(function ($v) { + $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true; + + return $v; + }) + ->end() + ->children() + ->booleanNode('enabled') + ->defaultFalse() + ; + + return $this; + } + + /** + * Adds an "enabled" boolean to enable the current section. + * + * By default, the section is enabled. + * + * @return ArrayNodeDefinition + */ + public function canBeDisabled() + { + $this + ->addDefaultsIfNotSet() + ->treatFalseLike(array('enabled' => false)) + ->treatTrueLike(array('enabled' => true)) + ->treatNullLike(array('enabled' => true)) + ->children() + ->booleanNode('enabled') + ->defaultTrue() + ; + + return $this; + } + + /** + * Disables the deep merging of the node. + * + * @return ArrayNodeDefinition + */ + public function performNoDeepMerging() + { + $this->performDeepMerging = false; + + return $this; + } + + /** + * Allows extra config keys to be specified under an array without + * throwing an exception. + * + * Those config values are simply ignored and removed from the + * resulting array. This should be used only in special cases where + * you want to send an entire configuration array through a special + * tree that processes only part of the array. + * + * @return ArrayNodeDefinition + */ + public function ignoreExtraKeys() + { + $this->ignoreExtraKeys = true; + + return $this; + } + + /** + * Sets key normalization. + * + * @param bool $bool Whether to enable key normalization + * + * @return ArrayNodeDefinition + */ + public function normalizeKeys($bool) + { + $this->normalizeKeys = (bool) $bool; + + return $this; + } + + /** + * Appends a node definition. + * + * $node = new ArrayNodeDefinition() + * ->children() + * ->scalarNode('foo')->end() + * ->scalarNode('baz')->end() + * ->end() + * ->append($this->getBarNodeDefinition()) + * ; + * + * @param NodeDefinition $node A NodeDefinition instance + * + * @return ArrayNodeDefinition This node + */ + public function append(NodeDefinition $node) + { + $this->children[$node->name] = $node->setParent($this); + + return $this; + } + + /** + * Returns a node builder to be used to add children and prototype. + * + * @return NodeBuilder The node builder + */ + protected function getNodeBuilder() + { + if (null === $this->nodeBuilder) { + $this->nodeBuilder = new NodeBuilder(); + } + + return $this->nodeBuilder->setParent($this); + } + + /** + * {@inheritdoc} + */ + protected function createNode() + { + if (null === $this->prototype) { + $node = new ArrayNode($this->name, $this->parent); + + $this->validateConcreteNode($node); + + $node->setAddIfNotSet($this->addDefaults); + + foreach ($this->children as $child) { + $child->parent = $node; + $node->addChild($child->getNode()); + } + } else { + $node = new PrototypedArrayNode($this->name, $this->parent); + + $this->validatePrototypeNode($node); + + if (null !== $this->key) { + $node->setKeyAttribute($this->key, $this->removeKeyItem); + } + + if (true === $this->atLeastOne) { + $node->setMinNumberOfElements(1); + } + + if ($this->default) { + $node->setDefaultValue($this->defaultValue); + } + + if (false !== $this->addDefaultChildren) { + $node->setAddChildrenIfNoneSet($this->addDefaultChildren); + if ($this->prototype instanceof static && null === $this->prototype->prototype) { + $this->prototype->addDefaultsIfNotSet(); + } + } + + $this->prototype->parent = $node; + $node->setPrototype($this->prototype->getNode()); + } + + $node->setAllowNewKeys($this->allowNewKeys); + $node->addEquivalentValue(null, $this->nullEquivalent); + $node->addEquivalentValue(true, $this->trueEquivalent); + $node->addEquivalentValue(false, $this->falseEquivalent); + $node->setPerformDeepMerging($this->performDeepMerging); + $node->setRequired($this->required); + $node->setIgnoreExtraKeys($this->ignoreExtraKeys); + $node->setNormalizeKeys($this->normalizeKeys); + + if (null !== $this->normalization) { + $node->setNormalizationClosures($this->normalization->before); + $node->setXmlRemappings($this->normalization->remappings); + } + + if (null !== $this->merge) { + $node->setAllowOverwrite($this->merge->allowOverwrite); + $node->setAllowFalse($this->merge->allowFalse); + } + + if (null !== $this->validation) { + $node->setFinalValidationClosures($this->validation->rules); + } + + return $node; + } + + /** + * Validate the configuration of a concrete node. + * + * @param ArrayNode $node The related node + * + * @throws InvalidDefinitionException + */ + protected function validateConcreteNode(ArrayNode $node) + { + $path = $node->getPath(); + + if (null !== $this->key) { + throw new InvalidDefinitionException( + sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path) + ); + } + + if (true === $this->atLeastOne) { + throw new InvalidDefinitionException( + sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path) + ); + } + + if ($this->default) { + throw new InvalidDefinitionException( + sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path) + ); + } + + if (false !== $this->addDefaultChildren) { + throw new InvalidDefinitionException( + sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path) + ); + } + } + + /** + * Validate the configuration of a prototype node. + * + * @param PrototypedArrayNode $node The related node + * + * @throws InvalidDefinitionException + */ + protected function validatePrototypeNode(PrototypedArrayNode $node) + { + $path = $node->getPath(); + + if ($this->addDefaults) { + throw new InvalidDefinitionException( + sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path) + ); + } + + if (false !== $this->addDefaultChildren) { + if ($this->default) { + throw new InvalidDefinitionException( + sprintf('A default value and default children might not be used together at path "%s"', $path) + ); + } + + if (null !== $this->key && (null === $this->addDefaultChildren || is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) { + throw new InvalidDefinitionException( + sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path) + ); + } + + if (null === $this->key && (is_string($this->addDefaultChildren) || is_array($this->addDefaultChildren))) { + throw new InvalidDefinitionException( + sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path) + ); + } + } + } +} diff --git a/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php b/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php new file mode 100644 index 00000000..db7ebc24 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.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 Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\BooleanNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +class BooleanNodeDefinition extends ScalarNodeDefinition +{ + /** + * {@inheritdoc} + */ + public function __construct($name, NodeParentInterface $parent = null) + { + parent::__construct($name, $parent); + + $this->nullEquivalent = true; + } + + /** + * Instantiate a Node. + * + * @return BooleanNode The node + */ + protected function instantiateNode() + { + return new BooleanNode($this->name, $this->parent); + } +} diff --git a/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php b/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php new file mode 100644 index 00000000..dc25fcbd --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.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\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\EnumNode; + +/** + * Enum Node Definition. + * + * @author Johannes M. Schmitt + */ +class EnumNodeDefinition extends ScalarNodeDefinition +{ + private $values; + + /** + * @param array $values + * + * @return EnumNodeDefinition|$this + */ + public function values(array $values) + { + $values = array_unique($values); + + if (count($values) <= 1) { + throw new \InvalidArgumentException('->values() must be called with at least two distinct values.'); + } + + $this->values = $values; + + return $this; + } + + /** + * Instantiate a Node. + * + * @return EnumNode The node + * + * @throws \RuntimeException + */ + protected function instantiateNode() + { + if (null === $this->values) { + throw new \RuntimeException('You must call ->values() on enum nodes.'); + } + + return new EnumNode($this->name, $this->parent, $this->values); + } +} diff --git a/vendor/symfony/config/Definition/Builder/ExprBuilder.php b/vendor/symfony/config/Definition/Builder/ExprBuilder.php new file mode 100644 index 00000000..3d79b298 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ExprBuilder.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\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; + +/** + * This class builds an if expression. + * + * @author Johannes M. Schmitt + * @author Christophe Coevoet + */ +class ExprBuilder +{ + protected $node; + public $ifPart; + public $thenPart; + + /** + * Constructor. + * + * @param NodeDefinition $node The related node + */ + public function __construct(NodeDefinition $node) + { + $this->node = $node; + } + + /** + * Marks the expression as being always used. + * + * @param \Closure $then + * + * @return ExprBuilder + */ + public function always(\Closure $then = null) + { + $this->ifPart = function ($v) { return true; }; + + if (null !== $then) { + $this->thenPart = $then; + } + + return $this; + } + + /** + * Sets a closure to use as tests. + * + * The default one tests if the value is true. + * + * @param \Closure $closure + * + * @return ExprBuilder + */ + public function ifTrue(\Closure $closure = null) + { + if (null === $closure) { + $closure = function ($v) { return true === $v; }; + } + + $this->ifPart = $closure; + + return $this; + } + + /** + * Tests if the value is a string. + * + * @return ExprBuilder + */ + public function ifString() + { + $this->ifPart = function ($v) { return is_string($v); }; + + return $this; + } + + /** + * Tests if the value is null. + * + * @return ExprBuilder + */ + public function ifNull() + { + $this->ifPart = function ($v) { return null === $v; }; + + return $this; + } + + /** + * Tests if the value is an array. + * + * @return ExprBuilder + */ + public function ifArray() + { + $this->ifPart = function ($v) { return is_array($v); }; + + return $this; + } + + /** + * Tests if the value is in an array. + * + * @param array $array + * + * @return ExprBuilder + */ + public function ifInArray(array $array) + { + $this->ifPart = function ($v) use ($array) { return in_array($v, $array, true); }; + + return $this; + } + + /** + * Tests if the value is not in an array. + * + * @param array $array + * + * @return ExprBuilder + */ + public function ifNotInArray(array $array) + { + $this->ifPart = function ($v) use ($array) { return !in_array($v, $array, true); }; + + return $this; + } + + /** + * Sets the closure to run if the test pass. + * + * @param \Closure $closure + * + * @return ExprBuilder + */ + public function then(\Closure $closure) + { + $this->thenPart = $closure; + + return $this; + } + + /** + * Sets a closure returning an empty array. + * + * @return ExprBuilder + */ + public function thenEmptyArray() + { + $this->thenPart = function ($v) { return array(); }; + + return $this; + } + + /** + * Sets a closure marking the value as invalid at validation time. + * + * if you want to add the value of the node in your message just use a %s placeholder. + * + * @param string $message + * + * @return ExprBuilder + * + * @throws \InvalidArgumentException + */ + public function thenInvalid($message) + { + $this->thenPart = function ($v) use ($message) {throw new \InvalidArgumentException(sprintf($message, json_encode($v))); }; + + return $this; + } + + /** + * Sets a closure unsetting this key of the array at validation time. + * + * @return ExprBuilder + * + * @throws UnsetKeyException + */ + public function thenUnset() + { + $this->thenPart = function ($v) { throw new UnsetKeyException('Unsetting key'); }; + + return $this; + } + + /** + * Returns the related node. + * + * @return NodeDefinition + * + * @throws \RuntimeException + */ + public function end() + { + if (null === $this->ifPart) { + throw new \RuntimeException('You must specify an if part.'); + } + if (null === $this->thenPart) { + throw new \RuntimeException('You must specify a then part.'); + } + + return $this->node; + } + + /** + * Builds the expressions. + * + * @param ExprBuilder[] $expressions An array of ExprBuilder instances to build + * + * @return array + */ + public static function buildExpressions(array $expressions) + { + foreach ($expressions as $k => $expr) { + if ($expr instanceof self) { + $if = $expr->ifPart; + $then = $expr->thenPart; + $expressions[$k] = function ($v) use ($if, $then) { + return $if($v) ? $then($v) : $v; + }; + } + } + + return $expressions; + } +} diff --git a/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php b/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php new file mode 100644 index 00000000..c0bed462 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.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\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\FloatNode; + +/** + * This class provides a fluent interface for defining a float node. + * + * @author Jeanmonod David + */ +class FloatNodeDefinition extends NumericNodeDefinition +{ + /** + * Instantiates a Node. + * + * @return FloatNode The node + */ + protected function instantiateNode() + { + return new FloatNode($this->name, $this->parent, $this->min, $this->max); + } +} diff --git a/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php b/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php new file mode 100644 index 00000000..f6c3c147 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.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\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\IntegerNode; + +/** + * This class provides a fluent interface for defining an integer node. + * + * @author Jeanmonod David + */ +class IntegerNodeDefinition extends NumericNodeDefinition +{ + /** + * Instantiates a Node. + * + * @return IntegerNode The node + */ + protected function instantiateNode() + { + return new IntegerNode($this->name, $this->parent, $this->min, $this->max); + } +} diff --git a/vendor/symfony/config/Definition/Builder/MergeBuilder.php b/vendor/symfony/config/Definition/Builder/MergeBuilder.php new file mode 100644 index 00000000..f908a499 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/MergeBuilder.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\Config\Definition\Builder; + +/** + * This class builds merge conditions. + * + * @author Johannes M. Schmitt + */ +class MergeBuilder +{ + protected $node; + public $allowFalse = false; + public $allowOverwrite = true; + + /** + * Constructor. + * + * @param NodeDefinition $node The related node + */ + public function __construct(NodeDefinition $node) + { + $this->node = $node; + } + + /** + * Sets whether the node can be unset. + * + * @param bool $allow + * + * @return MergeBuilder + */ + public function allowUnset($allow = true) + { + $this->allowFalse = $allow; + + return $this; + } + + /** + * Sets whether the node can be overwritten. + * + * @param bool $deny Whether the overwriting is forbidden or not + * + * @return MergeBuilder + */ + public function denyOverwrite($deny = true) + { + $this->allowOverwrite = !$deny; + + return $this; + } + + /** + * Returns the related node. + * + * @return NodeDefinition + */ + public function end() + { + return $this->node; + } +} diff --git a/vendor/symfony/config/Definition/Builder/NodeBuilder.php b/vendor/symfony/config/Definition/Builder/NodeBuilder.php new file mode 100644 index 00000000..b2b63368 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NodeBuilder.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\Config\Definition\Builder; + +/** + * This class provides a fluent interface for building a node. + * + * @author Johannes M. Schmitt + */ +class NodeBuilder implements NodeParentInterface +{ + protected $parent; + protected $nodeMapping; + + /** + * Constructor. + */ + public function __construct() + { + $this->nodeMapping = array( + 'variable' => __NAMESPACE__.'\\VariableNodeDefinition', + 'scalar' => __NAMESPACE__.'\\ScalarNodeDefinition', + 'boolean' => __NAMESPACE__.'\\BooleanNodeDefinition', + 'integer' => __NAMESPACE__.'\\IntegerNodeDefinition', + 'float' => __NAMESPACE__.'\\FloatNodeDefinition', + 'array' => __NAMESPACE__.'\\ArrayNodeDefinition', + 'enum' => __NAMESPACE__.'\\EnumNodeDefinition', + ); + } + + /** + * Set the parent node. + * + * @param ParentNodeDefinitionInterface $parent The parent node + * + * @return NodeBuilder This node builder + */ + public function setParent(ParentNodeDefinitionInterface $parent = null) + { + $this->parent = $parent; + + return $this; + } + + /** + * Creates a child array node. + * + * @param string $name The name of the node + * + * @return ArrayNodeDefinition The child node + */ + public function arrayNode($name) + { + return $this->node($name, 'array'); + } + + /** + * Creates a child scalar node. + * + * @param string $name the name of the node + * + * @return ScalarNodeDefinition The child node + */ + public function scalarNode($name) + { + return $this->node($name, 'scalar'); + } + + /** + * Creates a child Boolean node. + * + * @param string $name The name of the node + * + * @return BooleanNodeDefinition The child node + */ + public function booleanNode($name) + { + return $this->node($name, 'boolean'); + } + + /** + * Creates a child integer node. + * + * @param string $name the name of the node + * + * @return IntegerNodeDefinition The child node + */ + public function integerNode($name) + { + return $this->node($name, 'integer'); + } + + /** + * Creates a child float node. + * + * @param string $name the name of the node + * + * @return FloatNodeDefinition The child node + */ + public function floatNode($name) + { + return $this->node($name, 'float'); + } + + /** + * Creates a child EnumNode. + * + * @param string $name + * + * @return EnumNodeDefinition + */ + public function enumNode($name) + { + return $this->node($name, 'enum'); + } + + /** + * Creates a child variable node. + * + * @param string $name The name of the node + * + * @return VariableNodeDefinition The builder of the child node + */ + public function variableNode($name) + { + return $this->node($name, 'variable'); + } + + /** + * Returns the parent node. + * + * @return ParentNodeDefinitionInterface The parent node + */ + public function end() + { + return $this->parent; + } + + /** + * Creates a child node. + * + * @param string $name The name of the node + * @param string $type The type of the node + * + * @return NodeDefinition The child node + * + * @throws \RuntimeException When the node type is not registered + * @throws \RuntimeException When the node class is not found + */ + public function node($name, $type) + { + $class = $this->getNodeClass($type); + + $node = new $class($name); + + $this->append($node); + + return $node; + } + + /** + * Appends a node definition. + * + * Usage: + * + * $node = new ArrayNodeDefinition('name') + * ->children() + * ->scalarNode('foo')->end() + * ->scalarNode('baz')->end() + * ->append($this->getBarNodeDefinition()) + * ->end() + * ; + * + * @param NodeDefinition $node + * + * @return NodeBuilder This node builder + */ + public function append(NodeDefinition $node) + { + if ($node instanceof ParentNodeDefinitionInterface) { + $builder = clone $this; + $builder->setParent(null); + $node->setBuilder($builder); + } + + if (null !== $this->parent) { + $this->parent->append($node); + // Make this builder the node parent to allow for a fluid interface + $node->setParent($this); + } + + return $this; + } + + /** + * Adds or overrides a node Type. + * + * @param string $type The name of the type + * @param string $class The fully qualified name the node definition class + * + * @return NodeBuilder This node builder + */ + public function setNodeClass($type, $class) + { + $this->nodeMapping[strtolower($type)] = $class; + + return $this; + } + + /** + * Returns the class name of the node definition. + * + * @param string $type The node type + * + * @return string The node definition class name + * + * @throws \RuntimeException When the node type is not registered + * @throws \RuntimeException When the node class is not found + */ + protected function getNodeClass($type) + { + $type = strtolower($type); + + if (!isset($this->nodeMapping[$type])) { + throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type)); + } + + $class = $this->nodeMapping[$type]; + + if (!class_exists($class)) { + throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class)); + } + + return $class; + } +} diff --git a/vendor/symfony/config/Definition/Builder/NodeDefinition.php b/vendor/symfony/config/Definition/Builder/NodeDefinition.php new file mode 100644 index 00000000..f7f84bc0 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NodeDefinition.php @@ -0,0 +1,343 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +abstract class NodeDefinition implements NodeParentInterface +{ + protected $name; + protected $normalization; + protected $validation; + protected $defaultValue; + protected $default = false; + protected $required = false; + protected $merge; + protected $allowEmptyValue = true; + protected $nullEquivalent; + protected $trueEquivalent = true; + protected $falseEquivalent = false; + + /** + * @var NodeParentInterface|null + */ + protected $parent; + protected $attributes = array(); + + /** + * Constructor. + * + * @param string $name The name of the node + * @param NodeParentInterface|null $parent The parent + */ + public function __construct($name, NodeParentInterface $parent = null) + { + $this->parent = $parent; + $this->name = $name; + } + + /** + * Sets the parent node. + * + * @param NodeParentInterface $parent The parent + * + * @return NodeDefinition|$this + */ + public function setParent(NodeParentInterface $parent) + { + $this->parent = $parent; + + return $this; + } + + /** + * Sets info message. + * + * @param string $info The info text + * + * @return NodeDefinition|$this + */ + public function info($info) + { + return $this->attribute('info', $info); + } + + /** + * Sets example configuration. + * + * @param string|array $example + * + * @return NodeDefinition|$this + */ + public function example($example) + { + return $this->attribute('example', $example); + } + + /** + * Sets an attribute on the node. + * + * @param string $key + * @param mixed $value + * + * @return NodeDefinition|$this + */ + public function attribute($key, $value) + { + $this->attributes[$key] = $value; + + return $this; + } + + /** + * Returns the parent node. + * + * @return NodeParentInterface|null The builder of the parent node + */ + public function end() + { + return $this->parent; + } + + /** + * Creates the node. + * + * @param bool $forceRootNode Whether to force this node as the root node + * + * @return NodeInterface + */ + public function getNode($forceRootNode = false) + { + if ($forceRootNode) { + $this->parent = null; + } + + if (null !== $this->normalization) { + $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before); + } + + if (null !== $this->validation) { + $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules); + } + + $node = $this->createNode(); + $node->setAttributes($this->attributes); + + return $node; + } + + /** + * Sets the default value. + * + * @param mixed $value The default value + * + * @return NodeDefinition|$this + */ + public function defaultValue($value) + { + $this->default = true; + $this->defaultValue = $value; + + return $this; + } + + /** + * Sets the node as required. + * + * @return NodeDefinition|$this + */ + public function isRequired() + { + $this->required = true; + + return $this; + } + + /** + * Sets the equivalent value used when the node contains null. + * + * @param mixed $value + * + * @return NodeDefinition|$this + */ + public function treatNullLike($value) + { + $this->nullEquivalent = $value; + + return $this; + } + + /** + * Sets the equivalent value used when the node contains true. + * + * @param mixed $value + * + * @return NodeDefinition|$this + */ + public function treatTrueLike($value) + { + $this->trueEquivalent = $value; + + return $this; + } + + /** + * Sets the equivalent value used when the node contains false. + * + * @param mixed $value + * + * @return NodeDefinition|$this + */ + public function treatFalseLike($value) + { + $this->falseEquivalent = $value; + + return $this; + } + + /** + * Sets null as the default value. + * + * @return NodeDefinition|$this + */ + public function defaultNull() + { + return $this->defaultValue(null); + } + + /** + * Sets true as the default value. + * + * @return NodeDefinition|$this + */ + public function defaultTrue() + { + return $this->defaultValue(true); + } + + /** + * Sets false as the default value. + * + * @return NodeDefinition|$this + */ + public function defaultFalse() + { + return $this->defaultValue(false); + } + + /** + * Sets an expression to run before the normalization. + * + * @return ExprBuilder + */ + public function beforeNormalization() + { + return $this->normalization()->before(); + } + + /** + * Denies the node value being empty. + * + * @return NodeDefinition|$this + */ + public function cannotBeEmpty() + { + $this->allowEmptyValue = false; + + return $this; + } + + /** + * Sets an expression to run for the validation. + * + * The expression receives the value of the node and must return it. It can + * modify it. + * An exception should be thrown when the node is not valid. + * + * @return ExprBuilder + */ + public function validate() + { + return $this->validation()->rule(); + } + + /** + * Sets whether the node can be overwritten. + * + * @param bool $deny Whether the overwriting is forbidden or not + * + * @return NodeDefinition|$this + */ + public function cannotBeOverwritten($deny = true) + { + $this->merge()->denyOverwrite($deny); + + return $this; + } + + /** + * Gets the builder for validation rules. + * + * @return ValidationBuilder + */ + protected function validation() + { + if (null === $this->validation) { + $this->validation = new ValidationBuilder($this); + } + + return $this->validation; + } + + /** + * Gets the builder for merging rules. + * + * @return MergeBuilder + */ + protected function merge() + { + if (null === $this->merge) { + $this->merge = new MergeBuilder($this); + } + + return $this->merge; + } + + /** + * Gets the builder for normalization rules. + * + * @return NormalizationBuilder + */ + protected function normalization() + { + if (null === $this->normalization) { + $this->normalization = new NormalizationBuilder($this); + } + + return $this->normalization; + } + + /** + * Instantiate and configure the node according to this definition. + * + * @return NodeInterface $node The node instance + * + * @throws InvalidDefinitionException When the definition is invalid + */ + abstract protected function createNode(); +} diff --git a/vendor/symfony/config/Definition/Builder/NodeParentInterface.php b/vendor/symfony/config/Definition/Builder/NodeParentInterface.php new file mode 100644 index 00000000..305e9931 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NodeParentInterface.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\Config\Definition\Builder; + +/** + * An interface that must be implemented by all node parents. + * + * @author Victor Berchet + */ +interface NodeParentInterface +{ +} diff --git a/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php b/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php new file mode 100644 index 00000000..748c9f28 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NormalizationBuilder.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\Config\Definition\Builder; + +/** + * This class builds normalization conditions. + * + * @author Johannes M. Schmitt + */ +class NormalizationBuilder +{ + protected $node; + public $before = array(); + public $remappings = array(); + + /** + * Constructor. + * + * @param NodeDefinition $node The related node + */ + public function __construct(NodeDefinition $node) + { + $this->node = $node; + } + + /** + * Registers a key to remap to its plural form. + * + * @param string $key The key to remap + * @param string $plural The plural of the key in case of irregular plural + * + * @return NormalizationBuilder + */ + public function remap($key, $plural = null) + { + $this->remappings[] = array($key, null === $plural ? $key.'s' : $plural); + + return $this; + } + + /** + * Registers a closure to run before the normalization or an expression builder to build it if null is provided. + * + * @param \Closure $closure + * + * @return ExprBuilder|NormalizationBuilder + */ + public function before(\Closure $closure = null) + { + if (null !== $closure) { + $this->before[] = $closure; + + return $this; + } + + return $this->before[] = new ExprBuilder($this->node); + } +} diff --git a/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php b/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php new file mode 100644 index 00000000..ddd716d0 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.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\Config\Definition\Builder; + +/** + * Abstract class that contains common code of integer and float node definitions. + * + * @author David Jeanmonod + */ +abstract class NumericNodeDefinition extends ScalarNodeDefinition +{ + protected $min; + protected $max; + + /** + * Ensures that the value is smaller than the given reference. + * + * @param mixed $max + * + * @return NumericNodeDefinition + * + * @throws \InvalidArgumentException when the constraint is inconsistent + */ + public function max($max) + { + if (isset($this->min) && $this->min > $max) { + throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s)', $max, $this->min)); + } + $this->max = $max; + + return $this; + } + + /** + * Ensures that the value is bigger than the given reference. + * + * @param mixed $min + * + * @return NumericNodeDefinition + * + * @throws \InvalidArgumentException when the constraint is inconsistent + */ + public function min($min) + { + if (isset($this->max) && $this->max < $min) { + throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s)', $min, $this->max)); + } + $this->min = $min; + + return $this; + } +} diff --git a/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php b/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php new file mode 100644 index 00000000..575495bb --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.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\Config\Definition\Builder; + +/** + * An interface that must be implemented by nodes which can have children. + * + * @author Victor Berchet + */ +interface ParentNodeDefinitionInterface +{ + public function children(); + + public function append(NodeDefinition $node); + + public function setBuilder(NodeBuilder $builder); +} diff --git a/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php new file mode 100644 index 00000000..6170555c --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.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\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\ScalarNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +class ScalarNodeDefinition extends VariableNodeDefinition +{ + /** + * Instantiate a Node. + * + * @return ScalarNode The node + */ + protected function instantiateNode() + { + return new ScalarNode($this->name, $this->parent); + } +} diff --git a/vendor/symfony/config/Definition/Builder/TreeBuilder.php b/vendor/symfony/config/Definition/Builder/TreeBuilder.php new file mode 100644 index 00000000..5d02848a --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/TreeBuilder.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\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\NodeInterface; + +/** + * This is the entry class for building a config tree. + * + * @author Johannes M. Schmitt + */ +class TreeBuilder implements NodeParentInterface +{ + protected $tree; + protected $root; + protected $builder; + + /** + * Creates the root node. + * + * @param string $name The name of the root node + * @param string $type The type of the root node + * @param NodeBuilder $builder A custom node builder instance + * + * @return ArrayNodeDefinition|NodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array') + * + * @throws \RuntimeException When the node type is not supported + */ + public function root($name, $type = 'array', NodeBuilder $builder = null) + { + $builder = $builder ?: new NodeBuilder(); + + return $this->root = $builder->node($name, $type)->setParent($this); + } + + /** + * Builds the tree. + * + * @return NodeInterface + * + * @throws \RuntimeException + */ + public function buildTree() + { + if (null === $this->root) { + throw new \RuntimeException('The configuration tree has no root node.'); + } + if (null !== $this->tree) { + return $this->tree; + } + + return $this->tree = $this->root->getNode(true); + } +} diff --git a/vendor/symfony/config/Definition/Builder/ValidationBuilder.php b/vendor/symfony/config/Definition/Builder/ValidationBuilder.php new file mode 100644 index 00000000..e8858238 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ValidationBuilder.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\Config\Definition\Builder; + +/** + * This class builds validation conditions. + * + * @author Christophe Coevoet + */ +class ValidationBuilder +{ + protected $node; + public $rules = array(); + + /** + * Constructor. + * + * @param NodeDefinition $node The related node + */ + public function __construct(NodeDefinition $node) + { + $this->node = $node; + } + + /** + * Registers a closure to run as normalization or an expression builder to build it if null is provided. + * + * @param \Closure $closure + * + * @return ExprBuilder|ValidationBuilder + */ + public function rule(\Closure $closure = null) + { + if (null !== $closure) { + $this->rules[] = $closure; + + return $this; + } + + return $this->rules[] = new ExprBuilder($this->node); + } +} diff --git a/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php b/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php new file mode 100644 index 00000000..a46b7ea6 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.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\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\VariableNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +class VariableNodeDefinition extends NodeDefinition +{ + /** + * Instantiate a Node. + * + * @return VariableNode The node + */ + protected function instantiateNode() + { + return new VariableNode($this->name, $this->parent); + } + + /** + * {@inheritdoc} + */ + protected function createNode() + { + $node = $this->instantiateNode(); + + if (null !== $this->normalization) { + $node->setNormalizationClosures($this->normalization->before); + } + + if (null !== $this->merge) { + $node->setAllowOverwrite($this->merge->allowOverwrite); + } + + if (true === $this->default) { + $node->setDefaultValue($this->defaultValue); + } + + $node->setAllowEmptyValue($this->allowEmptyValue); + $node->addEquivalentValue(null, $this->nullEquivalent); + $node->addEquivalentValue(true, $this->trueEquivalent); + $node->addEquivalentValue(false, $this->falseEquivalent); + $node->setRequired($this->required); + + if (null !== $this->validation) { + $node->setFinalValidationClosures($this->validation->rules); + } + + return $node; + } +} diff --git a/vendor/symfony/config/Definition/ConfigurationInterface.php b/vendor/symfony/config/Definition/ConfigurationInterface.php new file mode 100644 index 00000000..d6456edb --- /dev/null +++ b/vendor/symfony/config/Definition/ConfigurationInterface.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\Config\Definition; + +/** + * Configuration interface. + * + * @author Victor Berchet + */ +interface ConfigurationInterface +{ + /** + * Generates the configuration tree builder. + * + * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder + */ + public function getConfigTreeBuilder(); +} diff --git a/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php new file mode 100644 index 00000000..c3b2fcde --- /dev/null +++ b/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php @@ -0,0 +1,300 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Dumper; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\EnumNode; +use Symfony\Component\Config\Definition\PrototypedArrayNode; + +/** + * Dumps a XML reference configuration for the given configuration/node instance. + * + * @author Wouter J + */ +class XmlReferenceDumper +{ + private $reference; + + public function dump(ConfigurationInterface $configuration, $namespace = null) + { + return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); + } + + public function dumpNode(NodeInterface $node, $namespace = null) + { + $this->reference = ''; + $this->writeNode($node, 0, true, $namespace); + $ref = $this->reference; + $this->reference = null; + + return $ref; + } + + /** + * @param NodeInterface $node + * @param int $depth + * @param bool $root If the node is the root node + * @param string $namespace The namespace of the node + */ + private function writeNode(NodeInterface $node, $depth = 0, $root = false, $namespace = null) + { + $rootName = ($root ? 'config' : $node->getName()); + $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null)); + + // xml remapping + if ($node->getParent()) { + $remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) { + return $rootName === $mapping[1]; + }); + + if (count($remapping)) { + list($singular) = current($remapping); + $rootName = $singular; + } + } + $rootName = str_replace('_', '-', $rootName); + + $rootAttributes = array(); + $rootAttributeComments = array(); + $rootChildren = array(); + $rootComments = array(); + + if ($node instanceof ArrayNode) { + $children = $node->getChildren(); + + // comments about the root node + if ($rootInfo = $node->getInfo()) { + $rootComments[] = $rootInfo; + } + + if ($rootNamespace) { + $rootComments[] = 'Namespace: '.$rootNamespace; + } + + // render prototyped nodes + if ($node instanceof PrototypedArrayNode) { + array_unshift($rootComments, 'prototype'); + + if ($key = $node->getKeyAttribute()) { + $rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key; + } + + $prototype = $node->getPrototype(); + + if ($prototype instanceof ArrayNode) { + $children = $prototype->getChildren(); + } else { + if ($prototype->hasDefaultValue()) { + $prototypeValue = $prototype->getDefaultValue(); + } else { + switch (get_class($prototype)) { + case 'Symfony\Component\Config\Definition\ScalarNode': + $prototypeValue = 'scalar value'; + break; + + case 'Symfony\Component\Config\Definition\FloatNode': + case 'Symfony\Component\Config\Definition\IntegerNode': + $prototypeValue = 'numeric value'; + break; + + case 'Symfony\Component\Config\Definition\BooleanNode': + $prototypeValue = 'true|false'; + break; + + case 'Symfony\Component\Config\Definition\EnumNode': + $prototypeValue = implode('|', array_map('json_encode', $prototype->getValues())); + break; + + default: + $prototypeValue = 'value'; + } + } + } + } + + // get attributes and elements + foreach ($children as $child) { + if (!$child instanceof ArrayNode) { + // get attributes + + // metadata + $name = str_replace('_', '-', $child->getName()); + $value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world + + // comments + $comments = array(); + if ($info = $child->getInfo()) { + $comments[] = $info; + } + + if ($example = $child->getExample()) { + $comments[] = 'Example: '.$example; + } + + if ($child->isRequired()) { + $comments[] = 'Required'; + } + + if ($child instanceof EnumNode) { + $comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues())); + } + + if (count($comments)) { + $rootAttributeComments[$name] = implode(";\n", $comments); + } + + // default values + if ($child->hasDefaultValue()) { + $value = $child->getDefaultValue(); + } + + // append attribute + $rootAttributes[$name] = $value; + } else { + // get elements + $rootChildren[] = $child; + } + } + } + + // render comments + + // root node comment + if (count($rootComments)) { + foreach ($rootComments as $comment) { + $this->writeLine('', $depth); + } + } + + // attribute comments + if (count($rootAttributeComments)) { + foreach ($rootAttributeComments as $attrName => $comment) { + $commentDepth = $depth + 4 + strlen($attrName) + 2; + $commentLines = explode("\n", $comment); + $multiline = (count($commentLines) > 1); + $comment = implode(PHP_EOL.str_repeat(' ', $commentDepth), $commentLines); + + if ($multiline) { + $this->writeLine('', $depth); + } else { + $this->writeLine('', $depth); + } + } + } + + // render start tag + attributes + $rootIsVariablePrototype = isset($prototypeValue); + $rootIsEmptyTag = (0 === count($rootChildren) && !$rootIsVariablePrototype); + $rootOpenTag = '<'.$rootName; + if (1 >= ($attributesCount = count($rootAttributes))) { + if (1 === $attributesCount) { + $rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes))); + } + + $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>'; + + if ($rootIsVariablePrototype) { + $rootOpenTag .= $prototypeValue.''; + } + + $this->writeLine($rootOpenTag, $depth); + } else { + $this->writeLine($rootOpenTag, $depth); + + $i = 1; + + foreach ($rootAttributes as $attrName => $attrValue) { + $attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue)); + + $this->writeLine($attr, $depth + 4); + + if ($attributesCount === $i++) { + $this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth); + + if ($rootIsVariablePrototype) { + $rootOpenTag .= $prototypeValue.''; + } + } + } + } + + // render children tags + foreach ($rootChildren as $child) { + $this->writeLine(''); + $this->writeNode($child, $depth + 4); + } + + // render end tag + if (!$rootIsEmptyTag && !$rootIsVariablePrototype) { + $this->writeLine(''); + + $rootEndTag = ''; + $this->writeLine($rootEndTag, $depth); + } + } + + /** + * Outputs a single config reference line. + * + * @param string $text + * @param int $indent + */ + private function writeLine($text, $indent = 0) + { + $indent = strlen($text) + $indent; + $format = '%'.$indent.'s'; + + $this->reference .= sprintf($format, $text).PHP_EOL; + } + + /** + * Renders the string conversion of the value. + * + * @param mixed $value + * + * @return string + */ + private function writeValue($value) + { + if ('%%%%not_defined%%%%' === $value) { + return ''; + } + + if (is_string($value) || is_numeric($value)) { + return $value; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + if (null === $value) { + return 'null'; + } + + if (empty($value)) { + return ''; + } + + if (is_array($value)) { + return implode(',', $value); + } + } +} diff --git a/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php new file mode 100644 index 00000000..f9da698e --- /dev/null +++ b/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.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\Config\Definition\Dumper; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\EnumNode; +use Symfony\Component\Config\Definition\PrototypedArrayNode; +use Symfony\Component\Yaml\Inline; + +/** + * Dumps a Yaml reference configuration for the given configuration/node instance. + * + * @author Kevin Bond + */ +class YamlReferenceDumper +{ + private $reference; + + public function dump(ConfigurationInterface $configuration) + { + return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree()); + } + + public function dumpNode(NodeInterface $node) + { + $this->reference = ''; + $this->writeNode($node); + $ref = $this->reference; + $this->reference = null; + + return $ref; + } + + /** + * @param NodeInterface $node + * @param int $depth + */ + private function writeNode(NodeInterface $node, $depth = 0) + { + $comments = array(); + $default = ''; + $defaultArray = null; + $children = null; + $example = $node->getExample(); + + // defaults + if ($node instanceof ArrayNode) { + $children = $node->getChildren(); + + if ($node instanceof PrototypedArrayNode) { + $prototype = $node->getPrototype(); + + if ($prototype instanceof ArrayNode) { + $children = $prototype->getChildren(); + } + + // check for attribute as key + if ($key = $node->getKeyAttribute()) { + $keyNodeClass = 'Symfony\Component\Config\Definition\\'.($prototype instanceof ArrayNode ? 'ArrayNode' : 'ScalarNode'); + $keyNode = new $keyNodeClass($key, $node); + $keyNode->setInfo('Prototype'); + + // add children + foreach ($children as $childNode) { + $keyNode->addChild($childNode); + } + $children = array($key => $keyNode); + } + } + + if (!$children) { + if ($node->hasDefaultValue() && count($defaultArray = $node->getDefaultValue())) { + $default = ''; + } elseif (!is_array($example)) { + $default = '[]'; + } + } + } elseif ($node instanceof EnumNode) { + $comments[] = 'One of '.implode('; ', array_map('json_encode', $node->getValues())); + $default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~'; + } else { + $default = '~'; + + if ($node->hasDefaultValue()) { + $default = $node->getDefaultValue(); + + if (is_array($default)) { + if (count($defaultArray = $node->getDefaultValue())) { + $default = ''; + } elseif (!is_array($example)) { + $default = '[]'; + } + } else { + $default = Inline::dump($default); + } + } + } + + // required? + if ($node->isRequired()) { + $comments[] = 'Required'; + } + + // example + if ($example && !is_array($example)) { + $comments[] = 'Example: '.$example; + } + + $default = (string) $default != '' ? ' '.$default : ''; + $comments = count($comments) ? '# '.implode(', ', $comments) : ''; + + $text = rtrim(sprintf('%-20s %s %s', $node->getName().':', $default, $comments), ' '); + + if ($info = $node->getInfo()) { + $this->writeLine(''); + // indenting multi-line info + $info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info); + $this->writeLine('# '.$info, $depth * 4); + } + + $this->writeLine($text, $depth * 4); + + // output defaults + if ($defaultArray) { + $this->writeLine(''); + + $message = count($defaultArray) > 1 ? 'Defaults' : 'Default'; + + $this->writeLine('# '.$message.':', $depth * 4 + 4); + + $this->writeArray($defaultArray, $depth + 1); + } + + if (is_array($example)) { + $this->writeLine(''); + + $message = count($example) > 1 ? 'Examples' : 'Example'; + + $this->writeLine('# '.$message.':', $depth * 4 + 4); + + $this->writeArray($example, $depth + 1); + } + + if ($children) { + foreach ($children as $childNode) { + $this->writeNode($childNode, $depth + 1); + } + } + } + + /** + * Outputs a single config reference line. + * + * @param string $text + * @param int $indent + */ + private function writeLine($text, $indent = 0) + { + $indent = strlen($text) + $indent; + $format = '%'.$indent.'s'; + + $this->reference .= sprintf($format, $text)."\n"; + } + + private function writeArray(array $array, $depth) + { + $isIndexed = array_values($array) === $array; + + foreach ($array as $key => $value) { + if (is_array($value)) { + $val = ''; + } else { + $val = $value; + } + + if ($isIndexed) { + $this->writeLine('- '.$val, $depth * 4); + } else { + $this->writeLine(sprintf('%-20s %s', $key.':', $val), $depth * 4); + } + + if (is_array($value)) { + $this->writeArray($value, $depth + 1); + } + } + } +} diff --git a/vendor/symfony/config/Definition/EnumNode.php b/vendor/symfony/config/Definition/EnumNode.php new file mode 100644 index 00000000..224871ab --- /dev/null +++ b/vendor/symfony/config/Definition/EnumNode.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\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * Node which only allows a finite set of values. + * + * @author Johannes M. Schmitt + */ +class EnumNode extends ScalarNode +{ + private $values; + + public function __construct($name, NodeInterface $parent = null, array $values = array()) + { + $values = array_unique($values); + if (count($values) <= 1) { + throw new \InvalidArgumentException('$values must contain at least two distinct elements.'); + } + + parent::__construct($name, $parent); + $this->values = $values; + } + + public function getValues() + { + return $this->values; + } + + protected function finalizeValue($value) + { + $value = parent::finalizeValue($value); + + if (!in_array($value, $this->values, true)) { + $ex = new InvalidConfigurationException(sprintf( + 'The value %s is not allowed for path "%s". Permissible values: %s', + json_encode($value), + $this->getPath(), + implode(', ', array_map('json_encode', $this->values)))); + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $value; + } +} diff --git a/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php b/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php new file mode 100644 index 00000000..48dd9325 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/DuplicateKeyException.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\Config\Definition\Exception; + +/** + * This exception is thrown whenever the key of an array is not unique. This can + * only be the case if the configuration is coming from an XML file. + * + * @author Johannes M. Schmitt + */ +class DuplicateKeyException extends InvalidConfigurationException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/Exception.php b/vendor/symfony/config/Definition/Exception/Exception.php new file mode 100644 index 00000000..8933a492 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/Exception.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\Config\Definition\Exception; + +/** + * Base exception for all configuration exceptions. + * + * @author Johannes M. Schmitt + */ +class Exception extends \RuntimeException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php b/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php new file mode 100644 index 00000000..726c07fb --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.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\Config\Definition\Exception; + +/** + * This exception is thrown when a configuration path is overwritten from a + * subsequent configuration file, but the entry node specifically forbids this. + * + * @author Johannes M. Schmitt + */ +class ForbiddenOverwriteException extends InvalidConfigurationException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php b/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php new file mode 100644 index 00000000..3dbc57b1 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.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\Config\Definition\Exception; + +/** + * A very general exception which can be thrown whenever non of the more specific + * exceptions is suitable. + * + * @author Johannes M. Schmitt + */ +class InvalidConfigurationException extends Exception +{ + private $path; + private $containsHints = false; + + public function setPath($path) + { + $this->path = $path; + } + + public function getPath() + { + return $this->path; + } + + /** + * Adds extra information that is suffixed to the original exception message. + * + * @param string $hint + */ + public function addHint($hint) + { + if (!$this->containsHints) { + $this->message .= "\nHint: ".$hint; + $this->containsHints = true; + } else { + $this->message .= ', '.$hint; + } + } +} diff --git a/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php b/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php new file mode 100644 index 00000000..98310dae --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.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\Config\Definition\Exception; + +/** + * Thrown when an error is detected in a node Definition. + * + * @author Victor Berchet + */ +class InvalidDefinitionException extends Exception +{ +} diff --git a/vendor/symfony/config/Definition/Exception/InvalidTypeException.php b/vendor/symfony/config/Definition/Exception/InvalidTypeException.php new file mode 100644 index 00000000..d7ca8c9d --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/InvalidTypeException.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\Config\Definition\Exception; + +/** + * This exception is thrown if an invalid type is encountered. + * + * @author Johannes M. Schmitt + */ +class InvalidTypeException extends InvalidConfigurationException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/UnsetKeyException.php b/vendor/symfony/config/Definition/Exception/UnsetKeyException.php new file mode 100644 index 00000000..863181a3 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/UnsetKeyException.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\Config\Definition\Exception; + +/** + * This exception is usually not encountered by the end-user, but only used + * internally to signal the parent scope to unset a key. + * + * @author Johannes M. Schmitt + */ +class UnsetKeyException extends Exception +{ +} diff --git a/vendor/symfony/config/Definition/FloatNode.php b/vendor/symfony/config/Definition/FloatNode.php new file mode 100644 index 00000000..5e1af17a --- /dev/null +++ b/vendor/symfony/config/Definition/FloatNode.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\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a float value in the config tree. + * + * @author Jeanmonod David + */ +class FloatNode extends NumericNode +{ + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + // Integers are also accepted, we just cast them + if (is_int($value)) { + $value = (float) $value; + } + + if (!is_float($value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected float, but got %s.', $this->getPath(), gettype($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } +} diff --git a/vendor/symfony/config/Definition/IntegerNode.php b/vendor/symfony/config/Definition/IntegerNode.php new file mode 100644 index 00000000..ba230702 --- /dev/null +++ b/vendor/symfony/config/Definition/IntegerNode.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\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents an integer value in the config tree. + * + * @author Jeanmonod David + */ +class IntegerNode extends NumericNode +{ + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + if (!is_int($value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected int, but got %s.', $this->getPath(), gettype($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } +} diff --git a/vendor/symfony/config/Definition/NodeInterface.php b/vendor/symfony/config/Definition/NodeInterface.php new file mode 100644 index 00000000..b9bddc49 --- /dev/null +++ b/vendor/symfony/config/Definition/NodeInterface.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\Config\Definition; + +/** + * Common Interface among all nodes. + * + * In most cases, it is better to inherit from BaseNode instead of implementing + * this interface yourself. + * + * @author Johannes M. Schmitt + */ +interface NodeInterface +{ + /** + * Returns the name of the node. + * + * @return string The name of the node + */ + public function getName(); + + /** + * Returns the path of the node. + * + * @return string The node path + */ + public function getPath(); + + /** + * Returns true when the node is required. + * + * @return bool If the node is required + */ + public function isRequired(); + + /** + * Returns true when the node has a default value. + * + * @return bool If the node has a default value + */ + public function hasDefaultValue(); + + /** + * Returns the default value of the node. + * + * @return mixed The default value + * + * @throws \RuntimeException if the node has no default value + */ + public function getDefaultValue(); + + /** + * Normalizes the supplied value. + * + * @param mixed $value The value to normalize + * + * @return mixed The normalized value + */ + public function normalize($value); + + /** + * Merges two values together. + * + * @param mixed $leftSide + * @param mixed $rightSide + * + * @return mixed The merged values + */ + public function merge($leftSide, $rightSide); + + /** + * Finalizes a value. + * + * @param mixed $value The value to finalize + * + * @return mixed The finalized value + */ + public function finalize($value); +} diff --git a/vendor/symfony/config/Definition/NumericNode.php b/vendor/symfony/config/Definition/NumericNode.php new file mode 100644 index 00000000..439935e4 --- /dev/null +++ b/vendor/symfony/config/Definition/NumericNode.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\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * This node represents a numeric value in the config tree. + * + * @author David Jeanmonod + */ +class NumericNode extends ScalarNode +{ + protected $min; + protected $max; + + public function __construct($name, NodeInterface $parent = null, $min = null, $max = null) + { + parent::__construct($name, $parent); + $this->min = $min; + $this->max = $max; + } + + /** + * {@inheritdoc} + */ + protected function finalizeValue($value) + { + $value = parent::finalizeValue($value); + + $errorMsg = null; + if (isset($this->min) && $value < $this->min) { + $errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min); + } + if (isset($this->max) && $value > $this->max) { + $errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max); + } + if (isset($errorMsg)) { + $ex = new InvalidConfigurationException($errorMsg); + $ex->setPath($this->getPath()); + throw $ex; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + protected function isValueEmpty($value) + { + // a numeric value cannot be empty + return false; + } +} diff --git a/vendor/symfony/config/Definition/Processor.php b/vendor/symfony/config/Definition/Processor.php new file mode 100644 index 00000000..025e6937 --- /dev/null +++ b/vendor/symfony/config/Definition/Processor.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\Config\Definition; + +/** + * This class is the entry point for config normalization/merging/finalization. + * + * @author Johannes M. Schmitt + */ +class Processor +{ + /** + * Processes an array of configurations. + * + * @param NodeInterface $configTree The node tree describing the configuration + * @param array $configs An array of configuration items to process + * + * @return array The processed configuration + */ + public function process(NodeInterface $configTree, array $configs) + { + $currentConfig = array(); + foreach ($configs as $config) { + $config = $configTree->normalize($config); + $currentConfig = $configTree->merge($currentConfig, $config); + } + + return $configTree->finalize($currentConfig); + } + + /** + * Processes an array of configurations. + * + * @param ConfigurationInterface $configuration The configuration class + * @param array $configs An array of configuration items to process + * + * @return array The processed configuration + */ + public function processConfiguration(ConfigurationInterface $configuration, array $configs) + { + return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs); + } + + /** + * Normalizes a configuration entry. + * + * This method returns a normalize configuration array for a given key + * to remove the differences due to the original format (YAML and XML mainly). + * + * Here is an example. + * + * The configuration in XML: + * + * twig.extension.foo + * twig.extension.bar + * + * And the same configuration in YAML: + * + * extensions: ['twig.extension.foo', 'twig.extension.bar'] + * + * @param array $config A config array + * @param string $key The key to normalize + * @param string $plural The plural form of the key if it is irregular + * + * @return array + */ + public static function normalizeConfig($config, $key, $plural = null) + { + if (null === $plural) { + $plural = $key.'s'; + } + + if (isset($config[$plural])) { + return $config[$plural]; + } + + if (isset($config[$key])) { + if (is_string($config[$key]) || !is_int(key($config[$key]))) { + // only one + return array($config[$key]); + } + + return $config[$key]; + } + + return array(); + } +} diff --git a/vendor/symfony/config/Definition/PrototypeNodeInterface.php b/vendor/symfony/config/Definition/PrototypeNodeInterface.php new file mode 100644 index 00000000..8bbb84d4 --- /dev/null +++ b/vendor/symfony/config/Definition/PrototypeNodeInterface.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\Config\Definition; + +/** + * This interface must be implemented by nodes which can be used as prototypes. + * + * @author Johannes M. Schmitt + */ +interface PrototypeNodeInterface extends NodeInterface +{ + /** + * Sets the name of the node. + * + * @param string $name The name of the node + */ + public function setName($name); +} diff --git a/vendor/symfony/config/Definition/PrototypedArrayNode.php b/vendor/symfony/config/Definition/PrototypedArrayNode.php new file mode 100644 index 00000000..931b4679 --- /dev/null +++ b/vendor/symfony/config/Definition/PrototypedArrayNode.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\DuplicateKeyException; +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; +use Symfony\Component\Config\Definition\Exception\Exception; + +/** + * Represents a prototyped Array node in the config tree. + * + * @author Johannes M. Schmitt + */ +class PrototypedArrayNode extends ArrayNode +{ + protected $prototype; + protected $keyAttribute; + protected $removeKeyAttribute = false; + protected $minNumberOfElements = 0; + protected $defaultValue = array(); + protected $defaultChildren; + + /** + * Sets the minimum number of elements that a prototype based node must + * contain. By default this is zero, meaning no elements. + * + * @param int $number + */ + public function setMinNumberOfElements($number) + { + $this->minNumberOfElements = $number; + } + + /** + * Sets the attribute which value is to be used as key. + * + * This is useful when you have an indexed array that should be an + * associative array. You can select an item from within the array + * to be the key of the particular item. For example, if "id" is the + * "key", then: + * + * array( + * array('id' => 'my_name', 'foo' => 'bar'), + * ); + * + * becomes + * + * array( + * 'my_name' => array('foo' => 'bar'), + * ); + * + * If you'd like "'id' => 'my_name'" to still be present in the resulting + * array, then you can set the second argument of this method to false. + * + * @param string $attribute The name of the attribute which value is to be used as a key + * @param bool $remove Whether or not to remove the key + */ + public function setKeyAttribute($attribute, $remove = true) + { + $this->keyAttribute = $attribute; + $this->removeKeyAttribute = $remove; + } + + /** + * Retrieves the name of the attribute which value should be used as key. + * + * @return string The name of the attribute + */ + public function getKeyAttribute() + { + return $this->keyAttribute; + } + + /** + * Sets the default value of this node. + * + * @param string $value + * + * @throws \InvalidArgumentException if the default value is not an array + */ + public function setDefaultValue($value) + { + if (!is_array($value)) { + throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.'); + } + + $this->defaultValue = $value; + } + + /** + * Checks if the node has a default value. + * + * @return bool + */ + public function hasDefaultValue() + { + return true; + } + + /** + * Adds default children when none are set. + * + * @param int|string|array|null $children The number of children|The child name|The children names to be added + */ + public function setAddChildrenIfNoneSet($children = array('defaults')) + { + if (null === $children) { + $this->defaultChildren = array('defaults'); + } else { + $this->defaultChildren = is_int($children) && $children > 0 ? range(1, $children) : (array) $children; + } + } + + /** + * Retrieves the default value. + * + * The default value could be either explicited or derived from the prototype + * default value. + * + * @return array The default value + */ + public function getDefaultValue() + { + if (null !== $this->defaultChildren) { + $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : array(); + $defaults = array(); + foreach (array_values($this->defaultChildren) as $i => $name) { + $defaults[null === $this->keyAttribute ? $i : $name] = $default; + } + + return $defaults; + } + + return $this->defaultValue; + } + + /** + * Sets the node prototype. + * + * @param PrototypeNodeInterface $node + */ + public function setPrototype(PrototypeNodeInterface $node) + { + $this->prototype = $node; + } + + /** + * Retrieves the prototype. + * + * @return PrototypeNodeInterface The prototype + */ + public function getPrototype() + { + return $this->prototype; + } + + /** + * Disable adding concrete children for prototyped nodes. + * + * @param NodeInterface $node The child node to add + * + * @throws Exception + */ + public function addChild(NodeInterface $node) + { + throw new Exception('A prototyped array node can not have concrete children.'); + } + + /** + * Finalizes the value of this node. + * + * @param mixed $value + * + * @return mixed The finalized value + * + * @throws UnsetKeyException + * @throws InvalidConfigurationException if the node doesn't have enough children + */ + protected function finalizeValue($value) + { + if (false === $value) { + $msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value)); + throw new UnsetKeyException($msg); + } + + foreach ($value as $k => $v) { + $this->prototype->setName($k); + try { + $value[$k] = $this->prototype->finalize($v); + } catch (UnsetKeyException $e) { + unset($value[$k]); + } + } + + if (count($value) < $this->minNumberOfElements) { + $msg = sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements); + $ex = new InvalidConfigurationException($msg); + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $value; + } + + /** + * Normalizes the value. + * + * @param mixed $value The value to normalize + * + * @return mixed The normalized value + * + * @throws InvalidConfigurationException + * @throws DuplicateKeyException + */ + protected function normalizeValue($value) + { + if (false === $value) { + return $value; + } + + $value = $this->remapXml($value); + + $isAssoc = array_keys($value) !== range(0, count($value) - 1); + $normalized = array(); + foreach ($value as $k => $v) { + if (null !== $this->keyAttribute && is_array($v)) { + if (!isset($v[$this->keyAttribute]) && is_int($k) && !$isAssoc) { + $msg = sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath()); + $ex = new InvalidConfigurationException($msg); + $ex->setPath($this->getPath()); + + throw $ex; + } elseif (isset($v[$this->keyAttribute])) { + $k = $v[$this->keyAttribute]; + + // remove the key attribute when required + if ($this->removeKeyAttribute) { + unset($v[$this->keyAttribute]); + } + + // if only "value" is left + if (1 == count($v) && isset($v['value'])) { + $v = $v['value']; + } + } + + if (array_key_exists($k, $normalized)) { + $msg = sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath()); + $ex = new DuplicateKeyException($msg); + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + $this->prototype->setName($k); + if (null !== $this->keyAttribute || $isAssoc) { + $normalized[$k] = $this->prototype->normalize($v); + } else { + $normalized[] = $this->prototype->normalize($v); + } + } + + return $normalized; + } + + /** + * Merges values together. + * + * @param mixed $leftSide The left side to merge. + * @param mixed $rightSide The right side to merge. + * + * @return mixed The merged values + * + * @throws InvalidConfigurationException + * @throws \RuntimeException + */ + protected function mergeValues($leftSide, $rightSide) + { + if (false === $rightSide) { + // if this is still false after the last config has been merged the + // finalization pass will take care of removing this key entirely + return false; + } + + if (false === $leftSide || !$this->performDeepMerging) { + return $rightSide; + } + + foreach ($rightSide as $k => $v) { + // prototype, and key is irrelevant, so simply append the element + if (null === $this->keyAttribute) { + $leftSide[] = $v; + continue; + } + + // no conflict + if (!array_key_exists($k, $leftSide)) { + if (!$this->allowNewKeys) { + $ex = new InvalidConfigurationException(sprintf( + 'You are not allowed to define new elements for path "%s". '. + 'Please define all elements for this path in one config file.', + $this->getPath() + )); + $ex->setPath($this->getPath()); + + throw $ex; + } + + $leftSide[$k] = $v; + continue; + } + + $this->prototype->setName($k); + $leftSide[$k] = $this->prototype->merge($leftSide[$k], $v); + } + + return $leftSide; + } +} diff --git a/vendor/symfony/config/Definition/ReferenceDumper.php b/vendor/symfony/config/Definition/ReferenceDumper.php new file mode 100644 index 00000000..09526cfe --- /dev/null +++ b/vendor/symfony/config/Definition/ReferenceDumper.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\Config\Definition; + +@trigger_error('The '.__NAMESPACE__.'\ReferenceDumper class is deprecated since version 2.4 and will be removed in 3.0. Use the Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper class instead.', E_USER_DEPRECATED); + +use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; + +/** + * @deprecated since version 2.4, to be removed in 3.0. + * Use {@link \Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper} instead. + */ +class ReferenceDumper extends YamlReferenceDumper +{ +} diff --git a/vendor/symfony/config/Definition/ScalarNode.php b/vendor/symfony/config/Definition/ScalarNode.php new file mode 100644 index 00000000..6b3fd0b6 --- /dev/null +++ b/vendor/symfony/config/Definition/ScalarNode.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\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a scalar value in the config tree. + * + * The following values are considered scalars: + * * booleans + * * strings + * * null + * * integers + * * floats + * + * @author Johannes M. Schmitt + */ +class ScalarNode extends VariableNode +{ + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + if (!is_scalar($value) && null !== $value) { + $ex = new InvalidTypeException(sprintf( + 'Invalid type for path "%s". Expected scalar, but got %s.', + $this->getPath(), + gettype($value) + )); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + /** + * {@inheritdoc} + */ + protected function isValueEmpty($value) + { + return null === $value || '' === $value; + } +} diff --git a/vendor/symfony/config/Definition/VariableNode.php b/vendor/symfony/config/Definition/VariableNode.php new file mode 100644 index 00000000..e2c06774 --- /dev/null +++ b/vendor/symfony/config/Definition/VariableNode.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\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * This node represents a value of variable type in the config tree. + * + * This node is intended for values of arbitrary type. + * Any PHP type is accepted as a value. + * + * @author Jeremy Mikola + */ +class VariableNode extends BaseNode implements PrototypeNodeInterface +{ + protected $defaultValueSet = false; + protected $defaultValue; + protected $allowEmptyValue = true; + + /** + * {@inheritdoc} + */ + public function setDefaultValue($value) + { + $this->defaultValueSet = true; + $this->defaultValue = $value; + } + + /** + * {@inheritdoc} + */ + public function hasDefaultValue() + { + return $this->defaultValueSet; + } + + /** + * {@inheritdoc} + */ + public function getDefaultValue() + { + $v = $this->defaultValue; + + return $v instanceof \Closure ? $v() : $v; + } + + /** + * Sets if this node is allowed to have an empty value. + * + * @param bool $boolean True if this entity will accept empty values. + */ + public function setAllowEmptyValue($boolean) + { + $this->allowEmptyValue = (bool) $boolean; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + } + + /** + * {@inheritdoc} + */ + protected function finalizeValue($value) + { + if (!$this->allowEmptyValue && $this->isValueEmpty($value)) { + $ex = new InvalidConfigurationException(sprintf( + 'The path "%s" cannot contain an empty value, but got %s.', + $this->getPath(), + json_encode($value) + )); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + protected function normalizeValue($value) + { + return $value; + } + + /** + * {@inheritdoc} + */ + protected function mergeValues($leftSide, $rightSide) + { + return $rightSide; + } + + /** + * Evaluates if the given value is to be treated as empty. + * + * By default, PHP's empty() function is used to test for emptiness. This + * method may be overridden by subtypes to better match their understanding + * of empty data. + * + * @param mixed $value + * + * @return bool + */ + protected function isValueEmpty($value) + { + return empty($value); + } +} diff --git a/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php b/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php new file mode 100644 index 00000000..6a3b01cf --- /dev/null +++ b/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.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\Config\Exception; + +/** + * Exception class for when a circular reference is detected when importing resources. + * + * @author Fabien Potencier + */ +class FileLoaderImportCircularReferenceException extends FileLoaderLoadException +{ + public function __construct(array $resources, $code = null, $previous = null) + { + $message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]); + + \Exception::__construct($message, $code, $previous); + } +} diff --git a/vendor/symfony/config/Exception/FileLoaderLoadException.php b/vendor/symfony/config/Exception/FileLoaderLoadException.php new file mode 100644 index 00000000..6af3dd0a --- /dev/null +++ b/vendor/symfony/config/Exception/FileLoaderLoadException.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\Config\Exception; + +/** + * Exception class for when a resource cannot be loaded or imported. + * + * @author Ryan Weaver + */ +class FileLoaderLoadException extends \Exception +{ + /** + * @param string $resource The resource that could not be imported + * @param string $sourceResource The original resource importing the new resource + * @param int $code The error code + * @param \Exception $previous A previous exception + */ + public function __construct($resource, $sourceResource = null, $code = null, $previous = null) + { + $message = ''; + if ($previous) { + // Include the previous exception, to help the user see what might be the underlying cause + + // Trim the trailing period of the previous message. We only want 1 period remove so no rtrim... + if ('.' === substr($previous->getMessage(), -1)) { + $trimmedMessage = substr($previous->getMessage(), 0, -1); + $message .= sprintf('%s', $trimmedMessage).' in '; + } else { + $message .= sprintf('%s', $previous->getMessage()).' in '; + } + $message .= $resource.' '; + + // show tweaked trace to complete the human readable sentence + if (null === $sourceResource) { + $message .= sprintf('(which is loaded in resource "%s")', $this->varToString($resource)); + } else { + $message .= sprintf('(which is being imported from "%s")', $this->varToString($sourceResource)); + } + $message .= '.'; + + // if there's no previous message, present it the default way + } elseif (null === $sourceResource) { + $message .= sprintf('Cannot load resource "%s".', $this->varToString($resource)); + } else { + $message .= sprintf('Cannot import resource "%s" from "%s".', $this->varToString($resource), $this->varToString($sourceResource)); + } + + // Is the resource located inside a bundle? + if ('@' === $resource[0]) { + $parts = explode(DIRECTORY_SEPARATOR, $resource); + $bundle = substr($parts[0], 1); + $message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle); + $message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource); + } + + parent::__construct($message, $code, $previous); + } + + 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/config/FileLocator.php b/vendor/symfony/config/FileLocator.php new file mode 100644 index 00000000..c6600c77 --- /dev/null +++ b/vendor/symfony/config/FileLocator.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\Config; + +/** + * FileLocator uses an array of pre-defined paths to find files. + * + * @author Fabien Potencier + */ +class FileLocator implements FileLocatorInterface +{ + protected $paths; + + /** + * Constructor. + * + * @param string|array $paths A path or an array of paths where to look for resources + */ + public function __construct($paths = array()) + { + $this->paths = (array) $paths; + } + + /** + * {@inheritdoc} + */ + public function locate($name, $currentPath = null, $first = true) + { + if ('' == $name) { + throw new \InvalidArgumentException('An empty file name is not valid to be located.'); + } + + if ($this->isAbsolutePath($name)) { + if (!file_exists($name)) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $name)); + } + + return $name; + } + + $paths = $this->paths; + + if (null !== $currentPath) { + array_unshift($paths, $currentPath); + } + + $paths = array_unique($paths); + $filepaths = array(); + + foreach ($paths as $path) { + if (file_exists($file = $path.DIRECTORY_SEPARATOR.$name)) { + if (true === $first) { + return $file; + } + $filepaths[] = $file; + } + } + + if (!$filepaths) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not exist (in: %s).', $name, implode(', ', $paths))); + } + + return $filepaths; + } + + /** + * Returns whether the file path is an absolute path. + * + * @param string $file A file path + * + * @return bool + */ + private function isAbsolutePath($file) + { + if ($file[0] === '/' || $file[0] === '\\' + || (strlen($file) > 3 && ctype_alpha($file[0]) + && $file[1] === ':' + && ($file[2] === '\\' || $file[2] === '/') + ) + || null !== parse_url($file, PHP_URL_SCHEME) + ) { + return true; + } + + return false; + } +} diff --git a/vendor/symfony/config/FileLocatorInterface.php b/vendor/symfony/config/FileLocatorInterface.php new file mode 100644 index 00000000..66057982 --- /dev/null +++ b/vendor/symfony/config/FileLocatorInterface.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\Config; + +/** + * @author Fabien Potencier + */ +interface FileLocatorInterface +{ + /** + * Returns a full path for a given file name. + * + * @param string $name The file name to locate + * @param string|null $currentPath The current path + * @param bool $first Whether to return the first occurrence or an array of filenames + * + * @return string|array The full path to the file or an array of file paths + * + * @throws \InvalidArgumentException When file is not found + */ + public function locate($name, $currentPath = null, $first = true); +} diff --git a/vendor/symfony/config/LICENSE b/vendor/symfony/config/LICENSE new file mode 100644 index 00000000..43028bc6 --- /dev/null +++ b/vendor/symfony/config/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2015 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/config/Loader/DelegatingLoader.php b/vendor/symfony/config/Loader/DelegatingLoader.php new file mode 100644 index 00000000..3097878b --- /dev/null +++ b/vendor/symfony/config/Loader/DelegatingLoader.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\Config\Loader; + +use Symfony\Component\Config\Exception\FileLoaderLoadException; + +/** + * DelegatingLoader delegates loading to other loaders using a loader resolver. + * + * This loader acts as an array of LoaderInterface objects - each having + * a chance to load a given resource (handled by the resolver) + * + * @author Fabien Potencier + */ +class DelegatingLoader extends Loader +{ + /** + * Constructor. + * + * @param LoaderResolverInterface $resolver A LoaderResolverInterface instance + */ + public function __construct(LoaderResolverInterface $resolver) + { + $this->resolver = $resolver; + } + + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + if (false === $loader = $this->resolver->resolve($resource, $type)) { + throw new FileLoaderLoadException($resource); + } + + return $loader->load($resource, $type); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return false !== $this->resolver->resolve($resource, $type); + } +} diff --git a/vendor/symfony/config/Loader/FileLoader.php b/vendor/symfony/config/Loader/FileLoader.php new file mode 100644 index 00000000..88ec070f --- /dev/null +++ b/vendor/symfony/config/Loader/FileLoader.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\Config\Loader; + +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Exception\FileLoaderLoadException; +use Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException; + +/** + * FileLoader is the abstract class used by all built-in loaders that are file based. + * + * @author Fabien Potencier + */ +abstract class FileLoader extends Loader +{ + /** + * @var array + */ + protected static $loading = array(); + + /** + * @var FileLocatorInterface + */ + protected $locator; + + private $currentDir; + + /** + * Constructor. + * + * @param FileLocatorInterface $locator A FileLocatorInterface instance + */ + public function __construct(FileLocatorInterface $locator) + { + $this->locator = $locator; + } + + /** + * Sets the current directory. + * + * @param string $dir + */ + public function setCurrentDir($dir) + { + $this->currentDir = $dir; + } + + /** + * Returns the file locator used by this loader. + * + * @return FileLocatorInterface + */ + public function getLocator() + { + return $this->locator; + } + + /** + * Imports a resource. + * + * @param mixed $resource A Resource + * @param string|null $type The resource type or null if unknown + * @param bool $ignoreErrors Whether to ignore import errors or not + * @param string|null $sourceResource The original resource importing the new resource + * + * @return mixed + * + * @throws FileLoaderLoadException + * @throws FileLoaderImportCircularReferenceException + */ + public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null) + { + try { + $loader = $this->resolve($resource, $type); + + if ($loader instanceof self && null !== $this->currentDir) { + // we fallback to the current locator to keep BC + // as some some loaders do not call the parent __construct() + // @deprecated should be removed in 3.0 + $locator = $loader->getLocator(); + if (null === $locator) { + @trigger_error('Not calling the parent constructor in '.get_class($loader).' which extends '.__CLASS__.' is deprecated since version 2.7 and will not be supported anymore in 3.0.', E_USER_DEPRECATED); + $locator = $this->locator; + } + + $resource = $locator->locate($resource, $this->currentDir, false); + } + + $resources = is_array($resource) ? $resource : array($resource); + for ($i = 0; $i < $resourcesCount = count($resources); ++$i) { + if (isset(self::$loading[$resources[$i]])) { + if ($i == $resourcesCount - 1) { + throw new FileLoaderImportCircularReferenceException(array_keys(self::$loading)); + } + } else { + $resource = $resources[$i]; + break; + } + } + self::$loading[$resource] = true; + + try { + $ret = $loader->load($resource, $type); + } catch (\Exception $e) { + unset(self::$loading[$resource]); + throw $e; + } + + unset(self::$loading[$resource]); + + return $ret; + } catch (FileLoaderImportCircularReferenceException $e) { + throw $e; + } catch (\Exception $e) { + if (!$ignoreErrors) { + // prevent embedded imports from nesting multiple exceptions + if ($e instanceof FileLoaderLoadException) { + throw $e; + } + + throw new FileLoaderLoadException($resource, $sourceResource, null, $e); + } + } + } +} diff --git a/vendor/symfony/config/Loader/Loader.php b/vendor/symfony/config/Loader/Loader.php new file mode 100644 index 00000000..de4e1273 --- /dev/null +++ b/vendor/symfony/config/Loader/Loader.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\Config\Loader; + +use Symfony\Component\Config\Exception\FileLoaderLoadException; + +/** + * Loader is the abstract class used by all built-in loaders. + * + * @author Fabien Potencier + */ +abstract class Loader implements LoaderInterface +{ + protected $resolver; + + /** + * {@inheritdoc} + */ + public function getResolver() + { + return $this->resolver; + } + + /** + * {@inheritdoc} + */ + public function setResolver(LoaderResolverInterface $resolver) + { + $this->resolver = $resolver; + } + + /** + * Imports a resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return mixed + */ + public function import($resource, $type = null) + { + return $this->resolve($resource, $type)->load($resource, $type); + } + + /** + * Finds a loader able to load an imported resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return LoaderInterface A LoaderInterface instance + * + * @throws FileLoaderLoadException If no loader is found + */ + public function resolve($resource, $type = null) + { + if ($this->supports($resource, $type)) { + return $this; + } + + $loader = null === $this->resolver ? false : $this->resolver->resolve($resource, $type); + + if (false === $loader) { + throw new FileLoaderLoadException($resource); + } + + return $loader; + } +} diff --git a/vendor/symfony/config/Loader/LoaderInterface.php b/vendor/symfony/config/Loader/LoaderInterface.php new file mode 100644 index 00000000..dd0a85a6 --- /dev/null +++ b/vendor/symfony/config/Loader/LoaderInterface.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\Config\Loader; + +/** + * LoaderInterface is the interface implemented by all loader classes. + * + * @author Fabien Potencier + */ +interface LoaderInterface +{ + /** + * Loads a resource. + * + * @param mixed $resource The resource + * @param string|null $type The resource type or null if unknown + * + * @throws \Exception If something went wrong + */ + public function load($resource, $type = null); + + /** + * Returns whether this class supports the given resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return bool True if this class supports the given resource, false otherwise + */ + public function supports($resource, $type = null); + + /** + * Gets the loader resolver. + * + * @return LoaderResolverInterface A LoaderResolverInterface instance + */ + public function getResolver(); + + /** + * Sets the loader resolver. + * + * @param LoaderResolverInterface $resolver A LoaderResolverInterface instance + */ + public function setResolver(LoaderResolverInterface $resolver); +} diff --git a/vendor/symfony/config/Loader/LoaderResolver.php b/vendor/symfony/config/Loader/LoaderResolver.php new file mode 100644 index 00000000..dc6846df --- /dev/null +++ b/vendor/symfony/config/Loader/LoaderResolver.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\Config\Loader; + +/** + * LoaderResolver selects a loader for a given resource. + * + * A resource can be anything (e.g. a full path to a config file or a Closure). + * Each loader determines whether it can load a resource and how. + * + * @author Fabien Potencier + */ +class LoaderResolver implements LoaderResolverInterface +{ + /** + * @var LoaderInterface[] An array of LoaderInterface objects + */ + private $loaders = array(); + + /** + * Constructor. + * + * @param LoaderInterface[] $loaders An array of loaders + */ + public function __construct(array $loaders = array()) + { + foreach ($loaders as $loader) { + $this->addLoader($loader); + } + } + + /** + * {@inheritdoc} + */ + public function resolve($resource, $type = null) + { + foreach ($this->loaders as $loader) { + if ($loader->supports($resource, $type)) { + return $loader; + } + } + + return false; + } + + /** + * Adds a loader. + * + * @param LoaderInterface $loader A LoaderInterface instance + */ + public function addLoader(LoaderInterface $loader) + { + $this->loaders[] = $loader; + $loader->setResolver($this); + } + + /** + * Returns the registered loaders. + * + * @return LoaderInterface[] An array of LoaderInterface instances + */ + public function getLoaders() + { + return $this->loaders; + } +} diff --git a/vendor/symfony/config/Loader/LoaderResolverInterface.php b/vendor/symfony/config/Loader/LoaderResolverInterface.php new file mode 100644 index 00000000..40f1a1a6 --- /dev/null +++ b/vendor/symfony/config/Loader/LoaderResolverInterface.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\Config\Loader; + +/** + * LoaderResolverInterface selects a loader for a given resource. + * + * @author Fabien Potencier + */ +interface LoaderResolverInterface +{ + /** + * Returns a loader able to load the resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return LoaderInterface|false The loader or false if none is able to load the resource + */ + public function resolve($resource, $type = null); +} diff --git a/vendor/symfony/config/README.md b/vendor/symfony/config/README.md new file mode 100644 index 00000000..690d7d74 --- /dev/null +++ b/vendor/symfony/config/README.md @@ -0,0 +1,17 @@ +Config Component +================ + +Config provides the infrastructure for loading configurations from different +data sources and optionally monitoring these data sources for changes. There +are additional tools for validating, normalizing and handling of defaults that +can optionally be used to convert from different formats to arrays. + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Config/ + $ composer install + $ phpunit + diff --git a/vendor/symfony/config/Resource/DirectoryResource.php b/vendor/symfony/config/Resource/DirectoryResource.php new file mode 100644 index 00000000..7ae5694f --- /dev/null +++ b/vendor/symfony/config/Resource/DirectoryResource.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\Config\Resource; + +/** + * DirectoryResource represents a resources stored in a subdirectory tree. + * + * @author Fabien Potencier + */ +class DirectoryResource implements ResourceInterface, \Serializable +{ + private $resource; + private $pattern; + + /** + * Constructor. + * + * @param string $resource The file path to the resource + * @param string|null $pattern A pattern to restrict monitored files + */ + public function __construct($resource, $pattern = null) + { + $this->resource = $resource; + $this->pattern = $pattern; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return md5(serialize(array($this->resource, $this->pattern))); + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + return $this->resource; + } + + /** + * Returns the pattern to restrict monitored files. + * + * @return string|null + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function isFresh($timestamp) + { + if (!is_dir($this->resource)) { + return false; + } + + $newestMTime = filemtime($this->resource); + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) { + // if regex filtering is enabled only check matching files + if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) { + continue; + } + + // always monitor directories for changes, except the .. entries + // (otherwise deleted files wouldn't get detected) + if ($file->isDir() && '/..' === substr($file, -3)) { + continue; + } + + $newestMTime = max($file->getMTime(), $newestMTime); + } + + return $newestMTime < $timestamp; + } + + public function serialize() + { + return serialize(array($this->resource, $this->pattern)); + } + + public function unserialize($serialized) + { + list($this->resource, $this->pattern) = unserialize($serialized); + } +} diff --git a/vendor/symfony/config/Resource/FileResource.php b/vendor/symfony/config/Resource/FileResource.php new file mode 100644 index 00000000..4c00ae41 --- /dev/null +++ b/vendor/symfony/config/Resource/FileResource.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\Config\Resource; + +/** + * FileResource represents a resource stored on the filesystem. + * + * The resource can be a file or a directory. + * + * @author Fabien Potencier + */ +class FileResource implements ResourceInterface, \Serializable +{ + /** + * @var string|false + */ + private $resource; + + /** + * Constructor. + * + * @param string $resource The file path to the resource + */ + public function __construct($resource) + { + $this->resource = realpath($resource); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return (string) $this->resource; + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function isFresh($timestamp) + { + if (false === $this->resource || !file_exists($this->resource)) { + return false; + } + + return filemtime($this->resource) <= $timestamp; + } + + public function serialize() + { + return serialize($this->resource); + } + + public function unserialize($serialized) + { + $this->resource = unserialize($serialized); + } +} diff --git a/vendor/symfony/config/Resource/ResourceInterface.php b/vendor/symfony/config/Resource/ResourceInterface.php new file mode 100644 index 00000000..db03d127 --- /dev/null +++ b/vendor/symfony/config/Resource/ResourceInterface.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\Config\Resource; + +/** + * ResourceInterface is the interface that must be implemented by all Resource classes. + * + * @author Fabien Potencier + */ +interface ResourceInterface +{ + /** + * Returns a string representation of the Resource. + * + * @return string A string representation of the Resource + */ + public function __toString(); + + /** + * Returns true if the resource has not been updated since the given timestamp. + * + * @param int $timestamp The last time the resource was loaded + * + * @return bool True if the resource has not been updated, false otherwise + */ + public function isFresh($timestamp); + + /** + * Returns the tied resource. + * + * @return mixed The resource + */ + public function getResource(); +} diff --git a/vendor/symfony/config/Tests/ConfigCacheFactoryTest.php b/vendor/symfony/config/Tests/ConfigCacheFactoryTest.php new file mode 100644 index 00000000..291243de --- /dev/null +++ b/vendor/symfony/config/Tests/ConfigCacheFactoryTest.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\Config\Tests; + +use Symfony\Component\Config\ConfigCacheFactory; + +class ConfigCacheFactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid type for callback argument. Expected callable, but got "object". + */ + public function testCachWithInvalidCallback() + { + $cacheFactory = new ConfigCacheFactory(true); + + $cacheFactory->cache('file', new \stdClass()); + } +} diff --git a/vendor/symfony/config/Tests/ConfigCacheTest.php b/vendor/symfony/config/Tests/ConfigCacheTest.php new file mode 100644 index 00000000..f3f2a446 --- /dev/null +++ b/vendor/symfony/config/Tests/ConfigCacheTest.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\Config\Tests; + +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\Resource\FileResource; + +class ConfigCacheTest extends \PHPUnit_Framework_TestCase +{ + private $resourceFile = null; + + private $cacheFile = null; + + private $metaFile = null; + + protected function setUp() + { + $this->resourceFile = tempnam(sys_get_temp_dir(), '_resource'); + $this->cacheFile = tempnam(sys_get_temp_dir(), 'config_'); + $this->metaFile = $this->cacheFile.'.meta'; + + $this->makeCacheFresh(); + $this->generateMetaFile(); + } + + protected function tearDown() + { + $files = array($this->cacheFile, $this->metaFile, $this->resourceFile); + + foreach ($files as $file) { + if (file_exists($file)) { + unlink($file); + } + } + } + + public function testGetPath() + { + $cache = new ConfigCache($this->cacheFile, true); + + $this->assertSame($this->cacheFile, $cache->getPath()); + } + + public function testCacheIsNotFreshIfFileDoesNotExist() + { + unlink($this->cacheFile); + + $cache = new ConfigCache($this->cacheFile, false); + + $this->assertFalse($cache->isFresh()); + } + + public function testCacheIsAlwaysFreshIfFileExistsWithDebugDisabled() + { + $this->makeCacheStale(); + + $cache = new ConfigCache($this->cacheFile, false); + + $this->assertTrue($cache->isFresh()); + } + + public function testCacheIsNotFreshWithoutMetaFile() + { + unlink($this->metaFile); + + $cache = new ConfigCache($this->cacheFile, true); + + $this->assertFalse($cache->isFresh()); + } + + public function testCacheIsFreshIfResourceIsFresh() + { + $cache = new ConfigCache($this->cacheFile, true); + + $this->assertTrue($cache->isFresh()); + } + + public function testCacheIsNotFreshIfOneOfTheResourcesIsNotFresh() + { + $this->makeCacheStale(); + + $cache = new ConfigCache($this->cacheFile, true); + + $this->assertFalse($cache->isFresh()); + } + + public function testWriteDumpsFile() + { + unlink($this->cacheFile); + unlink($this->metaFile); + + $cache = new ConfigCache($this->cacheFile, false); + $cache->write('FOOBAR'); + + $this->assertFileExists($this->cacheFile, 'Cache file is created'); + $this->assertSame('FOOBAR', file_get_contents($this->cacheFile)); + $this->assertFileNotExists($this->metaFile, 'Meta file is not created'); + } + + public function testWriteDumpsMetaFileWithDebugEnabled() + { + unlink($this->cacheFile); + unlink($this->metaFile); + + $metadata = array(new FileResource($this->resourceFile)); + + $cache = new ConfigCache($this->cacheFile, true); + $cache->write('FOOBAR', $metadata); + + $this->assertFileExists($this->cacheFile, 'Cache file is created'); + $this->assertFileExists($this->metaFile, 'Meta file is created'); + $this->assertSame(serialize($metadata), file_get_contents($this->metaFile)); + } + + private function makeCacheFresh() + { + touch($this->resourceFile, filemtime($this->cacheFile) - 3600); + } + + private function makeCacheStale() + { + touch($this->cacheFile, filemtime($this->resourceFile) - 3600); + } + + private function generateMetaFile() + { + file_put_contents($this->metaFile, serialize(array(new FileResource($this->resourceFile)))); + } +} diff --git a/vendor/symfony/config/Tests/Definition/ArrayNodeTest.php b/vendor/symfony/config/Tests/Definition/ArrayNodeTest.php new file mode 100644 index 00000000..291c2fd2 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/ArrayNodeTest.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\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\ScalarNode; + +class ArrayNodeTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException + */ + public function testNormalizeThrowsExceptionWhenFalseIsNotAllowed() + { + $node = new ArrayNode('root'); + $node->normalize(false); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage Unrecognized option "foo" under "root" + */ + public function testExceptionThrownOnUnrecognizedChild() + { + $node = new ArrayNode('root'); + $node->normalize(array('foo' => 'bar')); + } + + /** + * Tests that no exception is thrown for an unrecognized child if the + * ignoreExtraKeys option is set to true. + * + * Related to testExceptionThrownOnUnrecognizedChild + */ + public function testIgnoreExtraKeysNoException() + { + $node = new ArrayNode('roo'); + $node->setIgnoreExtraKeys(true); + + $node->normalize(array('foo' => 'bar')); + $this->assertTrue(true, 'No exception was thrown when setIgnoreExtraKeys is true'); + } + + /** + * @dataProvider getPreNormalizationTests + */ + public function testPreNormalize($denormalized, $normalized) + { + $node = new ArrayNode('foo'); + + $r = new \ReflectionMethod($node, 'preNormalize'); + $r->setAccessible(true); + + $this->assertSame($normalized, $r->invoke($node, $denormalized)); + } + + public function getPreNormalizationTests() + { + return array( + array( + array('foo-bar' => 'foo'), + array('foo_bar' => 'foo'), + ), + array( + array('foo-bar_moo' => 'foo'), + array('foo-bar_moo' => 'foo'), + ), + array( + array('foo-bar' => null, 'foo_bar' => 'foo'), + array('foo-bar' => null, 'foo_bar' => 'foo'), + ), + ); + } + + /** + * @dataProvider getZeroNamedNodeExamplesData + */ + public function testNodeNameCanBeZero($denormalized, $normalized) + { + $zeroNode = new ArrayNode(0); + $zeroNode->addChild(new ScalarNode('name')); + $fiveNode = new ArrayNode(5); + $fiveNode->addChild(new ScalarNode(0)); + $fiveNode->addChild(new ScalarNode('new_key')); + $rootNode = new ArrayNode('root'); + $rootNode->addChild($zeroNode); + $rootNode->addChild($fiveNode); + $rootNode->addChild(new ScalarNode('string_key')); + $r = new \ReflectionMethod($rootNode, 'normalizeValue'); + $r->setAccessible(true); + + $this->assertSame($normalized, $r->invoke($rootNode, $denormalized)); + } + + public function getZeroNamedNodeExamplesData() + { + return array( + array( + array( + 0 => array( + 'name' => 'something', + ), + 5 => array( + 0 => 'this won\'t work too', + 'new_key' => 'some other value', + ), + 'string_key' => 'just value', + ), + array( + 0 => array( + 'name' => 'something', + ), + 5 => array( + 0 => 'this won\'t work too', + 'new_key' => 'some other value', + ), + 'string_key' => 'just value', + ), + ), + ); + } + + /** + * @dataProvider getPreNormalizedNormalizedOrderedData + */ + public function testChildrenOrderIsMaintainedOnNormalizeValue($prenormalized, $normalized) + { + $scalar1 = new ScalarNode('1'); + $scalar2 = new ScalarNode('2'); + $scalar3 = new ScalarNode('3'); + $node = new ArrayNode('foo'); + $node->addChild($scalar1); + $node->addChild($scalar3); + $node->addChild($scalar2); + + $r = new \ReflectionMethod($node, 'normalizeValue'); + $r->setAccessible(true); + + $this->assertSame($normalized, $r->invoke($node, $prenormalized)); + } + + public function getPreNormalizedNormalizedOrderedData() + { + return array( + array( + array('2' => 'two', '1' => 'one', '3' => 'three'), + array('2' => 'two', '1' => 'one', '3' => 'three'), + ), + ); + } +} diff --git a/vendor/symfony/config/Tests/Definition/BooleanNodeTest.php b/vendor/symfony/config/Tests/Definition/BooleanNodeTest.php new file mode 100644 index 00000000..b0cb079e --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/BooleanNodeTest.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\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\BooleanNode; + +class BooleanNodeTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getValidValues + */ + public function testNormalize($value) + { + $node = new BooleanNode('test'); + $this->assertSame($value, $node->normalize($value)); + } + + /** + * @dataProvider getValidValues + * + * @param bool $value + */ + public function testValidNonEmptyValues($value) + { + $node = new BooleanNode('test'); + $node->setAllowEmptyValue(false); + + $this->assertSame($value, $node->finalize($value)); + } + + public function getValidValues() + { + return array( + array(false), + array(true), + ); + } + + /** + * @dataProvider getInvalidValues + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException + */ + public function testNormalizeThrowsExceptionOnInvalidValues($value) + { + $node = new BooleanNode('test'); + $node->normalize($value); + } + + public function getInvalidValues() + { + return array( + array(null), + array(''), + array('foo'), + array(0), + array(1), + array(0.0), + array(0.1), + array(array()), + array(array('foo' => 'bar')), + array(new \stdClass()), + ); + } +} diff --git a/vendor/symfony/config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php b/vendor/symfony/config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php new file mode 100644 index 00000000..e75ed34f --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Tests\Definition\Builder; + +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition; +use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; + +class ArrayNodeDefinitionTest extends \PHPUnit_Framework_TestCase +{ + public function testAppendingSomeNode() + { + $parent = new ArrayNodeDefinition('root'); + $child = new ScalarNodeDefinition('child'); + + $parent + ->children() + ->scalarNode('foo')->end() + ->scalarNode('bar')->end() + ->end() + ->append($child); + + $this->assertCount(3, $this->getField($parent, 'children')); + $this->assertTrue(in_array($child, $this->getField($parent, 'children'))); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException + * @dataProvider providePrototypeNodeSpecificCalls + */ + public function testPrototypeNodeSpecificOption($method, $args) + { + $node = new ArrayNodeDefinition('root'); + + call_user_func_array(array($node, $method), $args); + + $node->getNode(); + } + + public function providePrototypeNodeSpecificCalls() + { + return array( + array('defaultValue', array(array())), + array('addDefaultChildrenIfNoneSet', array()), + array('requiresAtLeastOneElement', array()), + array('useAttributeAsKey', array('foo')), + ); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException + */ + public function testConcreteNodeSpecificOption() + { + $node = new ArrayNodeDefinition('root'); + $node + ->addDefaultsIfNotSet() + ->prototype('array') + ; + $node->getNode(); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException + */ + public function testPrototypeNodesCantHaveADefaultValueWhenUsingDefaultChildren() + { + $node = new ArrayNodeDefinition('root'); + $node + ->defaultValue(array()) + ->addDefaultChildrenIfNoneSet('foo') + ->prototype('array') + ; + $node->getNode(); + } + + public function testPrototypedArrayNodeDefaultWhenUsingDefaultChildren() + { + $node = new ArrayNodeDefinition('root'); + $node + ->addDefaultChildrenIfNoneSet() + ->prototype('array') + ; + $tree = $node->getNode(); + $this->assertEquals(array(array()), $tree->getDefaultValue()); + } + + /** + * @dataProvider providePrototypedArrayNodeDefaults + */ + public function testPrototypedArrayNodeDefault($args, $shouldThrowWhenUsingAttrAsKey, $shouldThrowWhenNotUsingAttrAsKey, $defaults) + { + $node = new ArrayNodeDefinition('root'); + $node + ->addDefaultChildrenIfNoneSet($args) + ->prototype('array') + ; + + try { + $tree = $node->getNode(); + $this->assertFalse($shouldThrowWhenNotUsingAttrAsKey); + $this->assertEquals($defaults, $tree->getDefaultValue()); + } catch (InvalidDefinitionException $e) { + $this->assertTrue($shouldThrowWhenNotUsingAttrAsKey); + } + + $node = new ArrayNodeDefinition('root'); + $node + ->useAttributeAsKey('attr') + ->addDefaultChildrenIfNoneSet($args) + ->prototype('array') + ; + + try { + $tree = $node->getNode(); + $this->assertFalse($shouldThrowWhenUsingAttrAsKey); + $this->assertEquals($defaults, $tree->getDefaultValue()); + } catch (InvalidDefinitionException $e) { + $this->assertTrue($shouldThrowWhenUsingAttrAsKey); + } + } + + public function providePrototypedArrayNodeDefaults() + { + return array( + array(null, true, false, array(array())), + array(2, true, false, array(array(), array())), + array('2', false, true, array('2' => array())), + array('foo', false, true, array('foo' => array())), + array(array('foo'), false, true, array('foo' => array())), + array(array('foo', 'bar'), false, true, array('foo' => array(), 'bar' => array())), + ); + } + + public function testNestedPrototypedArrayNodes() + { + $node = new ArrayNodeDefinition('root'); + $node + ->addDefaultChildrenIfNoneSet() + ->prototype('array') + ->prototype('array') + ; + $node->getNode(); + } + + public function testEnabledNodeDefaults() + { + $node = new ArrayNodeDefinition('root'); + $node + ->canBeEnabled() + ->children() + ->scalarNode('foo')->defaultValue('bar')->end() + ; + + $this->assertEquals(array('enabled' => false, 'foo' => 'bar'), $node->getNode()->getDefaultValue()); + } + + /** + * @dataProvider getEnableableNodeFixtures + */ + public function testTrueEnableEnabledNode($expected, $config, $message) + { + $processor = new Processor(); + $node = new ArrayNodeDefinition('root'); + $node + ->canBeEnabled() + ->children() + ->scalarNode('foo')->defaultValue('bar')->end() + ; + + $this->assertEquals( + $expected, + $processor->process($node->getNode(), $config), + $message + ); + } + + public function getEnableableNodeFixtures() + { + return array( + array(array('enabled' => true, 'foo' => 'bar'), array(true), 'true enables an enableable node'), + array(array('enabled' => true, 'foo' => 'bar'), array(null), 'null enables an enableable node'), + array(array('enabled' => true, 'foo' => 'bar'), array(array('enabled' => true)), 'An enableable node can be enabled'), + array(array('enabled' => true, 'foo' => 'baz'), array(array('foo' => 'baz')), 'any configuration enables an enableable node'), + array(array('enabled' => false, 'foo' => 'baz'), array(array('foo' => 'baz', 'enabled' => false)), 'An enableable node can be disabled'), + array(array('enabled' => false, 'foo' => 'bar'), array(false), 'false disables an enableable node'), + ); + } + + protected function getField($object, $field) + { + $reflection = new \ReflectionProperty($object, $field); + $reflection->setAccessible(true); + + return $reflection->getValue($object); + } +} diff --git a/vendor/symfony/config/Tests/Definition/Builder/EnumNodeDefinitionTest.php b/vendor/symfony/config/Tests/Definition/Builder/EnumNodeDefinitionTest.php new file mode 100644 index 00000000..69f7fcfb --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/Builder/EnumNodeDefinitionTest.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\Config\Tests\Definition\Builder; + +use Symfony\Component\Config\Definition\Builder\EnumNodeDefinition; + +class EnumNodeDefinitionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage ->values() must be called with at least two distinct values. + */ + public function testNoDistinctValues() + { + $def = new EnumNodeDefinition('foo'); + $def->values(array('foo', 'foo')); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage You must call ->values() on enum nodes. + */ + public function testNoValuesPassed() + { + $def = new EnumNodeDefinition('foo'); + $def->getNode(); + } + + public function testGetNode() + { + $def = new EnumNodeDefinition('foo'); + $def->values(array('foo', 'bar')); + + $node = $def->getNode(); + $this->assertEquals(array('foo', 'bar'), $node->getValues()); + } +} diff --git a/vendor/symfony/config/Tests/Definition/Builder/ExprBuilderTest.php b/vendor/symfony/config/Tests/Definition/Builder/ExprBuilderTest.php new file mode 100644 index 00000000..147bf132 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/Builder/ExprBuilderTest.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\Config\Tests\Definition\Builder; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; + +class ExprBuilderTest extends \PHPUnit_Framework_TestCase +{ + public function testAlwaysExpression() + { + $test = $this->getTestBuilder() + ->always($this->returnClosure('new_value')) + ->end(); + + $this->assertFinalizedValueIs('new_value', $test); + } + + public function testIfTrueExpression() + { + $test = $this->getTestBuilder() + ->ifTrue() + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('new_value', $test, array('key' => true)); + + $test = $this->getTestBuilder() + ->ifTrue(function ($v) { return true; }) + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('new_value', $test); + + $test = $this->getTestBuilder() + ->ifTrue(function ($v) { return false; }) + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('value', $test); + } + + public function testIfStringExpression() + { + $test = $this->getTestBuilder() + ->ifString() + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('new_value', $test); + + $test = $this->getTestBuilder() + ->ifString() + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs(45, $test, array('key' => 45)); + } + + public function testIfNullExpression() + { + $test = $this->getTestBuilder() + ->ifNull() + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('new_value', $test, array('key' => null)); + + $test = $this->getTestBuilder() + ->ifNull() + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('value', $test); + } + + public function testIfArrayExpression() + { + $test = $this->getTestBuilder() + ->ifArray() + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('new_value', $test, array('key' => array())); + + $test = $this->getTestBuilder() + ->ifArray() + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('value', $test); + } + + public function testIfInArrayExpression() + { + $test = $this->getTestBuilder() + ->ifInArray(array('foo', 'bar', 'value')) + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('new_value', $test); + + $test = $this->getTestBuilder() + ->ifInArray(array('foo', 'bar')) + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('value', $test); + } + + public function testIfNotInArrayExpression() + { + $test = $this->getTestBuilder() + ->ifNotInArray(array('foo', 'bar')) + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('new_value', $test); + + $test = $this->getTestBuilder() + ->ifNotInArray(array('foo', 'bar', 'value_from_config')) + ->then($this->returnClosure('new_value')) + ->end(); + $this->assertFinalizedValueIs('new_value', $test); + } + + public function testThenEmptyArrayExpression() + { + $test = $this->getTestBuilder() + ->ifString() + ->thenEmptyArray() + ->end(); + $this->assertFinalizedValueIs(array(), $test); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + */ + public function testThenInvalid() + { + $test = $this->getTestBuilder() + ->ifString() + ->thenInvalid('Invalid value') + ->end(); + $this->finalizeTestBuilder($test); + } + + public function testThenUnsetExpression() + { + $test = $this->getTestBuilder() + ->ifString() + ->thenUnset() + ->end(); + $this->assertEquals(array(), $this->finalizeTestBuilder($test)); + } + + /** + * Create a test treebuilder with a variable node, and init the validation. + * + * @return TreeBuilder + */ + protected function getTestBuilder() + { + $builder = new TreeBuilder(); + + return $builder + ->root('test') + ->children() + ->variableNode('key') + ->validate() + ; + } + + /** + * Close the validation process and finalize with the given config. + * + * @param TreeBuilder $testBuilder The tree builder to finalize + * @param array $config The config you want to use for the finalization, if nothing provided + * a simple array('key'=>'value') will be used + * + * @return array The finalized config values + */ + protected function finalizeTestBuilder($testBuilder, $config = null) + { + return $testBuilder + ->end() + ->end() + ->end() + ->buildTree() + ->finalize(null === $config ? array('key' => 'value') : $config) + ; + } + + /** + * Return a closure that will return the given value. + * + * @param mixed $val The value that the closure must return + * + * @return \Closure + */ + protected function returnClosure($val) + { + return function ($v) use ($val) { + return $val; + }; + } + + /** + * Assert that the given test builder, will return the given value. + * + * @param mixed $value The value to test + * @param TreeBuilder $treeBuilder The tree builder to finalize + * @param mixed $config The config values that new to be finalized + */ + protected function assertFinalizedValueIs($value, $treeBuilder, $config = null) + { + $this->assertEquals(array('key' => $value), $this->finalizeTestBuilder($treeBuilder, $config)); + } +} diff --git a/vendor/symfony/config/Tests/Definition/Builder/NodeBuilderTest.php b/vendor/symfony/config/Tests/Definition/Builder/NodeBuilderTest.php new file mode 100644 index 00000000..22c399ca --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/Builder/NodeBuilderTest.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 Symfony\Component\Config\Tests\Definition\Builder; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder as BaseNodeBuilder; +use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition as BaseVariableNodeDefinition; + +class NodeBuilderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \RuntimeException + */ + public function testThrowsAnExceptionWhenTryingToCreateANonRegisteredNodeType() + { + $builder = new BaseNodeBuilder(); + $builder->node('', 'foobar'); + } + + /** + * @expectedException \RuntimeException + */ + public function testThrowsAnExceptionWhenTheNodeClassIsNotFound() + { + $builder = new BaseNodeBuilder(); + $builder + ->setNodeClass('noclasstype', '\\foo\\bar\\noclass') + ->node('', 'noclasstype'); + } + + public function testAddingANewNodeType() + { + $class = __NAMESPACE__.'\\SomeNodeDefinition'; + + $builder = new BaseNodeBuilder(); + $node = $builder + ->setNodeClass('newtype', $class) + ->node('', 'newtype'); + + $this->assertInstanceOf($class, $node); + } + + public function testOverridingAnExistingNodeType() + { + $class = __NAMESPACE__.'\\SomeNodeDefinition'; + + $builder = new BaseNodeBuilder(); + $node = $builder + ->setNodeClass('variable', $class) + ->node('', 'variable'); + + $this->assertInstanceOf($class, $node); + } + + public function testNodeTypesAreNotCaseSensitive() + { + $builder = new BaseNodeBuilder(); + + $node1 = $builder->node('', 'VaRiAbLe'); + $node2 = $builder->node('', 'variable'); + + $this->assertInstanceOf(get_class($node1), $node2); + + $builder->setNodeClass('CuStOm', __NAMESPACE__.'\\SomeNodeDefinition'); + + $node1 = $builder->node('', 'CUSTOM'); + $node2 = $builder->node('', 'custom'); + + $this->assertInstanceOf(get_class($node1), $node2); + } + + public function testNumericNodeCreation() + { + $builder = new BaseNodeBuilder(); + + $node = $builder->integerNode('foo')->min(3)->max(5); + $this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition', $node); + + $node = $builder->floatNode('bar')->min(3.0)->max(5.0); + $this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\FloatNodeDefinition', $node); + } +} + +class SomeNodeDefinition extends BaseVariableNodeDefinition +{ +} diff --git a/vendor/symfony/config/Tests/Definition/Builder/NumericNodeDefinitionTest.php b/vendor/symfony/config/Tests/Definition/Builder/NumericNodeDefinitionTest.php new file mode 100644 index 00000000..cf0813ac --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/Builder/NumericNodeDefinitionTest.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\Config\Tests\Definition\Builder; + +use Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition as NumericNodeDefinition; +use Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition; +use Symfony\Component\Config\Definition\Builder\FloatNodeDefinition; + +class NumericNodeDefinitionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You cannot define a min(4) as you already have a max(3) + */ + public function testIncoherentMinAssertion() + { + $def = new NumericNodeDefinition('foo'); + $def->max(3)->min(4); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You cannot define a max(2) as you already have a min(3) + */ + public function testIncoherentMaxAssertion() + { + $node = new NumericNodeDefinition('foo'); + $node->min(3)->max(2); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage The value 4 is too small for path "foo". Should be greater than or equal to 5 + */ + public function testIntegerMinAssertion() + { + $def = new IntegerNodeDefinition('foo'); + $def->min(5)->getNode()->finalize(4); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage The value 4 is too big for path "foo". Should be less than or equal to 3 + */ + public function testIntegerMaxAssertion() + { + $def = new IntegerNodeDefinition('foo'); + $def->max(3)->getNode()->finalize(4); + } + + public function testIntegerValidMinMaxAssertion() + { + $def = new IntegerNodeDefinition('foo'); + $node = $def->min(3)->max(7)->getNode(); + $this->assertEquals(4, $node->finalize(4)); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage The value 400 is too small for path "foo". Should be greater than or equal to 500 + */ + public function testFloatMinAssertion() + { + $def = new FloatNodeDefinition('foo'); + $def->min(5E2)->getNode()->finalize(4e2); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage The value 4.3 is too big for path "foo". Should be less than or equal to 0.3 + */ + public function testFloatMaxAssertion() + { + $def = new FloatNodeDefinition('foo'); + $def->max(0.3)->getNode()->finalize(4.3); + } + + public function testFloatValidMinMaxAssertion() + { + $def = new FloatNodeDefinition('foo'); + $node = $def->min(3.0)->max(7e2)->getNode(); + $this->assertEquals(4.5, $node->finalize(4.5)); + } +} diff --git a/vendor/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php b/vendor/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php new file mode 100644 index 00000000..00e27c63 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/Builder/TreeBuilderTest.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\Config\Tests\Definition\Builder; + +use Symfony\Component\Config\Tests\Definition\Builder\NodeBuilder as CustomNodeBuilder; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; + +require __DIR__.'/../../Fixtures/Builder/NodeBuilder.php'; +require __DIR__.'/../../Fixtures/Builder/BarNodeDefinition.php'; +require __DIR__.'/../../Fixtures/Builder/VariableNodeDefinition.php'; + +class TreeBuilderTest extends \PHPUnit_Framework_TestCase +{ + public function testUsingACustomNodeBuilder() + { + $builder = new TreeBuilder(); + $root = $builder->root('custom', 'array', new CustomNodeBuilder()); + + $nodeBuilder = $root->children(); + + $this->assertInstanceOf('Symfony\Component\Config\Tests\Definition\Builder\NodeBuilder', $nodeBuilder); + + $nodeBuilder = $nodeBuilder->arrayNode('deeper')->children(); + + $this->assertInstanceOf('Symfony\Component\Config\Tests\Definition\Builder\NodeBuilder', $nodeBuilder); + } + + public function testOverrideABuiltInNodeType() + { + $builder = new TreeBuilder(); + $root = $builder->root('override', 'array', new CustomNodeBuilder()); + + $definition = $root->children()->variableNode('variable'); + + $this->assertInstanceOf('Symfony\Component\Config\Tests\Definition\Builder\VariableNodeDefinition', $definition); + } + + public function testAddANodeType() + { + $builder = new TreeBuilder(); + $root = $builder->root('override', 'array', new CustomNodeBuilder()); + + $definition = $root->children()->barNode('variable'); + + $this->assertInstanceOf('Symfony\Component\Config\Tests\Definition\Builder\BarNodeDefinition', $definition); + } + + public function testCreateABuiltInNodeTypeWithACustomNodeBuilder() + { + $builder = new TreeBuilder(); + $root = $builder->root('builtin', 'array', new CustomNodeBuilder()); + + $definition = $root->children()->booleanNode('boolean'); + + $this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition', $definition); + } + + public function testPrototypedArrayNodeUseTheCustomNodeBuilder() + { + $builder = new TreeBuilder(); + $root = $builder->root('override', 'array', new CustomNodeBuilder()); + + $root->prototype('bar')->end(); + } + + public function testAnExtendedNodeBuilderGetsPropagatedToTheChildren() + { + $builder = new TreeBuilder(); + + $builder->root('propagation') + ->children() + ->setNodeClass('extended', 'Symfony\Component\Config\Tests\Definition\Builder\VariableNodeDefinition') + ->node('foo', 'extended')->end() + ->arrayNode('child') + ->children() + ->node('foo', 'extended') + ->end() + ->end() + ->end() + ->end(); + } + + public function testDefinitionInfoGetsTransferredToNode() + { + $builder = new TreeBuilder(); + + $builder->root('test')->info('root info') + ->children() + ->node('child', 'variable')->info('child info')->defaultValue('default') + ->end() + ->end(); + + $tree = $builder->buildTree(); + $children = $tree->getChildren(); + + $this->assertEquals('root info', $tree->getInfo()); + $this->assertEquals('child info', $children['child']->getInfo()); + } + + public function testDefinitionExampleGetsTransferredToNode() + { + $builder = new TreeBuilder(); + + $builder->root('test') + ->example(array('key' => 'value')) + ->children() + ->node('child', 'variable')->info('child info')->defaultValue('default')->example('example') + ->end() + ->end(); + + $tree = $builder->buildTree(); + $children = $tree->getChildren(); + + $this->assertTrue(is_array($tree->getExample())); + $this->assertEquals('example', $children['child']->getExample()); + } +} diff --git a/vendor/symfony/config/Tests/Definition/Dumper/XmlReferenceDumperTest.php b/vendor/symfony/config/Tests/Definition/Dumper/XmlReferenceDumperTest.php new file mode 100644 index 00000000..ff043f18 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/Dumper/XmlReferenceDumperTest.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\Config\Tests\Definition\Dumper; + +use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper; +use Symfony\Component\Config\Tests\Fixtures\Configuration\ExampleConfiguration; + +class XmlReferenceDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDumper() + { + $configuration = new ExampleConfiguration(); + + $dumper = new XmlReferenceDumper(); + $this->assertEquals($this->getConfigurationAsString(), $dumper->dump($configuration)); + } + + public function testNamespaceDumper() + { + $configuration = new ExampleConfiguration(); + + $dumper = new XmlReferenceDumper(); + $this->assertEquals(str_replace('http://example.org/schema/dic/acme_root', 'http://symfony.com/schema/dic/symfony', $this->getConfigurationAsString()), $dumper->dump($configuration, 'http://symfony.com/schema/dic/symfony')); + } + + private function getConfigurationAsString() + { + return str_replace("\n", PHP_EOL, << + + + + + + + + + + + scalar value + + + + + + +EOL + ); + } +} diff --git a/vendor/symfony/config/Tests/Definition/Dumper/YamlReferenceDumperTest.php b/vendor/symfony/config/Tests/Definition/Dumper/YamlReferenceDumperTest.php new file mode 100644 index 00000000..74b0f623 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/Dumper/YamlReferenceDumperTest.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\Config\Tests\Definition\Dumper; + +use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; +use Symfony\Component\Config\Tests\Fixtures\Configuration\ExampleConfiguration; + +class YamlReferenceDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDumper() + { + $configuration = new ExampleConfiguration(); + + $dumper = new YamlReferenceDumper(); + + $this->markTestIncomplete('The Yaml Dumper currently does not support prototyped arrays'); + $this->assertEquals($this->getConfigurationAsString(), $dumper->dump($configuration)); + } + + private function getConfigurationAsString() + { + return << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\EnumNode; + +class EnumNodeTest extends \PHPUnit_Framework_TestCase +{ + public function testFinalizeValue() + { + $node = new EnumNode('foo', null, array('foo', 'bar')); + $this->assertSame('foo', $node->finalize('foo')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructionWithOneValue() + { + new EnumNode('foo', null, array('foo', 'foo')); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage The value "foobar" is not allowed for path "foo". Permissible values: "foo", "bar" + */ + public function testFinalizeWithInvalidValue() + { + $node = new EnumNode('foo', null, array('foo', 'bar')); + $node->finalize('foobar'); + } +} diff --git a/vendor/symfony/config/Tests/Definition/FinalizationTest.php b/vendor/symfony/config/Tests/Definition/FinalizationTest.php new file mode 100644 index 00000000..19fc347d --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/FinalizationTest.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\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Config\Definition\NodeInterface; + +class FinalizationTest extends \PHPUnit_Framework_TestCase +{ + public function testUnsetKeyWithDeepHierarchy() + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('config', 'array') + ->children() + ->node('level1', 'array') + ->canBeUnset() + ->children() + ->node('level2', 'array') + ->canBeUnset() + ->children() + ->node('somevalue', 'scalar')->end() + ->node('anothervalue', 'scalar')->end() + ->end() + ->end() + ->node('level1_scalar', 'scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'level1' => array( + 'level2' => array( + 'somevalue' => 'foo', + 'anothervalue' => 'bar', + ), + 'level1_scalar' => 'foo', + ), + ); + + $b = array( + 'level1' => array( + 'level2' => false, + ), + ); + + $this->assertEquals(array( + 'level1' => array( + 'level1_scalar' => 'foo', + ), + ), $this->process($tree, array($a, $b))); + } + + protected function process(NodeInterface $tree, array $configs) + { + $processor = new Processor(); + + return $processor->process($tree, $configs); + } +} diff --git a/vendor/symfony/config/Tests/Definition/FloatNodeTest.php b/vendor/symfony/config/Tests/Definition/FloatNodeTest.php new file mode 100644 index 00000000..84afd6c1 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/FloatNodeTest.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\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\FloatNode; + +class FloatNodeTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getValidValues + */ + public function testNormalize($value) + { + $node = new FloatNode('test'); + $this->assertSame($value, $node->normalize($value)); + } + + /** + * @dataProvider getValidValues + * + * @param int $value + */ + public function testValidNonEmptyValues($value) + { + $node = new FloatNode('test'); + $node->setAllowEmptyValue(false); + + $this->assertSame($value, $node->finalize($value)); + } + + public function getValidValues() + { + return array( + array(1798.0), + array(-678.987), + array(12.56E45), + array(0.0), + // Integer are accepted too, they will be cast + array(17), + array(-10), + array(0), + ); + } + + /** + * @dataProvider getInvalidValues + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException + */ + public function testNormalizeThrowsExceptionOnInvalidValues($value) + { + $node = new FloatNode('test'); + $node->normalize($value); + } + + public function getInvalidValues() + { + return array( + array(null), + array(''), + array('foo'), + array(true), + array(false), + array(array()), + array(array('foo' => 'bar')), + array(new \stdClass()), + ); + } +} diff --git a/vendor/symfony/config/Tests/Definition/IntegerNodeTest.php b/vendor/symfony/config/Tests/Definition/IntegerNodeTest.php new file mode 100644 index 00000000..58d21485 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/IntegerNodeTest.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\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\IntegerNode; + +class IntegerNodeTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getValidValues + */ + public function testNormalize($value) + { + $node = new IntegerNode('test'); + $this->assertSame($value, $node->normalize($value)); + } + + /** + * @dataProvider getValidValues + * + * @param int $value + */ + public function testValidNonEmptyValues($value) + { + $node = new IntegerNode('test'); + $node->setAllowEmptyValue(false); + + $this->assertSame($value, $node->finalize($value)); + } + + public function getValidValues() + { + return array( + array(1798), + array(-678), + array(0), + ); + } + + /** + * @dataProvider getInvalidValues + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException + */ + public function testNormalizeThrowsExceptionOnInvalidValues($value) + { + $node = new IntegerNode('test'); + $node->normalize($value); + } + + public function getInvalidValues() + { + return array( + array(null), + array(''), + array('foo'), + array(true), + array(false), + array(0.0), + array(0.1), + array(array()), + array(array('foo' => 'bar')), + array(new \stdClass()), + ); + } +} diff --git a/vendor/symfony/config/Tests/Definition/MergeTest.php b/vendor/symfony/config/Tests/Definition/MergeTest.php new file mode 100644 index 00000000..08ddc320 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/MergeTest.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; + +class MergeTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException + */ + public function testForbiddenOverwrite() + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('root', 'array') + ->children() + ->node('foo', 'scalar') + ->cannotBeOverwritten() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'foo' => 'bar', + ); + + $b = array( + 'foo' => 'moo', + ); + + $tree->merge($a, $b); + } + + public function testUnsetKey() + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('root', 'array') + ->children() + ->node('foo', 'scalar')->end() + ->node('bar', 'scalar')->end() + ->node('unsettable', 'array') + ->canBeUnset() + ->children() + ->node('foo', 'scalar')->end() + ->node('bar', 'scalar')->end() + ->end() + ->end() + ->node('unsetted', 'array') + ->canBeUnset() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'foo' => 'bar', + 'unsettable' => array( + 'foo' => 'a', + 'bar' => 'b', + ), + 'unsetted' => false, + ); + + $b = array( + 'foo' => 'moo', + 'bar' => 'b', + 'unsettable' => false, + 'unsetted' => array('a', 'b'), + ); + + $this->assertEquals(array( + 'foo' => 'moo', + 'bar' => 'b', + 'unsettable' => false, + 'unsetted' => array('a', 'b'), + ), $tree->merge($a, $b)); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + */ + public function testDoesNotAllowNewKeysInSubsequentConfigs() + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('config', 'array') + ->children() + ->node('test', 'array') + ->disallowNewKeysInSubsequentConfigs() + ->useAttributeAsKey('key') + ->prototype('array') + ->children() + ->node('value', 'scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->buildTree(); + + $a = array( + 'test' => array( + 'a' => array('value' => 'foo'), + ), + ); + + $b = array( + 'test' => array( + 'b' => array('value' => 'foo'), + ), + ); + + $tree->merge($a, $b); + } + + public function testPerformsNoDeepMerging() + { + $tb = new TreeBuilder(); + + $tree = $tb + ->root('config', 'array') + ->children() + ->node('no_deep_merging', 'array') + ->performNoDeepMerging() + ->children() + ->node('foo', 'scalar')->end() + ->node('bar', 'scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'no_deep_merging' => array( + 'foo' => 'a', + 'bar' => 'b', + ), + ); + + $b = array( + 'no_deep_merging' => array( + 'c' => 'd', + ), + ); + + $this->assertEquals(array( + 'no_deep_merging' => array( + 'c' => 'd', + ), + ), $tree->merge($a, $b)); + } + + public function testPrototypeWithoutAKeyAttribute() + { + $tb = new TreeBuilder(); + + $tree = $tb + ->root('config', 'array') + ->children() + ->arrayNode('append_elements') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'append_elements' => array('a', 'b'), + ); + + $b = array( + 'append_elements' => array('c', 'd'), + ); + + $this->assertEquals(array('append_elements' => array('a', 'b', 'c', 'd')), $tree->merge($a, $b)); + } +} diff --git a/vendor/symfony/config/Tests/Definition/NormalizationTest.php b/vendor/symfony/config/Tests/Definition/NormalizationTest.php new file mode 100644 index 00000000..a896f962 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/NormalizationTest.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; + +class NormalizationTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getEncoderTests + */ + public function testNormalizeEncoders($denormalized) + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('root_name', 'array') + ->fixXmlConfig('encoder') + ->children() + ->node('encoders', 'array') + ->useAttributeAsKey('class') + ->prototype('array') + ->beforeNormalization()->ifString()->then(function ($v) { return array('algorithm' => $v); })->end() + ->children() + ->node('algorithm', 'scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $normalized = array( + 'encoders' => array( + 'foo' => array('algorithm' => 'plaintext'), + ), + ); + + $this->assertNormalized($tree, $denormalized, $normalized); + } + + public function getEncoderTests() + { + $configs = array(); + + // XML + $configs[] = array( + 'encoder' => array( + array('class' => 'foo', 'algorithm' => 'plaintext'), + ), + ); + + // XML when only one element of this type + $configs[] = array( + 'encoder' => array('class' => 'foo', 'algorithm' => 'plaintext'), + ); + + // YAML/PHP + $configs[] = array( + 'encoders' => array( + array('class' => 'foo', 'algorithm' => 'plaintext'), + ), + ); + + // YAML/PHP + $configs[] = array( + 'encoders' => array( + 'foo' => 'plaintext', + ), + ); + + // YAML/PHP + $configs[] = array( + 'encoders' => array( + 'foo' => array('algorithm' => 'plaintext'), + ), + ); + + return array_map(function ($v) { + return array($v); + }, $configs); + } + + /** + * @dataProvider getAnonymousKeysTests + */ + public function testAnonymousKeysArray($denormalized) + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('root', 'array') + ->children() + ->node('logout', 'array') + ->fixXmlConfig('handler') + ->children() + ->node('handlers', 'array') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $normalized = array('logout' => array('handlers' => array('a', 'b', 'c'))); + + $this->assertNormalized($tree, $denormalized, $normalized); + } + + public function getAnonymousKeysTests() + { + $configs = array(); + + $configs[] = array( + 'logout' => array( + 'handlers' => array('a', 'b', 'c'), + ), + ); + + $configs[] = array( + 'logout' => array( + 'handler' => array('a', 'b', 'c'), + ), + ); + + return array_map(function ($v) { return array($v); }, $configs); + } + + /** + * @dataProvider getNumericKeysTests + */ + public function testNumericKeysAsAttributes($denormalized) + { + $normalized = array( + 'thing' => array(42 => array('foo', 'bar'), 1337 => array('baz', 'qux')), + ); + + $this->assertNormalized($this->getNumericKeysTestTree(), $denormalized, $normalized); + } + + public function getNumericKeysTests() + { + $configs = array(); + + $configs[] = array( + 'thing' => array( + 42 => array('foo', 'bar'), 1337 => array('baz', 'qux'), + ), + ); + + $configs[] = array( + 'thing' => array( + array('foo', 'bar', 'id' => 42), array('baz', 'qux', 'id' => 1337), + ), + ); + + return array_map(function ($v) { return array($v); }, $configs); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage The attribute "id" must be set for path "root.thing". + */ + public function testNonAssociativeArrayThrowsExceptionIfAttributeNotSet() + { + $denormalized = array( + 'thing' => array( + array('foo', 'bar'), array('baz', 'qux'), + ), + ); + + $this->assertNormalized($this->getNumericKeysTestTree(), $denormalized, array()); + } + + public function testAssociativeArrayPreserveKeys() + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('root', 'array') + ->prototype('array') + ->children() + ->node('foo', 'scalar')->end() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $data = array('first' => array('foo' => 'bar')); + + $this->assertNormalized($tree, $data, $data); + } + + public static function assertNormalized(NodeInterface $tree, $denormalized, $normalized) + { + self::assertSame($normalized, $tree->normalize($denormalized)); + } + + private function getNumericKeysTestTree() + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('root', 'array') + ->children() + ->node('thing', 'array') + ->useAttributeAsKey('id') + ->prototype('array') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->buildTree() + ; + + return $tree; + } +} diff --git a/vendor/symfony/config/Tests/Definition/PrototypedArrayNodeTest.php b/vendor/symfony/config/Tests/Definition/PrototypedArrayNodeTest.php new file mode 100644 index 00000000..c343fcfd --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/PrototypedArrayNodeTest.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\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\PrototypedArrayNode; +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\ScalarNode; + +class PrototypedArrayNodeTest extends \PHPUnit_Framework_TestCase +{ + public function testGetDefaultValueReturnsAnEmptyArrayForPrototypes() + { + $node = new PrototypedArrayNode('root'); + $prototype = new ArrayNode(null, $node); + $node->setPrototype($prototype); + $this->assertEmpty($node->getDefaultValue()); + } + + public function testGetDefaultValueReturnsDefaultValueForPrototypes() + { + $node = new PrototypedArrayNode('root'); + $prototype = new ArrayNode(null, $node); + $node->setPrototype($prototype); + $node->setDefaultValue(array('test')); + $this->assertEquals(array('test'), $node->getDefaultValue()); + } + + // a remapped key (e.g. "mapping" -> "mappings") should be unset after being used + public function testRemappedKeysAreUnset() + { + $node = new ArrayNode('root'); + $mappingsNode = new PrototypedArrayNode('mappings'); + $node->addChild($mappingsNode); + + // each item under mappings is just a scalar + $prototype = new ScalarNode(null, $mappingsNode); + $mappingsNode->setPrototype($prototype); + + $remappings = array(); + $remappings[] = array('mapping', 'mappings'); + $node->setXmlRemappings($remappings); + + $normalized = $node->normalize(array('mapping' => array('foo', 'bar'))); + $this->assertEquals(array('mappings' => array('foo', 'bar')), $normalized); + } + + /** + * Tests that when a key attribute is mapped, that key is removed from the array. + * + * + * + * + * The above should finally be mapped to an array that looks like this + * (because "id" is the key attribute). + * + * array( + * 'things' => array( + * 'option1' => 'foo', + * 'option2' => 'bar', + * ) + * ) + */ + public function testMappedAttributeKeyIsRemoved() + { + $node = new PrototypedArrayNode('root'); + $node->setKeyAttribute('id', true); + + // each item under the root is an array, with one scalar item + $prototype = new ArrayNode(null, $node); + $prototype->addChild(new ScalarNode('foo')); + $node->setPrototype($prototype); + + $children = array(); + $children[] = array('id' => 'item_name', 'foo' => 'bar'); + $normalized = $node->normalize($children); + + $expected = array(); + $expected['item_name'] = array('foo' => 'bar'); + $this->assertEquals($expected, $normalized); + } + + /** + * Tests the opposite of the testMappedAttributeKeyIsRemoved because + * the removal can be toggled with an option. + */ + public function testMappedAttributeKeyNotRemoved() + { + $node = new PrototypedArrayNode('root'); + $node->setKeyAttribute('id', false); + + // each item under the root is an array, with two scalar items + $prototype = new ArrayNode(null, $node); + $prototype->addChild(new ScalarNode('foo')); + $prototype->addChild(new ScalarNode('id')); // the key attribute will remain + $node->setPrototype($prototype); + + $children = array(); + $children[] = array('id' => 'item_name', 'foo' => 'bar'); + $normalized = $node->normalize($children); + + $expected = array(); + $expected['item_name'] = array('id' => 'item_name', 'foo' => 'bar'); + $this->assertEquals($expected, $normalized); + } + + public function testAddDefaultChildren() + { + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setAddChildrenIfNoneSet(); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array(array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setKeyAttribute('foobar'); + $node->setAddChildrenIfNoneSet(); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array('defaults' => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setKeyAttribute('foobar'); + $node->setAddChildrenIfNoneSet('defaultkey'); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array('defaultkey' => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setKeyAttribute('foobar'); + $node->setAddChildrenIfNoneSet(array('defaultkey')); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array('defaultkey' => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setKeyAttribute('foobar'); + $node->setAddChildrenIfNoneSet(array('dk1', 'dk2')); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array('dk1' => array('foo' => 'bar'), 'dk2' => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setAddChildrenIfNoneSet(array(5, 6)); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array(0 => array('foo' => 'bar'), 1 => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setAddChildrenIfNoneSet(2); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array(array('foo' => 'bar'), array('foo' => 'bar')), $node->getDefaultValue()); + } + + public function testDefaultChildrenWinsOverDefaultValue() + { + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setAddChildrenIfNoneSet(); + $node->setDefaultValue(array('bar' => 'foo')); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array(array('foo' => 'bar')), $node->getDefaultValue()); + } + + protected function getPrototypeNodeWithDefaultChildren() + { + $node = new PrototypedArrayNode('root'); + $prototype = new ArrayNode(null, $node); + $child = new ScalarNode('foo'); + $child->setDefaultValue('bar'); + $prototype->addChild($child); + $prototype->setAddIfNotSet(true); + $node->setPrototype($prototype); + + return $node; + } +} diff --git a/vendor/symfony/config/Tests/Definition/ScalarNodeTest.php b/vendor/symfony/config/Tests/Definition/ScalarNodeTest.php new file mode 100644 index 00000000..86c18030 --- /dev/null +++ b/vendor/symfony/config/Tests/Definition/ScalarNodeTest.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\Config\Tests\Definition; + +use Symfony\Component\Config\Definition\ScalarNode; + +class ScalarNodeTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getValidValues + */ + public function testNormalize($value) + { + $node = new ScalarNode('test'); + $this->assertSame($value, $node->normalize($value)); + } + + public function getValidValues() + { + return array( + array(false), + array(true), + array(null), + array(''), + array('foo'), + array(0), + array(1), + array(0.0), + array(0.1), + ); + } + + /** + * @dataProvider getInvalidValues + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException + */ + public function testNormalizeThrowsExceptionOnInvalidValues($value) + { + $node = new ScalarNode('test'); + $node->normalize($value); + } + + public function getInvalidValues() + { + return array( + array(array()), + array(array('foo' => 'bar')), + array(new \stdClass()), + ); + } + + public function testNormalizeThrowsExceptionWithoutHint() + { + $node = new ScalarNode('test'); + + $this->setExpectedException('Symfony\Component\Config\Definition\Exception\InvalidTypeException', 'Invalid type for path "test". Expected scalar, but got array.'); + + $node->normalize(array()); + } + + public function testNormalizeThrowsExceptionWithErrorMessage() + { + $node = new ScalarNode('test'); + $node->setInfo('"the test value"'); + + $this->setExpectedException('Symfony\Component\Config\Definition\Exception\InvalidTypeException', "Invalid type for path \"test\". Expected scalar, but got array.\nHint: \"the test value\""); + + $node->normalize(array()); + } + + /** + * @dataProvider getValidNonEmptyValues + * + * @param mixed $value + */ + public function testValidNonEmptyValues($value) + { + $node = new ScalarNode('test'); + $node->setAllowEmptyValue(false); + + $this->assertSame($value, $node->finalize($value)); + } + + public function getValidNonEmptyValues() + { + return array( + array(false), + array(true), + array('foo'), + array(0), + array(1), + array(0.0), + array(0.1), + ); + } + + /** + * @dataProvider getEmptyValues + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * + * @param mixed $value + */ + public function testNotAllowedEmptyValuesThrowException($value) + { + $node = new ScalarNode('test'); + $node->setAllowEmptyValue(false); + $node->finalize($value); + } + + public function getEmptyValues() + { + return array( + array(null), + array(''), + ); + } +} diff --git a/vendor/symfony/config/Tests/Exception/FileLoaderLoadExceptionTest.php b/vendor/symfony/config/Tests/Exception/FileLoaderLoadExceptionTest.php new file mode 100644 index 00000000..c3d050c7 --- /dev/null +++ b/vendor/symfony/config/Tests/Exception/FileLoaderLoadExceptionTest.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\Config\Tests\Exception; + +use Symfony\Component\Config\Exception\FileLoaderLoadException; + +class FileLoaderLoadExceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testMessageCannotLoadResource() + { + $exception = new FileLoaderLoadException('resource', null); + $this->assertEquals('Cannot load resource "resource".', $exception->getMessage()); + } + + public function testMessageCannotImportResourceFromSource() + { + $exception = new FileLoaderLoadException('resource', 'sourceResource'); + $this->assertEquals('Cannot import resource "resource" from "sourceResource".', $exception->getMessage()); + } + + public function testMessageCannotImportBundleResource() + { + $exception = new FileLoaderLoadException('@resource', 'sourceResource'); + $this->assertEquals( + 'Cannot import resource "@resource" from "sourceResource". '. + 'Make sure the "resource" bundle is correctly registered and loaded in the application kernel class. '. + 'If the bundle is registered, make sure the bundle path "@resource" is not empty.', + $exception->getMessage() + ); + } + + public function testMessageHasPreviousErrorWithDotAndUnableToLoad() + { + $exception = new FileLoaderLoadException( + 'resource', + null, + null, + new \Exception('There was a previous error with an ending dot.') + ); + $this->assertEquals( + 'There was a previous error with an ending dot in resource (which is loaded in resource "resource").', + $exception->getMessage() + ); + } + + public function testMessageHasPreviousErrorWithoutDotAndUnableToLoad() + { + $exception = new FileLoaderLoadException( + 'resource', + null, + null, + new \Exception('There was a previous error with no ending dot') + ); + $this->assertEquals( + 'There was a previous error with no ending dot in resource (which is loaded in resource "resource").', + $exception->getMessage() + ); + } + + public function testMessageHasPreviousErrorAndUnableToLoadBundle() + { + $exception = new FileLoaderLoadException( + '@resource', + null, + null, + new \Exception('There was a previous error with an ending dot.') + ); + $this->assertEquals( + 'There was a previous error with an ending dot in @resource '. + '(which is loaded in resource "@resource"). '. + 'Make sure the "resource" bundle is correctly registered and loaded in the application kernel class. '. + 'If the bundle is registered, make sure the bundle path "@resource" is not empty.', + $exception->getMessage() + ); + } +} diff --git a/vendor/symfony/config/Tests/FileLocatorTest.php b/vendor/symfony/config/Tests/FileLocatorTest.php new file mode 100644 index 00000000..d479f256 --- /dev/null +++ b/vendor/symfony/config/Tests/FileLocatorTest.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\Config\Tests; + +use Symfony\Component\Config\FileLocator; + +class FileLocatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getIsAbsolutePathTests + */ + public function testIsAbsolutePath($path) + { + $loader = new FileLocator(array()); + $r = new \ReflectionObject($loader); + $m = $r->getMethod('isAbsolutePath'); + $m->setAccessible(true); + + $this->assertTrue($m->invoke($loader, $path), '->isAbsolutePath() returns true for an absolute path'); + } + + public function getIsAbsolutePathTests() + { + return array( + array('/foo.xml'), + array('c:\\\\foo.xml'), + array('c:/foo.xml'), + array('\\server\\foo.xml'), + array('https://server/foo.xml'), + array('phar://server/foo.xml'), + ); + } + + public function testLocate() + { + $loader = new FileLocator(__DIR__.'/Fixtures'); + + $this->assertEquals( + __DIR__.DIRECTORY_SEPARATOR.'FileLocatorTest.php', + $loader->locate('FileLocatorTest.php', __DIR__), + '->locate() returns the absolute filename if the file exists in the given path' + ); + + $this->assertEquals( + __DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', + $loader->locate('foo.xml', __DIR__), + '->locate() returns the absolute filename if the file exists in one of the paths given in the constructor' + ); + + $this->assertEquals( + __DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', + $loader->locate(__DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', __DIR__), + '->locate() returns the absolute filename if the file exists in one of the paths given in the constructor' + ); + + $loader = new FileLocator(array(__DIR__.'/Fixtures', __DIR__.'/Fixtures/Again')); + + $this->assertEquals( + array(__DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', __DIR__.'/Fixtures/Again'.DIRECTORY_SEPARATOR.'foo.xml'), + $loader->locate('foo.xml', __DIR__, false), + '->locate() returns an array of absolute filenames' + ); + + $this->assertEquals( + array(__DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', __DIR__.'/Fixtures/Again'.DIRECTORY_SEPARATOR.'foo.xml'), + $loader->locate('foo.xml', __DIR__.'/Fixtures', false), + '->locate() returns an array of absolute filenames' + ); + + $loader = new FileLocator(__DIR__.'/Fixtures/Again'); + + $this->assertEquals( + array(__DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', __DIR__.'/Fixtures/Again'.DIRECTORY_SEPARATOR.'foo.xml'), + $loader->locate('foo.xml', __DIR__.'/Fixtures', false), + '->locate() returns an array of absolute filenames' + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The file "foobar.xml" does not exist + */ + public function testLocateThrowsAnExceptionIfTheFileDoesNotExists() + { + $loader = new FileLocator(array(__DIR__.'/Fixtures')); + + $loader->locate('foobar.xml', __DIR__); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateThrowsAnExceptionIfTheFileDoesNotExistsInAbsolutePath() + { + $loader = new FileLocator(array(__DIR__.'/Fixtures')); + + $loader->locate(__DIR__.'/Fixtures/foobar.xml', __DIR__); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage An empty file name is not valid to be located. + */ + public function testLocateEmpty() + { + $loader = new FileLocator(array(__DIR__.'/Fixtures')); + + $loader->locate(null, __DIR__); + } +} diff --git a/vendor/symfony/config/Tests/Fixtures/Again/foo.xml b/vendor/symfony/config/Tests/Fixtures/Again/foo.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/config/Tests/Fixtures/Builder/BarNodeDefinition.php b/vendor/symfony/config/Tests/Fixtures/Builder/BarNodeDefinition.php new file mode 100644 index 00000000..47701c1b --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Builder/BarNodeDefinition.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\Config\Tests\Definition\Builder; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; + +class BarNodeDefinition extends NodeDefinition +{ + protected function createNode() + { + } +} diff --git a/vendor/symfony/config/Tests/Fixtures/Builder/NodeBuilder.php b/vendor/symfony/config/Tests/Fixtures/Builder/NodeBuilder.php new file mode 100644 index 00000000..aa598631 --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Builder/NodeBuilder.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\Config\Tests\Definition\Builder; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder as BaseNodeBuilder; + +class NodeBuilder extends BaseNodeBuilder +{ + public function barNode($name) + { + return $this->node($name, 'bar'); + } + + protected function getNodeClass($type) + { + switch ($type) { + case 'variable': + return __NAMESPACE__.'\\'.ucfirst($type).'NodeDefinition'; + case 'bar': + return __NAMESPACE__.'\\'.ucfirst($type).'NodeDefinition'; + default: + return parent::getNodeClass($type); + } + } +} diff --git a/vendor/symfony/config/Tests/Fixtures/Builder/VariableNodeDefinition.php b/vendor/symfony/config/Tests/Fixtures/Builder/VariableNodeDefinition.php new file mode 100644 index 00000000..1017880c --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Builder/VariableNodeDefinition.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\Config\Tests\Definition\Builder; + +use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition as BaseVariableNodeDefinition; + +class VariableNodeDefinition extends BaseVariableNodeDefinition +{ +} diff --git a/vendor/symfony/config/Tests/Fixtures/Configuration/ExampleConfiguration.php b/vendor/symfony/config/Tests/Fixtures/Configuration/ExampleConfiguration.php new file mode 100644 index 00000000..68aa0c05 --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Configuration/ExampleConfiguration.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\Config\Tests\Fixtures\Configuration; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class ExampleConfiguration implements ConfigurationInterface +{ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('acme_root'); + + $rootNode + ->fixXmlConfig('parameter') + ->fixXmlConfig('connection') + ->children() + ->booleanNode('boolean')->defaultTrue()->end() + ->scalarNode('scalar_empty')->end() + ->scalarNode('scalar_null')->defaultNull()->end() + ->scalarNode('scalar_true')->defaultTrue()->end() + ->scalarNode('scalar_false')->defaultFalse()->end() + ->scalarNode('scalar_default')->defaultValue('default')->end() + ->scalarNode('scalar_array_empty')->defaultValue(array())->end() + ->scalarNode('scalar_array_defaults')->defaultValue(array('elem1', 'elem2'))->end() + ->scalarNode('scalar_required')->isRequired()->end() + ->enumNode('enum_with_default')->values(array('this', 'that'))->defaultValue('this')->end() + ->enumNode('enum')->values(array('this', 'that'))->end() + ->arrayNode('array') + ->info('some info') + ->canBeUnset() + ->children() + ->scalarNode('child1')->end() + ->scalarNode('child2')->end() + ->scalarNode('child3') + ->info( + "this is a long\n". + "multi-line info text\n". + 'which should be indented' + ) + ->example('example setting') + ->end() + ->end() + ->end() + ->arrayNode('parameters') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->arrayNode('connections') + ->prototype('array') + ->children() + ->scalarNode('user')->end() + ->scalarNode('pass')->end() + ->end() + ->end() + ->end() + ->end() + ; + + return $treeBuilder; + } +} diff --git a/vendor/symfony/config/Tests/Fixtures/Util/document_type.xml b/vendor/symfony/config/Tests/Fixtures/Util/document_type.xml new file mode 100644 index 00000000..4c252282 --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Util/document_type.xml @@ -0,0 +1,3 @@ + +]> + diff --git a/vendor/symfony/config/Tests/Fixtures/Util/invalid.xml b/vendor/symfony/config/Tests/Fixtures/Util/invalid.xml new file mode 100644 index 00000000..a07af9fd --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Util/invalid.xml @@ -0,0 +1,2 @@ + + diff --git a/vendor/symfony/config/Tests/Fixtures/Util/invalid_schema.xml b/vendor/symfony/config/Tests/Fixtures/Util/invalid_schema.xml new file mode 100644 index 00000000..e2725a2c --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Util/invalid_schema.xml @@ -0,0 +1,2 @@ + + diff --git a/vendor/symfony/config/Tests/Fixtures/Util/schema.xsd b/vendor/symfony/config/Tests/Fixtures/Util/schema.xsd new file mode 100644 index 00000000..e56820f6 --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Util/schema.xsd @@ -0,0 +1,9 @@ + + + + + + diff --git a/vendor/symfony/config/Tests/Fixtures/Util/valid.xml b/vendor/symfony/config/Tests/Fixtures/Util/valid.xml new file mode 100644 index 00000000..a96bb382 --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Util/valid.xml @@ -0,0 +1,3 @@ + + + diff --git a/vendor/symfony/config/Tests/Fixtures/foo.xml b/vendor/symfony/config/Tests/Fixtures/foo.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/config/Tests/Loader/DelegatingLoaderTest.php b/vendor/symfony/config/Tests/Loader/DelegatingLoaderTest.php new file mode 100644 index 00000000..7641e248 --- /dev/null +++ b/vendor/symfony/config/Tests/Loader/DelegatingLoaderTest.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\Config\Tests\Loader; + +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Config\Loader\DelegatingLoader; + +class DelegatingLoaderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\Config\Loader\DelegatingLoader::__construct + */ + public function testConstructor() + { + $loader = new DelegatingLoader($resolver = new LoaderResolver()); + $this->assertTrue(true, '__construct() takes a loader resolver as its first argument'); + } + + /** + * @covers Symfony\Component\Config\Loader\DelegatingLoader::getResolver + * @covers Symfony\Component\Config\Loader\DelegatingLoader::setResolver + */ + public function testGetSetResolver() + { + $resolver = new LoaderResolver(); + $loader = new DelegatingLoader($resolver); + $this->assertSame($resolver, $loader->getResolver(), '->getResolver() gets the resolver loader'); + $loader->setResolver($resolver = new LoaderResolver()); + $this->assertSame($resolver, $loader->getResolver(), '->setResolver() sets the resolver loader'); + } + + /** + * @covers Symfony\Component\Config\Loader\DelegatingLoader::supports + */ + public function testSupports() + { + $loader1 = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $loader1->expects($this->once())->method('supports')->will($this->returnValue(true)); + $loader = new DelegatingLoader(new LoaderResolver(array($loader1))); + $this->assertTrue($loader->supports('foo.xml'), '->supports() returns true if the resource is loadable'); + + $loader1 = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $loader1->expects($this->once())->method('supports')->will($this->returnValue(false)); + $loader = new DelegatingLoader(new LoaderResolver(array($loader1))); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns false if the resource is not loadable'); + } + + /** + * @covers Symfony\Component\Config\Loader\DelegatingLoader::load + */ + public function testLoad() + { + $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $loader->expects($this->once())->method('supports')->will($this->returnValue(true)); + $loader->expects($this->once())->method('load'); + $resolver = new LoaderResolver(array($loader)); + $loader = new DelegatingLoader($resolver); + + $loader->load('foo'); + } + + /** + * @expectedException \Symfony\Component\Config\Exception\FileLoaderLoadException + */ + public function testLoadThrowsAnExceptionIfTheResourceCannotBeLoaded() + { + $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $loader->expects($this->once())->method('supports')->will($this->returnValue(false)); + $resolver = new LoaderResolver(array($loader)); + $loader = new DelegatingLoader($resolver); + + $loader->load('foo'); + } +} diff --git a/vendor/symfony/config/Tests/Loader/FileLoaderTest.php b/vendor/symfony/config/Tests/Loader/FileLoaderTest.php new file mode 100644 index 00000000..1442e94e --- /dev/null +++ b/vendor/symfony/config/Tests/Loader/FileLoaderTest.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\Config\Tests\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Loader\LoaderResolver; + +class FileLoaderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\Config\Loader\FileLoader + */ + public function testImportWithFileLocatorDelegation() + { + $locatorMock = $this->getMock('Symfony\Component\Config\FileLocatorInterface'); + + $locatorMockForAdditionalLoader = $this->getMock('Symfony\Component\Config\FileLocatorInterface'); + $locatorMockForAdditionalLoader->expects($this->any())->method('locate')->will($this->onConsecutiveCalls( + array('path/to/file1'), // Default + array('path/to/file1', 'path/to/file2'), // First is imported + array('path/to/file1', 'path/to/file2'), // Second is imported + array('path/to/file1'), // Exception + array('path/to/file1', 'path/to/file2') // Exception + )); + + $fileLoader = new TestFileLoader($locatorMock); + $fileLoader->setSupports(false); + $fileLoader->setCurrentDir('.'); + + $additionalLoader = new TestFileLoader($locatorMockForAdditionalLoader); + $additionalLoader->setCurrentDir('.'); + + $fileLoader->setResolver($loaderResolver = new LoaderResolver(array($fileLoader, $additionalLoader))); + + // Default case + $this->assertSame('path/to/file1', $fileLoader->import('my_resource')); + + // Check first file is imported if not already loading + $this->assertSame('path/to/file1', $fileLoader->import('my_resource')); + + // Check second file is imported if first is already loading + $fileLoader->addLoading('path/to/file1'); + $this->assertSame('path/to/file2', $fileLoader->import('my_resource')); + + // Check exception throws if first (and only available) file is already loading + try { + $fileLoader->import('my_resource'); + $this->fail('->import() throws a FileLoaderImportCircularReferenceException if the resource is already loading'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException', $e, '->import() throws a FileLoaderImportCircularReferenceException if the resource is already loading'); + } + + // Check exception throws if all files are already loading + try { + $fileLoader->addLoading('path/to/file2'); + $fileLoader->import('my_resource'); + $this->fail('->import() throws a FileLoaderImportCircularReferenceException if the resource is already loading'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException', $e, '->import() throws a FileLoaderImportCircularReferenceException if the resource is already loading'); + } + } +} + +class TestFileLoader extends FileLoader +{ + private $supports = true; + + public function load($resource, $type = null) + { + return $resource; + } + + public function supports($resource, $type = null) + { + return $this->supports; + } + + public function addLoading($resource) + { + self::$loading[$resource] = true; + } + + public function removeLoading($resource) + { + unset(self::$loading[$resource]); + } + + public function clearLoading() + { + self::$loading = array(); + } + + public function setSupports($supports) + { + $this->supports = $supports; + } +} diff --git a/vendor/symfony/config/Tests/Loader/LoaderResolverTest.php b/vendor/symfony/config/Tests/Loader/LoaderResolverTest.php new file mode 100644 index 00000000..8ee276b0 --- /dev/null +++ b/vendor/symfony/config/Tests/Loader/LoaderResolverTest.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\Config\Tests\Loader; + +use Symfony\Component\Config\Loader\LoaderResolver; + +class LoaderResolverTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\Config\Loader\LoaderResolver::__construct + */ + public function testConstructor() + { + $resolver = new LoaderResolver(array( + $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'), + )); + + $this->assertEquals(array($loader), $resolver->getLoaders(), '__construct() takes an array of loaders as its first argument'); + } + + /** + * @covers Symfony\Component\Config\Loader\LoaderResolver::resolve + */ + public function testResolve() + { + $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $resolver = new LoaderResolver(array($loader)); + $this->assertFalse($resolver->resolve('foo.foo'), '->resolve() returns false if no loader is able to load the resource'); + + $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $loader->expects($this->once())->method('supports')->will($this->returnValue(true)); + $resolver = new LoaderResolver(array($loader)); + $this->assertEquals($loader, $resolver->resolve(function () {}), '->resolve() returns the loader for the given resource'); + } + + /** + * @covers Symfony\Component\Config\Loader\LoaderResolver::getLoaders + * @covers Symfony\Component\Config\Loader\LoaderResolver::addLoader + */ + public function testLoaders() + { + $resolver = new LoaderResolver(); + $resolver->addLoader($loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface')); + + $this->assertEquals(array($loader), $resolver->getLoaders(), 'addLoader() adds a loader'); + } +} diff --git a/vendor/symfony/config/Tests/Loader/LoaderTest.php b/vendor/symfony/config/Tests/Loader/LoaderTest.php new file mode 100644 index 00000000..e938a4b0 --- /dev/null +++ b/vendor/symfony/config/Tests/Loader/LoaderTest.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\Config\Tests\Loader; + +use Symfony\Component\Config\Loader\Loader; + +class LoaderTest extends \PHPUnit_Framework_TestCase +{ + public function testGetSetResolver() + { + $resolver = $this->getMock('Symfony\Component\Config\Loader\LoaderResolverInterface'); + + $loader = new ProjectLoader1(); + $loader->setResolver($resolver); + + $this->assertSame($resolver, $loader->getResolver(), '->setResolver() sets the resolver loader'); + } + + public function testResolve() + { + $resolvedLoader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + + $resolver = $this->getMock('Symfony\Component\Config\Loader\LoaderResolverInterface'); + $resolver->expects($this->once()) + ->method('resolve') + ->with('foo.xml') + ->will($this->returnValue($resolvedLoader)); + + $loader = new ProjectLoader1(); + $loader->setResolver($resolver); + + $this->assertSame($loader, $loader->resolve('foo.foo'), '->resolve() finds a loader'); + $this->assertSame($resolvedLoader, $loader->resolve('foo.xml'), '->resolve() finds a loader'); + } + + /** + * @expectedException \Symfony\Component\Config\Exception\FileLoaderLoadException + */ + public function testResolveWhenResolverCannotFindLoader() + { + $resolver = $this->getMock('Symfony\Component\Config\Loader\LoaderResolverInterface'); + $resolver->expects($this->once()) + ->method('resolve') + ->with('FOOBAR') + ->will($this->returnValue(false)); + + $loader = new ProjectLoader1(); + $loader->setResolver($resolver); + + $loader->resolve('FOOBAR'); + } + + public function testImport() + { + $resolvedLoader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $resolvedLoader->expects($this->once()) + ->method('load') + ->with('foo') + ->will($this->returnValue('yes')); + + $resolver = $this->getMock('Symfony\Component\Config\Loader\LoaderResolverInterface'); + $resolver->expects($this->once()) + ->method('resolve') + ->with('foo') + ->will($this->returnValue($resolvedLoader)); + + $loader = new ProjectLoader1(); + $loader->setResolver($resolver); + + $this->assertEquals('yes', $loader->import('foo')); + } + + public function testImportWithType() + { + $resolvedLoader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $resolvedLoader->expects($this->once()) + ->method('load') + ->with('foo', 'bar') + ->will($this->returnValue('yes')); + + $resolver = $this->getMock('Symfony\Component\Config\Loader\LoaderResolverInterface'); + $resolver->expects($this->once()) + ->method('resolve') + ->with('foo', 'bar') + ->will($this->returnValue($resolvedLoader)); + + $loader = new ProjectLoader1(); + $loader->setResolver($resolver); + + $this->assertEquals('yes', $loader->import('foo', 'bar')); + } +} + +class ProjectLoader1 extends Loader +{ + public function load($resource, $type = null) + { + } + + public function supports($resource, $type = null) + { + return is_string($resource) && 'foo' === pathinfo($resource, PATHINFO_EXTENSION); + } + + public function getType() + { + } +} diff --git a/vendor/symfony/config/Tests/Resource/DirectoryResourceTest.php b/vendor/symfony/config/Tests/Resource/DirectoryResourceTest.php new file mode 100644 index 00000000..2bbaadc3 --- /dev/null +++ b/vendor/symfony/config/Tests/Resource/DirectoryResourceTest.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\Config\Tests\Resource; + +use Symfony\Component\Config\Resource\DirectoryResource; + +class DirectoryResourceTest extends \PHPUnit_Framework_TestCase +{ + protected $directory; + + protected function setUp() + { + $this->directory = sys_get_temp_dir().'/symfonyDirectoryIterator'; + if (!file_exists($this->directory)) { + mkdir($this->directory); + } + touch($this->directory.'/tmp.xml'); + } + + protected function tearDown() + { + if (!is_dir($this->directory)) { + return; + } + $this->removeDirectory($this->directory); + } + + protected function removeDirectory($directory) + { + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory), \RecursiveIteratorIterator::CHILD_FIRST); + foreach ($iterator as $path) { + if (preg_match('#[/\\\\]\.\.?$#', $path->__toString())) { + continue; + } + if ($path->isDir()) { + rmdir($path->__toString()); + } else { + unlink($path->__toString()); + } + } + rmdir($directory); + } + + public function testGetResource() + { + $resource = new DirectoryResource($this->directory); + $this->assertSame($this->directory, $resource->getResource(), '->getResource() returns the path to the resource'); + } + + public function testGetPattern() + { + $resource = new DirectoryResource('foo', 'bar'); + $this->assertEquals('bar', $resource->getPattern()); + } + + public function testIsFresh() + { + $resource = new DirectoryResource($this->directory); + $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if the resource has not changed'); + $this->assertFalse($resource->isFresh(time() - 86400), '->isFresh() returns false if the resource has been updated'); + + $resource = new DirectoryResource('/____foo/foobar'.mt_rand(1, 999999)); + $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the resource does not exist'); + } + + public function testIsFreshUpdateFile() + { + $resource = new DirectoryResource($this->directory); + touch($this->directory.'/tmp.xml', time() + 20); + $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if an existing file is modified'); + } + + public function testIsFreshNewFile() + { + $resource = new DirectoryResource($this->directory); + touch($this->directory.'/new.xml', time() + 20); + $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if a new file is added'); + } + + public function testIsFreshNewFileWithDifferentPattern() + { + $resource = new DirectoryResource($this->directory, '/.xml$/'); + touch($this->directory.'/new.yaml', time() + 20); + $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if a new file with a non-matching pattern is added'); + } + + public function testIsFreshDeleteFile() + { + $resource = new DirectoryResource($this->directory); + unlink($this->directory.'/tmp.xml'); + $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if an existing file is removed'); + } + + public function testIsFreshDeleteDirectory() + { + $resource = new DirectoryResource($this->directory); + $this->removeDirectory($this->directory); + $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the whole resource is removed'); + } + + public function testIsFreshCreateFileInSubdirectory() + { + $subdirectory = $this->directory.'/subdirectory'; + mkdir($subdirectory); + + $resource = new DirectoryResource($this->directory); + $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if an unmodified subdirectory exists'); + + touch($subdirectory.'/newfile.xml', time() + 20); + $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if a new file in a subdirectory is added'); + } + + public function testIsFreshModifySubdirectory() + { + $resource = new DirectoryResource($this->directory); + + $subdirectory = $this->directory.'/subdirectory'; + mkdir($subdirectory); + touch($subdirectory, time() + 20); + + $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if a subdirectory is modified (e.g. a file gets deleted)'); + } + + public function testFilterRegexListNoMatch() + { + $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/'); + + touch($this->directory.'/new.bar', time() + 20); + $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if a new file not matching the filter regex is created'); + } + + public function testFilterRegexListMatch() + { + $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/'); + + touch($this->directory.'/new.xml', time() + 20); + $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if an new file matching the filter regex is created '); + } + + public function testSerializeUnserialize() + { + $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/'); + + $unserialized = unserialize(serialize($resource)); + + $this->assertSame($this->directory, $resource->getResource()); + $this->assertSame('/\.(foo|xml)$/', $resource->getPattern()); + } + + public function testResourcesWithDifferentPatternsAreDifferent() + { + $resourceA = new DirectoryResource($this->directory, '/.xml$/'); + $resourceB = new DirectoryResource($this->directory, '/.yaml$/'); + + $this->assertEquals(2, count(array_unique(array($resourceA, $resourceB)))); + } +} diff --git a/vendor/symfony/config/Tests/Resource/FileResourceTest.php b/vendor/symfony/config/Tests/Resource/FileResourceTest.php new file mode 100644 index 00000000..db85cf7b --- /dev/null +++ b/vendor/symfony/config/Tests/Resource/FileResourceTest.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\Config\Tests\Resource; + +use Symfony\Component\Config\Resource\FileResource; + +class FileResourceTest extends \PHPUnit_Framework_TestCase +{ + protected $resource; + protected $file; + protected $time; + + protected function setUp() + { + $this->file = realpath(sys_get_temp_dir()).'/tmp.xml'; + $this->time = time(); + touch($this->file, $this->time); + $this->resource = new FileResource($this->file); + } + + protected function tearDown() + { + unlink($this->file); + } + + public function testGetResource() + { + $this->assertSame(realpath($this->file), $this->resource->getResource(), '->getResource() returns the path to the resource'); + } + + public function testToString() + { + $this->assertSame(realpath($this->file), (string) $this->resource); + } + + public function testIsFresh() + { + $this->assertTrue($this->resource->isFresh($this->time), '->isFresh() returns true if the resource has not changed in same second'); + $this->assertTrue($this->resource->isFresh($this->time + 10), '->isFresh() returns true if the resource has not changed'); + $this->assertFalse($this->resource->isFresh($this->time - 86400), '->isFresh() returns false if the resource has been updated'); + + $resource = new FileResource('/____foo/foobar'.mt_rand(1, 999999)); + $this->assertFalse($resource->isFresh($this->time), '->isFresh() returns false if the resource does not exist'); + } + + public function testSerializeUnserialize() + { + $unserialized = unserialize(serialize($this->resource)); + + $this->assertSame(realpath($this->file), $this->resource->getResource()); + } +} diff --git a/vendor/symfony/config/Tests/Util/XmlUtilsTest.php b/vendor/symfony/config/Tests/Util/XmlUtilsTest.php new file mode 100644 index 00000000..f9d3d14a --- /dev/null +++ b/vendor/symfony/config/Tests/Util/XmlUtilsTest.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\Config\Tests\Loader; + +use Symfony\Component\Config\Util\XmlUtils; + +class XmlUtilsTest extends \PHPUnit_Framework_TestCase +{ + public function testLoadFile() + { + $fixtures = __DIR__.'/../Fixtures/Util/'; + + try { + XmlUtils::loadFile($fixtures.'invalid.xml'); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertContains('ERROR 77', $e->getMessage()); + } + + try { + XmlUtils::loadFile($fixtures.'document_type.xml'); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertContains('Document types are not allowed', $e->getMessage()); + } + + try { + XmlUtils::loadFile($fixtures.'invalid_schema.xml', $fixtures.'schema.xsd'); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertContains('ERROR 1845', $e->getMessage()); + } + + try { + XmlUtils::loadFile($fixtures.'invalid_schema.xml', 'invalid_callback_or_file'); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertContains('XSD file or callable', $e->getMessage()); + } + + $mock = $this->getMock(__NAMESPACE__.'\Validator'); + $mock->expects($this->exactly(2))->method('validate')->will($this->onConsecutiveCalls(false, true)); + + try { + XmlUtils::loadFile($fixtures.'valid.xml', array($mock, 'validate')); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertContains('is not valid', $e->getMessage()); + } + + $this->assertInstanceOf('DOMDocument', XmlUtils::loadFile($fixtures.'valid.xml', array($mock, 'validate'))); + $this->assertSame(array(), libxml_get_errors()); + } + + public function testLoadFileWithInternalErrorsEnabled() + { + libxml_use_internal_errors(true); + + $this->assertSame(array(), libxml_get_errors()); + $this->assertInstanceOf('DOMDocument', XmlUtils::loadFile(__DIR__.'/../Fixtures/Util/invalid_schema.xml')); + $this->assertSame(array(), libxml_get_errors()); + } + + /** + * @dataProvider getDataForConvertDomToArray + */ + public function testConvertDomToArray($expected, $xml, $root = false, $checkPrefix = true) + { + $dom = new \DOMDocument(); + $dom->loadXML($root ? $xml : ''.$xml.''); + + $this->assertSame($expected, XmlUtils::convertDomElementToArray($dom->documentElement, $checkPrefix)); + } + + public function getDataForConvertDomToArray() + { + return array( + array(null, ''), + array('bar', 'bar'), + array(array('bar' => 'foobar'), '', true), + array(array('foo' => null), ''), + array(array('foo' => 'bar'), 'bar'), + array(array('foo' => array('foo' => 'bar')), ''), + array(array('foo' => array('foo' => 0)), '0'), + array(array('foo' => array('foo' => 'bar')), 'bar'), + array(array('foo' => array('foo' => 'bar', 'value' => 'text')), 'text'), + array(array('foo' => array('attr' => 'bar', 'foo' => 'text')), 'text'), + array(array('foo' => array('bar', 'text')), 'bartext'), + array(array('foo' => array(array('foo' => 'bar'), array('foo' => 'text'))), ''), + array(array('foo' => array('foo' => array('bar', 'text'))), 'text'), + array(array('foo' => 'bar'), 'bar'), + array(array('foo' => 'text'), 'text'), + array(array('foo' => array('bar' => 'bar', 'value' => 'text')), 'text', false, false), + array(array('attr' => 1, 'b' => 'hello'), 'hello2', true), + ); + } + + /** + * @dataProvider getDataForPhpize + */ + public function testPhpize($expected, $value) + { + $this->assertSame($expected, XmlUtils::phpize($value)); + } + + public function getDataForPhpize() + { + return array( + array('', ''), + array(null, 'null'), + array(true, 'true'), + array(false, 'false'), + array(null, 'Null'), + array(true, 'True'), + array(false, 'False'), + array(0, '0'), + array(1, '1'), + array(-1, '-1'), + array(0777, '0777'), + array(255, '0xFF'), + array(100.0, '1e2'), + array(-120.0, '-1.2E2'), + array(-10100.1, '-10100.1'), + array('-10,100.1', '-10,100.1'), + array('1234 5678 9101 1121 3141', '1234 5678 9101 1121 3141'), + array('1,2,3,4', '1,2,3,4'), + array('11,22,33,44', '11,22,33,44'), + array('11,222,333,4', '11,222,333,4'), + array('1,222,333,444', '1,222,333,444'), + array('11,222,333,444', '11,222,333,444'), + array('111,222,333,444', '111,222,333,444'), + array('1111,2222,3333,4444,5555', '1111,2222,3333,4444,5555'), + array('foo', 'foo'), + array(6, '0b0110'), + ); + } + + public function testLoadEmptyXmlFile() + { + $file = __DIR__.'/../Fixtures/foo.xml'; + $this->setExpectedException('InvalidArgumentException', 'File '.$file.' does not contain valid XML, it is empty.'); + XmlUtils::loadFile($file); + } + + // test for issue https://github.com/symfony/symfony/issues/9731 + public function testLoadWrongEmptyXMLWithErrorHandler() + { + $originalDisableEntities = libxml_disable_entity_loader(false); + $errorReporting = error_reporting(-1); + + set_error_handler(function ($errno, $errstr) { + throw new \Exception($errstr, $errno); + }); + + $file = __DIR__.'/../Fixtures/foo.xml'; + try { + try { + XmlUtils::loadFile($file); + $this->fail('An exception should have been raised'); + } catch (\InvalidArgumentException $e) { + $this->assertEquals(sprintf('File %s does not contain valid XML, it is empty.', $file), $e->getMessage()); + } + } catch (\Exception $e) { + restore_error_handler(); + error_reporting($errorReporting); + + throw $e; + } + + restore_error_handler(); + error_reporting($errorReporting); + + $disableEntities = libxml_disable_entity_loader(true); + libxml_disable_entity_loader($disableEntities); + + libxml_disable_entity_loader($originalDisableEntities); + + $this->assertFalse($disableEntities); + + // should not throw an exception + XmlUtils::loadFile(__DIR__.'/../Fixtures/Util/valid.xml', __DIR__.'/../Fixtures/Util/schema.xsd'); + } +} + +interface Validator +{ + public function validate(); +} diff --git a/vendor/symfony/config/Util/XmlUtils.php b/vendor/symfony/config/Util/XmlUtils.php new file mode 100644 index 00000000..d8d4eaa3 --- /dev/null +++ b/vendor/symfony/config/Util/XmlUtils.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\Config\Util; + +/** + * XMLUtils is a bunch of utility methods to XML operations. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Fabien Potencier + * @author Martin Hasoň + */ +class XmlUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation + * + * @return \DOMDocument + * + * @throws \InvalidArgumentException When loading of XML file returns error + */ + public static function loadFile($file, $schemaOrCallable = null) + { + $content = @file_get_contents($file); + if ('' === trim($content)) { + throw new \InvalidArgumentException(sprintf('File %s does not contain valid XML, it is empty.', $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($content, LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { + libxml_disable_entity_loader($disableEntities); + + throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors))); + } + + $dom->normalizeDocument(); + + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + throw new \InvalidArgumentException('Document types are not allowed.'); + } + } + + if (null !== $schemaOrCallable) { + $internalErrors = libxml_use_internal_errors(true); + libxml_clear_errors(); + + $e = null; + if (is_callable($schemaOrCallable)) { + try { + $valid = call_user_func($schemaOrCallable, $dom, $internalErrors); + } catch (\Exception $e) { + $valid = false; + } + } elseif (!is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) { + $schemaSource = file_get_contents((string) $schemaOrCallable); + $valid = @$dom->schemaValidateSource($schemaSource); + } else { + libxml_use_internal_errors($internalErrors); + + throw new \InvalidArgumentException('The schemaOrCallable argument has to be a valid path to XSD file or callable.'); + } + + if (!$valid) { + $messages = static::getXmlErrors($internalErrors); + if (empty($messages)) { + $messages = array(sprintf('The XML file "%s" is not valid.', $file)); + } + throw new \InvalidArgumentException(implode("\n", $messages), 0, $e); + } + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $dom; + } + + /** + * Converts a \DomElement object to a PHP array. + * + * The following rules applies during the conversion: + * + * * Each tag is converted to a key value or an array + * if there is more than one "value" + * + * * The content of a tag is set under a "value" key (bar) + * if the tag also has some nested tags + * + * * The attributes are converted to keys () + * + * * The nested-tags are converted to keys (bar) + * + * @param \DomElement $element A \DomElement instance + * @param bool $checkPrefix Check prefix in an element or an attribute name + * + * @return array A PHP array + */ + public static function convertDomElementToArray(\DomElement $element, $checkPrefix = true) + { + $prefix = (string) $element->prefix; + $empty = true; + $config = array(); + foreach ($element->attributes as $name => $node) { + if ($checkPrefix && !in_array((string) $node->prefix, array('', $prefix), true)) { + continue; + } + $config[$name] = static::phpize($node->value); + $empty = false; + } + + $nodeValue = false; + foreach ($element->childNodes as $node) { + if ($node instanceof \DOMText) { + if ('' !== trim($node->nodeValue)) { + $nodeValue = trim($node->nodeValue); + $empty = false; + } + } elseif ($checkPrefix && $prefix != (string) $node->prefix) { + continue; + } elseif (!$node instanceof \DOMComment) { + $value = static::convertDomElementToArray($node, $checkPrefix); + + $key = $node->localName; + if (isset($config[$key])) { + if (!is_array($config[$key]) || !is_int(key($config[$key]))) { + $config[$key] = array($config[$key]); + } + $config[$key][] = $value; + } else { + $config[$key] = $value; + } + + $empty = false; + } + } + + if (false !== $nodeValue) { + $value = static::phpize($nodeValue); + if (count($config)) { + $config['value'] = $value; + } else { + $config = $value; + } + } + + return !$empty ? $config : null; + } + + /** + * Converts an xml value to a PHP type. + * + * @param mixed $value + * + * @return mixed + */ + public static function phpize($value) + { + $value = (string) $value; + $lowercaseValue = strtolower($value); + + switch (true) { + case 'null' === $lowercaseValue: + return; + case ctype_digit($value): + $raw = $value; + $cast = (int) $value; + + return '0' == $value[0] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw); + case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)): + $raw = $value; + $cast = (int) $value; + + return '0' == $value[1] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw); + case 'true' === $lowercaseValue: + return true; + case 'false' === $lowercaseValue: + return false; + case isset($value[1]) && '0b' == $value[0].$value[1]: + return bindec($value); + case is_numeric($value): + return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value; + case preg_match('/^0x[0-9a-f]++$/i', $value): + return hexdec($value); + case preg_match('/^(-|\+)?[0-9]+(\.[0-9]+)?$/', $value): + return (float) $value; + default: + return $value; + } + } + + protected static 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 ?: 'n/a', + $error->line, + $error->column + ); + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $errors; + } +} diff --git a/vendor/symfony/config/composer.json b/vendor/symfony/config/composer.json new file mode 100644 index 00000000..a14d9357 --- /dev/null +++ b/vendor/symfony/config/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/config", + "type": "library", + "description": "Symfony Config Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Config\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + } +} diff --git a/vendor/symfony/config/phpunit.xml.dist b/vendor/symfony/config/phpunit.xml.dist new file mode 100644 index 00000000..3fe6fd87 --- /dev/null +++ b/vendor/symfony/config/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/console/.gitignore b/vendor/symfony/console/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/console/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/console/Application.php b/vendor/symfony/console/Application.php new file mode 100644 index 00000000..ae322829 --- /dev/null +++ b/vendor/symfony/console/Application.php @@ -0,0 +1,1179 @@ + + * + * 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\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Helper\QuestionHelper; +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\Input\InputAwareInterface; +use Symfony\Component\Console\Output\BufferedOutput; +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 = array(); + private $wantHelps = false; + private $runningCommand; + private $name; + private $version; + private $catchExceptions = true; + private $autoExit = true; + private $definition; + private $helperSet; + private $dispatcher; + private $terminalDimensions; + private $defaultCommand; + + /** + * 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->defaultCommand = 'list'; + $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 int 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; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return int 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 = $this->defaultCommand; + $input = new ArrayInput(array('command' => $this->defaultCommand)); + } + + // 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() + { + return $this->getLongVersion(); + } + + /** + * Sets whether to catch exceptions or not during commands execution. + * + * @param bool $boolean Whether to catch exceptions or not during commands execution + * + * @api + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + } + + /** + * Sets whether to automatically exit after a command execution or not. + * + * @param bool $boolean Whether to automatically exit after a command execution or not + * + * @api + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $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; + } + + if (null === $command->getDefinition()) { + throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); + } + + $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 bool 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 = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($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(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); + $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + 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); + } + + $exact = in_array($namespace, $namespaces, true); + if (count($namespaces) > 1 && !$exact) { + throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * 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) + { + $allCommands = array_keys($this->commands); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); + $commands = preg_grep('{^'.$expr.'}', $allCommands); + + if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + // check if a namespace exists and contains commands + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + 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); + } + + // filter out aliases for commands which are already on the list + if (count($commands) > 1) { + $commandList = $this->commands; + $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { + $commandName = $commandList[$nameOrAlias]->getName(); + + return $commandName === $nameOrAlias || !in_array($commandName, $commands); + }); + } + + $exact = in_array($name, $commands, true); + if (count($commands) > 1 && !$exact) { + $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); + + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); + } + + return $this->get($exact ? $name : reset($commands)); + } + + /** + * 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 bool $raw Whether to return raw command list + * + * @return string A string representing the Application + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asText($namespace = null, $raw = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new TextDescriptor(); + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw); + $descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true)); + + return $output->fetch(); + } + + /** + * Returns an XML representation of the Application. + * + * @param string $namespace An optional namespace name + * @param bool $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the Application + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($namespace = null, $asDom = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getApplicationDocument($this, $namespace); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this, array('namespace' => $namespace)); + + return $output->fetch(); + } + + /** + * Renders a caught exception. + * + * @param \Exception $e An exception instance + * @param OutputInterface $output An OutputInterface instance + */ + public function renderException($e, $output) + { + do { + $title = sprintf(' [%s] ', get_class($e)); + + $len = $this->stringWidth($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $formatter = $output->getFormatter(); + $lines = array(); + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + // pre-format lines to get the right string length + $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4; + $lines[] = array($line, $lineLength); + + $len = max($lineLength, $len); + } + } + + $messages = array('', ''); + $messages[] = $emptyLine = $formatter->format(sprintf('%s', str_repeat(' ', $len))); + $messages[] = $formatter->format(sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))))); + foreach ($lines as $line) { + $messages[] = $formatter->format(sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1]))); + } + $messages[] = $emptyLine; + $messages[] = ''; + $messages[] = ''; + + $output->writeln($messages, OutputInterface::OUTPUT_RAW); + + 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 ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + // 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); + } + + /** + * Sets terminal dimensions. + * + * Can be useful to force terminal dimensions for functional tests. + * + * @param int $width The width + * @param int $height The height + * + * @return Application The current application + */ + public function setTerminalDimensions($width, $height) + { + $this->terminalDimensions = array($width, $height); + + return $this; + } + + /** + * 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('question')) { + $inputStream = $this->getHelperSet()->get('question')->getInputStream(); + if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { + $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 int 0 if everything went fine, or an error code + * + * @throws \Exception when the command being run threw an exception + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) + { + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + + if ($event->commandShouldRun()) { + try { + $exitCode = $command->run($input, $output); + } catch (\Exception $e) { + $event = new ConsoleExceptionEvent($command, $input, $output, $e, $e->getCode()); + $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); + + $e = $event->getException(); + + $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode()); + $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + + throw $e; + } + } else { + $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; + } + + $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(false), + new ProgressHelper(false), + new TableHelper(false), + new DebugFormatterHelper(), + new ProcessHelper(), + new QuestionHelper(), + )); + } + + /** + * 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 of $name among $collection, + * if nothing is found in $collection, try in $abbrevs. + * + * @param string $name The string + * @param array|\Traversable $collection The collection + * + * @return array A sorted array of similar string + */ + private function findAlternatives($name, $collection) + { + $threshold = 1e3; + $alternatives = array(); + + $collectionParts = array(); + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + asort($alternatives); + + return array_keys($alternatives); + } + + /** + * Sets the default Command name. + * + * @param string $commandName The Command name + */ + public function setDefaultCommand($commandName) + { + $this->defaultCommand = $commandName; + } + + private function stringWidth($string) + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + private function splitStringByWidth($string, $width) + { + // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. + // additionally, array_slice() is not enough as some character has doubled width. + // we need a function to split string not by character count but by string width + + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = array(); + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + // test if $char could be appended to current line + if (mb_strwidth($line.$char, 'utf8') <= $width) { + $line .= $char; + continue; + } + // if not, push current line to array and make new line + $lines[] = str_pad($line, $width); + $line = $char; + } + if ('' !== $line) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + /** + * Returns all namespaces of the command name. + * + * @param string $name The full name of the command + * + * @return array The namespaces of the command + */ + private function extractAllNamespaces($name) + { + // -1 as third argument is needed to skip the command short name when exploding + $parts = explode(':', $name, -1); + $namespaces = array(); + + foreach ($parts as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces).':'.$part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } +} diff --git a/vendor/symfony/console/CHANGELOG.md b/vendor/symfony/console/CHANGELOG.md new file mode 100644 index 00000000..07254c67 --- /dev/null +++ b/vendor/symfony/console/CHANGELOG.md @@ -0,0 +1,62 @@ +CHANGELOG +========= + +2.6.0 +----- + + * added a Process helper + * added a DebugFormatter helper + +2.5.0 +----- + + * deprecated the dialog helper (use the question helper instead) + * deprecated TableHelper in favor of Table + * deprecated ProgressHelper in favor of ProgressBar + * added ConsoleLogger + * added a question helper + * added a way to set the process name of a command + * added a way to set a default command instead of `ListCommand` + +2.4.0 +----- + + * added a way to force terminal dimensions + * added a convenient method to detect verbosity level + * [BC BREAK] made descriptors use output instead of returning a string + +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/Command/Command.php b/vendor/symfony/console/Command/Command.php new file mode 100644 index 00000000..519778c5 --- /dev/null +++ b/vendor/symfony/console/Command/Command.php @@ -0,0 +1,697 @@ + + * + * 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\BufferedOutput; +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 $processTitle; + private $aliases = array(); + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $applicationDefinitionMerged = false; + private $applicationDefinitionMergedWithArgs = false; + private $code; + private $synopsis = array(); + private $usages = array(); + private $helperSet; + + /** + * Constructor. + * + * @param string|null $name The name of the command; passing null means it must be set in configure() + * + * @throws \LogicException When the command name is empty + * + * @api + */ + public function __construct($name = null) + { + $this->definition = new InputDefinition(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); + } + } + + /** + * 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 bool + */ + 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|int 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. + * + * This method is executed before the InputDefinition is validated. + * This means that this is the only place where the command can + * interactively ask for values of missing required arguments. + * + * @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 int 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(true); + $this->getSynopsis(false); + + // 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 (null !== $this->processTitle) { + if (function_exists('cli_set_process_title')) { + cli_set_process_title($this->processTitle); + } elseif (function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + 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 bool $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 int $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 int $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 the name is invalid + * + * @api + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Sets the process title of the command. + * + * This feature should be used only when creating a long process command, + * like a daemon. + * + * PHP 5.5+ or the proctitle PECL library is required + * + * @param string $title The process title + * + * @return Command The current instance + */ + public function setProcessTitle($title) + { + $this->processTitle = $title; + + 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 ?: $this->description; + } + + /** + * 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 string[] $aliases An array of aliases for the command + * + * @return Command The current instance + * + * @throws \InvalidArgumentException When an alias is invalid + * + * @api + */ + public function setAliases($aliases) + { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + 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. + * + * @param bool $short Whether to show the short version of the synopsis (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * Add a command usage example. + * + * @param string $usage The usage, it'll be prefixed with the command name + */ + public function addUsage($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * Returns alternative usages of the command. + * + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * 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 since version 2.3, to be removed in 3.0. + */ + public function asText() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new TextDescriptor(); + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $descriptor->describe($output, $this, array('raw_output' => true)); + + return $output->fetch(); + } + + /** + * Returns an XML representation of the command. + * + * @param bool $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the command + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getCommandDocument($this); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this); + + return $output->fetch(); + } + + /** + * Validates a command name. + * + * It must be non-empty and parts can optionally be separated by ":". + * + * @param string $name + * + * @throws \InvalidArgumentException When the name is invalid + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/vendor/symfony/console/Command/HelpCommand.php b/vendor/symfony/console/Command/HelpCommand.php new file mode 100644 index 00000000..8c20b570 --- /dev/null +++ b/vendor/symfony/console/Command/HelpCommand.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\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, 'The output format (txt, xml, json, or md)', 'txt'), + 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')) { + @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); + + $input->setOption('format', 'xml'); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + )); + + $this->command = null; + } +} diff --git a/vendor/symfony/console/Command/ListCommand.php b/vendor/symfony/console/Command/ListCommand.php new file mode 100644 index 00000000..5f970e82 --- /dev/null +++ b/vendor/symfony/console/Command/ListCommand.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\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')) { + @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); + + $input->setOption('format', 'xml'); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'namespace' => $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, 'The output format (txt, xml, json, or md)', 'txt'), + )); + } +} diff --git a/vendor/symfony/console/ConsoleEvents.php b/vendor/symfony/console/ConsoleEvents.php new file mode 100644 index 00000000..1ed41b7d --- /dev/null +++ b/vendor/symfony/console/ConsoleEvents.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\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. + * + * @Event + * + * @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. + * + * @Event + * + * @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. + * + * @Event + * + * @var string + */ + const EXCEPTION = 'console.exception'; +} diff --git a/vendor/symfony/console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Descriptor/ApplicationDescription.php new file mode 100644 index 00000000..c481be4d --- /dev/null +++ b/vendor/symfony/console/Descriptor/ApplicationDescription.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\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; + +/** + * @author Jean-François Simon + * + * @internal + */ +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 &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/vendor/symfony/console/Descriptor/Descriptor.php b/vendor/symfony/console/Descriptor/Descriptor.php new file mode 100644 index 00000000..49e21939 --- /dev/null +++ b/vendor/symfony/console/Descriptor/Descriptor.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\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; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jean-François Simon + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + /** + * @var OutputInterface + */ + private $output; + + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Application: + $this->describeApplication($object, $options); + break; + default: + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + } + + /** + * Writes content to output. + * + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * 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/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Descriptor/DescriptorInterface.php new file mode 100644 index 00000000..3929b6d9 --- /dev/null +++ b/vendor/symfony/console/Descriptor/DescriptorInterface.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\Console\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Descriptor interface. + * + * @author Jean-François Simon + */ +interface DescriptorInterface +{ + /** + * Describes an InputArgument instance. + * + * @param OutputInterface $output + * @param object $object + * @param array $options + */ + public function describe(OutputInterface $output, $object, array $options = array()); +} diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php new file mode 100644 index 00000000..87e38fdb --- /dev/null +++ b/vendor/symfony/console/Descriptor/JsonDescriptor.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\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 + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->writeData($this->getInputArgumentData($argument), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->writeData($this->getInputOptionData($option), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $this->writeData($this->getInputDefinitionData($definition), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $this->writeData($this->getCommandData($command), $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->getCommandData($command); + } + + $data = $describedNamespace + ? array('commands' => $commands, 'namespace' => $describedNamespace) + : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces())); + + $this->writeData($data, $options); + } + + /** + * Writes data as json. + * + * @param array $data + * @param array $options + * + * @return array|string + */ + private function writeData(array $data, array $options) + { + $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); + } + + /** + * @param InputArgument $argument + * + * @return array + */ + private function getInputArgumentData(InputArgument $argument) + { + return array( + 'name' => $argument->getName(), + 'is_required' => $argument->isRequired(), + 'is_array' => $argument->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), + 'default' => $argument->getDefault(), + ); + } + + /** + * @param InputOption $option + * + * @return array + */ + private function getInputOptionData(InputOption $option) + { + return 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' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), + 'default' => $option->getDefault(), + ); + } + + /** + * @param InputDefinition $definition + * + * @return array + */ + private function getInputDefinitionData(InputDefinition $definition) + { + $inputArguments = array(); + foreach ($definition->getArguments() as $name => $argument) { + $inputArguments[$name] = $this->getInputArgumentData($argument); + } + + $inputOptions = array(); + foreach ($definition->getOptions() as $name => $option) { + $inputOptions[$name] = $this->getInputOptionData($option); + } + + return array('arguments' => $inputArguments, 'options' => $inputOptions); + } + + /** + * @param Command $command + * + * @return array + */ + private function getCommandData(Command $command) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + return array( + 'name' => $command->getName(), + 'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()), + 'description' => $command->getDescription(), + 'help' => $command->getProcessedHelp(), + 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), + ); + } +} diff --git a/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 00000000..d3d76a42 --- /dev/null +++ b/vendor/symfony/console/Descriptor/MarkdownDescriptor.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\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 + * + * @internal + */ +class MarkdownDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->write( + '**'.$argument->getName().':**'."\n\n" + .'* Name: '.($argument->getName() ?: '')."\n" + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $argument->getDescription() ?: '')."\n" + .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->write( + '**'.$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: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $option->getDescription() ?: '')."\n" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + if ($showArguments = count($definition->getArguments()) > 0) { + $this->write('### Arguments:'); + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->write($this->describeInputArgument($argument)); + } + } + + if (count($definition->getOptions()) > 0) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write('### Options:'); + foreach ($definition->getOptions() as $option) { + $this->write("\n\n"); + $this->write($this->describeInputOption($option)); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $this->write( + $command->getName()."\n" + .str_repeat('-', strlen($command->getName()))."\n\n" + .'* Description: '.($command->getDescription() ?: '')."\n" + .'* Usage:'."\n\n" + .array_reduce(array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()), function ($carry, $usage) { + return $carry .= ' * `'.$usage.'`'."\n"; + }) + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + if ($command->getNativeDefinition()) { + $this->write("\n\n"); + $this->describeInputDefinition($command->getNativeDefinition()); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + + $this->write($application->getName()."\n".str_repeat('=', strlen($application->getName()))); + + foreach ($description->getNamespaces() as $namespace) { + if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->write("\n\n"); + $this->write('**'.$namespace['id'].':**'); + } + + $this->write("\n\n"); + $this->write(implode("\n", array_map(function ($commandName) { + return '* '.$commandName; + }, $namespace['commands']))); + } + + foreach ($description->getCommands() as $command) { + $this->write("\n\n"); + $this->write($this->describeCommand($command)); + } + } +} diff --git a/vendor/symfony/console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Descriptor/TextDescriptor.php new file mode 100644 index 00000000..57e09e4d --- /dev/null +++ b/vendor/symfony/console/Descriptor/TextDescriptor.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\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 + * + * @internal + */ +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 = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); + $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; + + $this->writeText(sprintf(' %s%s%s%s', + $argument->getName(), + str_repeat(' ', $spacingWidth), + // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 17), $argument->getDescription()), + $default + ), $options); + } + + /** + * {@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 = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '='.strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '['.$value.']'; + } + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option)); + $synopsis = sprintf('%s%s', + $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', + sprintf('--%s%s', $option->getName(), $value) + ); + + $spacingWidth = $totalWidth - strlen($synopsis) + 2; + + $this->writeText(sprintf(' %s%s%s%s%s', + $synopsis, + str_repeat(' ', $spacingWidth), + // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 17), $option->getDescription()), + $default, + $option->isArray() ? ' (multiple values allowed)' : '' + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth))); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = array(); + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (strlen($option->getShortcut()) > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeApplicationDefinition(false); + + $this->writeText('Usage:', $options); + foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' '.$usage, $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + if ($help = $command->getProcessedHelp()) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' '.str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $application->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $width = $this->getColumnWidth($description->getCommands()); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' '.$namespace['id'].'', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - strlen($name); + $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText($content, array $options = array()) + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } + + /** + * Formats input option/argument default value. + * + * @param mixed $default + * + * @return string + */ + private function formatDefaultValue($default) + { + if (PHP_VERSION_ID < 50400) { + 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) + { + $widths = array(); + + foreach ($commands as $command) { + $widths[] = strlen($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = strlen($alias); + } + } + + return max($widths) + 2; + } + + /** + * @param InputOption[] $options + * + * @return int + */ + private function calculateTotalWidthForOptions($options) + { + $totalWidth = 0; + foreach ($options as $option) { + // "-" + shortcut + ", --" + name + $nameLength = 1 + max(strlen($option->getShortcut()), 1) + 4 + strlen($option->getName()); + + if ($option->acceptValue()) { + $valueLength = 1 + strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/vendor/symfony/console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Descriptor/XmlDescriptor.php new file mode 100644 index 00000000..b5676beb --- /dev/null +++ b/vendor/symfony/console/Descriptor/XmlDescriptor.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\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 + * + * @internal + */ +class XmlDescriptor extends Descriptor +{ + /** + * @param InputDefinition $definition + * + * @return \DOMDocument + */ + public function getInputDefinitionDocument(InputDefinition $definition) + { + $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->getInputArgumentDocument($argument)); + } + + $definitionXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($definition->getOptions() as $option) { + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + } + + return $dom; + } + + /** + * @param Command $command + * + * @return \DOMDocument + */ + public function getCommandDocument(Command $command) + { + $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($usagesXML = $dom->createElement('usages')); + + foreach (array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()) as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + + $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()))); + + $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + + return $dom; + } + + /** + * @param Application $application + * @param string|null $namespace + * + * @return \DOMDocument + */ + public function getApplicationDocument(Application $application, $namespace = null) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($rootXml = $dom->createElement('symfony')); + + if ($application->getName() !== 'UNKNOWN') { + $rootXml->setAttribute('name', $application->getName()); + if ($application->getVersion() !== 'UNKNOWN') { + $rootXml->setAttribute('version', $application->getVersion()); + } + } + + $rootXml->appendChild($commandsXML = $dom->createElement('commands')); + + $description = new ApplicationDescription($application, $namespace); + + if ($namespace) { + $commandsXML->setAttribute('namespace', $namespace); + } + + foreach ($description->getCommands() as $command) { + $this->appendDocument($commandsXML, $this->getCommandDocument($command)); + } + + if (!$namespace) { + $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); + + foreach ($description->getNamespaces() as $namespaceDescription) { + $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); + $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); + + foreach ($namespaceDescription['commands'] as $name) { + $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); + $commandXML->appendChild($dom->createTextNode($name)); + } + } + } + + return $dom; + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->writeDocument($this->getInputArgumentDocument($argument)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->writeDocument($this->getInputOptionDocument($option)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $this->writeDocument($this->getInputDefinitionDocument($definition)); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $this->writeDocument($this->getCommandDocument($command)); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); + } + + /** + * 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)); + } + } + + /** + * Writes DOM document. + * + * @param \DOMDocument $dom + * + * @return \DOMDocument|string + */ + private function writeDocument(\DOMDocument $dom) + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + /** + * @param InputArgument $argument + * + * @return \DOMDocument + */ + private function getInputArgumentDocument(InputArgument $argument) + { + $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 $dom; + } + + /** + * @param InputOption $option + * + * @return \DOMDocument + */ + private function getInputOptionDocument(InputOption $option) + { + $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 $dom; + } +} diff --git a/vendor/symfony/console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Event/ConsoleCommandEvent.php new file mode 100644 index 00000000..92adf1ef --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleCommandEvent.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\Event; + +/** + * Allows to do things before the command is executed, like skipping the command or changing the input. + * + * @author Fabien Potencier + */ +class ConsoleCommandEvent extends ConsoleEvent +{ + /** + * The return code for skipped commands, this will also be passed into the terminate event. + */ + const RETURN_CODE_DISABLED = 113; + + /** + * Indicates if the command should be run or skipped. + * + * @var bool + */ + private $commandShouldRun = true; + + /** + * Disables the command, so it won't be run. + * + * @return bool + */ + public function disableCommand() + { + return $this->commandShouldRun = false; + } + + /** + * Enables the command. + * + * @return bool + */ + public function enableCommand() + { + return $this->commandShouldRun = true; + } + + /** + * Returns true if the command is runnable, false otherwise. + * + * @return bool + */ + public function commandShouldRun() + { + return $this->commandShouldRun; + } +} diff --git a/vendor/symfony/console/Event/ConsoleEvent.php b/vendor/symfony/console/Event/ConsoleEvent.php new file mode 100644 index 00000000..ab620c46 --- /dev/null +++ b/vendor/symfony/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/Event/ConsoleExceptionEvent.php b/vendor/symfony/console/Event/ConsoleExceptionEvent.php new file mode 100644 index 00000000..603b7eed --- /dev/null +++ b/vendor/symfony/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 = (int) $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 int The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Event/ConsoleTerminateEvent.php new file mode 100644 index 00000000..b6a5d7c0 --- /dev/null +++ b/vendor/symfony/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 int + */ + 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 int $exitCode The command exit code + */ + public function setExitCode($exitCode) + { + $this->exitCode = (int) $exitCode; + } + + /** + * Gets the exit code. + * + * @return int The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatter.php b/vendor/symfony/console/Formatter/OutputFormatter.php new file mode 100644 index 00000000..331b204f --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatter.php @@ -0,0 +1,242 @@ + + * + * 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 +{ + 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 = false, array $styles = array()) + { + $this->decorated = (bool) $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 bool $decorated Whether to decorate the messages or not + * + * @api + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * Gets the decorated flag. + * + * @return bool 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 bool + * + * @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 = (string) $message; + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9_=;-]*'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + // add the text up to the next tag + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); + $offset = $pos + strlen($text); + + // opening tag? + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + return str_replace('\\<', '<', $output); + } + + /** + * @return OutputFormatterStyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * Tries to create new style instance from string. + * + * @param string $string + * + * @return OutputFormatterStyle|bool false if string is not format string + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$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 { + try { + $style->setOption($match[1]); + } catch (\InvalidArgumentException $e) { + return false; + } + } + } + + 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/Formatter/OutputFormatterInterface.php b/vendor/symfony/console/Formatter/OutputFormatterInterface.php new file mode 100644 index 00000000..52efa314 --- /dev/null +++ b/vendor/symfony/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 bool $decorated Whether to decorate the messages or not + * + * @api + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool 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 bool + * + * @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/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Formatter/OutputFormatterStyle.php new file mode 100644 index 00000000..ee62cdbd --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,229 @@ + + * + * 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' => array('set' => 30, 'unset' => 39), + 'red' => array('set' => 31, 'unset' => 39), + 'green' => array('set' => 32, 'unset' => 39), + 'yellow' => array('set' => 33, 'unset' => 39), + 'blue' => array('set' => 34, 'unset' => 39), + 'magenta' => array('set' => 35, 'unset' => 39), + 'cyan' => array('set' => 36, 'unset' => 39), + 'white' => array('set' => 37, 'unset' => 39), + 'default' => array('set' => 39, 'unset' => 39), + ); + private static $availableBackgroundColors = array( + 'black' => array('set' => 40, 'unset' => 49), + 'red' => array('set' => 41, 'unset' => 49), + 'green' => array('set' => 42, 'unset' => 49), + 'yellow' => array('set' => 43, 'unset' => 49), + 'blue' => array('set' => 44, 'unset' => 49), + 'magenta' => array('set' => 45, 'unset' => 49), + 'cyan' => array('set' => 46, 'unset' => 49), + 'white' => array('set' => 47, 'unset' => 49), + 'default' => array('set' => 49, 'unset' => 49), + ); + private static $availableOptions = array( + 'bold' => array('set' => 1, 'unset' => 22), + 'underscore' => array('set' => 4, 'unset' => 24), + 'blink' => array('set' => 5, 'unset' => 25), + 'reverse' => array('set' => 7, 'unset' => 27), + 'conceal' => array('set' => 8, 'unset' => 28), + ); + + private $foreground; + private $background; + private $options = array(); + + /** + * Initializes output formatter style. + * + * @param string|null $foreground The style foreground color name + * @param string|null $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|null $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|null $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 (!in_array(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) + { + $setCodes = array(); + $unsetCodes = array(); + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 00000000..e8642b3c --- /dev/null +++ b/vendor/symfony/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/Formatter/OutputFormatterStyleStack.php b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 00000000..b64c87fa --- /dev/null +++ b/vendor/symfony/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|null $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|null $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/Helper/DebugFormatterHelper.php b/vendor/symfony/console/Helper/DebugFormatterHelper.php new file mode 100644 index 00000000..1119b795 --- /dev/null +++ b/vendor/symfony/console/Helper/DebugFormatterHelper.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\Helper; + +/** + * Helps outputting debug information when running an external program from a command. + * + * An external program can be a Process, an HTTP request, or anything else. + * + * @author Fabien Potencier + */ +class DebugFormatterHelper extends Helper +{ + private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'); + private $started = array(); + private $count = -1; + + /** + * Starts a debug formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param string $prefix The prefix to use + * + * @return string + */ + public function start($id, $message, $prefix = 'RUN') + { + $this->started[$id] = array('border' => ++$this->count % count($this->colors)); + + return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + * + * @param string $id The id of the formatting session + * @param string $buffer The message to display + * @param bool $error Whether to consider the buffer as error + * @param string $prefix The prefix for output + * @param string $errorPrefix The prefix for error output + * + * @return string + */ + public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') + { + $message = ''; + + if ($error) { + if (isset($this->started[$id]['out'])) { + $message .= "\n"; + unset($this->started[$id]['out']); + } + if (!isset($this->started[$id]['err'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); + $this->started[$id]['err'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); + } else { + if (isset($this->started[$id]['err'])) { + $message .= "\n"; + unset($this->started[$id]['err']); + } + if (!isset($this->started[$id]['out'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); + $this->started[$id]['out'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); + } + + return $message; + } + + /** + * Stops a formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param bool $successful Whether to consider the result as success + * @param string $prefix The prefix for the end output + * + * @return string + */ + public function stop($id, $message, $successful, $prefix = 'RES') + { + $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; + + if ($successful) { + return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + } + + $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; + } + + /** + * @param string $id The id of the formatting session + * + * @return string + */ + private function getBorder($id) + { + return sprintf(' ', $this->colors[$this->started[$id]['border']]); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'debug_formatter'; + } +} diff --git a/vendor/symfony/console/Helper/DescriptorHelper.php b/vendor/symfony/console/Helper/DescriptorHelper.php new file mode 100644 index 00000000..c324c994 --- /dev/null +++ b/vendor/symfony/console/Helper/DescriptorHelper.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\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. + * + * Available options are: + * * format: string, the output format name + * * raw_text: boolean, sets output type as raw + * + * @param OutputInterface $output + * @param object $object + * @param array $options + * + * @throws \InvalidArgumentException when the given format is not supported + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $options = array_merge(array( + 'raw_text' => false, + 'format' => 'txt', + ), $options); + + if (!isset($this->descriptors[$options['format']])) { + throw new \InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); + } + + $descriptor = $this->descriptors[$options['format']]; + $descriptor->describe($output, $object, $options); + } + + /** + * 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/Helper/DialogHelper.php b/vendor/symfony/console/Helper/DialogHelper.php new file mode 100644 index 00000000..dbadac2c --- /dev/null +++ b/vendor/symfony/console/Helper/DialogHelper.php @@ -0,0 +1,483 @@ + + * + * 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 + * + * @deprecated since version 2.5, to be removed in 3.0. + * Use {@link \Symfony\Component\Console\Helper\QuestionHelper} instead. + */ +class DialogHelper extends InputAwareHelper +{ + private $inputStream; + private static $shell; + private static $stty; + + public function __construct($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); + } + } + + /** + * 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 bool|string $default The default answer if the user enters nothing + * @param bool|int $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 bool $multiselect Select more than one value separated by comma + * + * @return int|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)); + } + $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) + { + if ($this->input && !$this->input->isInteractive()) { + return $default; + } + + $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 (isset($c[2]) && ('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 bool $default The default answer if the user enters nothing + * + * @return bool 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 bool $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 ('\\' === DIRECTORY_SEPARATOR) { + $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 int|false $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 int|false $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param bool $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 resource|null The input stream or null if the default STDIN is used + */ + public function getInputStream() + { + return $this->inputStream; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'dialog'; + } + + /** + * Return a valid Unix shell. + * + * @return string|bool 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 int|false $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) + { + $e = null; + while (false === $attempts || $attempts--) { + if (null !== $e) { + $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($e->getMessage(), 'error')); + } + + try { + return call_user_func($validator, $interviewer()); + } catch (\Exception $e) { + } + } + + throw $e; + } +} diff --git a/vendor/symfony/console/Helper/FormatterHelper.php b/vendor/symfony/console/Helper/FormatterHelper.php new file mode 100644 index 00000000..ac736f98 --- /dev/null +++ b/vendor/symfony/console/Helper/FormatterHelper.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\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 bool $large Whether to return a large block + * + * @return string The formatter message + */ + public function formatBlock($messages, $style, $large = false) + { + if (!is_array($messages)) { + $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(); + for ($i = 0; isset($lines[$i]); ++$i) { + $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i])); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + for ($i = 0; isset($messages[$i]); ++$i) { + $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); + } + + return implode("\n", $messages); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'formatter'; + } +} diff --git a/vendor/symfony/console/Helper/Helper.php b/vendor/symfony/console/Helper/Helper.php new file mode 100644 index 00000000..b288d446 --- /dev/null +++ b/vendor/symfony/console/Helper/Helper.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\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * 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_strwidth if it is available. + * + * @param string $string The string to check its length + * + * @return int The length of the string + */ + public static function strlen($string) + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + public static function formatTime($secs) + { + static $timeFormats = array( + array(0, '< 1 sec'), + 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), + ); + + foreach ($timeFormats as $format) { + if ($secs >= $format[0]) { + continue; + } + + if (2 == count($format)) { + return $format[1]; + } + + return ceil($secs / $format[2]).' '.$format[1]; + } + } + + public static function formatMemory($memory) + { + if ($memory >= 1024 * 1024 * 1024) { + return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); + } + + if ($memory >= 1024 * 1024) { + return sprintf('%.1f MiB', $memory / 1024 / 1024); + } + + if ($memory >= 1024) { + return sprintf('%d KiB', $memory / 1024); + } + + return sprintf('%d B', $memory); + } + + public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) + { + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + // remove <...> formatting + $string = $formatter->format($string); + // remove already formatted characters + $string = preg_replace("/\033\[[^m]*m/", '', $string); + $formatter->setDecorated($isDecorated); + + return self::strlen($string); + } +} diff --git a/vendor/symfony/console/Helper/HelperInterface.php b/vendor/symfony/console/Helper/HelperInterface.php new file mode 100644 index 00000000..6d394496 --- /dev/null +++ b/vendor/symfony/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/Helper/HelperSet.php b/vendor/symfony/console/Helper/HelperSet.php new file mode 100644 index 00000000..00354dd9 --- /dev/null +++ b/vendor/symfony/console/Helper/HelperSet.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\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 implements \IteratorAggregate +{ + private $helpers = array(); + private $command; + + /** + * Constructor. + * + * @param Helper[] $helpers An array of helper. + */ + public function __construct(array $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 bool 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)); + } + + if ('dialog' === $name && $this->helpers[$name] instanceof DialogHelper) { + @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); + } elseif ('progress' === $name && $this->helpers[$name] instanceof ProgressHelper) { + @trigger_error('"Symfony\Component\Console\Helper\ProgressHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\ProgressBar" instead.', E_USER_DEPRECATED); + } elseif ('table' === $name && $this->helpers[$name] instanceof TableHelper) { + @trigger_error('"Symfony\Component\Console\Helper\TableHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\Table" instead.', E_USER_DEPRECATED); + } + + 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; + } + + public function getIterator() + { + return new \ArrayIterator($this->helpers); + } +} diff --git a/vendor/symfony/console/Helper/InputAwareHelper.php b/vendor/symfony/console/Helper/InputAwareHelper.php new file mode 100644 index 00000000..42617674 --- /dev/null +++ b/vendor/symfony/console/Helper/InputAwareHelper.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\Console\Helper; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputAwareInterface; + +/** + * An implementation of InputAwareInterface for Helpers. + * + * @author Wouter J + */ +abstract class InputAwareHelper extends Helper implements InputAwareInterface +{ + protected $input; + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + } +} diff --git a/vendor/symfony/console/Helper/ProcessHelper.php b/vendor/symfony/console/Helper/ProcessHelper.php new file mode 100644 index 00000000..a811eb48 --- /dev/null +++ b/vendor/symfony/console/Helper/ProcessHelper.php @@ -0,0 +1,151 @@ + + * + * 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\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\ProcessBuilder; + +/** + * The ProcessHelper class provides helpers to run external processes. + * + * @author Fabien Potencier + */ +class ProcessHelper extends Helper +{ + /** + * Runs an external process. + * + * @param OutputInterface $output An OutputInterface instance + * @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param int $verbosity The threshold for verbosity + * + * @return Process The process that ran + */ + public function run(OutputInterface $output, $cmd, $error = null, $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + if (is_array($cmd)) { + $process = ProcessBuilder::create($cmd)->getProcess(); + } elseif ($cmd instanceof Process) { + $process = $cmd; + } else { + $process = new Process($cmd); + } + + if ($verbosity <= $output->getVerbosity()) { + $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); + } + + if ($output->isDebug()) { + $callback = $this->wrapCallback($output, $process, $callback); + } + + $process->run($callback); + + if ($verbosity <= $output->getVerbosity()) { + $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); + $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); + } + + if (!$process->isSuccessful() && null !== $error) { + $output->writeln(sprintf('%s', $this->escapeString($error))); + } + + return $process; + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param OutputInterface $output An OutputInterface instance + * @param string|Process $cmd An instance of Process or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return Process The process that ran + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null) + { + $process = $this->run($output, $cmd, $error, $callback); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return $process; + } + + /** + * Wraps a Process callback to add debugging output. + * + * @param OutputInterface $output An OutputInterface interface + * @param Process $process The Process + * @param callable|null $callback A PHP callable + * + * @return callable + */ + public function wrapCallback(OutputInterface $output, Process $process, $callback = null) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + $that = $this; + + return function ($type, $buffer) use ($output, $process, $callback, $formatter, $that) { + $output->write($formatter->progress(spl_object_hash($process), $that->escapeString($buffer), Process::ERR === $type)); + + if (null !== $callback) { + call_user_func($callback, $type, $buffer); + } + }; + } + + /** + * This method is public for PHP 5.3 compatibility, it should be private. + * + * @internal + */ + public function escapeString($str) + { + return str_replace('<', '\\<', $str); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'process'; + } +} diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php new file mode 100644 index 00000000..7f4b2efb --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressBar.php @@ -0,0 +1,620 @@ + + * + * 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\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The ProgressBar provides helpers to display progress output. + * + * @author Fabien Potencier + * @author Chris Jones + */ +class ProgressBar +{ + // options + private $barWidth = 28; + private $barChar; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format = null; + private $redrawFreq = 1; + + /** + * @var OutputInterface + */ + private $output; + private $step = 0; + private $max; + private $startTime; + private $stepWidth; + private $percent = 0.0; + private $lastMessagesLength = 0; + private $formatLineCount; + private $messages; + private $overwrite = true; + + private static $formatters; + private static $formats; + + /** + * Constructor. + * + * @param OutputInterface $output An OutputInterface instance + * @param int $max Maximum steps (0 if unknown) + */ + public function __construct(OutputInterface $output, $max = 0) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->output = $output; + $this->setMaxSteps($max); + + if (!$this->output->isDecorated()) { + // disable overwrite when output does not support ANSI codes. + $this->overwrite = false; + + if ($this->max > 10) { + // set a reasonable redraw frequency so output isn't flooded + $this->setRedrawFrequency($max / 10); + } + } + + $this->setFormat($this->determineBestFormat()); + + $this->startTime = time(); + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition($name, $callable) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition($name) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; + } + + /** + * Sets a format for a given name. + * + * This method also allow you to override an existing format. + * + * @param string $name The format name + * @param string $format A format string + */ + public static function setFormatDefinition($name, $format) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + self::$formats[$name] = $format; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + * + * @return string|null A format string + */ + public static function getFormatDefinition($name) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return isset(self::$formats[$name]) ? self::$formats[$name] : null; + } + + public function setMessage($message, $name = 'message') + { + $this->messages[$name] = $message; + } + + public function getMessage($name = 'message') + { + return $this->messages[$name]; + } + + /** + * Gets the progress bar start time. + * + * @return int The progress bar start time + */ + public function getStartTime() + { + return $this->startTime; + } + + /** + * Gets the progress bar maximal steps. + * + * @return int The progress bar max steps + */ + public function getMaxSteps() + { + return $this->max; + } + + /** + * Gets the progress bar step. + * + * @deprecated since version 2.6, to be removed in 3.0. Use {@link getProgress()} instead. + * + * @return int The progress bar step + */ + public function getStep() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the getProgress() method instead.', E_USER_DEPRECATED); + + return $this->getProgress(); + } + + /** + * Gets the current step position. + * + * @return int The progress bar step + */ + public function getProgress() + { + return $this->step; + } + + /** + * Gets the progress bar step width. + * + * @internal This method is public for PHP 5.3 compatibility, it should not be used. + * + * @return int The progress bar step width + */ + public function getStepWidth() + { + return $this->stepWidth; + } + + /** + * Gets the current progress bar percent. + * + * @return float The current progress bar percent + */ + public function getProgressPercent() + { + return $this->percent; + } + + /** + * Sets the progress bar width. + * + * @param int $size The progress bar size + */ + public function setBarWidth($size) + { + $this->barWidth = (int) $size; + } + + /** + * Gets the progress bar width. + * + * @return int The progress bar size + */ + public function getBarWidth() + { + return $this->barWidth; + } + + /** + * Sets the bar character. + * + * @param string $char A character + */ + public function setBarCharacter($char) + { + $this->barChar = $char; + } + + /** + * Gets the bar character. + * + * @return string A character + */ + public function getBarCharacter() + { + if (null === $this->barChar) { + return $this->max ? '=' : $this->emptyBarChar; + } + + return $this->barChar; + } + + /** + * Sets the empty bar character. + * + * @param string $char A character + */ + public function setEmptyBarCharacter($char) + { + $this->emptyBarChar = $char; + } + + /** + * Gets the empty bar character. + * + * @return string A character + */ + public function getEmptyBarCharacter() + { + return $this->emptyBarChar; + } + + /** + * Sets the progress bar character. + * + * @param string $char A character + */ + public function setProgressCharacter($char) + { + $this->progressChar = $char; + } + + /** + * Gets the progress bar character. + * + * @return string A character + */ + public function getProgressCharacter() + { + return $this->progressChar; + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + public function setFormat($format) + { + // try to use the _nomax variant if available + if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { + $this->format = self::getFormatDefinition($format.'_nomax'); + } elseif (null !== self::getFormatDefinition($format)) { + $this->format = self::getFormatDefinition($format); + } else { + $this->format = $format; + } + + $this->formatLineCount = substr_count($this->format, "\n"); + } + + /** + * Sets the redraw frequency. + * + * @param int $freq The frequency in steps + */ + public function setRedrawFrequency($freq) + { + $this->redrawFreq = (int) $freq; + } + + /** + * Starts the progress output. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + */ + public function start($max = null) + { + $this->startTime = time(); + $this->step = 0; + $this->percent = 0.0; + + if (null !== $max) { + $this->setMaxSteps($max); + } + + $this->display(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + * + * @throws \LogicException + */ + public function advance($step = 1) + { + $this->setProgress($this->step + $step); + } + + /** + * Sets the current progress. + * + * @deprecated since version 2.6, to be removed in 3.0. Use {@link setProgress()} instead. + * + * @param int $step The current progress + * + * @throws \LogicException + */ + public function setCurrent($step) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the setProgress() method instead.', E_USER_DEPRECATED); + + $this->setProgress($step); + } + + /** + * Sets whether to overwrite the progressbar, false for new line. + * + * @param bool $overwrite + */ + public function setOverwrite($overwrite) + { + $this->overwrite = (bool) $overwrite; + } + + /** + * Sets the current progress. + * + * @param int $step The current progress + * + * @throws \LogicException + */ + public function setProgress($step) + { + $step = (int) $step; + if ($step < $this->step) { + throw new \LogicException('You can\'t regress the progress bar.'); + } + + if ($this->max && $step > $this->max) { + $this->max = $step; + } + + $prevPeriod = (int) ($this->step / $this->redrawFreq); + $currPeriod = (int) ($step / $this->redrawFreq); + $this->step = $step; + $this->percent = $this->max ? (float) $this->step / $this->max : 0; + if ($prevPeriod !== $currPeriod || $this->max === $step) { + $this->display(); + } + } + + /** + * Finishes the progress output. + */ + public function finish() + { + if (!$this->max) { + $this->max = $this->step; + } + + if ($this->step === $this->max && !$this->overwrite) { + // prevent double 100% output + return; + } + + $this->setProgress($this->max); + } + + /** + * Outputs the current progress string. + */ + public function display() + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + // these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped. + $self = $this; + $output = $this->output; + $messages = $this->messages; + $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) { + if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { + $text = call_user_func($formatter, $self, $output); + } elseif (isset($messages[$matches[1]])) { + $text = $messages[$matches[1]]; + } else { + return $matches[0]; + } + + if (isset($matches[2])) { + $text = sprintf('%'.$matches[2], $text); + } + + return $text; + }, $this->format)); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear() + { + if (!$this->overwrite) { + return; + } + + $this->overwrite(str_repeat("\n", $this->formatLineCount)); + } + + /** + * Sets the progress bar maximal steps. + * + * @param int $max The progress bar max steps + */ + private function setMaxSteps($max) + { + $this->max = max(0, (int) $max); + $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4; + } + + /** + * Overwrites a previous message to the output. + * + * @param string $message The message + */ + private function overwrite($message) + { + $lines = explode("\n", $message); + + // append whitespace to match the line's length + if (null !== $this->lastMessagesLength) { + foreach ($lines as $i => $line) { + if ($this->lastMessagesLength > Helper::strlenWithoutDecoration($this->output->getFormatter(), $line)) { + $lines[$i] = str_pad($line, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + } + } + } + + if ($this->overwrite) { + // move back to the beginning of the progress bar before redrawing it + $this->output->write("\x0D"); + } elseif ($this->step > 0) { + // move to new line + $this->output->writeln(''); + } + + if ($this->formatLineCount) { + $this->output->write(sprintf("\033[%dA", $this->formatLineCount)); + } + $this->output->write(implode("\n", $lines)); + + $this->lastMessagesLength = 0; + foreach ($lines as $line) { + $len = Helper::strlenWithoutDecoration($this->output->getFormatter(), $line); + if ($len > $this->lastMessagesLength) { + $this->lastMessagesLength = $len; + } + } + } + + private function determineBestFormat() + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->max ? 'verbose' : 'verbose_nomax'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + return $this->max ? 'very_verbose' : 'very_verbose_nomax'; + case OutputInterface::VERBOSITY_DEBUG: + return $this->max ? 'debug' : 'debug_nomax'; + default: + return $this->max ? 'normal' : 'normal_nomax'; + } + } + + private static function initPlaceholderFormatters() + { + return array( + 'bar' => function (ProgressBar $bar, OutputInterface $output) { + $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth()); + $display = str_repeat($bar->getBarCharacter(), $completeBars); + if ($completeBars < $bar->getBarWidth()) { + $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); + $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); + } + + return $display; + }, + 'elapsed' => function (ProgressBar $bar) { + return Helper::formatTime(time() - $bar->getStartTime()); + }, + 'remaining' => function (ProgressBar $bar) { + if (!$bar->getMaxSteps()) { + throw new \LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $remaining = 0; + } else { + $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); + } + + return Helper::formatTime($remaining); + }, + 'estimated' => function (ProgressBar $bar) { + if (!$bar->getMaxSteps()) { + throw new \LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $estimated = 0; + } else { + $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); + } + + return Helper::formatTime($estimated); + }, + 'memory' => function (ProgressBar $bar) { + return Helper::formatMemory(memory_get_usage(true)); + }, + 'current' => function (ProgressBar $bar) { + return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); + }, + 'max' => function (ProgressBar $bar) { + return $bar->getMaxSteps(); + }, + 'percent' => function (ProgressBar $bar) { + return floor($bar->getProgressPercent() * 100); + }, + ); + } + + private static function initFormats() + { + return array( + 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', + 'normal_nomax' => ' %current% [%bar%]', + + 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + ); + } +} diff --git a/vendor/symfony/console/Helper/ProgressHelper.php b/vendor/symfony/console/Helper/ProgressHelper.php new file mode 100644 index 00000000..bd885662 --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressHelper.php @@ -0,0 +1,465 @@ + + * + * 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\NullOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The Progress class provides helpers to display progress output. + * + * @author Chris Jones + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0 + * Use {@link ProgressBar} instead. + */ +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 int + */ + private $current; + + /** + * Maximum number of steps. + * + * @var int + */ + private $max; + + /** + * Start time of the progress bar. + * + * @var int + */ + 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), + ); + + public function __construct($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__CLASS__.' class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\ProgressBar class instead.', E_USER_DEPRECATED); + } + } + + /** + * 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 int|null $max Maximum steps + */ + public function start(OutputInterface $output, $max = null) + { + $this->startTime = time(); + $this->current = 0; + $this->max = (int) $max; + + // Disabling output when it does not support ANSI codes as it would result in a broken display anyway. + $this->output = $output->isDecorated() ? $output : new NullOutput(); + $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 int $step Number of steps to advance + * @param bool $redraw Whether to redraw or not + * + * @throws \LogicException + */ + public function advance($step = 1, $redraw = false) + { + $this->setCurrent($this->current + $step, $redraw); + } + + /** + * Sets the current progress. + * + * @param int $current The current progress + * @param bool $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; + } + + $prevPeriod = (int) ($this->current / $this->redrawFreq); + + $this->current = $current; + + $currPeriod = (int) ($this->current / $this->redrawFreq); + if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) { + $this->display(); + } + } + + /** + * Outputs the current progress string. + * + * @param bool $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); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear() + { + $this->overwrite($this->output, ''); + } + + /** + * 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 bool $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 = (float) $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 int $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/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php new file mode 100644 index 00000000..a533f872 --- /dev/null +++ b/vendor/symfony/console/Helper/QuestionHelper.php @@ -0,0 +1,446 @@ + + * + * 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\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Question\ChoiceQuestion; + +/** + * The QuestionHelper class provides helpers to interact with the user. + * + * @author Fabien Potencier + */ +class QuestionHelper extends Helper +{ + private $inputStream; + private static $shell; + private static $stty; + + /** + * Asks a question to the user. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * @param Question $question The question to ask + * + * @return string The user answer + * + * @throws \RuntimeException If there is no data to read in the input stream + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if (!$input->isInteractive()) { + return $question->getDefault(); + } + + if (!$question->getValidator()) { + return $this->doAsk($output, $question); + } + + $that = $this; + + $interviewer = function () use ($output, $question, $that) { + return $that->doAsk($output, $question); + }; + + return $this->validateAttempts($interviewer, $output, $question); + } + + /** + * 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 + * + * @throws \InvalidArgumentException In case the stream is not a resource + */ + public function setInputStream($stream) + { + if (!is_resource($stream)) { + throw new \InvalidArgumentException('Input stream must be a valid resource.'); + } + + $this->inputStream = $stream; + } + + /** + * Returns the helper's input stream. + * + * @return resource + */ + public function getInputStream() + { + return $this->inputStream; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'question'; + } + + /** + * Asks the question to the user. + * + * This method is public for PHP 5.3 compatibility, it should be private. + * + * @param OutputInterface $output + * @param Question $question + * + * @return bool|mixed|null|string + * + * @throws \Exception + * @throws \RuntimeException + */ + public function doAsk(OutputInterface $output, Question $question) + { + $this->writePrompt($output, $question); + + $inputStream = $this->inputStream ?: STDIN; + $autocomplete = $question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($output, $inputStream)); + } catch (\RuntimeException $e) { + if (!$question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new \RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($output, $question, $inputStream)); + } + + $ret = strlen($ret) > 0 ? $ret : $question->getDefault(); + + if ($normalizer = $question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + /** + * Outputs the question prompt. + * + * @param OutputInterface $output + * @param Question $question + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $message = $question->getQuestion(); + + if ($question instanceof ChoiceQuestion) { + $width = max(array_map('strlen', array_keys($question->getChoices()))); + + $messages = (array) $question->getQuestion(); + foreach ($question->getChoices() as $key => $value) { + $messages[] = sprintf(" [%-${width}s] %s", $key, $value); + } + + $output->writeln($messages); + + $message = $question->getPrompt(); + } + + $output->write($message); + } + + /** + * Outputs an error message. + * + * @param OutputInterface $output + * @param \Exception $error + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { + $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); + } else { + $message = ''.$error->getMessage().''; + } + + $output->writeln($message); + } + + /** + * Autocompletes a question. + * + * @param OutputInterface $output + * @param Question $question + * + * @return string + */ + private function autocomplete(OutputInterface $output, Question $question, $inputStream) + { + $autocomplete = $question->getAutocompleterValues(); + $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 (isset($c[2]) && ('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 $ret; + } + + /** + * Gets a hidden response from user. + * + * @param OutputInterface $output An Output instance + * + * @return string The answer + * + * @throws \RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function getHiddenResponse(OutputInterface $output, $inputStream) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $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; + } + + $value = rtrim(shell_exec($exe)); + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 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()) { + $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; + } + + throw new \RuntimeException('Unable to hide the response.'); + } + + /** + * Validates an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * @param OutputInterface $output An Output instance + * @param Question $question A Question instance + * + * @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, Question $question) + { + $error = null; + $attempts = $question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->writeError($output, $error); + } + + try { + return call_user_func($question->getValidator(), $interviewer()); + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * Returns a valid unix shell. + * + * @return string|bool 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; + } + + /** + * Returns whether Stty is available or not. + * + * @return bool + */ + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = $exitcode === 0; + } +} diff --git a/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php new file mode 100644 index 00000000..77130f97 --- /dev/null +++ b/vendor/symfony/console/Helper/SymfonyQuestionHelper.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\Console\Helper; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Symfony Style Guide compliant question helper. + * + * @author Kevin Bond + */ +class SymfonyQuestionHelper extends QuestionHelper +{ + /** + * {@inheritdoc} + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + $validator = $question->getValidator(); + $question->setValidator(function ($value) use ($validator) { + if (null !== $validator && is_callable($validator)) { + $value = $validator($value); + } + + // make required + if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) { + throw new \Exception('A value is required.'); + } + + return $value; + }); + + return parent::ask($input, $output, $question); + } + + /** + * {@inheritdoc} + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $text = $question->getQuestion(); + $default = $question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $question instanceof ConfirmationQuestion: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $question instanceof ChoiceQuestion: + $choices = $question->getChoices(); + $text = sprintf(' %s [%s]:', $text, $choices[$default]); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, $default); + } + + $output->writeln($text); + + if ($question instanceof ChoiceQuestion) { + $width = max(array_map('strlen', array_keys($question->getChoices()))); + + foreach ($question->getChoices() as $key => $value) { + $output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); + } + } + + $output->write(' > '); + } + + /** + * {@inheritdoc} + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if ($output instanceof SymfonyStyle) { + $output->newLine(); + $output->error($error->getMessage()); + + return; + } + + parent::writeError($output, $error); + } +} diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php new file mode 100644 index 00000000..6f5fbd04 --- /dev/null +++ b/vendor/symfony/console/Helper/Table.php @@ -0,0 +1,606 @@ + + * + * 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; + +/** + * Provides helpers to display a table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Abdellatif Ait boudad + */ +class Table +{ + /** + * Table headers. + * + * @var array + */ + private $headers = array(); + + /** + * Table rows. + * + * @var array + */ + private $rows = array(); + + /** + * Column widths cache. + * + * @var array + */ + private $columnWidths = array(); + + /** + * Number of columns cache. + * + * @var array + */ + private $numberOfColumns; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var TableStyle + */ + private $style; + + private static $styles; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + $this->setStyle('default'); + } + + /** + * Sets a style definition. + * + * @param string $name The style name + * @param TableStyle $style A TableStyle instance + */ + public static function setStyleDefinition($name, TableStyle $style) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + self::$styles[$name] = $style; + } + + /** + * Gets a style definition by name. + * + * @param string $name The style name + * + * @return TableStyle A TableStyle instance + */ + public static function getStyleDefinition($name) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + if (!self::$styles[$name]) { + throw new \InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + return self::$styles[$name]; + } + + /** + * Sets table style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return Table + */ + public function setStyle($name) + { + if ($name instanceof TableStyle) { + $this->style = $name; + } elseif (isset(self::$styles[$name])) { + $this->style = self::$styles[$name]; + } else { + throw new \InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + return $this; + } + + /** + * Gets the current table style. + * + * @return TableStyle + */ + public function getStyle() + { + return $this->style; + } + + public function setHeaders(array $headers) + { + $headers = array_values($headers); + if (!empty($headers) && !is_array($headers[0])) { + $headers = array($headers); + } + + $this->headers = $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($row) + { + if ($row instanceof TableSeparator) { + $this->rows[] = $row; + + return $this; + } + + if (!is_array($row)) { + throw new \InvalidArgumentException('A row must be an array or a TableSeparator instance.'); + } + + $this->rows[] = array_values($row); + + return $this; + } + + public function setRow($column, array $row) + { + $this->rows[$column] = $row; + + 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 | + * +---------------+-----------------------+------------------+ + */ + public function render() + { + $this->calculateNumberOfColumns(); + $this->rows = $this->buildTableRows($this->rows); + $this->headers = $this->buildTableRows($this->headers); + + $this->renderRowSeparator(); + if (!empty($this->headers)) { + foreach ($this->headers as $header) { + $this->renderRow($header, $this->style->getCellHeaderFormat()); + $this->renderRowSeparator(); + } + } + foreach ($this->rows as $row) { + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + } else { + $this->renderRow($row, $this->style->getCellRowFormat()); + } + } + if (!empty($this->rows)) { + $this->renderRowSeparator(); + } + + $this->cleanup(); + } + + /** + * Renders horizontal header separator. + * + * Example: +-----+-----------+-------+ + */ + private function renderRowSeparator() + { + if (0 === $count = $this->numberOfColumns) { + return; + } + + if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { + return; + } + + $markup = $this->style->getCrossingChar(); + for ($column = 0; $column < $count; ++$column) { + $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->getColumnWidth($column)).$this->style->getCrossingChar(); + } + + $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator() + { + $this->output->write(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); + } + + /** + * 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(); + foreach ($this->getRowColumns($row) as $column) { + $this->renderCell($row, $column, $cellFormat); + $this->renderColumnSeparator(); + } + $this->output->writeln(''); + } + + /** + * Renders table cell with padding. + * + * @param array $row + * @param int $column + * @param string $cellFormat + */ + private function renderCell(array $row, $column, $cellFormat) + { + $cell = isset($row[$column]) ? $row[$column] : ''; + $width = $this->getColumnWidth($column); + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // add the width of the following columns(numbers of colspan). + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { + $width += $this->getColumnSeparatorWidth() + $this->getColumnWidth($nextColumn); + } + } + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (function_exists('mb_strwidth') && false !== $encoding = mb_detect_encoding($cell)) { + $width += strlen($cell) - mb_strwidth($cell, $encoding); + } + + if ($cell instanceof TableSeparator) { + $this->output->write(sprintf($this->style->getBorderFormat(), str_repeat($this->style->getHorizontalBorderChar(), $width))); + } else { + $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + $content = sprintf($this->style->getCellRowContentFormat(), $cell); + $this->output->write(sprintf($cellFormat, str_pad($content, $width, $this->style->getPaddingChar(), $this->style->getPadType()))); + } + } + + /** + * Calculate number of columns for this table. + */ + private function calculateNumberOfColumns() + { + if (null !== $this->numberOfColumns) { + return; + } + + $columns = array(0); + foreach (array_merge($this->headers, $this->rows) as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + $columns[] = $this->getNumberOfColumns($row); + } + + return $this->numberOfColumns = max($columns); + } + + private function buildTableRows($rows) + { + $unmergedRows = array(); + for ($rowKey = 0; $rowKey < count($rows); ++$rowKey) { + $rows = $this->fillNextRows($rows, $rowKey); + + // Remove any new line breaks and replace it with a new line + foreach ($rows[$rowKey] as $column => $cell) { + $rows[$rowKey] = $this->fillCells($rows[$rowKey], $column); + if (!strstr($cell, "\n")) { + continue; + } + $lines = explode("\n", $cell); + foreach ($lines as $lineKey => $line) { + if ($cell instanceof TableCell) { + $line = new TableCell($line, array('colspan' => $cell->getColspan())); + } + if (0 === $lineKey) { + $rows[$rowKey][$column] = $line; + } else { + $unmergedRows[$rowKey][$lineKey][$column] = $line; + } + } + } + } + + $tableRows = array(); + foreach ($rows as $rowKey => $row) { + $tableRows[] = $row; + if (isset($unmergedRows[$rowKey])) { + $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); + } + } + + return $tableRows; + } + + /** + * fill rows that contains rowspan > 1. + * + * @param array $rows + * @param int $line + * + * @return array + */ + private function fillNextRows($rows, $line) + { + $unmergedRows = array(); + foreach ($rows[$line] as $column => $cell) { + if ($cell instanceof TableCell && $cell->getRowspan() > 1) { + $nbLines = $cell->getRowspan() - 1; + $lines = array($cell); + if (strstr($cell, "\n")) { + $lines = explode("\n", $cell); + $nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; + + $rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan())); + unset($lines[0]); + } + + // create a two dimensional array (rowspan x colspan) + $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, ''), $unmergedRows); + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan())); + } + } + } + + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + // we need to know if $unmergedRow will be merged or inserted into $rows + if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + foreach ($unmergedRow as $cellKey => $cell) { + // insert cell into row at cellKey position + array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell)); + } + } else { + $row = $this->copyRow($rows, $unmergedRowKey - 1); + foreach ($unmergedRow as $column => $cell) { + if (!empty($cell)) { + $row[$column] = $unmergedRow[$column]; + } + } + array_splice($rows, $unmergedRowKey, 0, array($row)); + } + } + + return $rows; + } + + /** + * fill cells for a row that contains colspan > 1. + * + * @param array $row + * @param int $column + * + * @return array + */ + private function fillCells($row, $column) + { + $cell = $row[$column]; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { + // insert empty value into rows at column position + array_splice($row, $position, 0, ''); + } + } + + return $row; + } + + /** + * @param array $rows + * @param int $line + * + * @return array + */ + private function copyRow($rows, $line) + { + $row = $rows[$line]; + foreach ($row as $cellKey => $cellValue) { + $row[$cellKey] = ''; + if ($cellValue instanceof TableCell) { + $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan())); + } + } + + return $row; + } + + /** + * Gets number of columns by row. + * + * @param array $row + * + * @return int + */ + private function getNumberOfColumns(array $row) + { + $columns = count($row); + foreach ($row as $column) { + $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; + } + + return $columns; + } + + /** + * Gets list of columns for the given row. + * + * @param array $row + * + * @return array() + */ + private function getRowColumns($row) + { + $columns = range(0, $this->numberOfColumns - 1); + foreach ($row as $cellKey => $cell) { + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // exclude grouped columns. + $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); + } + } + + return $columns; + } + + /** + * Gets column width. + * + * @param int $column + * + * @return int + */ + private function getColumnWidth($column) + { + if (isset($this->columnWidths[$column])) { + return $this->columnWidths[$column]; + } + + foreach (array_merge($this->headers, $this->rows) as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + $lengths[] = $this->getCellWidth($row, $column); + } + + return $this->columnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2; + } + + /** + * Gets column width. + * + * @param int $column + * + * @return int + */ + private function getColumnSeparatorWidth() + { + return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); + } + + /** + * Gets cell width. + * + * @param array $row + * @param int $column + * + * @return int + */ + private function getCellWidth(array $row, $column) + { + if (isset($row[$column])) { + $cell = $row[$column]; + $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // we assume that cell value will be across more than one column. + $cellWidth = $cellWidth / $cell->getColspan(); + } + + return $cellWidth; + } + + return 0; + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup() + { + $this->columnWidths = array(); + $this->numberOfColumns = null; + } + + private static function initStyles() + { + $borderless = new TableStyle(); + $borderless + ->setHorizontalBorderChar('=') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ; + + $compact = new TableStyle(); + $compact + ->setHorizontalBorderChar('') + ->setVerticalBorderChar(' ') + ->setCrossingChar('') + ->setCellRowContentFormat('%s') + ; + + $styleGuide = new TableStyle(); + $styleGuide + ->setHorizontalBorderChar('-') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ->setCellHeaderFormat('%s') + ; + + return array( + 'default' => new TableStyle(), + 'borderless' => $borderless, + 'compact' => $compact, + 'symfony-style-guide' => $styleGuide, + ); + } +} diff --git a/vendor/symfony/console/Helper/TableCell.php b/vendor/symfony/console/Helper/TableCell.php new file mode 100644 index 00000000..aa0d3180 --- /dev/null +++ b/vendor/symfony/console/Helper/TableCell.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\Console\Helper; + +/** + * @author Abdellatif Ait boudad + */ +class TableCell +{ + /** + * @var string + */ + private $value; + + /** + * @var array + */ + private $options = array( + 'rowspan' => 1, + 'colspan' => 1, + ); + + /** + * @param string $value + * @param array $options + */ + public function __construct($value = '', array $options = array()) + { + $this->value = $value; + + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new \InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * Returns the cell value. + * + * @return string + */ + public function __toString() + { + return $this->value; + } + + /** + * Gets number of colspan. + * + * @return int + */ + public function getColspan() + { + return (int) $this->options['colspan']; + } + + /** + * Gets number of rowspan. + * + * @return int + */ + public function getRowspan() + { + return (int) $this->options['rowspan']; + } +} diff --git a/vendor/symfony/console/Helper/TableHelper.php b/vendor/symfony/console/Helper/TableHelper.php new file mode 100644 index 00000000..29535225 --- /dev/null +++ b/vendor/symfony/console/Helper/TableHelper.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\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\NullOutput; + +/** + * Provides helpers to display table output. + * + * @author Саша Стаменковић + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0 + * Use {@link Table} instead. + */ +class TableHelper extends Helper +{ + const LAYOUT_DEFAULT = 0; + const LAYOUT_BORDERLESS = 1; + const LAYOUT_COMPACT = 2; + + /** + * @var Table + */ + private $table; + + public function __construct($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__CLASS__.' class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\Table class instead.', E_USER_DEPRECATED); + } + + $this->table = new Table(new NullOutput()); + } + + /** + * Sets table layout type. + * + * @param int $layout self::LAYOUT_* + * + * @return TableHelper + * + * @throws \InvalidArgumentException when the table layout is not known + */ + public function setLayout($layout) + { + switch ($layout) { + case self::LAYOUT_BORDERLESS: + $this->table->setStyle('borderless'); + break; + + case self::LAYOUT_COMPACT: + $this->table->setStyle('compact'); + break; + + case self::LAYOUT_DEFAULT: + $this->table->setStyle('default'); + break; + + default: + throw new \InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout)); + }; + + return $this; + } + + public function setHeaders(array $headers) + { + $this->table->setHeaders($headers); + + return $this; + } + + public function setRows(array $rows) + { + $this->table->setRows($rows); + + return $this; + } + + public function addRows(array $rows) + { + $this->table->addRows($rows); + + return $this; + } + + public function addRow(array $row) + { + $this->table->addRow($row); + + return $this; + } + + public function setRow($column, array $row) + { + $this->table->setRow($column, $row); + + return $this; + } + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return TableHelper + */ + public function setPaddingChar($paddingChar) + { + $this->table->getStyle()->setPaddingChar($paddingChar); + + return $this; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return TableHelper + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + $this->table->getStyle()->setHorizontalBorderChar($horizontalBorderChar); + + return $this; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return TableHelper + */ + public function setVerticalBorderChar($verticalBorderChar) + { + $this->table->getStyle()->setVerticalBorderChar($verticalBorderChar); + + return $this; + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return TableHelper + */ + public function setCrossingChar($crossingChar) + { + $this->table->getStyle()->setCrossingChar($crossingChar); + + return $this; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return TableHelper + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->table->getStyle()->setCellHeaderFormat($cellHeaderFormat); + + return $this; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return TableHelper + */ + public function setCellRowFormat($cellRowFormat) + { + $this->table->getStyle()->setCellHeaderFormat($cellRowFormat); + + return $this; + } + + /** + * Sets row cell content format. + * + * @param string $cellRowContentFormat + * + * @return TableHelper + */ + public function setCellRowContentFormat($cellRowContentFormat) + { + $this->table->getStyle()->setCellRowContentFormat($cellRowContentFormat); + + return $this; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return TableHelper + */ + public function setBorderFormat($borderFormat) + { + $this->table->getStyle()->setBorderFormat($borderFormat); + + return $this; + } + + /** + * Sets cell padding type. + * + * @param int $padType STR_PAD_* + * + * @return TableHelper + */ + public function setPadType($padType) + { + $this->table->getStyle()->setPadType($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) + { + $p = new \ReflectionProperty($this->table, 'output'); + $p->setAccessible(true); + $p->setValue($this->table, $output); + + $this->table->render(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'table'; + } +} diff --git a/vendor/symfony/console/Helper/TableSeparator.php b/vendor/symfony/console/Helper/TableSeparator.php new file mode 100644 index 00000000..8cbbc661 --- /dev/null +++ b/vendor/symfony/console/Helper/TableSeparator.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\Console\Helper; + +/** + * Marks a row as being a separator. + * + * @author Fabien Potencier + */ +class TableSeparator extends TableCell +{ + /** + * @param string $value + * @param array $options + */ + public function __construct(array $options = array()) + { + parent::__construct('', $options); + } +} diff --git a/vendor/symfony/console/Helper/TableStyle.php b/vendor/symfony/console/Helper/TableStyle.php new file mode 100644 index 00000000..f0f46c71 --- /dev/null +++ b/vendor/symfony/console/Helper/TableStyle.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\Console\Helper; + +/** + * Defines the styles for a Table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + */ +class TableStyle +{ + private $paddingChar = ' '; + private $horizontalBorderChar = '-'; + private $verticalBorderChar = '|'; + private $crossingChar = '+'; + private $cellHeaderFormat = '%s'; + private $cellRowFormat = '%s'; + private $cellRowContentFormat = ' %s '; + private $borderFormat = '%s'; + private $padType = STR_PAD_RIGHT; + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return TableStyle + */ + public function setPaddingChar($paddingChar) + { + if (!$paddingChar) { + throw new \LogicException('The padding char must not be empty'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Gets padding character, used for cell padding. + * + * @return string + */ + public function getPaddingChar() + { + return $this->paddingChar; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return TableStyle + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + $this->horizontalBorderChar = $horizontalBorderChar; + + return $this; + } + + /** + * Gets horizontal border character. + * + * @return string + */ + public function getHorizontalBorderChar() + { + return $this->horizontalBorderChar; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return TableStyle + */ + public function setVerticalBorderChar($verticalBorderChar) + { + $this->verticalBorderChar = $verticalBorderChar; + + return $this; + } + + /** + * Gets vertical border character. + * + * @return string + */ + public function getVerticalBorderChar() + { + return $this->verticalBorderChar; + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return TableStyle + */ + public function setCrossingChar($crossingChar) + { + $this->crossingChar = $crossingChar; + + return $this; + } + + /** + * Gets crossing character. + * + * @return string $crossingChar + */ + public function getCrossingChar() + { + return $this->crossingChar; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return TableStyle + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Gets header cell format. + * + * @return string + */ + public function getCellHeaderFormat() + { + return $this->cellHeaderFormat; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return TableStyle + */ + public function setCellRowFormat($cellRowFormat) + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Gets row cell format. + * + * @return string + */ + public function getCellRowFormat() + { + return $this->cellRowFormat; + } + + /** + * Sets row cell content format. + * + * @param string $cellRowContentFormat + * + * @return TableStyle + */ + public function setCellRowContentFormat($cellRowContentFormat) + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Gets row cell content format. + * + * @return string + */ + public function getCellRowContentFormat() + { + return $this->cellRowContentFormat; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return TableStyle + */ + public function setBorderFormat($borderFormat) + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Gets table border format. + * + * @return string + */ + public function getBorderFormat() + { + return $this->borderFormat; + } + + /** + * Sets cell padding type. + * + * @param int $padType STR_PAD_* + * + * @return TableStyle + */ + public function setPadType($padType) + { + if (!in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) { + throw new \InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); + } + + $this->padType = $padType; + + return $this; + } + + /** + * Gets cell padding type. + * + * @return int + */ + public function getPadType() + { + return $this->padType; + } +} diff --git a/vendor/symfony/console/Input/ArgvInput.php b/vendor/symfony/console/Input/ArgvInput.php new file mode 100644 index 00000000..a6c21327 --- /dev/null +++ b/vendor/symfony/console/Input/ArgvInput.php @@ -0,0 +1,353 @@ + + * + * 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] && '-' !== $token) { + $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 empty values to null + if (!isset($value[0])) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + + 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 bool 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 (0 < count($tokens)) { + $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/Input/ArrayInput.php b/vendor/symfony/console/Input/ArrayInput.php new file mode 100644 index 00000000..5743bb8a --- /dev/null +++ b/vendor/symfony/console/Input/ArrayInput.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\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 bool 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)) { + if (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/Input/Input.php b/vendor/symfony/console/Input/Input.php new file mode 100644 index 00000000..5e7c1408 --- /dev/null +++ b/vendor/symfony/console/Input/Input.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\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 +{ + /** + * @var InputDefinition + */ + protected $definition; + protected $options = array(); + protected $arguments = array(); + protected $interactive = true; + + /** + * Constructor. + * + * @param InputDefinition $definition A InputDefinition instance + */ + public function __construct(InputDefinition $definition = null) + { + if (null === $definition) { + $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 bool Returns true if the input is interactive + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * Sets the input interactivity. + * + * @param bool $interactive If the input should be interactive + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $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|int $name The InputArgument name or position + * + * @return bool 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|bool $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 bool 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/Input/InputArgument.php b/vendor/symfony/console/Input/InputArgument.php new file mode 100644 index 00000000..1167da9a --- /dev/null +++ b/vendor/symfony/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 int $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 bool 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 bool 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/Input/InputAwareInterface.php b/vendor/symfony/console/Input/InputAwareInterface.php new file mode 100644 index 00000000..d0f11e98 --- /dev/null +++ b/vendor/symfony/console/Input/InputAwareInterface.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\Console\Input; + +/** + * InputAwareInterface should be implemented by classes that depends on the + * Console Input. + * + * @author Wouter J + */ +interface InputAwareInterface +{ + /** + * Sets the Console Input. + * + * @param InputInterface + */ + public function setInput(InputInterface $input); +} diff --git a/vendor/symfony/console/Input/InputDefinition.php b/vendor/symfony/console/Input/InputDefinition.php new file mode 100644 index 00000000..21ac832c --- /dev/null +++ b/vendor/symfony/console/Input/InputDefinition.php @@ -0,0 +1,485 @@ + + * + * 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; +use Symfony\Component\Console\Output\BufferedOutput; + +/** + * 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|int $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|int $name The InputArgument name or position + * + * @return bool 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 int The number of InputArguments + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + * + * @return int 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 bool 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 bool 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. + * + * @param bool $short Whether to return the short version (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $elements = array(); + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf( + ' %s%s%s', + $option->isValueOptional() ? '[' : '', + strtoupper($option->getName()), + $option->isValueOptional() ? ']' : '' + ); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<'.$argument->getName().'>'; + if (!$argument->isRequired()) { + $element = '['.$element.']'; + } elseif ($argument->isArray()) { + $element = $element.' ('.$element.')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } + + /** + * Returns a textual representation of the InputDefinition. + * + * @return string A string representing the InputDefinition + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asText() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new TextDescriptor(); + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $descriptor->describe($output, $this, array('raw_output' => true)); + + return $output->fetch(); + } + + /** + * Returns an XML representation of the InputDefinition. + * + * @param bool $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the InputDefinition + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getInputDefinitionDocument($this); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this); + + return $output->fetch(); + } +} diff --git a/vendor/symfony/console/Input/InputInterface.php b/vendor/symfony/console/Input/InputInterface.php new file mode 100644 index 00000000..6ef2f264 --- /dev/null +++ b/vendor/symfony/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 bool 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|int $name The InputArgument name or position + * + * @return bool 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|bool $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 bool true if the InputOption object exists, false otherwise + */ + public function hasOption($name); + + /** + * Is this input means interactive? + * + * @return bool + */ + public function isInteractive(); + + /** + * Sets the input interactivity. + * + * @param bool $interactive If the input should be interactive + */ + public function setInteractive($interactive); +} diff --git a/vendor/symfony/console/Input/InputOption.php b/vendor/symfony/console/Input/InputOption.php new file mode 100644 index 00000000..3a48ca36 --- /dev/null +++ b/vendor/symfony/console/Input/InputOption.php @@ -0,0 +1,213 @@ + + * + * 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 int $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 bool 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 bool 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 bool 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 bool 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 bool + */ + 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/Input/StringInput.php b/vendor/symfony/console/Input/StringInput.php new file mode 100644 index 00000000..40d1dfbc --- /dev/null +++ b/vendor/symfony/console/Input/StringInput.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\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 + throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); + } + + $cursor += strlen($match[0]); + } + + return $tokens; + } +} diff --git a/vendor/symfony/console/LICENSE b/vendor/symfony/console/LICENSE new file mode 100644 index 00000000..43028bc6 --- /dev/null +++ b/vendor/symfony/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2015 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/Logger/ConsoleLogger.php b/vendor/symfony/console/Logger/ConsoleLogger.php new file mode 100644 index 00000000..1f7417ea --- /dev/null +++ b/vendor/symfony/console/Logger/ConsoleLogger.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\Console\Logger; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; + +/** + * PSR-3 compliant console logger. + * + * @author Kévin Dunglas + * + * @link http://www.php-fig.org/psr/psr-3/ + */ +class ConsoleLogger extends AbstractLogger +{ + const INFO = 'info'; + const ERROR = 'error'; + + /** + * @var OutputInterface + */ + private $output; + /** + * @var array + */ + private $verbosityLevelMap = array( + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, + LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, + LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ); + /** + * @var array + */ + private $formatLevelMap = array( + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ); + + /** + * @param OutputInterface $output + * @param array $verbosityLevelMap + * @param array $formatLevelMap + */ + public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array()) + { + $this->output = $output; + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = array()) + { + if (!isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + // Write to the error output if necessary and available + if ($this->formatLevelMap[$level] === self::ERROR && $this->output instanceof ConsoleOutputInterface) { + $output = $this->output->getErrorOutput(); + } else { + $output = $this->output; + } + + if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { + $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context))); + } + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + * + * @param string $message + * @param array $context + * + * @return string + */ + private function interpolate($message, array $context) + { + // build a replacement array with braces around the context keys + $replace = array(); + foreach ($context as $key => $val) { + if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { + $replace[sprintf('{%s}', $key)] = $val; + } + } + + // interpolate replacement values into the message and return + return strtr($message, $replace); + } +} diff --git a/vendor/symfony/console/Output/BufferedOutput.php b/vendor/symfony/console/Output/BufferedOutput.php new file mode 100644 index 00000000..5682fc24 --- /dev/null +++ b/vendor/symfony/console/Output/BufferedOutput.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\Console\Output; + +/** + * @author Jean-François Simon + */ +class BufferedOutput extends Output +{ + /** + * @var string + */ + private $buffer = ''; + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= "\n"; + } + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutput.php b/vendor/symfony/console/Output/ConsoleOutput.php new file mode 100644 index 00000000..04bd51b7 --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutput.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\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * 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 +{ + /** + * @var StreamOutput + */ + private $stderr; + + /** + * Constructor. + * + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|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) + { + parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); + + $actualDecorated = $this->isDecorated(); + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); + + if (null === $decorated) { + $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); + } + } + + /** + * {@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. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Checks if current executing environment is IBM iSeries (OS400), which + * doesn't properly convert character-encodings between ASCII to EBCDIC. + * + * @return bool + */ + private function isRunningOS400() + { + return 'OS400' === php_uname('s'); + } + + /** + * @return resource + */ + private function openOutputStream() + { + $outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output'; + + return @fopen($outputStream, 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + $errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output'; + + return fopen($errorStream, 'w'); + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutputInterface.php b/vendor/symfony/console/Output/ConsoleOutputInterface.php new file mode 100644 index 00000000..5eb4fc7a --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutputInterface.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\Console\Output; + +/** + * 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/Output/NullOutput.php b/vendor/symfony/console/Output/NullOutput.php new file mode 100644 index 00000000..557f8afe --- /dev/null +++ b/vendor/symfony/console/Output/NullOutput.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\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; + } + + public function isQuiet() + { + return true; + } + + public function isVerbose() + { + return false; + } + + public function isVeryVerbose() + { + return false; + } + + public function isDebug() + { + return false; + } + + /** + * {@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/Output/Output.php b/vendor/symfony/console/Output/Output.php new file mode 100644 index 00000000..cb0e40d2 --- /dev/null +++ b/vendor/symfony/console/Output/Output.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 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 int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool $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 = $formatter ?: new OutputFormatter(); + $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; + } + + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $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 bool $newline Whether to add a newline or not + */ + abstract protected function doWrite($message, $newline); +} diff --git a/vendor/symfony/console/Output/OutputInterface.php b/vendor/symfony/console/Output/OutputInterface.php new file mode 100644 index 00000000..edffb9ca --- /dev/null +++ b/vendor/symfony/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 bool $newline Whether to add a newline + * @param int $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 or a single string + * @param int $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 int $level The level of verbosity (one of the VERBOSITY constants) + * + * @api + */ + public function setVerbosity($level); + + /** + * Gets the current verbosity of the output. + * + * @return int The current level of verbosity (one of the VERBOSITY constants) + * + * @api + */ + public function getVerbosity(); + + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages + * + * @api + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool 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/Output/StreamOutput.php b/vendor/symfony/console/Output/StreamOutput.php new file mode 100644 index 00000000..ecf0bb8c --- /dev/null +++ b/vendor/symfony/console/Output/StreamOutput.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\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 resource $stream A stream resource + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|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 : ''))) { + // should never happen + throw new \RuntimeException('Unable to write output.'); + } + + 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 bool true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport() + { + if (DIRECTORY_SEPARATOR === '\\') { + return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'); + } + + return function_exists('posix_isatty') && @posix_isatty($this->stream); + } +} diff --git a/vendor/symfony/console/Question/ChoiceQuestion.php b/vendor/symfony/console/Question/ChoiceQuestion.php new file mode 100644 index 00000000..a36c739e --- /dev/null +++ b/vendor/symfony/console/Question/ChoiceQuestion.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a choice question. + * + * @author Fabien Potencier + */ +class ChoiceQuestion extends Question +{ + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * Constructor. + * + * @param string $question The question to ask to the user + * @param array $choices The list of available choices + * @param mixed $default The default answer to return + */ + public function __construct($question, array $choices, $default = null) + { + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * Returns available choices. + * + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * Sets multiselect option. + * + * When multiselect is set to true, multiple choices can be answered. + * + * @param bool $multiselect + * + * @return ChoiceQuestion The current instance + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Gets the prompt for choices. + * + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * Sets the prompt for choices. + * + * @param string $prompt + * + * @return ChoiceQuestion The current instance + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * Sets the error message for invalid values. + * + * The error message has a string placeholder (%s) for the invalid value. + * + * @param string $errorMessage + * + * @return ChoiceQuestion The current instance + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns the default answer validator. + * + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + 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, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = array($selected); + } + + $multiselectChoices = array(); + foreach ($selectedChoices as $value) { + $results = array(); + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (count($results) > 1) { + throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (false !== $result) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (false === $result && isset($choices[$value])) { + $result = $value; + } + + if (false === $result) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + + $multiselectChoices[] = (string) $result; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/symfony/console/Question/ConfirmationQuestion.php b/vendor/symfony/console/Question/ConfirmationQuestion.php new file mode 100644 index 00000000..29d98879 --- /dev/null +++ b/vendor/symfony/console/Question/ConfirmationQuestion.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\Console\Question; + +/** + * Represents a yes/no question. + * + * @author Fabien Potencier + */ +class ConfirmationQuestion extends Question +{ + private $trueAnswerRegex; + + /** + * Constructor. + * + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param string $trueAnswerRegex A regex to match the "yes" answer + */ + public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * Returns the default answer normalizer. + * + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/vendor/symfony/console/Question/Question.php b/vendor/symfony/console/Question/Question.php new file mode 100644 index 00000000..01702b20 --- /dev/null +++ b/vendor/symfony/console/Question/Question.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\Console\Question; + +/** + * Represents a Question. + * + * @author Fabien Potencier + */ +class Question +{ + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterValues; + private $validator; + private $default; + private $normalizer; + + /** + * Constructor. + * + * @param string $question The question to ask to the user + * @param mixed $default The default answer to return if the user enters nothing + */ + public function __construct($question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * Returns the question. + * + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * Returns the default answer. + * + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns whether the user response must be hidden. + * + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets whether the user response must be hidden or not. + * + * @param bool $hidden + * + * @return Question The current instance + * + * @throws \LogicException In case the autocompleter is also used + */ + public function setHidden($hidden) + { + if ($this->autocompleterValues) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * In case the response can not be hidden, whether to fallback on non-hidden question or not. + * + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * Sets whether to fallback on non-hidden question if the response can not be hidden. + * + * @param bool $fallback + * + * @return Question The current instance + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * Gets values for the autocompleter. + * + * @return null|array|\Traversable + */ + public function getAutocompleterValues() + { + return $this->autocompleterValues; + } + + /** + * Sets values for the autocompleter. + * + * @param null|array|\Traversable $values + * + * @return Question The current instance + * + * @throws \InvalidArgumentException + * @throws \LogicException + */ + public function setAutocompleterValues($values) + { + if (is_array($values) && $this->isAssoc($values)) { + $values = array_merge(array_keys($values), array_values($values)); + } + + if (null !== $values && !is_array($values)) { + if (!$values instanceof \Traversable || $values instanceof \Countable) { + throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); + } + } + + if ($this->hidden) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterValues = $values; + + return $this; + } + + /** + * Sets a validator for the question. + * + * @param null|callable $validator + * + * @return Question The current instance + */ + public function setValidator($validator) + { + $this->validator = $validator; + + return $this; + } + + /** + * Gets the validator for the question. + * + * @return null|callable + */ + public function getValidator() + { + return $this->validator; + } + + /** + * Sets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @param null|int $attempts + * + * @return Question The current instance + * + * @throws \InvalidArgumentException In case the number of attempts is invalid. + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * Gets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @return null|int + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * Sets a normalizer for the response. + * + * The normalizer can be a callable (a string), a closure or a class implementing __invoke. + * + * @param string|\Closure $normalizer + * + * @return Question The current instance + */ + public function setNormalizer($normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * Gets the normalizer for the response. + * + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * + * @return string|\Closure + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } +} diff --git a/vendor/symfony/console/README.md b/vendor/symfony/console/README.md new file mode 100644 index 00000000..25f700c8 --- /dev/null +++ b/vendor/symfony/console/README.md @@ -0,0 +1,67 @@ +Console Component +================= + +Console eases the creation of beautiful and testable command line interfaces. + +The Application object manages the CLI application: + +```php +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: + +```php +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 install + $ 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](https://symfony.com/doc/current/components/console.html) + +[How to create a Console Command](https://symfony.com/doc/current/cookbook/console/console_command.html) diff --git a/vendor/symfony/console/Resources/bin/hiddeninput.exe b/vendor/symfony/console/Resources/bin/hiddeninput.exe new file mode 100644 index 00000000..c8cf65e8 Binary files /dev/null and b/vendor/symfony/console/Resources/bin/hiddeninput.exe differ diff --git a/vendor/symfony/console/Shell.php b/vendor/symfony/console/Shell.php new file mode 100644 index 00000000..eaaadfd8 --- /dev/null +++ b/vendor/symfony/console/Shell.php @@ -0,0 +1,228 @@ + + * + * 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\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 = false; + + /** + * 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(); + } + + /** + * 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 bool|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 = (false === $line || '' === $line) ? false : rtrim($line); + } + + return $line; + } + + public function getProcessIsolation() + { + return $this->processIsolation; + } + + public function setProcessIsolation($processIsolation) + { + $this->processIsolation = (bool) $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/Style/OutputStyle.php b/vendor/symfony/console/Style/OutputStyle.php new file mode 100644 index 00000000..8371bb53 --- /dev/null +++ b/vendor/symfony/console/Style/OutputStyle.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\Console\Style; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Decorates output to add console style guide helpers. + * + * @author Kevin Bond + */ +abstract class OutputStyle implements OutputInterface, StyleInterface +{ + private $output; + + /** + * @param OutputInterface $output + */ + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + $this->output->write(str_repeat(PHP_EOL, $count)); + } + + /** + * @param int $max + * + * @return ProgressBar + */ + public function createProgressBar($max = 0) + { + return new ProgressBar($this->output, $max); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->output->write($messages, $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->output->writeln($messages, $type); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->output->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->output->getVerbosity(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->output->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->output->getFormatter(); + } +} diff --git a/vendor/symfony/console/Style/StyleInterface.php b/vendor/symfony/console/Style/StyleInterface.php new file mode 100644 index 00000000..2448547f --- /dev/null +++ b/vendor/symfony/console/Style/StyleInterface.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\Console\Style; + +/** + * Output style helpers. + * + * @author Kevin Bond + */ +interface StyleInterface +{ + /** + * Formats a command title. + * + * @param string $message + */ + public function title($message); + + /** + * Formats a section title. + * + * @param string $message + */ + public function section($message); + + /** + * Formats a list. + * + * @param array $elements + */ + public function listing(array $elements); + + /** + * Formats informational text. + * + * @param string|array $message + */ + public function text($message); + + /** + * Formats a success result bar. + * + * @param string|array $message + */ + public function success($message); + + /** + * Formats an error result bar. + * + * @param string|array $message + */ + public function error($message); + + /** + * Formats an warning result bar. + * + * @param string|array $message + */ + public function warning($message); + + /** + * Formats a note admonition. + * + * @param string|array $message + */ + public function note($message); + + /** + * Formats a caution admonition. + * + * @param string|array $message + */ + public function caution($message); + + /** + * Formats a table. + * + * @param array $headers + * @param array $rows + */ + public function table(array $headers, array $rows); + + /** + * Asks a question. + * + * @param string $question + * @param string|null $default + * @param callable|null $validator + * + * @return string + */ + public function ask($question, $default = null, $validator = null); + + /** + * Asks a question with the user input hidden. + * + * @param string $question + * @param callable|null $validator + * + * @return string + */ + public function askHidden($question, $validator = null); + + /** + * Asks for confirmation. + * + * @param string $question + * @param bool $default + * + * @return bool + */ + public function confirm($question, $default = true); + + /** + * Asks a choice question. + * + * @param string $question + * @param array $choices + * @param string|int|null $default + * + * @return string + */ + public function choice($question, array $choices, $default = null); + + /** + * Add newline(s). + * + * @param int $count The number of newlines + */ + public function newLine($count = 1); + + /** + * Starts the progress output. + * + * @param int $max Maximum steps (0 if unknown) + */ + public function progressStart($max = 0); + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function progressAdvance($step = 1); + + /** + * Finishes the progress output. + */ + public function progressFinish(); +} diff --git a/vendor/symfony/console/Style/SymfonyStyle.php b/vendor/symfony/console/Style/SymfonyStyle.php new file mode 100644 index 00000000..0d366c7e --- /dev/null +++ b/vendor/symfony/console/Style/SymfonyStyle.php @@ -0,0 +1,406 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\SymfonyQuestionHelper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; + +/** + * Output decorator helpers for the Symfony Style Guide. + * + * @author Kevin Bond + */ +class SymfonyStyle extends OutputStyle +{ + const MAX_LINE_LENGTH = 120; + + private $input; + private $questionHelper; + private $progressBar; + private $lineLength; + private $bufferedOutput; + + /** + * @param InputInterface $input + * @param OutputInterface $output + */ + public function __construct(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); + // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. + $this->lineLength = min($this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); + + parent::__construct($output); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string|null $type The block type (added in [] on first line) + * @param string|null $style The style to apply to the whole block + * @param string $prefix The prefix for the block + * @param bool $padding Whether to add vertical padding + */ + public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false) + { + $this->autoPrependBlock(); + $messages = is_array($messages) ? array_values($messages) : array($messages); + $lines = array(); + + // add type + if (null !== $type) { + $messages[0] = sprintf('[%s] %s', $type, $messages[0]); + } + + // wrap and add newlines for each element + foreach ($messages as $key => $message) { + $message = OutputFormatter::escape($message); + $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - Helper::strlen($prefix), PHP_EOL, true))); + + if (count($messages) > 1 && $key < count($messages) - 1) { + $lines[] = ''; + } + } + + if ($padding && $this->isDecorated()) { + array_unshift($lines, ''); + $lines[] = ''; + } + + foreach ($lines as &$line) { + $line = sprintf('%s%s', $prefix, $line); + $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line)); + + if ($style) { + $line = sprintf('<%s>%s', $style, $line); + } + } + + $this->writeln($lines); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function title($message) + { + $this->autoPrependBlock(); + $this->writeln(array( + sprintf('%s', $message), + sprintf('%s', str_repeat('=', strlen($message))), + )); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function section($message) + { + $this->autoPrependBlock(); + $this->writeln(array( + sprintf('%s', $message), + sprintf('%s', str_repeat('-', strlen($message))), + )); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function listing(array $elements) + { + $this->autoPrependText(); + $elements = array_map(function ($element) { + return sprintf(' * %s', $element); + }, $elements); + + $this->writeln($elements); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function text($message) + { + $this->autoPrependText(); + + if (!is_array($message)) { + $this->writeln(sprintf(' // %s', $message)); + + return; + } + + foreach ($message as $element) { + $this->text($element); + } + } + + /** + * {@inheritdoc} + */ + public function success($message) + { + $this->block($message, 'OK', 'fg=white;bg=green', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function error($message) + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function warning($message) + { + $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function note($message) + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); + } + + /** + * {@inheritdoc} + */ + public function caution($message) + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); + } + + /** + * {@inheritdoc} + */ + public function table(array $headers, array $rows) + { + $headers = array_map(function ($value) { return sprintf('%s', $value); }, $headers); + + $table = new Table($this); + $table->setHeaders($headers); + $table->setRows($rows); + $table->setStyle('symfony-style-guide'); + + $table->render(); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function ask($question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function askHidden($question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function confirm($question, $default = true) + { + return $this->askQuestion(new ConfirmationQuestion($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice($question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); + } + + /** + * {@inheritdoc} + */ + public function progressStart($max = 0) + { + $this->progressBar = $this->createProgressBar($max); + $this->progressBar->start(); + } + + /** + * {@inheritdoc} + */ + public function progressAdvance($step = 1) + { + $this->getProgressBar()->advance($step); + } + + /** + * {@inheritdoc} + */ + public function progressFinish() + { + $this->getProgressBar()->finish(); + $this->newLine(2); + $this->progressBar = null; + } + + /** + * {@inheritdoc} + */ + public function createProgressBar($max = 0) + { + $progressBar = parent::createProgressBar($max); + + if ('\\' === DIRECTORY_SEPARATOR) { + $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 + $progressBar->setProgressCharacter(''); + $progressBar->setBarCharacter('▓'); // dark shade character \u2593 + } + + return $progressBar; + } + + /** + * @param Question $question + * + * @return string + */ + public function askQuestion(Question $question) + { + if ($this->input->isInteractive()) { + $this->autoPrependBlock(); + } + + if (!$this->questionHelper) { + $this->questionHelper = new SymfonyQuestionHelper(); + } + + $answer = $this->questionHelper->ask($this->input, $this, $question); + + if ($this->input->isInteractive()) { + $this->newLine(); + $this->bufferedOutput->write("\n"); + } + + return $answer; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + parent::writeln($messages, $type); + $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + parent::write($messages, $newline, $type); + $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + parent::newLine($count); + $this->bufferedOutput->write(str_repeat("\n", $count)); + } + + /** + * @return ProgressBar + */ + private function getProgressBar() + { + if (!$this->progressBar) { + throw new \RuntimeException('The ProgressBar is not started.'); + } + + return $this->progressBar; + } + + private function getTerminalWidth() + { + $application = new Application(); + $dimensions = $application->getTerminalDimensions(); + + return $dimensions[0] ?: self::MAX_LINE_LENGTH; + } + + private function autoPrependBlock() + { + $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); + + if (!isset($chars[0])) { + return $this->newLine(); //empty history, so we should start with a new line. + } + //Prepend new line for each non LF chars (This means no blank line was output before) + $this->newLine(2 - substr_count($chars, "\n")); + } + + private function autoPrependText() + { + $fetched = $this->bufferedOutput->fetch(); + //Prepend new line if last char isn't EOL: + if ("\n" !== substr($fetched, -1)) { + $this->newLine(); + } + } + + private function reduceBuffer($messages) + { + // We need to know if the two last chars are PHP_EOL + // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer + return array_map(function ($value) { + return substr($value, -4); + }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages)); + } +} diff --git a/vendor/symfony/console/Tester/ApplicationTester.php b/vendor/symfony/console/Tester/ApplicationTester.php new file mode 100644 index 00000000..da8a19ce --- /dev/null +++ b/vendor/symfony/console/Tester/ApplicationTester.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\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; + private $statusCode; + + /** + * 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 int 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->statusCode = $this->application->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the application. + * + * @param bool $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; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } +} diff --git a/vendor/symfony/console/Tester/CommandTester.php b/vendor/symfony/console/Tester/CommandTester.php new file mode 100644 index 00000000..8d6486e1 --- /dev/null +++ b/vendor/symfony/console/Tester/CommandTester.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\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier + */ +class CommandTester +{ + private $command; + private $input; + private $output; + private $statusCode; + + /** + * Constructor. + * + * @param Command $command A Command instance to test. + */ + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Executes the command. + * + * Available execution 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 command arguments and options + * @param array $options An array of execution options + * + * @return int The command exit code + */ + public function execute(array $input, array $options = array()) + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input = array_merge(array('command' => $this->command->getName()), $input); + } + + $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->statusCode = $this->command->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the command. + * + * @param bool $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; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } +} diff --git a/vendor/symfony/console/Tests/ApplicationTest.php b/vendor/symfony/console/Tests/ApplicationTest.php new file mode 100644 index 00000000..d33e5848 --- /dev/null +++ b/vendor/symfony/console/Tests/ApplicationTest.php @@ -0,0 +1,1060 @@ + + * + * 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\ArgvInput; +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\Output\StreamOutput; +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'; + require_once self::$fixturesPath.'/Foo5Command.php'; + require_once self::$fixturesPath.'/FoobarCommand.php'; + require_once self::$fixturesPath.'/BarBucCommand.php'; + require_once self::$fixturesPath.'/FooSubnamespaced1Command.php'; + require_once self::$fixturesPath.'/FooSubnamespaced2Command.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()), '->getHelp() returns a help message'); + } + + public function testAll() + { + $application = new Application(); + $commands = $application->all(); + $this->assertInstanceOf('Symfony\\Component\\Console\\Command\\HelpCommand', $commands['help'], '->all() returns the registered commands'); + + $application->add(new \FooCommand()); + $commands = $application->all('foo'); + $this->assertCount(1, $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'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Command class "Foo5Command" is not correctly initialized. You probably forgot to call the parent constructor. + */ + public function testAddCommandWithEmptyConstructor() + { + $application = new Application(); + $application->add(new \Foo5Command()); + } + + 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'); + } + + public function testFindNamespaceWithSubnamespaces() + { + $application = new Application(); + $application->add(new \FooSubnamespaced1Command()); + $application->add(new \FooSubnamespaced2Command()); + $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns commands even if the commands are only contained in subnamespaces'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The namespace "f" is ambiguous (foo, foo1). + */ + public function testFindAmbiguousNamespace() + { + $application = new Application(); + $application->add(new \BarBucCommand()); + $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'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Command "foo1" is not defined + */ + public function testFindUniqueNameButNamespaceName() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + $application->find($commandName = 'foo1'); + } + + 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 and 1 more).'), + ); + } + + 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'); + } + + public function testFindCommandWithAmbiguousNamespacesButUniqueName() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \FoobarCommand()); + + $this->assertInstanceOf('FoobarCommand', $application->find('f:f')); + } + + public function testFindCommandWithMissingNamespace() + { + $application = new Application(); + $application->add(new \Foo4Command()); + + $this->assertInstanceOf('Foo4Command', $application->find('f::t')); + } + + /** + * @dataProvider provideInvalidCommandNamesSingle + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Did you mean this + */ + public function testFindAlternativeExceptionMessageSingle($name) + { + $application = new Application(); + $application->add(new \Foo3Command()); + $application->find($name); + } + + public function provideInvalidCommandNamesSingle() + { + return array( + array('foo3:baR'), + array('foO3: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'); + $this->assertRegExp('/foo1:bar/', $e->getMessage()); + $this->assertRegExp('/foo:bar/', $e->getMessage()); + } + + // 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'); + $this->assertRegExp('/foo1/', $e->getMessage()); + } + + $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'); + } + + // Test if "bar1" command throw an "\InvalidArgumentException" and does not contain + // "foo:bar" as alternative because "bar1" is too far from "foo:bar" + try { + $application->find($commandName = 'bar1'); + $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('/afoobar1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "afoobar1"'); + $this->assertRegExp('/foo:bar1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar1"'); + $this->assertNotRegExp('/foo:bar(?>!1)/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, without "foo:bar" alternative'); + } + } + + public function testFindAlternativeCommandsWithAnAlias() + { + $fooCommand = new \FooCommand(); + $fooCommand->setAliases(array('foo2')); + + $application = new Application(); + $application->add($fooCommand); + + $result = $application->find('foo'); + + $this->assertSame($fooCommand, $result); + } + + 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')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Command "foo::bar" is not defined. + */ + public function testFindWithDoubleColonInNameThrowsException() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo4Command()); + $application->find('foo::bar'); + } + + 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'); + } + } + + /** + * @group legacy + */ + public function testLegacyAsText() + { + $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'); + } + + /** + * @group legacy + */ + public function testLegacyAsXml() + { + $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'); + + $tester->run(array('command' => 'foo3:bar'), array('decorated' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3decorated.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 testRenderExceptionWithDoubleWidthCharacters() + { + if (!function_exists('mb_strwidth')) { + $this->markTestSkipped('The "mb_strwidth" function is not available'); + } + + $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth')); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(120)); + $application->register('foo')->setCode(function () { + throw new \Exception('エラーメッセージ'); + }); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $tester->run(array('command' => 'foo'), array('decorated' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1decorated.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)); + $application->register('foo')->setCode(function () { + throw new \Exception('コマンドの実行中にエラーが発生しました。'); + }); + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth2.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->assertInstanceOf('Symfony\Component\Console\Input\ArgvInput', $command->input, '->run() creates an ArgvInput by default if none is given'); + $this->assertInstanceOf('Symfony\Component\Console\Output\ConsoleOutput', $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'); + } + + /** + * Issue #9285. + * + * If the "verbose" option is just before an argument in ArgvInput, + * an argument value should not be treated as verbosity value. + * This test will fail with "Not enough arguments." if broken + */ + public function testVerboseValueNotBreakArguments() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add(new \FooCommand()); + + $output = new StreamOutput(fopen('php://memory', 'w', false)); + + $input = new ArgvInput(array('cli.php', '-v', 'foo:bar')); + $application->run($input, $output); + + $input = new ArgvInput(array('cli.php', '--verbose', 'foo:bar')); + $application->run($input, $output); + } + + 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() + { + $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.'.PHP_EOL, $tester->getDisplay()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage caught + */ + public function testRunWithExceptionAndDispatcher() + { + $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() + { + $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.caught.after.', $tester->getDisplay()); + } + + public function testRunWithDispatcherSkippingCommand() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher(true)); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $exitCode = $tester->run(array('command' => 'foo')); + $this->assertContains('before.after.', $tester->getDisplay()); + $this->assertEquals(ConsoleCommandEvent::RETURN_CODE_DISABLED, $exitCode); + } + + public function testTerminalDimensions() + { + $application = new Application(); + $originalDimensions = $application->getTerminalDimensions(); + $this->assertCount(2, $originalDimensions); + + $width = 80; + if ($originalDimensions[0] == $width) { + $width = 100; + } + + $application->setTerminalDimensions($width, 80); + $this->assertSame(array($width, 80), $application->getTerminalDimensions()); + } + + protected function getDispatcher($skipCommand = false) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) use ($skipCommand) { + $event->getOutput()->write('before.'); + + if ($skipCommand) { + $event->disableCommand(); + } + }); + $dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) use ($skipCommand) { + $event->getOutput()->writeln('after.'); + + if (!$skipCommand) { + $event->setExitCode(113); + } + }); + $dispatcher->addListener('console.exception', function (ConsoleExceptionEvent $event) { + $event->getOutput()->write('caught.'); + + $event->setException(new \LogicException('caught.', $event->getExitCode(), $event->getException())); + }); + + return $dispatcher; + } + + public function testSetRunCustomDefaultCommand() + { + $command = new \FooCommand(); + + $application = new Application(); + $application->setAutoExit(false); + $application->add($command); + $application->setDefaultCommand($command->getName()); + + $tester = new ApplicationTester($application); + $tester->run(array()); + $this->assertEquals('interact called'.PHP_EOL.'called'.PHP_EOL, $tester->getDisplay(), 'Application runs the default set command if different from \'list\' command'); + + $application = new CustomDefaultCommandApplication(); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array()); + + $this->assertEquals('interact called'.PHP_EOL.'called'.PHP_EOL, $tester->getDisplay(), 'Application runs the default set command if different from \'list\' command'); + } + + public function testCanCheckIfTerminalIsInteractive() + { + if (!function_exists('posix_isatty')) { + $this->markTestSkipped('posix_isatty function is required'); + } + + $application = new CustomDefaultCommandApplication(); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'help')); + + $this->assertFalse($tester->getInput()->hasParameterOption(array('--no-interaction', '-n'))); + + $inputStream = $application->getHelperSet()->get('question')->getInputStream(); + $this->assertEquals($tester->getInput()->isInteractive(), @posix_isatty($inputStream)); + } +} + +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())); + } +} + +class CustomDefaultCommandApplication extends Application +{ + /** + * Overwrites the constructor in order to set a different default command. + */ + public function __construct() + { + parent::__construct(); + + $command = new \FooCommand(); + $this->add($command); + $this->setDefaultCommand($command->getName()); + } +} diff --git a/vendor/symfony/console/Tests/ClockMock.php b/vendor/symfony/console/Tests/ClockMock.php new file mode 100644 index 00000000..0e923166 --- /dev/null +++ b/vendor/symfony/console/Tests/ClockMock.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\Console\Helper; + +use Symfony\Component\Console\Tests; + +function time() +{ + return Tests\time(); +} + +namespace Symfony\Component\Console\Tests; + +function with_clock_mock($enable = null) +{ + static $enabled; + + if (null === $enable) { + return $enabled; + } + + $enabled = $enable; +} + +function time() +{ + if (!with_clock_mock()) { + return \time(); + } + + return $_SERVER['REQUEST_TIME']; +} diff --git a/vendor/symfony/console/Tests/Command/CommandTest.php b/vendor/symfony/console/Tests/Command/CommandTest.php new file mode 100644 index 00000000..7ab993bb --- /dev/null +++ b/vendor/symfony/console/Tests/Command/CommandTest.php @@ -0,0 +1,337 @@ + + * + * 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 defined in "Symfony\Component\Console\Command\Command" cannot have an empty name. + */ + 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'); + $command->setHelp(''); + $this->assertEquals('description', $command->getHelp(), '->getHelp() fallback to the description'); + } + + 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('bar'); + $this->assertEquals('namespace:name [--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 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 command 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 testExecuteMethodNeedsToBeOverridden() + { + $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...'); + } + + /** + * @group legacy + */ + public function testLegacyAsText() + { + $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'); + } + + /** + * @group legacy + */ + public function testLegacyAsXml() + { + $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/Tests/Command/HelpCommandTest.php b/vendor/symfony/console/Tests/Command/HelpCommandTest.php new file mode 100644 index 00000000..9e068587 --- /dev/null +++ b/vendor/symfony/console/Tests/Command/HelpCommandTest.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\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'), array('decorated' => false)); + $this->assertContains('list [options] [--] []', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias'); + $this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias'); + $this->assertContains('raw', $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(), array('decorated' => false)); + $this->assertContains('list [options] [--] []', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('raw', $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->assertContains('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->assertContains('list [options] [--] []', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('raw', $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->assertContains('list [--xml] [--raw] [--format FORMAT] [--] [<namespace>]', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('getDisplay(), '->execute() returns an XML help text if --format=xml is passed'); + } +} diff --git a/vendor/symfony/console/Tests/Command/ListCommandTest.php b/vendor/symfony/console/Tests/Command/ListCommandTest.php new file mode 100644 index 00000000..3578d488 --- /dev/null +++ b/vendor/symfony/console/Tests/Command/ListCommandTest.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\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\s{2,}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/Tests/Descriptor/AbstractDescriptorTest.php b/vendor/symfony/console/Tests/Descriptor/AbstractDescriptorTest.php new file mode 100644 index 00000000..f582e7f5 --- /dev/null +++ b/vendor/symfony/console/Tests/Descriptor/AbstractDescriptorTest.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\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; +use Symfony\Component\Console\Output\BufferedOutput; + +abstract class AbstractDescriptorTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getDescribeInputArgumentTestData */ + public function testDescribeInputArgument(InputArgument $argument, $expectedDescription) + { + $this->assertDescription($expectedDescription, $argument); + } + + /** @dataProvider getDescribeInputOptionTestData */ + public function testDescribeInputOption(InputOption $option, $expectedDescription) + { + $this->assertDescription($expectedDescription, $option); + } + + /** @dataProvider getDescribeInputDefinitionTestData */ + public function testDescribeInputDefinition(InputDefinition $definition, $expectedDescription) + { + $this->assertDescription($expectedDescription, $definition); + } + + /** @dataProvider getDescribeCommandTestData */ + public function testDescribeCommand(Command $command, $expectedDescription) + { + $this->assertDescription($expectedDescription, $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->assertDescription($expectedDescription, $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; + } + + protected function assertDescription($expectedDescription, $describedObject) + { + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $this->getDescriptor()->describe($output, $describedObject, array('raw_output' => true)); + $this->assertEquals(trim($expectedDescription), trim(str_replace(PHP_EOL, "\n", $output->fetch()))); + } +} diff --git a/vendor/symfony/console/Tests/Descriptor/JsonDescriptorTest.php b/vendor/symfony/console/Tests/Descriptor/JsonDescriptorTest.php new file mode 100644 index 00000000..f9a15612 --- /dev/null +++ b/vendor/symfony/console/Tests/Descriptor/JsonDescriptorTest.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\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Output\BufferedOutput; + +class JsonDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new JsonDescriptor(); + } + + protected function getFormat() + { + return 'json'; + } + + protected function assertDescription($expectedDescription, $describedObject) + { + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $this->getDescriptor()->describe($output, $describedObject, array('raw_output' => true)); + $this->assertEquals(json_decode(trim($expectedDescription), true), json_decode(trim(str_replace(PHP_EOL, "\n", $output->fetch())), true)); + } +} diff --git a/vendor/symfony/console/Tests/Descriptor/MarkdownDescriptorTest.php b/vendor/symfony/console/Tests/Descriptor/MarkdownDescriptorTest.php new file mode 100644 index 00000000..c85e8a59 --- /dev/null +++ b/vendor/symfony/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/Tests/Descriptor/ObjectsProvider.php b/vendor/symfony/console/Tests/Descriptor/ObjectsProvider.php new file mode 100644 index 00000000..45b3b2ff --- /dev/null +++ b/vendor/symfony/console/Tests/Descriptor/ObjectsProvider.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\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'), + 'input_argument_4' => new InputArgument('argument_name', InputArgument::REQUIRED, "multiline\nargument description"), + ); + } + + 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()), + 'input_option_5' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, "multiline\noption description"), + 'input_option_6' => new InputOption('option_name', array('o', 'O'), InputOption::VALUE_REQUIRED, 'option with multiple shortcuts'), + ); + } + + 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/Tests/Descriptor/TextDescriptorTest.php b/vendor/symfony/console/Tests/Descriptor/TextDescriptorTest.php new file mode 100644 index 00000000..350b6795 --- /dev/null +++ b/vendor/symfony/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/Tests/Descriptor/XmlDescriptorTest.php b/vendor/symfony/console/Tests/Descriptor/XmlDescriptorTest.php new file mode 100644 index 00000000..59a5d1ed --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/BarBucCommand.php b/vendor/symfony/console/Tests/Fixtures/BarBucCommand.php new file mode 100644 index 00000000..52b619e8 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/BarBucCommand.php @@ -0,0 +1,11 @@ +setName('bar:buc'); + } +} diff --git a/vendor/symfony/console/Tests/Fixtures/DescriptorApplication1.php b/vendor/symfony/console/Tests/Fixtures/DescriptorApplication1.php new file mode 100644 index 00000000..132b6d57 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/DescriptorApplication2.php b/vendor/symfony/console/Tests/Fixtures/DescriptorApplication2.php new file mode 100644 index 00000000..ff551358 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/DescriptorCommand1.php b/vendor/symfony/console/Tests/Fixtures/DescriptorCommand1.php new file mode 100644 index 00000000..ede05d7a --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/DescriptorCommand2.php b/vendor/symfony/console/Tests/Fixtures/DescriptorCommand2.php new file mode 100644 index 00000000..51106b96 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/DescriptorCommand2.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\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') + ->addUsage('-o|--option_name ') + ->addUsage('') + ->addArgument('argument_name', InputArgument::REQUIRED) + ->addOption('option_name', 'o', InputOption::VALUE_NONE) + ; + } +} diff --git a/vendor/symfony/console/Tests/Fixtures/DummyOutput.php b/vendor/symfony/console/Tests/Fixtures/DummyOutput.php new file mode 100644 index 00000000..0070c0a4 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/DummyOutput.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\Console\Tests\Fixtures; + +use Symfony\Component\Console\Output\BufferedOutput; + +/** + * Dummy output. + * + * @author Kévin Dunglas + */ +class DummyOutput extends BufferedOutput +{ + /** + * @return array + */ + public function getLogs() + { + $logs = array(); + foreach (explode("\n", trim($this->fetch())) as $message) { + preg_match('/^\[(.*)\] (.*)/', $message, $matches); + $logs[] = sprintf('%s %s', $matches[1], $matches[2]); + } + + return $logs; + } +} diff --git a/vendor/symfony/console/Tests/Fixtures/Foo1Command.php b/vendor/symfony/console/Tests/Fixtures/Foo1Command.php new file mode 100644 index 00000000..254162f3 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/Foo2Command.php b/vendor/symfony/console/Tests/Fixtures/Foo2Command.php new file mode 100644 index 00000000..8071dc8f --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/Foo3Command.php b/vendor/symfony/console/Tests/Fixtures/Foo3Command.php new file mode 100644 index 00000000..6c890faf --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Foo3Command.php @@ -0,0 +1,29 @@ +setName('foo3:bar') + ->setDescription('The foo3:bar command') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + try { + throw new \Exception('First exception

    this is html

    '); + } catch (\Exception $e) { + throw new \Exception('Second exception comment', 0, $e); + } + } catch (\Exception $e) { + throw new \Exception('Third exception comment', 0, $e); + } + } +} diff --git a/vendor/symfony/console/Tests/Fixtures/Foo4Command.php b/vendor/symfony/console/Tests/Fixtures/Foo4Command.php new file mode 100644 index 00000000..1c546399 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Foo4Command.php @@ -0,0 +1,11 @@ +setName('foo3:bar:toh'); + } +} diff --git a/vendor/symfony/console/Tests/Fixtures/Foo5Command.php b/vendor/symfony/console/Tests/Fixtures/Foo5Command.php new file mode 100644 index 00000000..a1c60827 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Foo5Command.php @@ -0,0 +1,10 @@ +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/Tests/Fixtures/FooSubnamespaced1Command.php b/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced1Command.php new file mode 100644 index 00000000..fc50c72b --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced1Command.php @@ -0,0 +1,26 @@ +setName('foo:bar:baz') + ->setDescription('The foo:bar:baz command') + ->setAliases(array('foobarbaz')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + } +} diff --git a/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced2Command.php b/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced2Command.php new file mode 100644 index 00000000..1cf31ff1 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced2Command.php @@ -0,0 +1,26 @@ +setName('foo:go:bret') + ->setDescription('The foo:bar:go command') + ->setAliases(array('foobargo')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + } +} diff --git a/vendor/symfony/console/Tests/Fixtures/FoobarCommand.php b/vendor/symfony/console/Tests/Fixtures/FoobarCommand.php new file mode 100644 index 00000000..96816280 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/FoobarCommand.php @@ -0,0 +1,25 @@ +setName('foobar:foo') + ->setDescription('The foobar:foo command') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + } +} diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_0.php b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_0.php new file mode 100644 index 00000000..8fe7c077 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_0.php @@ -0,0 +1,11 @@ +caution('Lorem ipsum dolor sit amet'); +}; diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_1.php b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_1.php new file mode 100644 index 00000000..e5c700d6 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_1.php @@ -0,0 +1,13 @@ +title('Title'); + $output->warning('Lorem ipsum dolor sit amet'); + $output->title('Title'); +}; diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_2.php b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_2.php new file mode 100644 index 00000000..791b626f --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_2.php @@ -0,0 +1,16 @@ +warning('Warning'); + $output->caution('Caution'); + $output->error('Error'); + $output->success('Success'); + $output->note('Note'); + $output->block('Custom block', 'CUSTOM', 'fg=white;bg=green', 'X ', true); +}; diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_3.php b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_3.php new file mode 100644 index 00000000..99253a6c --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_3.php @@ -0,0 +1,12 @@ +title('First title'); + $output->title('Second title'); +}; diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4.php b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4.php new file mode 100644 index 00000000..0c5d3fb2 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4.php @@ -0,0 +1,34 @@ +write('Lorem ipsum dolor sit amet'); + $output->title('First title'); + + $output->writeln('Lorem ipsum dolor sit amet'); + $output->title('Second title'); + + $output->write('Lorem ipsum dolor sit amet'); + $output->write(''); + $output->title('Third title'); + + //Ensure edge case by appending empty strings to history: + $output->write('Lorem ipsum dolor sit amet'); + $output->write(array('', '', '')); + $output->title('Fourth title'); + + //Ensure have manual control over number of blank lines: + $output->writeln('Lorem ipsum dolor sit amet'); + $output->writeln(array('', '')); //Should append an extra blank line + $output->title('Fifth title'); + + $output->writeln('Lorem ipsum dolor sit amet'); + $output->newLine(2); //Should append an extra blank line + $output->title('Fifth title'); +}; diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_5.php b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_5.php new file mode 100644 index 00000000..4543ad8c --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_5.php @@ -0,0 +1,29 @@ +writeln('Lorem ipsum dolor sit amet'); + $output->listing(array( + 'Lorem ipsum dolor sit amet', + 'consectetur adipiscing elit', + )); + + //Even using write: + $output->write('Lorem ipsum dolor sit amet'); + $output->listing(array( + 'Lorem ipsum dolor sit amet', + 'consectetur adipiscing elit', + )); + + $output->write('Lorem ipsum dolor sit amet'); + $output->text(array( + 'Lorem ipsum dolor sit amet', + 'consectetur adipiscing elit', + )); +}; diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_6.php b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_6.php new file mode 100644 index 00000000..8031ec9c --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_6.php @@ -0,0 +1,16 @@ +listing(array( + 'Lorem ipsum dolor sit amet', + 'consectetur adipiscing elit', + )); + $output->success('Lorem ipsum dolor sit amet'); +}; diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_7.php b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_7.php new file mode 100644 index 00000000..203eb5b1 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_7.php @@ -0,0 +1,15 @@ +title('Title'); + $output->askHidden('Hidden question'); + $output->choice('Choice question with default', array('choice1', 'choice2'), 'choice1'); + $output->confirm('Confirmation with yes default', true); + $output->text('Duis aute irure dolor in reprehenderit in voluptate velit esse'); +}; diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_0.txt b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_0.txt new file mode 100644 index 00000000..a42e0f79 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_0.txt @@ -0,0 +1,3 @@ + + ! [CAUTION] Lorem ipsum dolor sit amet + diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_1.txt b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_1.txt new file mode 100644 index 00000000..334875f7 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_1.txt @@ -0,0 +1,9 @@ + +Title +===== + + [WARNING] Lorem ipsum dolor sit amet + +Title +===== + diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_2.txt b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_2.txt new file mode 100644 index 00000000..ca609760 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_2.txt @@ -0,0 +1,13 @@ + + [WARNING] Warning + + ! [CAUTION] Caution + + [ERROR] Error + + [OK] Success + + ! [NOTE] Note + +X [CUSTOM] Custom block + diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_3.txt b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_3.txt new file mode 100644 index 00000000..f4b6d582 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_3.txt @@ -0,0 +1,7 @@ + +First title +=========== + +Second title +============ + diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4.txt b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4.txt new file mode 100644 index 00000000..2646d858 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4.txt @@ -0,0 +1,32 @@ +Lorem ipsum dolor sit amet + +First title +=========== + +Lorem ipsum dolor sit amet + +Second title +============ + +Lorem ipsum dolor sit amet + +Third title +=========== + +Lorem ipsum dolor sit amet + +Fourth title +============ + +Lorem ipsum dolor sit amet + + +Fifth title +=========== + +Lorem ipsum dolor sit amet + + +Fifth title +=========== + diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_5.txt b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_5.txt new file mode 100644 index 00000000..910240fb --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_5.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet + * Lorem ipsum dolor sit amet + * consectetur adipiscing elit + +Lorem ipsum dolor sit amet + * Lorem ipsum dolor sit amet + * consectetur adipiscing elit + +Lorem ipsum dolor sit amet + // Lorem ipsum dolor sit amet + // consectetur adipiscing elit diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt new file mode 100644 index 00000000..5f2d33c1 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt @@ -0,0 +1,6 @@ + + * Lorem ipsum dolor sit amet + * consectetur adipiscing elit + + [OK] Lorem ipsum dolor sit amet + diff --git a/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7.txt b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7.txt new file mode 100644 index 00000000..ab18e5dc --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7.txt @@ -0,0 +1,5 @@ + +Title +===== + + // Duis aute irure dolor in reprehenderit in voluptate velit esse diff --git a/vendor/symfony/console/Tests/Fixtures/TestCommand.php b/vendor/symfony/console/Tests/Fixtures/TestCommand.php new file mode 100644 index 00000000..dcd32739 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/application_1.json b/vendor/symfony/console/Tests/Fixtures/application_1.json new file mode 100644 index 00000000..b17b38d8 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_1.json @@ -0,0 +1 @@ +{"commands":[{"name":"help","usage":["help [--xml] [--format FORMAT] [--raw] [--] []"],"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.","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":"The output format (txt, xml, json, or md)","default":"txt"},"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 FORMAT] [--] []"],"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>","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":"The output format (txt, xml, json, or md)","default":"txt"}}}}],"namespaces":[{"id":"_global","commands":["help","list"]}]} diff --git a/vendor/symfony/console/Tests/Fixtures/application_1.md b/vendor/symfony/console/Tests/Fixtures/application_1.md new file mode 100644 index 00000000..82a605da --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_1.md @@ -0,0 +1,201 @@ +UNKNOWN +======= + +* help +* list + +help +---- + +* Description: Displays help for a command +* Usage: + + * `help [--xml] [--format FORMAT] [--raw] [--] []` + +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: The output format (txt, xml, json, or md) +* Default: `'txt'` + +**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 FORMAT] [--] []` + +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: The output format (txt, xml, json, or md) +* Default: `'txt'` diff --git a/vendor/symfony/console/Tests/Fixtures/application_1.txt b/vendor/symfony/console/Tests/Fixtures/application_1.txt new file mode 100644 index 00000000..c4cf8f21 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_1.txt @@ -0,0 +1,17 @@ +Console Tool + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + help Displays help for a command + list Lists commands diff --git a/vendor/symfony/console/Tests/Fixtures/application_1.xml b/vendor/symfony/console/Tests/Fixtures/application_1.xml new file mode 100644 index 00000000..35d1db4d --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_1.xml @@ -0,0 +1,110 @@ + + + + + + help [--xml] [--format 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 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/Tests/Fixtures/application_2.json b/vendor/symfony/console/Tests/Fixtures/application_2.json new file mode 100644 index 00000000..e47a7a96 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_2.json @@ -0,0 +1 @@ +{"commands":[{"name":"help","usage":["help [--xml] [--format FORMAT] [--raw] [--] []"],"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.","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":"The output format (txt, xml, json, or md)","default":"txt"},"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 FORMAT] [--] []"],"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>","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":"The output format (txt, xml, json, or md)","default":"txt"}}}},{"name":"descriptor:command1","usage":["descriptor:command1", "alias1", "alias2"],"description":"command 1 description","help":"command 1 help","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] [--] ", "descriptor:command2 -o|--option_name ", "descriptor:command2 "],"description":"command 2 description","help":"command 2 help","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"]}]} \ No newline at end of file diff --git a/vendor/symfony/console/Tests/Fixtures/application_2.md b/vendor/symfony/console/Tests/Fixtures/application_2.md new file mode 100644 index 00000000..f031c9e5 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_2.md @@ -0,0 +1,396 @@ +My Symfony application +====================== + +* alias1 +* alias2 +* help +* list + +**descriptor:** + +* descriptor:command1 +* descriptor:command2 + +help +---- + +* Description: Displays help for a command +* Usage: + + * `help [--xml] [--format FORMAT] [--raw] [--] []` + +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: The output format (txt, xml, json, or md) +* Default: `'txt'` + +**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 FORMAT] [--] []` + +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: The output format (txt, xml, json, or md) +* Default: `'txt'` + +descriptor:command1 +------------------- + +* Description: command 1 description +* Usage: + + * `descriptor:command1` + * `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] [--] ` + * `descriptor:command2 -o|--option_name ` + * `descriptor:command2 ` + +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/Tests/Fixtures/application_2.txt b/vendor/symfony/console/Tests/Fixtures/application_2.txt new file mode 100644 index 00000000..292aa829 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_2.txt @@ -0,0 +1,22 @@ +My Symfony application version v1.0 + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +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/Tests/Fixtures/application_2.xml b/vendor/symfony/console/Tests/Fixtures/application_2.xml new file mode 100644 index 00000000..bc8ab219 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_2.xml @@ -0,0 +1,190 @@ + + + + + + help [--xml] [--format 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 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 + alias1 + alias2 + + command 1 description + command 1 help + + + + + + + + + + + + + + descriptor:command2 [-o|--option_name] [--] <argument_name> + descriptor:command2 -o|--option_name <argument_name> + descriptor:command2 <argument_name> + + command 2 description + command 2 help + + + + + + + + + + + + + + + + + + + + + alias1 + alias2 + help + list + + + descriptor:command1 + descriptor:command2 + + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_astext1.txt b/vendor/symfony/console/Tests/Fixtures/application_astext1.txt new file mode 100644 index 00000000..19dacb23 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_astext1.txt @@ -0,0 +1,20 @@ +Console Tool + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + afoobar The foo:bar command + help Displays help for a command + list Lists commands + foo + foo:bar The foo:bar command diff --git a/vendor/symfony/console/Tests/Fixtures/application_astext2.txt b/vendor/symfony/console/Tests/Fixtures/application_astext2.txt new file mode 100644 index 00000000..c99ccdda --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_astext2.txt @@ -0,0 +1,16 @@ +Console Tool + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands for the "foo" namespace: + foo:bar The foo:bar command diff --git a/vendor/symfony/console/Tests/Fixtures/application_asxml1.txt b/vendor/symfony/console/Tests/Fixtures/application_asxml1.txt new file mode 100644 index 00000000..8277d9e6 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_asxml1.txt @@ -0,0 +1,146 @@ + + + + + + help [--xml] [--format 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 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 + afoobar + + The foo:bar command + The foo:bar command + + + + + + + + + + + + + + + afoobar + help + list + + + foo:bar + + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_asxml2.txt b/vendor/symfony/console/Tests/Fixtures/application_asxml2.txt new file mode 100644 index 00000000..93d6d4e9 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_asxml2.txt @@ -0,0 +1,37 @@ + + + + + + foo:bar + afoobar + + The foo:bar command + The foo:bar command + + + + + + + + + + + + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_gethelp.txt b/vendor/symfony/console/Tests/Fixtures/application_gethelp.txt new file mode 100644 index 00000000..0c16e3c8 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_gethelp.txt @@ -0,0 +1 @@ +Console Tool \ No newline at end of file diff --git a/vendor/symfony/console/Tests/Fixtures/application_renderexception1.txt b/vendor/symfony/console/Tests/Fixtures/application_renderexception1.txt new file mode 100644 index 00000000..4629345c --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_renderexception1.txt @@ -0,0 +1,8 @@ + + + + [InvalidArgumentException] + Command "foo" is not defined. + + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_renderexception2.txt b/vendor/symfony/console/Tests/Fixtures/application_renderexception2.txt new file mode 100644 index 00000000..3d9d363b --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_renderexception2.txt @@ -0,0 +1,11 @@ + + + + [InvalidArgumentException] + The "--foo" option does not exist. + + + +list [--xml] [--raw] [--format FORMAT] [--] [] + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_renderexception3.txt b/vendor/symfony/console/Tests/Fixtures/application_renderexception3.txt new file mode 100644 index 00000000..72a72867 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_renderexception3.txt @@ -0,0 +1,27 @@ + + + + [Exception] + Third exception comment + + + + + + + [Exception] + Second exception comment + + + + + + + [Exception] + First exception

    this is html

    + + + +foo3:bar + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_renderexception3decorated.txt b/vendor/symfony/console/Tests/Fixtures/application_renderexception3decorated.txt new file mode 100644 index 00000000..b44d50b0 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_renderexception3decorated.txt @@ -0,0 +1,27 @@ + + +  + [Exception]  + Third exception comment  +  + + + + +  + [Exception]  + Second exception comment  +  + + + + +  + [Exception]  + First exception 

    this is html

      +  + + +foo3:bar + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_renderexception4.txt b/vendor/symfony/console/Tests/Fixtures/application_renderexception4.txt new file mode 100644 index 00000000..19f893b0 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_renderexception4.txt @@ -0,0 +1,9 @@ + + + + [InvalidArgumentException] + Command "foo" is not define + d. + + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1.txt b/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1.txt new file mode 100644 index 00000000..6a986603 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1.txt @@ -0,0 +1,11 @@ + + + + [Exception] + エラーメッセージ + + + +foo + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt b/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt new file mode 100644 index 00000000..8c8801b1 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt @@ -0,0 +1,11 @@ + + +  + [Exception]  + エラーメッセージ  +  + + +foo + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth2.txt b/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth2.txt new file mode 100644 index 00000000..545cd7b0 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_renderexception_doublewidth2.txt @@ -0,0 +1,12 @@ + + + + [Exception] + コマンドの実行中にエラーが + 発生しました。 + + + +foo + + diff --git a/vendor/symfony/console/Tests/Fixtures/application_run1.txt b/vendor/symfony/console/Tests/Fixtures/application_run1.txt new file mode 100644 index 00000000..0dc27309 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_run1.txt @@ -0,0 +1,17 @@ +Console Tool + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + help Displays help for a command + list Lists commands diff --git a/vendor/symfony/console/Tests/Fixtures/application_run2.txt b/vendor/symfony/console/Tests/Fixtures/application_run2.txt new file mode 100644 index 00000000..d28b928e --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_run2.txt @@ -0,0 +1,29 @@ +Usage: + help [options] [--] [] + +Arguments: + command The command to execute + command_name The command name [default: "help"] + +Options: + --xml To output help as XML + --format=FORMAT The output format (txt, xml, json, or md) [default: "txt"] + --raw To output raw command help + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +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/Tests/Fixtures/application_run3.txt b/vendor/symfony/console/Tests/Fixtures/application_run3.txt new file mode 100644 index 00000000..bc51995f --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_run3.txt @@ -0,0 +1,27 @@ +Usage: + list [options] [--] [] + +Arguments: + namespace The namespace name + +Options: + --xml To output list as XML + --raw To output raw command list + --format=FORMAT The output format (txt, xml, json, or md) [default: "txt"] + +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/Tests/Fixtures/application_run4.txt b/vendor/symfony/console/Tests/Fixtures/application_run4.txt new file mode 100644 index 00000000..47187fc2 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/application_run4.txt @@ -0,0 +1 @@ +Console Tool diff --git a/vendor/symfony/console/Tests/Fixtures/command_1.json b/vendor/symfony/console/Tests/Fixtures/command_1.json new file mode 100644 index 00000000..20f310b4 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_1.json @@ -0,0 +1 @@ +{"name":"descriptor:command1","usage":["descriptor:command1", "alias1", "alias2"],"description":"command 1 description","help":"command 1 help","definition":{"arguments":[],"options":[]}} diff --git a/vendor/symfony/console/Tests/Fixtures/command_1.md b/vendor/symfony/console/Tests/Fixtures/command_1.md new file mode 100644 index 00000000..34ed3ea7 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_1.md @@ -0,0 +1,11 @@ +descriptor:command1 +------------------- + +* Description: command 1 description +* Usage: + + * `descriptor:command1` + * `alias1` + * `alias2` + +command 1 help diff --git a/vendor/symfony/console/Tests/Fixtures/command_1.txt b/vendor/symfony/console/Tests/Fixtures/command_1.txt new file mode 100644 index 00000000..28e14a05 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_1.txt @@ -0,0 +1,7 @@ +Usage: + descriptor:command1 + alias1 + alias2 + +Help: + command 1 help diff --git a/vendor/symfony/console/Tests/Fixtures/command_1.xml b/vendor/symfony/console/Tests/Fixtures/command_1.xml new file mode 100644 index 00000000..838b9bd9 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_1.xml @@ -0,0 +1,12 @@ + + + + descriptor:command1 + alias1 + alias2 + + command 1 description + command 1 help + + + diff --git a/vendor/symfony/console/Tests/Fixtures/command_2.json b/vendor/symfony/console/Tests/Fixtures/command_2.json new file mode 100644 index 00000000..38edd1e2 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_2.json @@ -0,0 +1 @@ +{"name":"descriptor:command2","usage":["descriptor:command2 [-o|--option_name] [--] ", "descriptor:command2 -o|--option_name ", "descriptor:command2 "],"description":"command 2 description","help":"command 2 help","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/Tests/Fixtures/command_2.md b/vendor/symfony/console/Tests/Fixtures/command_2.md new file mode 100644 index 00000000..6f538b64 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_2.md @@ -0,0 +1,33 @@ +descriptor:command2 +------------------- + +* Description: command 2 description +* Usage: + + * `descriptor:command2 [-o|--option_name] [--] ` + * `descriptor:command2 -o|--option_name ` + * `descriptor:command2 ` + +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/Tests/Fixtures/command_2.txt b/vendor/symfony/console/Tests/Fixtures/command_2.txt new file mode 100644 index 00000000..72f7ce05 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_2.txt @@ -0,0 +1,13 @@ +Usage: + descriptor:command2 [options] [--] + descriptor:command2 -o|--option_name + descriptor:command2 + +Arguments: + argument_name + +Options: + -o, --option_name + +Help: + command 2 help diff --git a/vendor/symfony/console/Tests/Fixtures/command_2.xml b/vendor/symfony/console/Tests/Fixtures/command_2.xml new file mode 100644 index 00000000..67364caa --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_2.xml @@ -0,0 +1,21 @@ + + + + descriptor:command2 [-o|--option_name] [--] <argument_name> + descriptor:command2 -o|--option_name <argument_name> + descriptor:command2 <argument_name> + + command 2 description + command 2 help + + + + + + + + + + diff --git a/vendor/symfony/console/Tests/Fixtures/command_astext.txt b/vendor/symfony/console/Tests/Fixtures/command_astext.txt new file mode 100644 index 00000000..7e206388 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_astext.txt @@ -0,0 +1,18 @@ +Usage: + namespace:name + name + +Arguments: + command The command to execute + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Help: + help diff --git a/vendor/symfony/console/Tests/Fixtures/command_asxml.txt b/vendor/symfony/console/Tests/Fixtures/command_asxml.txt new file mode 100644 index 00000000..5e776238 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/command_asxml.txt @@ -0,0 +1,38 @@ + + + + namespace:name + name + + description + help + + + The command to execute + + + + + + + + + + + + + diff --git a/vendor/symfony/console/Tests/Fixtures/definition_astext.txt b/vendor/symfony/console/Tests/Fixtures/definition_astext.txt new file mode 100644 index 00000000..0431c072 --- /dev/null +++ b/vendor/symfony/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: + -f, --foo=FOO The foo option + --baz[=BAZ] The baz option [default: false] + -b, --bar[=BAR] The bar option [default: "bar"] + --qux[=QUX] The qux option [default: ["http://foo.com/","bar"]] (multiple values allowed) + --qux2[=QUX2] The qux2 option [default: {"foo":"bar"}] (multiple values allowed) \ No newline at end of file diff --git a/vendor/symfony/console/Tests/Fixtures/definition_asxml.txt b/vendor/symfony/console/Tests/Fixtures/definition_asxml.txt new file mode 100644 index 00000000..eec8c079 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_argument_1.json b/vendor/symfony/console/Tests/Fixtures/input_argument_1.json new file mode 100644 index 00000000..b8173b6b --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_argument_1.md b/vendor/symfony/console/Tests/Fixtures/input_argument_1.md new file mode 100644 index 00000000..88f311ab --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_argument_1.txt b/vendor/symfony/console/Tests/Fixtures/input_argument_1.txt new file mode 100644 index 00000000..55035183 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_1.txt @@ -0,0 +1 @@ + argument_name diff --git a/vendor/symfony/console/Tests/Fixtures/input_argument_1.xml b/vendor/symfony/console/Tests/Fixtures/input_argument_1.xml new file mode 100644 index 00000000..cb37f812 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_1.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_argument_2.json b/vendor/symfony/console/Tests/Fixtures/input_argument_2.json new file mode 100644 index 00000000..ef06b09a --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_argument_2.md b/vendor/symfony/console/Tests/Fixtures/input_argument_2.md new file mode 100644 index 00000000..3cdb00cc --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_argument_2.txt b/vendor/symfony/console/Tests/Fixtures/input_argument_2.txt new file mode 100644 index 00000000..e7136607 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_2.txt @@ -0,0 +1 @@ + argument_name argument description diff --git a/vendor/symfony/console/Tests/Fixtures/input_argument_2.xml b/vendor/symfony/console/Tests/Fixtures/input_argument_2.xml new file mode 100644 index 00000000..629da5a9 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_2.xml @@ -0,0 +1,5 @@ + + + argument description + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_argument_3.json b/vendor/symfony/console/Tests/Fixtures/input_argument_3.json new file mode 100644 index 00000000..de8484e6 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_argument_3.md b/vendor/symfony/console/Tests/Fixtures/input_argument_3.md new file mode 100644 index 00000000..be1c443a --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_argument_3.txt b/vendor/symfony/console/Tests/Fixtures/input_argument_3.txt new file mode 100644 index 00000000..6b76639e --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_3.txt @@ -0,0 +1 @@ + argument_name argument description [default: "default_value"] diff --git a/vendor/symfony/console/Tests/Fixtures/input_argument_3.xml b/vendor/symfony/console/Tests/Fixtures/input_argument_3.xml new file mode 100644 index 00000000..399a5c86 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_3.xml @@ -0,0 +1,7 @@ + + + argument description + + default_value + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_argument_4.json b/vendor/symfony/console/Tests/Fixtures/input_argument_4.json new file mode 100644 index 00000000..8067a4d1 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_4.json @@ -0,0 +1 @@ +{"name":"argument_name","is_required":true,"is_array":false,"description":"multiline argument description","default":null} diff --git a/vendor/symfony/console/Tests/Fixtures/input_argument_4.md b/vendor/symfony/console/Tests/Fixtures/input_argument_4.md new file mode 100644 index 00000000..f026ab37 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_4.md @@ -0,0 +1,8 @@ +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: multiline + argument description +* Default: `NULL` diff --git a/vendor/symfony/console/Tests/Fixtures/input_argument_4.txt b/vendor/symfony/console/Tests/Fixtures/input_argument_4.txt new file mode 100644 index 00000000..aa74e8ce --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_4.txt @@ -0,0 +1,2 @@ + argument_name multiline + argument description diff --git a/vendor/symfony/console/Tests/Fixtures/input_argument_4.xml b/vendor/symfony/console/Tests/Fixtures/input_argument_4.xml new file mode 100644 index 00000000..5ca135ec --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_argument_4.xml @@ -0,0 +1,6 @@ + + + multiline +argument description + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_1.json b/vendor/symfony/console/Tests/Fixtures/input_definition_1.json new file mode 100644 index 00000000..c7a7d838 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_definition_1.json @@ -0,0 +1 @@ +{"arguments":[],"options":[]} diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_1.md b/vendor/symfony/console/Tests/Fixtures/input_definition_1.md new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_1.txt b/vendor/symfony/console/Tests/Fixtures/input_definition_1.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_1.xml b/vendor/symfony/console/Tests/Fixtures/input_definition_1.xml new file mode 100644 index 00000000..b5481ce1 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_definition_1.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_2.json b/vendor/symfony/console/Tests/Fixtures/input_definition_2.json new file mode 100644 index 00000000..9964a55a --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_definition_2.md b/vendor/symfony/console/Tests/Fixtures/input_definition_2.md new file mode 100644 index 00000000..923191cd --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_definition_2.txt b/vendor/symfony/console/Tests/Fixtures/input_definition_2.txt new file mode 100644 index 00000000..73b0f308 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_definition_2.txt @@ -0,0 +1,2 @@ +Arguments: + argument_name diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_2.xml b/vendor/symfony/console/Tests/Fixtures/input_definition_2.xml new file mode 100644 index 00000000..102efc14 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_definition_2.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_3.json b/vendor/symfony/console/Tests/Fixtures/input_definition_3.json new file mode 100644 index 00000000..6a860560 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_definition_3.md b/vendor/symfony/console/Tests/Fixtures/input_definition_3.md new file mode 100644 index 00000000..40fd7b0a --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_definition_3.txt b/vendor/symfony/console/Tests/Fixtures/input_definition_3.txt new file mode 100644 index 00000000..c02766fd --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_definition_3.txt @@ -0,0 +1,2 @@ +Options: + -o, --option_name diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_3.xml b/vendor/symfony/console/Tests/Fixtures/input_definition_3.xml new file mode 100644 index 00000000..bc951515 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_definition_3.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_4.json b/vendor/symfony/console/Tests/Fixtures/input_definition_4.json new file mode 100644 index 00000000..c5a0019f --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_definition_4.md b/vendor/symfony/console/Tests/Fixtures/input_definition_4.md new file mode 100644 index 00000000..a31feea4 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_definition_4.txt b/vendor/symfony/console/Tests/Fixtures/input_definition_4.txt new file mode 100644 index 00000000..63aa81d2 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_definition_4.txt @@ -0,0 +1,5 @@ +Arguments: + argument_name + +Options: + -o, --option_name diff --git a/vendor/symfony/console/Tests/Fixtures/input_definition_4.xml b/vendor/symfony/console/Tests/Fixtures/input_definition_4.xml new file mode 100644 index 00000000..cffceece --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_definition_4.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_1.json b/vendor/symfony/console/Tests/Fixtures/input_option_1.json new file mode 100644 index 00000000..60c5b56c --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_option_1.md b/vendor/symfony/console/Tests/Fixtures/input_option_1.md new file mode 100644 index 00000000..6f9e9a7e --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_option_1.txt b/vendor/symfony/console/Tests/Fixtures/input_option_1.txt new file mode 100644 index 00000000..3a5e4eed --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_1.txt @@ -0,0 +1 @@ + -o, --option_name diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_1.xml b/vendor/symfony/console/Tests/Fixtures/input_option_1.xml new file mode 100644 index 00000000..8a64ea65 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_1.xml @@ -0,0 +1,4 @@ + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_2.json b/vendor/symfony/console/Tests/Fixtures/input_option_2.json new file mode 100644 index 00000000..04e4228e --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_option_2.md b/vendor/symfony/console/Tests/Fixtures/input_option_2.md new file mode 100644 index 00000000..634ac0b0 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_option_2.txt b/vendor/symfony/console/Tests/Fixtures/input_option_2.txt new file mode 100644 index 00000000..1009eff1 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_2.txt @@ -0,0 +1 @@ + -o, --option_name[=OPTION_NAME] option description [default: "default_value"] diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_2.xml b/vendor/symfony/console/Tests/Fixtures/input_option_2.xml new file mode 100644 index 00000000..4afac5b0 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_2.xml @@ -0,0 +1,7 @@ + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_3.json b/vendor/symfony/console/Tests/Fixtures/input_option_3.json new file mode 100644 index 00000000..c1ea120c --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_option_3.md b/vendor/symfony/console/Tests/Fixtures/input_option_3.md new file mode 100644 index 00000000..34282896 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_option_3.txt b/vendor/symfony/console/Tests/Fixtures/input_option_3.txt new file mode 100644 index 00000000..947bb652 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_3.txt @@ -0,0 +1 @@ + -o, --option_name=OPTION_NAME option description diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_3.xml b/vendor/symfony/console/Tests/Fixtures/input_option_3.xml new file mode 100644 index 00000000..dcc0631c --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_3.xml @@ -0,0 +1,5 @@ + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_4.json b/vendor/symfony/console/Tests/Fixtures/input_option_4.json new file mode 100644 index 00000000..1b671d80 --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_option_4.md b/vendor/symfony/console/Tests/Fixtures/input_option_4.md new file mode 100644 index 00000000..8ffba56e --- /dev/null +++ b/vendor/symfony/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/Tests/Fixtures/input_option_4.txt b/vendor/symfony/console/Tests/Fixtures/input_option_4.txt new file mode 100644 index 00000000..27edf77b --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_4.txt @@ -0,0 +1 @@ + -o, --option_name[=OPTION_NAME] option description (multiple values allowed) diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_4.xml b/vendor/symfony/console/Tests/Fixtures/input_option_4.xml new file mode 100644 index 00000000..5e2418b1 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_4.xml @@ -0,0 +1,5 @@ + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_5.json b/vendor/symfony/console/Tests/Fixtures/input_option_5.json new file mode 100644 index 00000000..35a1405f --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_5.json @@ -0,0 +1 @@ +{"name":"--option_name","shortcut":"-o","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"multiline option description","default":null} diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_5.md b/vendor/symfony/console/Tests/Fixtures/input_option_5.md new file mode 100644 index 00000000..82f51cad --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_5.md @@ -0,0 +1,10 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: multiline + option description +* Default: `NULL` diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_5.txt b/vendor/symfony/console/Tests/Fixtures/input_option_5.txt new file mode 100644 index 00000000..4368883c --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_5.txt @@ -0,0 +1,2 @@ + -o, --option_name=OPTION_NAME multiline + option description diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_5.xml b/vendor/symfony/console/Tests/Fixtures/input_option_5.xml new file mode 100644 index 00000000..90040ccd --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_5.xml @@ -0,0 +1,6 @@ + + diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_6.json b/vendor/symfony/console/Tests/Fixtures/input_option_6.json new file mode 100644 index 00000000..d84e8721 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_6.json @@ -0,0 +1 @@ +{"name":"--option_name","shortcut":"-o|-O","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"option with multiple shortcuts","default":null} diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_6.md b/vendor/symfony/console/Tests/Fixtures/input_option_6.md new file mode 100644 index 00000000..ed1ea1c8 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_6.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o|-O` +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: option with multiple shortcuts +* Default: `NULL` diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_6.txt b/vendor/symfony/console/Tests/Fixtures/input_option_6.txt new file mode 100644 index 00000000..0e6c9759 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_6.txt @@ -0,0 +1 @@ + -o|O, --option_name=OPTION_NAME option with multiple shortcuts diff --git a/vendor/symfony/console/Tests/Fixtures/input_option_6.xml b/vendor/symfony/console/Tests/Fixtures/input_option_6.xml new file mode 100644 index 00000000..06126a2f --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/input_option_6.xml @@ -0,0 +1,5 @@ + + diff --git a/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleStackTest.php b/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleStackTest.php new file mode 100644 index 00000000..774df268 --- /dev/null +++ b/vendor/symfony/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/Tests/Formatter/OutputFormatterStyleTest.php b/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.php new file mode 100644 index 00000000..0abfb3ce --- /dev/null +++ b/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.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\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[39;49;22;24m", $style->apply('foo')); + + $style = new OutputFormatterStyle('red', null, array('blink')); + $this->assertEquals("\033[31;5mfoo\033[39;25m", $style->apply('foo')); + + $style = new OutputFormatterStyle(null, 'white'); + $this->assertEquals("\033[47mfoo\033[49m", $style->apply('foo')); + } + + public function testForeground() + { + $style = new OutputFormatterStyle(); + + $style->setForeground('black'); + $this->assertEquals("\033[30mfoo\033[39m", $style->apply('foo')); + + $style->setForeground('blue'); + $this->assertEquals("\033[34mfoo\033[39m", $style->apply('foo')); + + $style->setForeground('default'); + $this->assertEquals("\033[39mfoo\033[39m", $style->apply('foo')); + + $this->setExpectedException('InvalidArgumentException'); + $style->setForeground('undefined-color'); + } + + public function testBackground() + { + $style = new OutputFormatterStyle(); + + $style->setBackground('black'); + $this->assertEquals("\033[40mfoo\033[49m", $style->apply('foo')); + + $style->setBackground('yellow'); + $this->assertEquals("\033[43mfoo\033[49m", $style->apply('foo')); + + $style->setBackground('default'); + $this->assertEquals("\033[49mfoo\033[49m", $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[27;28m", $style->apply('foo')); + + $style->setOption('bold'); + $this->assertEquals("\033[7;8;1mfoo\033[27;28;22m", $style->apply('foo')); + + $style->unsetOption('reverse'); + $this->assertEquals("\033[8;1mfoo\033[28;22m", $style->apply('foo')); + + $style->setOption('bold'); + $this->assertEquals("\033[8;1mfoo\033[28;22m", $style->apply('foo')); + + $style->setOptions(array('bold')); + $this->assertEquals("\033[1mfoo\033[22m", $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/Tests/Formatter/OutputFormatterTest.php b/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php new file mode 100644 index 00000000..510a4e74 --- /dev/null +++ b/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php @@ -0,0 +1,273 @@ + + * + * 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 OutputFormatterTest 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[39m", + $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[39;49m", + $formatter->format('some error') + ); + $this->assertEquals( + "\033[32msome info\033[39m", + $formatter->format('some info') + ); + $this->assertEquals( + "\033[33msome comment\033[39m", + $formatter->format('some comment') + ); + $this->assertEquals( + "\033[30;46msome question\033[39;49m", + $formatter->format('some question') + ); + } + + public function testNestedStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "\033[37;41msome \033[39;49m\033[32msome info\033[39m\033[37;41m error\033[39;49m", + $formatter->format('some some info error') + ); + } + + public function testAdjacentStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "\033[37;41msome error\033[39;49m\033[32msome info\033[39m", + $formatter->format('some errorsome info') + ); + } + + public function testStyleMatchingNotGreedy() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "(\033[32m>=2.0,<2.3\033[39m)", + $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,)') + ); + + $this->assertEquals( + "\033[32msome error\033[39m", + $formatter->format(''.$formatter->escape('some error').'') + ); + } + + public function testDeepNestedStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "\033[37;41merror\033[39;49m\033[32minfo\033[39m\033[33mcomment\033[39m\033[37;41merror\033[39;49m", + $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')); + + $style = new OutputFormatterStyle('blue', 'white'); + $formatter->setStyle('b', $style); + + $this->assertEquals("\033[34;47msome \033[39;49m\033[34;47mcustom\033[39;49m\033[34;47m msg\033[39;49m", $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[39;49m", $formatter->format('some custom msg')); + } + + public function testInlineStyle() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals("\033[34;41msome text\033[39;49m", $formatter->format('some text')); + $this->assertEquals("\033[34;41msome text\033[39;49m", $formatter->format('some text')); + } + + public function testNonStyleTag() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals("\033[32msome \033[39m\033[32m\033[39m\033[32m \033[39m\033[32m\033[39m\033[32m styled \033[39m\033[32m

    \033[39m\033[32msingle-char tag\033[39m\033[32m

    \033[39m", $formatter->format('some styled

    single-char tag

    ')); + } + + public function testFormatLongString() + { + $formatter = new OutputFormatter(true); + $long = str_repeat('\\', 14000); + $this->assertEquals("\033[37;41msome error\033[39;49m".$long, $formatter->format('some error'.$long)); + } + + public function testFormatToStringObject() + { + $formatter = new OutputFormatter(false); + $this->assertEquals( + 'some info', $formatter->format(new TableCell()) + ); + } + + 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[39;49m", $formatter->format('some error') + ); + $this->assertEquals( + "\033[32msome info\033[39m", $formatter->format('some info') + ); + $this->assertEquals( + "\033[33msome comment\033[39m", $formatter->format('some comment') + ); + $this->assertEquals( + "\033[30;46msome question\033[39;49m", $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 + )); + } +} + +class TableCell +{ + public function __toString() + { + return 'some info'; + } +} diff --git a/vendor/symfony/console/Tests/Helper/FormatterHelperTest.php b/vendor/symfony/console/Tests/Helper/FormatterHelperTest.php new file mode 100644 index 00000000..e3327747 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/FormatterHelperTest.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\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 (!function_exists('mb_detect_encoding')) { + $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 testFormatBlockWithDoubleWidthDiacriticLetters() + { + if (!extension_loaded('mbstring')) { + $this->markTestSkipped('This test requires mbstring to work.'); + } + $formatter = new FormatterHelper(); + $this->assertEquals( + ' '."\n". + ' 表示するテキスト '."\n". + ' ', + $formatter->formatBlock('表示するテキスト', '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/Tests/Helper/HelperSetTest.php b/vendor/symfony/console/Tests/Helper/HelperSetTest.php new file mode 100644 index 00000000..bf58a456 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/HelperSetTest.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\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'); + } + + /** + * @covers \Symfony\Component\Console\Helper\HelperSet::getIterator + */ + public function testIteration() + { + $helperset = new HelperSet(); + $helperset->set($this->getGenericMockHelper('fake_helper_01', $helperset)); + $helperset->set($this->getGenericMockHelper('fake_helper_02', $helperset)); + + $helpers = array('fake_helper_01', 'fake_helper_02'); + $i = 0; + + foreach ($helperset as $helper) { + $this->assertEquals($helpers[$i++], $helper->getName()); + } + } + + /** + * 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/Tests/Helper/LegacyDialogHelperTest.php b/vendor/symfony/console/Tests/Helper/LegacyDialogHelperTest.php new file mode 100644 index 00000000..e130cdc6 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/LegacyDialogHelperTest.php @@ -0,0 +1,195 @@ + + * + * 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\Input\ArrayInput; +use Symfony\Component\Console\Helper\DialogHelper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * @group legacy + */ +class LegacyDialogHelperTest 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)); + } + + /** + * @group tty + */ + public function testAskHiddenResponse() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $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()); + } + } + + public function testNoInteraction() + { + $dialog = new DialogHelper(); + + $input = new ArrayInput(array()); + $input->setInteractive(false); + + $dialog->setInput($input); + + $this->assertEquals('not yet', $dialog->ask($this->getOutputStream(), 'Do you have a job?', 'not yet')); + } + + protected function getInputStream($input) + { + $stream = fopen('php://memory', 'r+', false); + fwrite($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/Tests/Helper/LegacyProgressHelperTest.php b/vendor/symfony/console/Tests/Helper/LegacyProgressHelperTest.php new file mode 100644 index 00000000..e0272463 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/LegacyProgressHelperTest.php @@ -0,0 +1,240 @@ + + * + * 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; +use Symfony\Component\Console\Tests; + +require_once __DIR__.'/../ClockMock.php'; + +/** + * @group legacy + */ +class LegacyProgressHelperTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + Tests\with_clock_mock(true); + } + + protected function tearDown() + { + Tests\with_clock_mock(false); + } + + 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 testRedrawFrequency() + { + $progress = $this->getMock('Symfony\Component\Console\Helper\ProgressHelper', array('display')); + $progress->expects($this->exactly(4)) + ->method('display'); + + $progress->setRedrawFrequency(2); + + $progress->start($output = $this->getOutputStream(), 6); + $progress->setCurrent(1); + $progress->advance(2); + $progress->advance(2); + $progress->advance(1); + } + + 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 testClear() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->setCurrent(25); + $progress->clear(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 25/50 [==============>-------------] 50%').$this->generateOutput(''), + 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())); + } + + public function testNonDecoratedOutput() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(false)); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals('', stream_get_contents($output->getStream())); + } + + protected function getOutputStream($decorated = true) + { + return new StreamOutput(fopen('php://memory', 'r+', false), StreamOutput::VERBOSITY_NORMAL, $decorated); + } + + 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/Tests/Helper/LegacyTableHelperTest.php b/vendor/symfony/console/Tests/Helper/LegacyTableHelperTest.php new file mode 100644 index 00000000..3b324237 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/LegacyTableHelperTest.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\Console\Tests\Helper; + +use Symfony\Component\Console\Helper\TableHelper; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * @group legacy + */ +class LegacyTableHelperTest 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() + { + $books = 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'), + ); + + return array( + array( + array('ISBN', 'Title', 'Author'), + $books, + TableHelper::LAYOUT_DEFAULT, +<< 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'), + ), + TableHelper::LAYOUT_DEFAULT, +<<
    array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-700', 'Divine Com', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + ), + TableHelper::LAYOUT_DEFAULT, +<<
    99921-58-10-700 | Divine Com | Dante Alighieri | +| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | ++----------------------------------+----------------------+-----------------+ + +TABLE + ), + ); + } + + public function testRenderMultiByte() + { + if (!function_exists('mb_strwidth')) { + $this->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)); + } + + public function testRenderFullWidthCharacters() + { + if (!function_exists('mb_strwidth')) { + $this->markTestSkipped('The "mbstring" extension is not available'); + } + + $table = new TableHelper(); + $table + ->setHeaders(array('あいうえお')) + ->setRows(array(array(1234567890))) + ->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/Tests/Helper/ProcessHelperTest.php b/vendor/symfony/console/Tests/Helper/ProcessHelperTest.php new file mode 100644 index 00000000..a51fb435 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/ProcessHelperTest.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\Tests\Helper; + +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Process\Process; + +class ProcessHelperTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provideCommandsAndOutput + */ + public function testVariousProcessRuns($expected, $cmd, $verbosity, $error) + { + $helper = new ProcessHelper(); + $helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); + $output = $this->getOutputStream($verbosity); + $helper->run($output, $cmd, $error); + $this->assertEquals($expected, $this->getOutput($output)); + } + + public function testPassedCallbackIsExecuted() + { + $helper = new ProcessHelper(); + $helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); + $output = $this->getOutputStream(StreamOutput::VERBOSITY_NORMAL); + + $executed = false; + $callback = function () use (&$executed) { $executed = true; }; + + $helper->run($output, 'php -r "echo 42;"', null, $callback); + $this->assertTrue($executed); + } + + public function provideCommandsAndOutput() + { + $successOutputVerbose = <<42';" + OUT 42 + RES Command ran successfully + +EOT; + $successOutputProcessDebug = <<42\';"', StreamOutput::VERBOSITY_DEBUG, null), + array('', 'php -r "syntax error"', StreamOutput::VERBOSITY_VERBOSE, null), + array($syntaxErrorOutputVerbose, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, null), + array($syntaxErrorOutputDebug, 'php -r "fwrite(STDERR, \'error message\');usleep(500000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, null), + array($errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage), + array($syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage), + array($syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(500000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage), + array($successOutputProcessDebug, array('php', '-r', 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null), + array($successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null), + ); + } + + private function getOutputStream($verbosity) + { + return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, false); + } + + private function getOutput(StreamOutput $output) + { + rewind($output->getStream()); + + return stream_get_contents($output->getStream()); + } +} diff --git a/vendor/symfony/console/Tests/Helper/ProgressBarTest.php b/vendor/symfony/console/Tests/Helper/ProgressBarTest.php new file mode 100644 index 00000000..e37853e4 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/ProgressBarTest.php @@ -0,0 +1,612 @@ + + * + * 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\ProgressBar; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Tests; + +class ProgressBarTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + Tests\with_clock_mock(true); + } + + protected function tearDown() + { + Tests\with_clock_mock(false); + } + + public function testMultipleStart() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->advance(); + $bar->start(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0 [>---------------------------]'). + $this->generateOutput(' 1 [->--------------------------]'). + $this->generateOutput(' 0 [>---------------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testAdvance() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0 [>---------------------------]'). + $this->generateOutput(' 1 [->--------------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testAdvanceWithStep() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->advance(5); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0 [>---------------------------]'). + $this->generateOutput(' 5 [----->----------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testAdvanceMultipleTimes() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->advance(3); + $bar->advance(2); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0 [>---------------------------]'). + $this->generateOutput(' 3 [--->------------------------]'). + $this->generateOutput(' 5 [----->----------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testAdvanceOverMax() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 10); + $bar->setProgress(9); + $bar->advance(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 9/10 [=========================>--] 90%'). + $this->generateOutput(' 10/10 [============================] 100%'). + $this->generateOutput(' 11/11 [============================] 100%'), + stream_get_contents($output->getStream()) + ); + } + + public function testCustomizations() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 10); + $bar->setBarWidth(10); + $bar->setBarCharacter('_'); + $bar->setEmptyBarCharacter(' '); + $bar->setProgressCharacter('/'); + $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%'); + $bar->start(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/10 [/ ] 0%'). + $this->generateOutput(' 1/10 [_/ ] 10%'), + stream_get_contents($output->getStream()) + ); + } + + public function testDisplayWithoutStart() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->display(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%'), + stream_get_contents($output->getStream()) + ); + } + + public function testDisplayWithQuietVerbosity() + { + $bar = new ProgressBar($output = $this->getOutputStream(true, StreamOutput::VERBOSITY_QUIET), 50); + $bar->display(); + + rewind($output->getStream()); + $this->assertEquals( + '', + stream_get_contents($output->getStream()) + ); + } + + public function testFinishWithoutStart() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 50/50 [============================] 100%'), + stream_get_contents($output->getStream()) + ); + } + + public function testPercent() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->start(); + $bar->display(); + $bar->advance(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 1/50 [>---------------------------] 2%'). + $this->generateOutput(' 2/50 [=>--------------------------] 4%'), + stream_get_contents($output->getStream()) + ); + } + + public function testOverwriteWithShorterLine() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%'); + $bar->start(); + $bar->display(); + $bar->advance(); + + // set shorter format + $bar->setFormat(' %current%/%max% [%bar%]'); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 1/50 [>---------------------------] 2%'). + $this->generateOutput(' 2/50 [=>--------------------------] '), + stream_get_contents($output->getStream()) + ); + } + + public function testStartWithMax() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->setFormat('%current%/%max% [%bar%]'); + $bar->start(50); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------]'). + $this->generateOutput(' 1/50 [>---------------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testSetCurrentProgress() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->start(); + $bar->display(); + $bar->advance(); + $bar->setProgress(15); + $bar->setProgress(25); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $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()) + ); + } + + /** + */ + public function testSetCurrentBeforeStarting() + { + $bar = new ProgressBar($this->getOutputStream()); + $bar->setProgress(15); + $this->assertNotNull($bar->getStartTime()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You can't regress the progress bar + */ + public function testRegressProgress() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->start(); + $bar->setProgress(15); + $bar->setProgress(10); + } + + public function testRedrawFrequency() + { + $bar = $this->getMock('Symfony\Component\Console\Helper\ProgressBar', array('display'), array($output = $this->getOutputStream(), 6)); + $bar->expects($this->exactly(4))->method('display'); + + $bar->setRedrawFrequency(2); + $bar->start(); + $bar->setProgress(1); + $bar->advance(2); + $bar->advance(2); + $bar->advance(1); + } + + public function testMultiByteSupport() + { + if (!function_exists('mb_strlen') || (false === $encoding = mb_detect_encoding('■'))) { + $this->markTestSkipped('The mbstring extension is needed for multi-byte support'); + } + + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->start(); + $bar->setBarCharacter('■'); + $bar->advance(3); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0 [>---------------------------]'). + $this->generateOutput(' 3 [■■■>------------------------]'), + stream_get_contents($output->getStream()) + ); + } + + public function testClear() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 50); + $bar->start(); + $bar->setProgress(25); + $bar->clear(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%'). + $this->generateOutput(' 25/50 [==============>-------------] 50%'). + $this->generateOutput(' '), + stream_get_contents($output->getStream()) + ); + } + + public function testPercentNotHundredBeforeComplete() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 200); + $bar->start(); + $bar->display(); + $bar->advance(199); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/200 [>---------------------------] 0%'). + $this->generateOutput(' 0/200 [>---------------------------] 0%'). + $this->generateOutput(' 199/200 [===========================>] 99%'). + $this->generateOutput(' 200/200 [============================] 100%'), + stream_get_contents($output->getStream()) + ); + } + + public function testNonDecoratedOutput() + { + $bar = new ProgressBar($output = $this->getOutputStream(false), 200); + $bar->start(); + + for ($i = 0; $i < 200; ++$i) { + $bar->advance(); + } + + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/200 [>---------------------------] 0%'.PHP_EOL. + ' 20/200 [==>-------------------------] 10%'.PHP_EOL. + ' 40/200 [=====>----------------------] 20%'.PHP_EOL. + ' 60/200 [========>-------------------] 30%'.PHP_EOL. + ' 80/200 [===========>----------------] 40%'.PHP_EOL. + ' 100/200 [==============>-------------] 50%'.PHP_EOL. + ' 120/200 [================>-----------] 60%'.PHP_EOL. + ' 140/200 [===================>--------] 70%'.PHP_EOL. + ' 160/200 [======================>-----] 80%'.PHP_EOL. + ' 180/200 [=========================>--] 90%'.PHP_EOL. + ' 200/200 [============================] 100%', + stream_get_contents($output->getStream()) + ); + } + + public function testNonDecoratedOutputWithClear() + { + $bar = new ProgressBar($output = $this->getOutputStream(false), 50); + $bar->start(); + $bar->setProgress(25); + $bar->clear(); + $bar->setProgress(50); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/50 [>---------------------------] 0%'.PHP_EOL. + ' 25/50 [==============>-------------] 50%'.PHP_EOL. + ' 50/50 [============================] 100%', + stream_get_contents($output->getStream()) + ); + } + + public function testNonDecoratedOutputWithoutMax() + { + $bar = new ProgressBar($output = $this->getOutputStream(false)); + $bar->start(); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + ' 0 [>---------------------------]'.PHP_EOL. + ' 1 [->--------------------------]', + stream_get_contents($output->getStream()) + ); + } + + public function testParallelBars() + { + $output = $this->getOutputStream(); + $bar1 = new ProgressBar($output, 2); + $bar2 = new ProgressBar($output, 3); + $bar2->setProgressCharacter('#'); + $bar3 = new ProgressBar($output); + + $bar1->start(); + $output->write("\n"); + $bar2->start(); + $output->write("\n"); + $bar3->start(); + + for ($i = 1; $i <= 3; ++$i) { + // up two lines + $output->write("\033[2A"); + if ($i <= 2) { + $bar1->advance(); + } + $output->write("\n"); + $bar2->advance(); + $output->write("\n"); + $bar3->advance(); + } + $output->write("\033[2A"); + $output->write("\n"); + $output->write("\n"); + $bar3->finish(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/2 [>---------------------------] 0%')."\n". + $this->generateOutput(' 0/3 [#---------------------------] 0%')."\n". + rtrim($this->generateOutput(' 0 [>---------------------------]')). + + "\033[2A". + $this->generateOutput(' 1/2 [==============>-------------] 50%')."\n". + $this->generateOutput(' 1/3 [=========#------------------] 33%')."\n". + rtrim($this->generateOutput(' 1 [->--------------------------]')). + + "\033[2A". + $this->generateOutput(' 2/2 [============================] 100%')."\n". + $this->generateOutput(' 2/3 [==================#---------] 66%')."\n". + rtrim($this->generateOutput(' 2 [-->-------------------------]')). + + "\033[2A". + "\n". + $this->generateOutput(' 3/3 [============================] 100%')."\n". + rtrim($this->generateOutput(' 3 [--->------------------------]')). + + "\033[2A". + "\n". + "\n". + rtrim($this->generateOutput(' 3 [============================]')), + stream_get_contents($output->getStream()) + ); + } + + public function testWithoutMax() + { + $output = $this->getOutputStream(); + + $bar = new ProgressBar($output); + $bar->start(); + $bar->advance(); + $bar->advance(); + $bar->advance(); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + rtrim($this->generateOutput(' 0 [>---------------------------]')). + rtrim($this->generateOutput(' 1 [->--------------------------]')). + rtrim($this->generateOutput(' 2 [-->-------------------------]')). + rtrim($this->generateOutput(' 3 [--->------------------------]')). + rtrim($this->generateOutput(' 3 [============================]')), + stream_get_contents($output->getStream()) + ); + } + + public function testAddingPlaceholderFormatter() + { + ProgressBar::setPlaceholderFormatterDefinition('remaining_steps', function (ProgressBar $bar) { + return $bar->getMaxSteps() - $bar->getProgress(); + }); + $bar = new ProgressBar($output = $this->getOutputStream(), 3); + $bar->setFormat(' %remaining_steps% [%bar%]'); + + $bar->start(); + $bar->advance(); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 3 [>---------------------------]'). + $this->generateOutput(' 2 [=========>------------------]'). + $this->generateOutput(' 0 [============================]'), + stream_get_contents($output->getStream()) + ); + } + + public function testMultilineFormat() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 3); + $bar->setFormat("%bar%\nfoobar"); + + $bar->start(); + $bar->advance(); + $bar->clear(); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(">---------------------------\nfoobar"). + $this->generateOutput("=========>------------------\nfoobar "). + $this->generateOutput(" \n "). + $this->generateOutput("============================\nfoobar "), + stream_get_contents($output->getStream()) + ); + } + + /** + * @requires extension mbstring + */ + public function testAnsiColorsAndEmojis() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 15); + ProgressBar::setPlaceholderFormatterDefinition('memory', function (ProgressBar $bar) { + static $i = 0; + $mem = 100000 * $i; + $colors = $i++ ? '41;37' : '44;37'; + + return "\033[".$colors.'m '.Helper::formatMemory($mem)." \033[0m"; + }); + $bar->setFormat(" \033[44;37m %title:-37s% \033[0m\n %current%/%max% %bar% %percent:3s%%\n 🏁 %remaining:-10s% %memory:37s%"); + $bar->setBarCharacter($done = "\033[32m●\033[0m"); + $bar->setEmptyBarCharacter($empty = "\033[31m●\033[0m"); + $bar->setProgressCharacter($progress = "\033[32m➤ \033[0m"); + + $bar->setMessage('Starting the demo... fingers crossed', 'title'); + $bar->start(); + $bar->setMessage('Looks good to me...', 'title'); + $bar->advance(4); + $bar->setMessage('Thanks, bye', 'title'); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput( + " \033[44;37m Starting the demo... fingers crossed \033[0m\n". + ' 0/15 '.$progress.str_repeat($empty, 26)." 0%\n". + " \xf0\x9f\x8f\x81 1 sec \033[44;37m 0 B \033[0m" + ). + $this->generateOutput( + " \033[44;37m Looks good to me... \033[0m\n". + ' 4/15 '.str_repeat($done, 7).$progress.str_repeat($empty, 19)." 26%\n". + " \xf0\x9f\x8f\x81 1 sec \033[41;37m 97 KiB \033[0m" + ). + $this->generateOutput( + " \033[44;37m Thanks, bye \033[0m\n". + ' 15/15 '.str_repeat($done, 28)." 100%\n". + " \xf0\x9f\x8f\x81 1 sec \033[41;37m 195 KiB \033[0m" + ), + stream_get_contents($output->getStream()) + ); + } + + public function testSetFormat() + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->setFormat('normal'); + $bar->start(); + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0 [>---------------------------]'), + stream_get_contents($output->getStream()) + ); + + $bar = new ProgressBar($output = $this->getOutputStream(), 10); + $bar->setFormat('normal'); + $bar->start(); + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/10 [>---------------------------] 0%'), + stream_get_contents($output->getStream()) + ); + } + + /** + * @dataProvider provideFormat + */ + public function testFormatsWithoutMax($format) + { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->setFormat($format); + $bar->start(); + + rewind($output->getStream()); + $this->assertNotEmpty(stream_get_contents($output->getStream())); + } + + /** + * Provides each defined format. + * + * @return array + */ + public function provideFormat() + { + return array( + array('normal'), + array('verbose'), + array('very_verbose'), + array('debug'), + ); + } + + protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL) + { + return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated); + } + + protected function generateOutput($expected) + { + $count = substr_count($expected, "\n"); + + return "\x0D".($count ? sprintf("\033[%dA", $count) : '').$expected; + } +} diff --git a/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php b/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php new file mode 100644 index 00000000..a1f85b17 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/QuestionHelperTest.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\Console\Tests\Helper; + +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; + +/** + * @group tty + */ +class QuestionHelperTest extends \PHPUnit_Framework_TestCase +{ + public function testAskChoice() + { + $questionHelper = new QuestionHelper(); + + $helperSet = new HelperSet(array(new FormatterHelper())); + $questionHelper->setHelperSet($helperSet); + + $heroes = array('Superman', 'Batman', 'Spiderman'); + + $questionHelper->setInputStream($this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n")); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '2'); + $question->setMaxAttempts(1); + // first answer is an empty answer, we're supposed to receive the default value + $this->assertEquals('Spiderman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); + $question->setMaxAttempts(1); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); + $question->setErrorMessage('Input "%s" is not a superhero!'); + $question->setMaxAttempts(2); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + + rewind($output->getStream()); + $stream = stream_get_contents($output->getStream()); + $this->assertContains('Input "Fabien" is not a superhero!', $stream); + + try { + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '1'); + $question->setMaxAttempts(1); + $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals('Value "Fabien" is invalid', $e->getMessage()); + } + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0,1'); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, ' 0 , 1 '); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + public function testAsk() + { + $dialog = new QuestionHelper(); + + $dialog->setInputStream($this->getInputStream("\n8AM\n")); + + $question = new Question('What time is it?', '2PM'); + $this->assertEquals('2PM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new Question('What time is it?', '2PM'); + $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + + 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 QuestionHelper(); + $dialog->setInputStream($inputStream); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new Question('Please select a bundle', 'FrameworkBundle'); + $question->setAutocompleterValues(array('AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle')); + + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FrameworkBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('SecurityBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + public function testAskHiddenResponse() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test is not supported on Windows'); + } + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream("8AM\n")); + + $question = new Question('What time is it?'); + $question->setHidden(true); + + $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + /** + * @dataProvider getAskConfirmationData + */ + public function testAskConfirmation($question, $expected, $default = true) + { + $dialog = new QuestionHelper(); + + $dialog->setInputStream($this->getInputStream($question."\n")); + $question = new ConfirmationQuestion('Do you like French fries?', $default); + $this->assertEquals($expected, $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel')); + } + + public function getAskConfirmationData() + { + return array( + array('', true), + array('', false, false), + array('y', true), + array('yes', true), + array('n', false), + array('no', false), + ); + } + + public function testAskConfirmationWithCustomTrueAnswer() + { + $dialog = new QuestionHelper(); + + $dialog->setInputStream($this->getInputStream("j\ny\n")); + $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i'); + $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i'); + $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + public function testAskAndValidate() + { + $dialog = new QuestionHelper(); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $error = 'This is not a color!'; + $validator = function ($color) use ($error) { + if (!in_array($color, array('white', 'black'))) { + throw new \InvalidArgumentException($error); + } + + return $color; + }; + + $question = new Question('What color was the white horse of Henry IV?', 'white'); + $question->setValidator($validator); + $question->setMaxAttempts(2); + + $dialog->setInputStream($this->getInputStream("\nblack\n")); + $this->assertEquals('white', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('black', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $dialog->setInputStream($this->getInputStream("green\nyellow\norange\n")); + try { + $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals($error, $e->getMessage()); + } + } + + /** + * @dataProvider simpleAnswerProvider + */ + public function testSelectChoiceFromSimpleChoices($providedAnswer, $expectedValue) + { + $possibleChoices = array( + 'My environment 1', + 'My environment 2', + 'My environment 3', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + public function simpleAnswerProvider() + { + return array( + array(0, 'My environment 1'), + array(1, 'My environment 2'), + array(2, 'My environment 3'), + array('My environment 1', 'My environment 1'), + array('My environment 2', 'My environment 2'), + array('My environment 3', 'My environment 3'), + ); + } + + /** + * @dataProvider mixedKeysChoiceListAnswerProvider + */ + public function testChoiceFromChoicelistWithMixedKeys($providedAnswer, $expectedValue) + { + $possibleChoices = array( + '0' => 'No environment', + '1' => 'My environment 1', + 'env_2' => 'My environment 2', + 3 => 'My environment 3', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + public function mixedKeysChoiceListAnswerProvider() + { + return array( + array('0', '0'), + array('No environment', '0'), + array('1', '1'), + array('env_2', 'env_2'), + array(3, '3'), + array('My environment 1', '1'), + ); + } + + /** + * @dataProvider answerProvider + */ + public function testSelectChoiceFromChoiceList($providedAnswer, $expectedValue) + { + $possibleChoices = array( + 'env_1' => 'My environment 1', + 'env_2' => 'My environment', + 'env_3' => 'My environment', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The provided answer is ambiguous. Value should be one of env_2 or env_3. + */ + public function testAmbiguousChoiceFromChoicelist() + { + $possibleChoices = array( + 'env_1' => 'My first environment', + 'env_2' => 'My environment', + 'env_3' => 'My environment', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream("My environment\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + + $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + } + + public function answerProvider() + { + return array( + array('env_1', 'env_1'), + array('env_2', 'env_2'), + array('env_3', 'env_3'), + array('My environment 1', 'env_1'), + ); + } + + public function testNoInteraction() + { + $dialog = new QuestionHelper(); + $question = new Question('Do you have a job?', 'not yet'); + $this->assertEquals('not yet', $dialog->ask($this->createInputInterfaceMock(false), $this->createOutputInterface(), $question)); + } + + protected function getInputStream($input) + { + $stream = fopen('php://memory', 'r+', false); + fwrite($stream, $input); + rewind($stream); + + return $stream; + } + + protected function createOutputInterface() + { + return new StreamOutput(fopen('php://memory', 'r+', false)); + } + + protected function createInputInterfaceMock($interactive = true) + { + $mock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $mock->expects($this->any()) + ->method('isInteractive') + ->will($this->returnValue($interactive)); + + return $mock; + } + + private function hasSttyAvailable() + { + exec('stty 2>&1', $output, $exitcode); + + return $exitcode === 0; + } +} diff --git a/vendor/symfony/console/Tests/Helper/TableStyleTest.php b/vendor/symfony/console/Tests/Helper/TableStyleTest.php new file mode 100644 index 00000000..587d8414 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/TableStyleTest.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\Helper; + +use Symfony\Component\Console\Helper\TableStyle; + +class TableStyleTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH). + */ + public function testSetPadTypeWithInvalidType() + { + $style = new TableStyle(); + $style->setPadType('TEST'); + } +} diff --git a/vendor/symfony/console/Tests/Helper/TableTest.php b/vendor/symfony/console/Tests/Helper/TableTest.php new file mode 100644 index 00000000..ad053794 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/TableTest.php @@ -0,0 +1,568 @@ + + * + * 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\Table; +use Symfony\Component\Console\Helper\TableStyle; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Helper\TableCell; +use Symfony\Component\Console\Output\StreamOutput; + +class TableTest 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, $style, $expected) + { + $table = new Table($output = $this->getOutputStream()); + $table + ->setHeaders($headers) + ->setRows($rows) + ->setStyle($style) + ; + $table->render(); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + /** + * @dataProvider testRenderProvider + */ + public function testRenderAddRows($headers, $rows, $style, $expected) + { + $table = new Table($output = $this->getOutputStream()); + $table + ->setHeaders($headers) + ->addRows($rows) + ->setStyle($style) + ; + $table->render(); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + /** + * @dataProvider testRenderProvider + */ + public function testRenderAddRowsOneByOne($headers, $rows, $style, $expected) + { + $table = new Table($output = $this->getOutputStream()); + $table + ->setHeaders($headers) + ->setStyle($style) + ; + foreach ($rows as $row) { + $table->addRow($row); + } + $table->render(); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + public function testRenderProvider() + { + $books = 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'), + ); + + return array( + array( + array('ISBN', 'Title', 'Author'), + $books, + 'default', +<<
    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'), + ), + 'default', +<<
    array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-700', 'Divine Com', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + ), + 'default', +<<
    99921-58-10-700 | Divine Com | Dante Alighieri | +| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | ++----------------------------------+----------------------+-----------------+ + +TABLE + ), + 'Cell with colspan' => array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + new TableSeparator(), + array(new TableCell('Divine Comedy(Dante Alighieri)', array('colspan' => 3))), + new TableSeparator(), + array( + new TableCell('Arduino: A Quick-Start Guide', array('colspan' => 2)), + 'Mark Schmidt', + ), + new TableSeparator(), + array( + '9971-5-0210-0', + new TableCell("A Tale of \nTwo Cities", array('colspan' => 2)), + ), + ), + 'default', +<<
    array( + array('ISBN', 'Title', 'Author'), + array( + array( + new TableCell('9971-5-0210-0', array('rowspan' => 3)), + 'Divine Comedy', + 'Dante Alighieri', + ), + array('A Tale of Two Cities', 'Charles Dickens'), + array("The Lord of \nthe Rings", "J. R. \nR. Tolkien"), + new TableSeparator(), + array('80-902734-1-6', new TableCell("And Then \nThere \nWere None", array('rowspan' => 3)), 'Agatha Christie'), + array('80-902734-1-7', 'Test'), + ), + 'default', +<<
    array( + array('ISBN', 'Title', 'Author'), + array( + array( + new TableCell('9971-5-0210-0', array('rowspan' => 2, 'colspan' => 2)), + 'Dante Alighieri', + ), + array('Charles Dickens'), + new TableSeparator(), + array( + 'Dante Alighieri', + new TableCell('9971-5-0210-0', array('rowspan' => 3, 'colspan' => 2)), + ), + array('J. R. R. Tolkien'), + array('J. R. R'), + ), + 'default', +<<
    array( + array('ISBN', 'Title', 'Author'), + array( + array( + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + 'Dante Alighieri', + ), + array('Charles Dickens'), + new TableSeparator(), + array( + 'Dante Alighieri', + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + ), + array('Charles Dickens'), + new TableSeparator(), + array( + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + new TableCell("Dante \nAlighieri", array('rowspan' => 2, 'colspan' => 1)), + ), + ), + 'default', +<<
    array( + array('ISBN', 'Title', 'Author'), + array( + array( + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + 'Dante Alighieri', + ), + array('Charles Dickens'), + array( + 'Dante Alighieri', + new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)), + ), + array('Charles Dickens'), + ), + 'default', +<<
    array( + array('ISBN', 'Author'), + array( + array( + new TableCell('9971-5-0210-0', array('rowspan' => 3, 'colspan' => 1)), + 'Dante Alighieri', + ), + array(new TableSeparator()), + array('Charles Dickens'), + ), + 'default', +<<
    array( + array( + array(new TableCell('Main title', array('colspan' => 3))), + array('ISBN', 'Title', 'Author'), + ), + array(), + 'default', +<<
    markTestSkipped('The "mbstring" extension is not available'); + } + + $table = new Table($output = $this->getOutputStream()); + $table + ->setHeaders(array('■■')) + ->setRows(array(array(1234))) + ->setStyle('default') + ; + $table->render(); + + $expected = +<<
    assertEquals($expected, $this->getOutputContent($output)); + } + + public function testStyle() + { + $style = new TableStyle(); + $style + ->setHorizontalBorderChar('.') + ->setVerticalBorderChar('.') + ->setCrossingChar('.') + ; + + Table::setStyleDefinition('dotfull', $style); + $table = new Table($output = $this->getOutputStream()); + $table + ->setHeaders(array('Foo')) + ->setRows(array(array('Bar'))) + ->setStyle('dotfull'); + $table->render(); + + $expected = +<<
    assertEquals($expected, $this->getOutputContent($output)); + } + + public function testRowSeparator() + { + $table = new Table($output = $this->getOutputStream()); + $table + ->setHeaders(array('Foo')) + ->setRows(array( + array('Bar1'), + new TableSeparator(), + array('Bar2'), + new TableSeparator(), + array('Bar3'), + )); + $table->render(); + + $expected = +<<
    assertEquals($expected, $this->getOutputContent($output)); + + $this->assertEquals($table, $table->addRow(new TableSeparator()), 'fluent interface on addRow() with a single TableSeparator() works'); + } + + 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/Tests/Input/ArgvInputTest.php b/vendor/symfony/console/Tests/Input/ArgvInputTest.php new file mode 100644 index 00000000..d2c540e6 --- /dev/null +++ b/vendor/symfony/console/Tests/Input/ArgvInputTest.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\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->assertNull($input->getFirstArgument(), '->getFirstArgument() returns null when there is no arguments'); + + $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'), + array(array('app/console', 'foo:bar', '--env=dev', '', '--en=1'), array('--en'), '1'), + ); + } + + public function testParseSingleDashAsArgument() + { + $input = new ArgvInput(array('cli.php', '-')); + $input->bind(new InputDefinition(array(new InputArgument('file')))); + $this->assertEquals(array('file' => '-'), $input->getArguments(), '->parse() parses single dash as an argument'); + } +} diff --git a/vendor/symfony/console/Tests/Input/ArrayInputTest.php b/vendor/symfony/console/Tests/Input/ArrayInputTest.php new file mode 100644 index 00000000..cc89083c --- /dev/null +++ b/vendor/symfony/console/Tests/Input/ArrayInputTest.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\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 testGetParameterOption() + { + $input = new ArrayInput(array('name' => 'Fabien', '--foo' => 'bar')); + $this->assertEquals('bar', $input->getParameterOption('--foo'), '->getParameterOption() returns the option of specified name'); + + $input = new ArrayInput(array('Fabien', '--foo' => 'bar')); + $this->assertEquals('bar', $input->getParameterOption('--foo'), '->getParameterOption() returns the option of specified name'); + } + + 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/Tests/Input/InputArgumentTest.php b/vendor/symfony/console/Tests/Input/InputArgumentTest.php new file mode 100644 index 00000000..cfb37cd4 --- /dev/null +++ b/vendor/symfony/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/Tests/Input/InputDefinitionTest.php b/vendor/symfony/console/Tests/Input/InputDefinitionTest.php new file mode 100644 index 00000000..7e0a2425 --- /dev/null +++ b/vendor/symfony/console/Tests/Input/InputDefinitionTest.php @@ -0,0 +1,437 @@ + + * + * 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' => false, + 'foo2' => null, + 'foo3' => 'default', + 'foo4' => null, + 'foo5' => 'default', + 'foo6' => array(), + 'foo7' => array(1, 2), + ); + $this->assertSame($defaults, $definition->getOptionDefaults(), '->getOptionDefaults() returns the default values for all options'); + } + + /** + * @dataProvider getGetSynopsisData + */ + public function testGetSynopsis(InputDefinition $definition, $expectedSynopsis, $message = null) + { + $this->assertEquals($expectedSynopsis, $definition->getSynopsis(), $message ? '->getSynopsis() '.$message : ''); + } + + public function getGetSynopsisData() + { + return array( + array(new InputDefinition(array(new InputOption('foo'))), '[--foo]', 'puts optional options in square brackets'), + array(new InputDefinition(array(new InputOption('foo', 'f'))), '[-f|--foo]', 'separates shortcut with a pipe'), + array(new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))), '[-f|--foo FOO]', 'uses shortcut as value placeholder'), + array(new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL))), '[-f|--foo [FOO]]', 'puts optional values in square brackets'), + + array(new InputDefinition(array(new InputArgument('foo', InputArgument::REQUIRED))), '', 'puts arguments in angle brackets'), + array(new InputDefinition(array(new InputArgument('foo'))), '[]', 'puts optional arguments in square brackets'), + array(new InputDefinition(array(new InputArgument('foo', InputArgument::IS_ARRAY))), '[]...', 'uses an ellipsis for array arguments'), + array(new InputDefinition(array(new InputArgument('foo', InputArgument::REQUIRED | InputArgument::IS_ARRAY))), ' ()...', 'uses parenthesis and ellipsis for required array arguments'), + + array(new InputDefinition(array(new InputOption('foo'), new InputArgument('foo', InputArgument::REQUIRED))), '[--foo] [--] ', 'puts [--] between options and arguments'), + ); + } + + public function testGetShortSynopsis() + { + $definition = new InputDefinition(array(new InputOption('foo'), new InputOption('bar'), new InputArgument('cat'))); + $this->assertEquals('[options] [--] []', $definition->getSynopsis(true), '->getSynopsis(true) groups options in [options]'); + } + + /** + * @group legacy + */ + public function testLegacyAsText() + { + $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'); + } + + /** + * @group legacy + */ + public function testLegacyAsXml() + { + $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(), '->asXml() returns an XML 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/Tests/Input/InputOptionTest.php b/vendor/symfony/console/Tests/Input/InputOptionTest.php new file mode 100644 index 00000000..53ce1df8 --- /dev/null +++ b/vendor/symfony/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/Tests/Input/InputTest.php b/vendor/symfony/console/Tests/Input/InputTest.php new file mode 100644 index 00000000..0b3e38fb --- /dev/null +++ b/vendor/symfony/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/Tests/Input/StringInputTest.php b/vendor/symfony/console/Tests/Input/StringInputTest.php new file mode 100644 index 00000000..ccf9289c --- /dev/null +++ b/vendor/symfony/console/Tests/Input/StringInputTest.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\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')); + } + + /** + * @group legacy + */ + public function testLegacyInputOptionDefinitionInConstructor() + { + $definition = new InputDefinition( + array(new InputOption('foo', null, InputOption::VALUE_REQUIRED)) + ); + + $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/Tests/Logger/ConsoleLoggerTest.php b/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php new file mode 100644 index 00000000..c5eca2ca --- /dev/null +++ b/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.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\Tests\Logger; + +use Psr\Log\Test\LoggerInterfaceTest; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Tests\Fixtures\DummyOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Console logger test. + * + * @author Kévin Dunglas + */ +class ConsoleLoggerTest extends LoggerInterfaceTest +{ + /** + * @var DummyOutput + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function getLogger() + { + $this->output = new DummyOutput(OutputInterface::VERBOSITY_VERBOSE); + + return new ConsoleLogger($this->output, array( + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, + LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL, + LogLevel::DEBUG => OutputInterface::VERBOSITY_NORMAL, + )); + } + + /** + * {@inheritdoc} + */ + public function getLogs() + { + return $this->output->getLogs(); + } +} diff --git a/vendor/symfony/console/Tests/Output/ConsoleOutputTest.php b/vendor/symfony/console/Tests/Output/ConsoleOutputTest.php new file mode 100644 index 00000000..1afbbb6e --- /dev/null +++ b/vendor/symfony/console/Tests/Output/ConsoleOutputTest.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\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'); + $this->assertSame($output->getFormatter(), $output->getErrorOutput()->getFormatter(), '__construct() takes a formatter or null as the third argument'); + } +} diff --git a/vendor/symfony/console/Tests/Output/NullOutputTest.php b/vendor/symfony/console/Tests/Output/NullOutputTest.php new file mode 100644 index 00000000..b20ae4e8 --- /dev/null +++ b/vendor/symfony/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/Tests/Output/OutputTest.php b/vendor/symfony/console/Tests/Output/OutputTest.php new file mode 100644 index 00000000..cfb4afe1 --- /dev/null +++ b/vendor/symfony/console/Tests/Output/OutputTest.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\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'); + + $this->assertTrue($output->isQuiet()); + $this->assertFalse($output->isVerbose()); + $this->assertFalse($output->isVeryVerbose()); + $this->assertFalse($output->isDebug()); + + $output->setVerbosity(Output::VERBOSITY_NORMAL); + $this->assertFalse($output->isQuiet()); + $this->assertFalse($output->isVerbose()); + $this->assertFalse($output->isVeryVerbose()); + $this->assertFalse($output->isDebug()); + + $output->setVerbosity(Output::VERBOSITY_VERBOSE); + $this->assertFalse($output->isQuiet()); + $this->assertTrue($output->isVerbose()); + $this->assertFalse($output->isVeryVerbose()); + $this->assertFalse($output->isDebug()); + + $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); + $this->assertFalse($output->isQuiet()); + $this->assertTrue($output->isVerbose()); + $this->assertTrue($output->isVeryVerbose()); + $this->assertFalse($output->isDebug()); + + $output->setVerbosity(Output::VERBOSITY_DEBUG); + $this->assertFalse($output->isQuiet()); + $this->assertTrue($output->isVerbose()); + $this->assertTrue($output->isVeryVerbose()); + $this->assertTrue($output->isDebug()); + } + + 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[39;49;25m\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/Tests/Output/StreamOutputTest.php b/vendor/symfony/console/Tests/Output/StreamOutputTest.php new file mode 100644 index 00000000..2fd4f612 --- /dev/null +++ b/vendor/symfony/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/Tests/Style/SymfonyStyleTest.php b/vendor/symfony/console/Tests/Style/SymfonyStyleTest.php new file mode 100644 index 00000000..2df4f40e --- /dev/null +++ b/vendor/symfony/console/Tests/Style/SymfonyStyleTest.php @@ -0,0 +1,64 @@ +command = new Command('sfstyle'); + $this->tester = new CommandTester($this->command); + } + + protected function tearDown() + { + $this->command = null; + $this->tester = null; + } + + /** + * @dataProvider inputCommandToOutputFilesProvider + */ + public function testOutputs($inputCommandFilepath, $outputFilepath) + { + $code = require $inputCommandFilepath; + $this->command->setCode($code); + $this->tester->execute(array(), array('interactive' => false, 'decorated' => false)); + $this->assertStringEqualsFile($outputFilepath, $this->tester->getDisplay(true)); + } + + public function inputCommandToOutputFilesProvider() + { + $baseDir = __DIR__.'/../Fixtures/Style/SymfonyStyle'; + + return array_map(null, glob($baseDir.'/command/command_*.php'), glob($baseDir.'/output/output_*.txt')); + } + + public function testLongWordsBlockWrapping() + { + $word = 'Lopadotemachoselachogaleokranioleipsanodrimhypotrimmatosilphioparaomelitokatakechymenokichlepikossyphophattoperisteralektryonoptekephalliokigklopeleiolagoiosiraiobaphetraganopterygon'; + $wordLength = strlen($word); + $maxLineLength = SymfonyStyle::MAX_LINE_LENGTH - 3; + + $this->command->setCode(function (InputInterface $input, OutputInterface $output) use ($word) { + $sfStyle = new SymfonyStyle($input, $output); + $sfStyle->block($word, 'CUSTOM', 'fg=white;bg=blue', ' § ', false); + }); + + $this->tester->execute(array(), array('interactive' => false, 'decorated' => false)); + $expectedCount = (int) ceil($wordLength / ($maxLineLength)) + (int) ($wordLength > $maxLineLength - 5); + $this->assertSame($expectedCount, substr_count($this->tester->getDisplay(true), ' § ')); + } +} diff --git a/vendor/symfony/console/Tests/Tester/ApplicationTesterTest.php b/vendor/symfony/console/Tests/Tester/ApplicationTesterTest.php new file mode 100644 index 00000000..a8389dd1 --- /dev/null +++ b/vendor/symfony/console/Tests/Tester/ApplicationTesterTest.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\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'); + } + + public function testGetStatusCode() + { + $this->assertSame(0, $this->tester->getStatusCode(), '->getStatusCode() returns the status code'); + } +} diff --git a/vendor/symfony/console/Tests/Tester/CommandTesterTest.php b/vendor/symfony/console/Tests/Tester/CommandTesterTest.php new file mode 100644 index 00000000..b54c00e8 --- /dev/null +++ b/vendor/symfony/console/Tests/Tester/CommandTesterTest.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\Tester; + +use Symfony\Component\Console\Application; +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'); + } + + public function testGetStatusCode() + { + $this->assertSame(0, $this->tester->getStatusCode(), '->getStatusCode() returns the status code'); + } + + public function testCommandFromApplication() + { + $application = new Application(); + $application->setAutoExit(false); + + $command = new Command('foo'); + $command->setCode(function ($input, $output) { $output->writeln('foo'); }); + + $application->add($command); + + $tester = new CommandTester($application->find('foo')); + + // check that there is no need to pass the command name here + $this->assertEquals(0, $tester->execute(array())); + } +} diff --git a/vendor/symfony/console/composer.json b/vendor/symfony/console/composer.json new file mode 100644 index 00000000..16b7b812 --- /dev/null +++ b/vendor/symfony/console/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/console", + "type": "library", + "description": "Symfony Console Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7", + "symfony/event-dispatcher": "~2.1", + "symfony/process": "~2.1", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/event-dispatcher": "", + "symfony/process": "", + "psr/log": "For using the console logger" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Console\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + } +} diff --git a/vendor/symfony/console/phpunit.xml.dist b/vendor/symfony/console/phpunit.xml.dist new file mode 100644 index 00000000..ae0dcbea --- /dev/null +++ b/vendor/symfony/console/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/filesystem/.gitignore b/vendor/symfony/filesystem/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/filesystem/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/filesystem/CHANGELOG.md b/vendor/symfony/filesystem/CHANGELOG.md new file mode 100644 index 00000000..a4c0479f --- /dev/null +++ b/vendor/symfony/filesystem/CHANGELOG.md @@ -0,0 +1,28 @@ +CHANGELOG +========= + +2.6.0 +----- + + * added LockHandler + +2.3.12 +------ + + * deprecated dumpFile() file mode argument. + +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/Exception/ExceptionInterface.php b/vendor/symfony/filesystem/Exception/ExceptionInterface.php new file mode 100644 index 00000000..c212e664 --- /dev/null +++ b/vendor/symfony/filesystem/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\Filesystem\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Romain Neutron + * + * @api + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/filesystem/Exception/FileNotFoundException.php b/vendor/symfony/filesystem/Exception/FileNotFoundException.php new file mode 100644 index 00000000..bcc8fe81 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/FileNotFoundException.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\Filesystem\Exception; + +/** + * Exception class thrown when a file couldn't be found. + * + * @author Fabien Potencier + * @author Christian Gärtner + */ +class FileNotFoundException extends IOException +{ + public function __construct($message = null, $code = 0, \Exception $previous = null, $path = null) + { + if (null === $message) { + if (null === $path) { + $message = 'File could not be found.'; + } else { + $message = sprintf('File "%s" could not be found.', $path); + } + } + + parent::__construct($message, $code, $previous, $path); + } +} diff --git a/vendor/symfony/filesystem/Exception/IOException.php b/vendor/symfony/filesystem/Exception/IOException.php new file mode 100644 index 00000000..f68a8202 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/IOException.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\Filesystem\Exception; + +/** + * Exception class thrown when a filesystem operation failure happens. + * + * @author Romain Neutron + * @author Christian Gärtner + * @author Fabien Potencier + * + * @api + */ +class IOException extends \RuntimeException implements IOExceptionInterface +{ + private $path; + + public function __construct($message, $code = 0, \Exception $previous = null, $path = null) + { + $this->path = $path; + + parent::__construct($message, $code, $previous); + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/symfony/filesystem/Exception/IOExceptionInterface.php b/vendor/symfony/filesystem/Exception/IOExceptionInterface.php new file mode 100644 index 00000000..35350587 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/IOExceptionInterface.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\Filesystem\Exception; + +/** + * IOException interface for file and input/output stream related exceptions thrown by the component. + * + * @author Christian Gärtner + */ +interface IOExceptionInterface extends ExceptionInterface +{ + /** + * Returns the associated path for the exception. + * + * @return string The path. + */ + public function getPath(); +} diff --git a/vendor/symfony/filesystem/Filesystem.php b/vendor/symfony/filesystem/Filesystem.php new file mode 100644 index 00000000..1bb7db35 --- /dev/null +++ b/vendor/symfony/filesystem/Filesystem.php @@ -0,0 +1,499 @@ + + * + * 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; +use Symfony\Component\Filesystem\Exception\FileNotFoundException; + +/** + * 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 bool $override Whether to override an existing file or not + * + * @throws FileNotFoundException When originFile doesn't exist + * @throws IOException When copy fails + */ + public function copy($originFile, $targetFile, $override = false) + { + if (stream_is_local($originFile) && !is_file($originFile)) { + throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile); + } + + $this->mkdir(dirname($targetFile)); + + $doCopy = true; + if (!$override && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) { + $doCopy = filemtime($originFile) > filemtime($targetFile); + } + + if ($doCopy) { + // https://bugs.php.net/bug.php?id=64634 + if (false === $source = @fopen($originFile, 'r')) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile); + } + + // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default + if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('overwrite' => true))))) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile); + } + + $bytesCopied = 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), 0, null, $originFile); + } + + // Like `cp`, preserve executable permission bits + @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111)); + + if (stream_is_local($originFile) && $bytesCopied !== ($bytesOrigin = filesize($originFile))) { + throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile); + } + } + } + + /** + * Creates a directory recursively. + * + * @param string|array|\Traversable $dirs The directory path + * @param int $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)) { + $error = error_get_last(); + if (!is_dir($dir)) { + // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one + if ($error) { + throw new IOException(sprintf('Failed to create "%s": %s.', $dir, $error['message']), 0, null, $dir); + } + throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $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 bool 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 int $time The touch time as a Unix timestamp + * @param int $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), 0, null, $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), 0, null, $file); + } + } else { + // https://bugs.php.net/bug.php?id=52176 + if ('\\' === DIRECTORY_SEPARATOR && is_dir($file)) { + if (true !== @rmdir($file)) { + throw new IOException(sprintf('Failed to remove file "%s".', $file), 0, null, $file); + } + } else { + if (true !== @unlink($file)) { + throw new IOException(sprintf('Failed to remove file "%s".', $file), 0, null, $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 int $mode The new mode (octal) + * @param int $umask The mode mask (octal) + * @param bool $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), 0, null, $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 bool $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), 0, null, $file); + } + } else { + if (true !== @chown($file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $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 bool $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) || (defined('HHVM_VERSION') && !posix_getgrnam($group))) { + throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); + } + } else { + if (true !== @chgrp($file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); + } + } + } + } + + /** + * Renames a file or a directory. + * + * @param string $origin The origin filename or directory + * @param string $target The new filename or directory + * @param bool $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 exists.', $target), 0, null, $target); + } + + if (true !== @rename($origin, $target)) { + throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target); + } + } + + /** + * Creates a symbolic link or copy a directory. + * + * @param string $originDir The origin directory path + * @param string $targetDir The symbolic link name + * @param bool $copyOnWindows Whether to copy files if on Windows + * + * @throws IOException When symlink fails + */ + public function symlink($originDir, $targetDir, $copyOnWindows = false) + { + if ('\\' === DIRECTORY_SEPARATOR && $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 && true !== @symlink($originDir, $targetDir)) { + $report = error_get_last(); + if (is_array($report)) { + if ('\\' === DIRECTORY_SEPARATOR && 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?', 0, null, $targetDir); + } + } + throw new IOException(sprintf('Failed to create symbolic link from "%s" to "%s".', $originDir, $targetDir), 0, null, $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 ('\\' === DIRECTORY_SEPARATOR) { + $endPath = str_replace('\\', '/', $endPath); + $startPath = str_replace('\\', '/', $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.('' !== $endPathRemainder ? $endPathRemainder.'/' : ''); + + return '' === $relativePath ? './' : $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'])) { + $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); + } + + if ($this->exists($originDir)) { + $this->mkdir($targetDir); + } + + 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), 0, null, $file); + } + } else { + if (is_link($file)) { + $this->symlink($file->getRealPath(), $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), 0, null, $file); + } + } + } + } + + /** + * Returns whether the file path is an absolute path. + * + * @param string $file A file path + * + * @return bool + */ + public function isAbsolutePath($file) + { + return (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) + ); + } + + /** + * 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 null|int $mode The file mode (octal). If null, file permissions are not modified + * Deprecated since version 2.3.12, to be removed in 3.0. + * + * @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), 0, null, $dir); + } + + $tmpFile = tempnam($dir, basename($filename)); + + if (false === @file_put_contents($tmpFile, $content)) { + throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); + } + + $this->rename($tmpFile, $filename, true); + if (null !== $mode) { + if (func_num_args() > 2) { + @trigger_error('Support for modifying file permissions is deprecated since version 2.3.12 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + $this->chmod($filename, $mode); + } + } + + /** + * @param mixed $files + * + * @return \Traversable + */ + private function toIterator($files) + { + if (!$files instanceof \Traversable) { + $files = new \ArrayObject(is_array($files) ? $files : array($files)); + } + + return $files; + } +} diff --git a/vendor/symfony/filesystem/LICENSE b/vendor/symfony/filesystem/LICENSE new file mode 100644 index 00000000..43028bc6 --- /dev/null +++ b/vendor/symfony/filesystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2015 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/LockHandler.php b/vendor/symfony/filesystem/LockHandler.php new file mode 100644 index 00000000..fa2575f9 --- /dev/null +++ b/vendor/symfony/filesystem/LockHandler.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\Filesystem; + +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * LockHandler class provides a simple abstraction to lock anything by means of + * a file lock. + * + * A locked file is created based on the lock name when calling lock(). Other + * lock handlers will not be able to lock the same name until it is released + * (explicitly by calling release() or implicitly when the instance holding the + * lock is destroyed). + * + * @author Grégoire Pineau + * @author Romain Neutron + * @author Nicolas Grekas + */ +class LockHandler +{ + private $file; + private $handle; + + /** + * @param string $name The lock name + * @param string|null $lockPath The directory to store the lock. Default values will use temporary directory + * + * @throws IOException If the lock directory could not be created or is not writable + */ + public function __construct($name, $lockPath = null) + { + $lockPath = $lockPath ?: sys_get_temp_dir(); + + if (!is_dir($lockPath)) { + $fs = new Filesystem(); + $fs->mkdir($lockPath); + } + + if (!is_writable($lockPath)) { + throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath); + } + + $this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name)); + } + + /** + * Lock the resource. + * + * @param bool $blocking wait until the lock is released + * + * @return bool Returns true if the lock was acquired, false otherwise + * + * @throws IOException If the lock file could not be created or opened + */ + public function lock($blocking = false) + { + if ($this->handle) { + return true; + } + + // Silence both userland and native PHP error handlers + $errorLevel = error_reporting(0); + set_error_handler('var_dump', 0); + + if (!$this->handle = fopen($this->file, 'r')) { + if ($this->handle = fopen($this->file, 'x')) { + chmod($this->file, 0444); + } elseif (!$this->handle = fopen($this->file, 'r')) { + usleep(100); // Give some time for chmod() to complete + $this->handle = fopen($this->file, 'r'); + } + } + restore_error_handler(); + error_reporting($errorLevel); + + if (!$this->handle) { + $error = error_get_last(); + throw new IOException($error['message'], 0, null, $this->file); + } + + // On Windows, even if PHP doc says the contrary, LOCK_NB works, see + // https://bugs.php.net/54129 + if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) { + fclose($this->handle); + $this->handle = null; + + return false; + } + + return true; + } + + /** + * Release the resource. + */ + public function release() + { + if ($this->handle) { + flock($this->handle, LOCK_UN | LOCK_NB); + fclose($this->handle); + $this->handle = null; + } + } +} diff --git a/vendor/symfony/filesystem/README.md b/vendor/symfony/filesystem/README.md new file mode 100644 index 00000000..df09f93d --- /dev/null +++ b/vendor/symfony/filesystem/README.md @@ -0,0 +1,47 @@ +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->exists($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 install + $ phpunit diff --git a/vendor/symfony/filesystem/Tests/ExceptionTest.php b/vendor/symfony/filesystem/Tests/ExceptionTest.php new file mode 100644 index 00000000..53bd8db7 --- /dev/null +++ b/vendor/symfony/filesystem/Tests/ExceptionTest.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\Filesystem\Tests; + +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Exception\FileNotFoundException; + +/** + * Test class for Filesystem. + */ +class ExceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testGetPath() + { + $e = new IOException('', 0, null, '/foo'); + $this->assertEquals('/foo', $e->getPath(), 'The pass should be returned.'); + } + + public function testGeneratedMessage() + { + $e = new FileNotFoundException(null, 0, null, '/foo'); + $this->assertEquals('/foo', $e->getPath()); + $this->assertEquals('File "/foo" could not be found.', $e->getMessage(), 'A message should be generated.'); + } + + public function testGeneratedMessageWithoutPath() + { + $e = new FileNotFoundException(); + $this->assertEquals('File could not be found.', $e->getMessage(), 'A message should be generated.'); + } + + public function testCustomMessage() + { + $e = new FileNotFoundException('bar', 0, null, '/foo'); + $this->assertEquals('bar', $e->getMessage(), 'A custom message should be possible still.'); + } +} diff --git a/vendor/symfony/filesystem/Tests/FilesystemTest.php b/vendor/symfony/filesystem/Tests/FilesystemTest.php new file mode 100644 index 00000000..760000cb --- /dev/null +++ b/vendor/symfony/filesystem/Tests/FilesystemTest.php @@ -0,0 +1,1015 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Tests; + +/** + * Test class for Filesystem. + */ +class FilesystemTest extends FilesystemTestCase +{ + 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); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testCopyUnreadableFileFails() + { + // skip test on Windows; PHP can't easily set file as unreadable on Windows + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test cannot run on Windows.'); + } + + $sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file'; + $targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file'; + + file_put_contents($sourceFilePath, 'SOURCE FILE'); + + // make sure target cannot be read + $this->filesystem->chmod($sourceFilePath, 0222); + + $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)); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testCopyWithOverrideWithReadOnlyTargetFails() + { + // skip test on Windows; PHP can't easily set file as unwritable on Windows + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test cannot run on Windows.'); + } + + $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); + + // make sure target is read-only + $this->filesystem->chmod($targetFilePath, 0444); + + $this->filesystem->copy($sourceFilePath, $targetFilePath, true); + } + + 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 testCopyForOriginUrlsAndExistingLocalFileDefaultsToNotCopy() + { + $sourceFilePath = 'http://symfony.com/images/common/logo/logo_symfony_header.png'; + $targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file'; + + file_put_contents($targetFilePath, 'TARGET FILE'); + + $this->filesystem->copy($sourceFilePath, $targetFilePath, false); + + $this->assertFileExists($targetFilePath); + $this->assertEquals(file_get_contents($sourceFilePath), 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 nonexistent 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('fs_test_', true); + $newPath = $this->workspace.DIRECTORY_SEPARATOR.'new_file'; + + $this->filesystem->rename($file, $newPath); + } + + public function testSymlink() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support creating "broken" symlinks'); + } + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $link = $this->workspace.DIRECTORY_SEPARATOR.'link'; + + // $file does not exists right now: creating "broken" links is a wanted feature + $this->filesystem->symlink($file, $link); + + $this->assertTrue(is_link($link)); + + // Create the linked file AFTER creating the link + touch($file); + + $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 ('\\' === DIRECTORY_SEPARATOR) { + $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 testMirrorCreatesEmptyDirectory() + { + $sourcePath = $this->workspace.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR; + + mkdir($sourcePath); + + $targetPath = $this->workspace.DIRECTORY_SEPARATOR.'target'.DIRECTORY_SEPARATOR; + + $this->filesystem->mirror($sourcePath, $targetPath); + + $this->assertTrue(is_dir($targetPath)); + + $this->filesystem->remove($sourcePath); + } + + 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')); + } + + public function testMirrorCopiesRelativeLinkedContents() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $sourcePath = $this->workspace.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR; + $oldPath = getcwd(); + + mkdir($sourcePath.'nested/', 0777, true); + file_put_contents($sourcePath.'/nested/file1.txt', 'FILE1'); + // Note: Create relative symlink + chdir($sourcePath); + symlink('nested', 'link1'); + + chdir($oldPath); + + $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')); + $this->assertEquals($sourcePath.'nested', readlink($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'); + + $this->assertFileExists($filename); + $this->assertSame('bar', file_get_contents($filename)); + } + + /** + * @group legacy + */ + public function testDumpFileAndSetPermissions() + { + $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 ('\\' !== DIRECTORY_SEPARATOR) { + $this->assertFilePermissions(753, $filename); + } + } + + public function testDumpFileWithNullMode() + { + $filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt'; + + $this->filesystem->dumpFile($filename, 'bar', null); + + $this->assertFileExists($filename); + $this->assertSame('bar', file_get_contents($filename)); + + // skip mode check on Windows + if ('\\' !== DIRECTORY_SEPARATOR) { + $this->assertFilePermissions(600, $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)); + } + + public function testCopyShouldKeepExecutionPermission() + { + $this->markAsSkippedIfChmodIsMissing(); + + $sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file'; + $targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file'; + + file_put_contents($sourceFilePath, 'SOURCE FILE'); + chmod($sourceFilePath, 0745); + + $this->filesystem->copy($sourceFilePath, $targetFilePath); + + $this->assertFilePermissions(767, $targetFilePath); + } +} diff --git a/vendor/symfony/filesystem/Tests/FilesystemTestCase.php b/vendor/symfony/filesystem/Tests/FilesystemTestCase.php new file mode 100644 index 00000000..0ef9c8dd --- /dev/null +++ b/vendor/symfony/filesystem/Tests/FilesystemTestCase.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\Filesystem\Tests; + +use Symfony\Component\Filesystem\Filesystem; + +class FilesystemTestCase extends \PHPUnit_Framework_TestCase +{ + private $umask; + + /** + * @var \Symfony\Component\Filesystem\Filesystem + */ + protected $filesystem = null; + + /** + * @var string + */ + protected $workspace = null; + + private static $symlinkOnWindows = null; + + public static function setUpBeforeClass() + { + if ('\\' === DIRECTORY_SEPARATOR && null === self::$symlinkOnWindows) { + $target = tempnam(sys_get_temp_dir(), 'sl'); + $link = sys_get_temp_dir().'/sl'.microtime(true).mt_rand(); + if (self::$symlinkOnWindows = @symlink($target, $link)) { + unlink($link); + } + unlink($target); + } + } + + protected function setUp() + { + $this->umask = umask(0); + $this->workspace = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.time().mt_rand(0, 1000); + mkdir($this->workspace, 0777, true); + $this->workspace = realpath($this->workspace); + $this->filesystem = new Filesystem(); + } + + protected function tearDown() + { + $this->filesystem->remove($this->workspace); + umask($this->umask); + } + + /** + * @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']; + } + + $this->markTestSkipped('Unable to retrieve file group name'); + } + + protected function markAsSkippedIfSymlinkIsMissing() + { + if (!function_exists('symlink')) { + $this->markTestSkipped('symlink is not supported'); + } + + if ('\\' === DIRECTORY_SEPARATOR && false === self::$symlinkOnWindows) { + $this->markTestSkipped('symlink requires "Create symbolic links" privilege on windows'); + } + } + + protected function markAsSkippedIfChmodIsMissing() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('chmod is not supported on windows'); + } + } + + protected function markAsSkippedIfPosixIsMissing() + { + if ('\\' === DIRECTORY_SEPARATOR || !function_exists('posix_isatty')) { + $this->markTestSkipped('Posix is not supported'); + } + } +} diff --git a/vendor/symfony/filesystem/Tests/LockHandlerTest.php b/vendor/symfony/filesystem/Tests/LockHandlerTest.php new file mode 100644 index 00000000..9ab55374 --- /dev/null +++ b/vendor/symfony/filesystem/Tests/LockHandlerTest.php @@ -0,0 +1,91 @@ +markTestSkipped('This test will fail if run under superuser'); + } + new LockHandler('lock', '/a/b/c/d/e'); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + * @expectedExceptionMessage The directory "/" is not writable. + */ + public function testConstructWhenRepositoryIsNotWriteable() + { + if (!getenv('USER') || 'root' === getenv('USER')) { + $this->markTestSkipped('This test will fail if run under superuser'); + } + new LockHandler('lock', '/'); + } + + public function testConstructSanitizeName() + { + $lock = new LockHandler(''); + + $file = sprintf('%s/sf.-php-echo-hello-word-.4b3d9d0d27ddef3a78a64685dda3a963e478659a9e5240feaf7b4173a8f28d5f.lock', sys_get_temp_dir()); + // ensure the file does not exist before the lock + @unlink($file); + + $lock->lock(); + + $this->assertFileExists($file); + + $lock->release(); + } + + public function testLockRelease() + { + $name = 'symfony-test-filesystem.lock'; + + $l1 = new LockHandler($name); + $l2 = new LockHandler($name); + + $this->assertTrue($l1->lock()); + $this->assertFalse($l2->lock()); + + $l1->release(); + + $this->assertTrue($l2->lock()); + $l2->release(); + } + + public function testLockTwice() + { + $name = 'symfony-test-filesystem.lock'; + + $lockHandler = new LockHandler($name); + + $this->assertTrue($lockHandler->lock()); + $this->assertTrue($lockHandler->lock()); + + $lockHandler->release(); + } + + public function testLockIsReleased() + { + $name = 'symfony-test-filesystem.lock'; + + $l1 = new LockHandler($name); + $l2 = new LockHandler($name); + + $this->assertTrue($l1->lock()); + $this->assertFalse($l2->lock()); + + $l1 = null; + + $this->assertTrue($l2->lock()); + $l2->release(); + } +} diff --git a/vendor/symfony/filesystem/composer.json b/vendor/symfony/filesystem/composer.json new file mode 100644 index 00000000..820d9f1b --- /dev/null +++ b/vendor/symfony/filesystem/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/filesystem", + "type": "library", + "description": "Symfony Filesystem Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Filesystem\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + } +} diff --git a/vendor/symfony/filesystem/phpunit.xml.dist b/vendor/symfony/filesystem/phpunit.xml.dist new file mode 100644 index 00000000..7c6ba7ab --- /dev/null +++ b/vendor/symfony/filesystem/phpunit.xml.dist @@ -0,0 +1,27 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + + + + diff --git a/vendor/symfony/yaml/.gitignore b/vendor/symfony/yaml/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/yaml/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/yaml/CHANGELOG.md b/vendor/symfony/yaml/CHANGELOG.md new file mode 100644 index 00000000..096cf654 --- /dev/null +++ b/vendor/symfony/yaml/CHANGELOG.md @@ -0,0 +1,8 @@ +CHANGELOG +========= + +2.1.0 +----- + + * Yaml::parse() does not evaluate loaded files as PHP files by default + anymore (call Yaml::enablePhpParsing() to get back the old behavior) diff --git a/vendor/symfony/yaml/Dumper.php b/vendor/symfony/yaml/Dumper.php new file mode 100644 index 00000000..39cdcfc5 --- /dev/null +++ b/vendor/symfony/yaml/Dumper.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\Yaml; + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @author Fabien Potencier + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + * + * @var int + */ + protected $indentation = 4; + + /** + * Sets the indentation. + * + * @param int $num The amount of spaces to use for indentation of nested nodes. + */ + public function setIndentation($num) + { + $this->indentation = (int) $num; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The level of indentation (used internally) + * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport true if object support is enabled, false otherwise + * + * @return string The YAML representation of the PHP value + */ + public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false) + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + + if ($inline <= 0 || !is_array($input) || empty($input)) { + $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport); + } else { + $isAHash = array_keys($input) !== range(0, count($input) - 1); + + foreach ($input as $key => $value) { + $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/vendor/symfony/yaml/Escaper.php b/vendor/symfony/yaml/Escaper.php new file mode 100644 index 00000000..ac325a2c --- /dev/null +++ b/vendor/symfony/yaml/Escaper.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\Yaml; + +/** + * Escaper encapsulates escaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + */ +class Escaper +{ + // Characters that would cause a dumped string to require double quoting. + const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + + // Mapping arrays for escaping a double quoted string. The backslash is + // first to ensure proper escaping because str_replace operates iteratively + // on the input arrays. This ordering of the characters avoids the use of strtr, + // which performs more slowly. + private static $escapees = array('\\', '\\\\', '\\"', '"', + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9",); + private static $escaped = array('\\\\', '\\"', '\\\\', '\\"', + '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', + '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', + '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\N', '\\_', '\\L', '\\P',); + + /** + * Determines if a PHP value would require double quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require double quotes. + */ + public static function requiresDoubleQuoting($value) + { + return preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); + } + + /** + * Escapes and surrounds a PHP value with double quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithDoubleQuotes($value) + { + return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value)); + } + + /** + * Determines if a PHP value would require single quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require single quotes. + */ + public static function requiresSingleQuoting($value) + { + // Determines if a PHP value is entirely composed of a value that would + // require single quoting in YAML. + if (in_array(strtolower($value), array('null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'))) { + return true; + } + + // Determines if the PHP value contains any single characters that would + // cause it to require single quoting in YAML. + return preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value); + } + + /** + * Escapes and surrounds a PHP value with single quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithSingleQuotes($value) + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/vendor/symfony/yaml/Exception/DumpException.php b/vendor/symfony/yaml/Exception/DumpException.php new file mode 100644 index 00000000..9b3e6de0 --- /dev/null +++ b/vendor/symfony/yaml/Exception/DumpException.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\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during dumping. + * + * @author Fabien Potencier + * + * @api + */ +class DumpException extends RuntimeException +{ +} diff --git a/vendor/symfony/yaml/Exception/ExceptionInterface.php b/vendor/symfony/yaml/Exception/ExceptionInterface.php new file mode 100644 index 00000000..92e5c2ea --- /dev/null +++ b/vendor/symfony/yaml/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\Yaml\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + * + * @api + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/yaml/Exception/ParseException.php b/vendor/symfony/yaml/Exception/ParseException.php new file mode 100644 index 00000000..0447dff1 --- /dev/null +++ b/vendor/symfony/yaml/Exception/ParseException.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\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier + * + * @api + */ +class ParseException extends RuntimeException +{ + private $parsedFile; + private $parsedLine; + private $snippet; + private $rawMessage; + + /** + * Constructor. + * + * @param string $message The error message + * @param int $parsedLine The line where the error occurred + * @param int $snippet The snippet of code near the problem + * @param string $parsedFile The file name where the error occurred + * @param \Exception $previous The previous exception + */ + public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null) + { + $this->parsedFile = $parsedFile; + $this->parsedLine = $parsedLine; + $this->snippet = $snippet; + $this->rawMessage = $message; + + $this->updateRepr(); + + parent::__construct($this->message, 0, $previous); + } + + /** + * Gets the snippet of code near the error. + * + * @return string The snippet of code + */ + public function getSnippet() + { + return $this->snippet; + } + + /** + * Sets the snippet of code near the error. + * + * @param string $snippet The code snippet + */ + public function setSnippet($snippet) + { + $this->snippet = $snippet; + + $this->updateRepr(); + } + + /** + * Gets the filename where the error occurred. + * + * This method returns null if a string is parsed. + * + * @return string The filename + */ + public function getParsedFile() + { + return $this->parsedFile; + } + + /** + * Sets the filename where the error occurred. + * + * @param string $parsedFile The filename + */ + public function setParsedFile($parsedFile) + { + $this->parsedFile = $parsedFile; + + $this->updateRepr(); + } + + /** + * Gets the line where the error occurred. + * + * @return int The file line + */ + public function getParsedLine() + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + * + * @param int $parsedLine The file line + */ + public function setParsedLine($parsedLine) + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr() + { + $this->message = $this->rawMessage; + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + if (PHP_VERSION_ID >= 50400) { + $jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } else { + $jsonOptions = 0; + } + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, $jsonOptions)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/vendor/symfony/yaml/Exception/RuntimeException.php b/vendor/symfony/yaml/Exception/RuntimeException.php new file mode 100644 index 00000000..3573bf15 --- /dev/null +++ b/vendor/symfony/yaml/Exception/RuntimeException.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\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Romain Neutron + * + * @api + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/yaml/Inline.php b/vendor/symfony/yaml/Inline.php new file mode 100644 index 00000000..6e3877bf --- /dev/null +++ b/vendor/symfony/yaml/Inline.php @@ -0,0 +1,546 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Exception\DumpException; + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @author Fabien Potencier + */ +class Inline +{ + const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')'; + + private static $exceptionOnInvalidType = false; + private static $objectSupport = false; + private static $objectForMap = false; + + /** + * Converts a YAML string to a PHP array. + * + * @param string $value A YAML string + * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport true if object support is enabled, false otherwise + * @param bool $objectForMap true if maps should return a stdClass instead of array() + * @param array $references Mapping of variable names to values + * + * @return array A PHP array representing the YAML string + * + * @throws ParseException + */ + public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array()) + { + self::$exceptionOnInvalidType = $exceptionOnInvalidType; + self::$objectSupport = $objectSupport; + self::$objectForMap = $objectForMap; + + $value = trim($value); + + if ('' === $value) { + return ''; + } + + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + $i = 0; + switch ($value[0]) { + case '[': + $result = self::parseSequence($value, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references); + } + + // some comments are allowed at the end + if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i))); + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + return $result; + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport true if object support is enabled, false otherwise + * + * @return string The YAML string representing the PHP array + * + * @throws DumpException When trying to dump PHP resource + */ + public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) + { + switch (true) { + case is_resource($value): + if ($exceptionOnInvalidType) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + + return 'null'; + case is_object($value): + if ($objectSupport) { + return '!!php/object:'.serialize($value); + } + + if ($exceptionOnInvalidType) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + + return 'null'; + case is_array($value): + return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport); + case null === $value: + return 'null'; + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case ctype_digit($value): + return is_string($value) ? "'$value'" : (int) $value; + case is_numeric($value): + $locale = setlocale(LC_NUMERIC, 0); + if (false !== $locale) { + setlocale(LC_NUMERIC, 'C'); + } + if (is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + // Preserve float data type since storing a whole number will result in integer value. + $repr = '!!float '.$repr; + } + } else { + $repr = is_string($value) ? "'$value'" : (string) $value; + } + if (false !== $locale) { + setlocale(LC_NUMERIC, $locale); + } + + return $repr; + case '' == $value: + return "''"; + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + case preg_match(self::getHexRegex(), $value): + case preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport true if object support is enabled, false otherwise + * + * @return string The YAML string representing the PHP array + */ + private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport) + { + // array + $keys = array_keys($value); + $keysCount = count($keys); + if ((1 === $keysCount && '0' == $keys[0]) + || ($keysCount > 1 && array_reduce($keys, function ($v, $w) { return (int) $v + $w; }, 0) === $keysCount * ($keysCount - 1) / 2) + ) { + $output = array(); + foreach ($value as $val) { + $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // mapping + $output = array(); + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + /** + * Parses a scalar to a YAML string. + * + * @param string $scalar + * @param string $delimiters + * @param array $stringDelimiters + * @param int &$i + * @param bool $evaluate + * @param array $references + * + * @return string A YAML string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array()) + { + if (in_array($scalar[$i], $stringDelimiters)) { + // quoted scalar + $output = self::parseQuotedScalar($scalar, $i); + + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), ' '); + if (!in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i))); + } + } + } else { + // "normal" string + if (!$delimiters) { + $output = substr($scalar, $i); + $i += strlen($output); + + // remove comments + if (false !== $strpos = strpos($output, ' #')) { + $output = rtrim(substr($output, 0, $strpos)); + } + } elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += strlen($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string (%s).', $scalar)); + } + + if ($evaluate) { + $output = self::evaluateScalar($output, $references); + } + } + + return $output; + } + + /** + * Parses a quoted scalar to YAML. + * + * @param string $scalar + * @param int &$i + * + * @return string A YAML string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar($scalar, &$i) + { + if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i))); + } + + $output = substr($match[0], 1, strlen($match[0]) - 2); + + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + + $i += strlen($match[0]); + + return $output; + } + + /** + * Parses a sequence to a YAML string. + * + * @param string $sequence + * @param int &$i + * @param array $references + * + * @return string A YAML string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence($sequence, &$i = 0, $references = array()) + { + $output = array(); + $len = strlen($sequence); + ++$i; + + // [foo, bar, ...] + while ($i < $len) { + switch ($sequence[$i]) { + case '[': + // nested sequence + $output[] = self::parseSequence($sequence, $i, $references); + break; + case '{': + // nested mapping + $output[] = self::parseMapping($sequence, $i, $references); + break; + case ']': + return $output; + case ',': + case ' ': + break; + default: + $isQuoted = in_array($sequence[$i], array('"', "'")); + $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references); + + // the value can be an array if a reference has been resolved to an array var + if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) { + // embedded mapping? + try { + $pos = 0; + $value = self::parseMapping('{'.$value.'}', $pos, $references); + } catch (\InvalidArgumentException $e) { + // no, it's not + } + } + + $output[] = $value; + + --$i; + } + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence)); + } + + /** + * Parses a mapping to a YAML string. + * + * @param string $mapping + * @param int &$i + * @param array $references + * + * @return string A YAML string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping($mapping, &$i = 0, $references = array()) + { + $output = array(); + $len = strlen($mapping); + ++$i; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + + return $output; + } + + // key + $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); + + // value + $done = false; + + while ($i < $len) { + switch ($mapping[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($mapping, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + if (!isset($output[$key])) { + $output[$key] = $value; + } + $done = true; + break; + case '{': + // nested mapping + $value = self::parseMapping($mapping, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + if (!isset($output[$key])) { + $output[$key] = $value; + } + $done = true; + break; + case ':': + case ' ': + break; + default: + $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + if (!isset($output[$key])) { + $output[$key] = $value; + } + $done = true; + --$i; + } + + ++$i; + + if ($done) { + continue 2; + } + } + } + + throw new ParseException(sprintf('Malformed inline YAML string %s', $mapping)); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @param string $scalar + * @param array $references + * + * @return string A YAML string + * + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved + */ + private static function evaluateScalar($scalar, $references = array()) + { + $scalar = trim($scalar); + $scalarLower = strtolower($scalar); + + if (0 === strpos($scalar, '*')) { + if (false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.'); + } + + if (!array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value)); + } + + return $references[$value]; + } + + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return; + case 'true' === $scalarLower: + return true; + case 'false' === $scalarLower: + return false; + // Optimise for returning strings. + case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]): + switch (true) { + case 0 === strpos($scalar, '!str'): + return (string) substr($scalar, 5); + case 0 === strpos($scalar, '! '): + return (int) self::parseScalar(substr($scalar, 2)); + case 0 === strpos($scalar, '!!php/object:'): + if (self::$objectSupport) { + return unserialize(substr($scalar, 13)); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.'); + } + + return; + case 0 === strpos($scalar, '!!float '): + return (float) substr($scalar, 8); + case ctype_digit($scalar): + $raw = $scalar; + $cast = (int) $scalar; + + return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $raw = $scalar; + $cast = (int) $scalar; + + return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); + case is_numeric($scalar): + case preg_match(self::getHexRegex(), $scalar): + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): + return (float) str_replace(',', '', $scalar); + case preg_match(self::getTimestampRegex(), $scalar): + return strtotime($scalar); + } + default: + return (string) $scalar; + } + } + + /** + * Gets a regex that matches a YAML date. + * + * @return string The regular expression + * + * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 + */ + private static function getTimestampRegex() + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } + + /** + * Gets a regex that matches a YAML number in hexadecimal notation. + * + * @return string + */ + private static function getHexRegex() + { + return '~^0x[0-9a-f]++$~i'; + } +} diff --git a/vendor/symfony/yaml/LICENSE b/vendor/symfony/yaml/LICENSE new file mode 100644 index 00000000..43028bc6 --- /dev/null +++ b/vendor/symfony/yaml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2015 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/yaml/Parser.php b/vendor/symfony/yaml/Parser.php new file mode 100644 index 00000000..651c0170 --- /dev/null +++ b/vendor/symfony/yaml/Parser.php @@ -0,0 +1,704 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @author Fabien Potencier + */ +class Parser +{ + const FOLDED_SCALAR_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + + private $offset = 0; + private $lines = array(); + private $currentLineNb = -1; + private $currentLine = ''; + private $refs = array(); + + /** + * Constructor. + * + * @param int $offset The offset of YAML document (used for line numbers in error messages) + */ + public function __construct($offset = 0) + { + $this->offset = $offset; + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport true if object support is enabled, false otherwise + * @param bool $objectForMap true if maps should return a stdClass instead of array() + * + * @return mixed A PHP value + * + * @throws ParseException If the YAML is not valid + */ + public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) + { + if (!preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.'); + } + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + } + + $data = array(); + $context = null; + $allowOverwrite = false; + while ($this->moveToNextLine()) { + if ($this->isCurrentLineEmpty()) { + continue; + } + + // tab? + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + $isRef = $mergeNode = false; + if (preg_match('#^\-((?P\s+)(?P.+?))?\s*$#u', $this->currentLine, $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping'); + } + $context = 'sequence'; + + if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + // array + if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new self($c); + $parser->refs = &$this->refs; + $data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap); + } else { + if (isset($values['leadspaces']) + && preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $values['value'], $matches) + ) { + // this is a compact notation element, add to next block and parse + $c = $this->getRealCurrentLineNb(); + $parser = new self($c); + $parser->refs = &$this->refs; + + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1); + } + + $data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } else { + $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap); + } + } + if ($isRef) { + $this->refs[$isRef] = end($data); + } + } elseif (preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+?))?\s*$#u', $this->currentLine, $values) && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))) { + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence'); + } + $context = 'mapping'; + + // force correct settings + Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + // Convert float keys to strings, to avoid being converted to integers by PHP + if (is_float($key)) { + $key = (string) $key; + } + + if ('<<' === $key) { + $mergeNode = true; + $allowOverwrite = true; + if (isset($values['value']) && 0 === strpos($values['value'], '*')) { + $refName = substr($values['value'], 1); + if (!array_key_exists($refName, $this->refs)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + $refValue = $this->refs[$refName]; + + if (!is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + foreach ($refValue as $key => $value) { + if (!isset($data[$key])) { + $data[$key] = $value; + } + } + } else { + if (isset($values['value']) && $values['value'] !== '') { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $c = $this->getRealCurrentLineNb() + 1; + $parser = new self($c); + $parser->refs = &$this->refs; + $parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap); + + if (!is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + if (isset($parsed[0])) { + // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes + // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier + // in the sequence override keys specified in later mapping nodes. + foreach ($parsed as $parsedItem) { + if (!is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem); + } + + foreach ($parsedItem as $key => $value) { + if (!isset($data[$key])) { + $data[$key] = $value; + } + } + } + } else { + // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the + // current mapping, unless the key already exists in it. + foreach ($parsed as $key => $value) { + if (!isset($data[$key])) { + $data[$key] = $value; + } + } + } + } + } elseif (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + if ($mergeNode) { + // Merge keys + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + // hash + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = null; + } + } else { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new self($c); + $parser->refs = &$this->refs; + $value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } + } + } else { + $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + } + } else { + // multiple documents are not supported + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.'); + } + + // 1-liner optionally followed by newline(s) + if (is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + if (is_array($value)) { + $first = reset($value); + if (is_string($first) && 0 === strpos($first, '*')) { + $data = array(); + foreach ($value as $alias) { + $data[] = $this->refs[substr($alias, 1)]; + } + $value = $data; + } + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + return $value; + } + + switch (preg_last_error()) { + case PREG_INTERNAL_ERROR: + $error = 'Internal PCRE error.'; + break; + case PREG_BACKTRACK_LIMIT_ERROR: + $error = 'pcre.backtrack_limit reached.'; + break; + case PREG_RECURSION_LIMIT_ERROR: + $error = 'pcre.recursion_limit reached.'; + break; + case PREG_BAD_UTF8_ERROR: + $error = 'Malformed UTF-8 data.'; + break; + case PREG_BAD_UTF8_OFFSET_ERROR: + $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; + break; + default: + $error = 'Unable to parse.'; + } + + throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + return empty($data) ? null : $data; + } + + /** + * Returns the current line number (takes the offset into account). + * + * @return int The current line number + */ + private function getRealCurrentLineNb() + { + return $this->currentLineNb + $this->offset; + } + + /** + * Returns the current line indentation. + * + * @return int The current line indentation + */ + private function getCurrentLineIndentation() + { + return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param int $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence + * + * @return string A YAML string + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock($indentation = null, $inSequence = false) + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + + if (!$this->moveToNextLine()) { + return; + } + + if (null === $indentation) { + $newIndent = $this->getCurrentLineIndentation(); + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem($this->currentLine); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + $newIndent = $indentation; + } + + $data = array(); + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } else { + $this->moveToPreviousLine(); + + return; + } + + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return; + } + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine); + + // Comments must not be removed inside a string block (ie. after a line ending with "|") + $removeCommentsPattern = '~'.self::FOLDED_SCALAR_PATTERN.'$~'; + $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); + + while ($this->moveToNextLine()) { + $indent = $this->getCurrentLineIndentation(); + + if ($indent === $newIndent) { + $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); + } + + if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine) && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + + if ($removeComments && $this->isCurrentLineComment()) { + continue; + } + + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + + return implode("\n", $data); + } + + /** + * Moves the parser to the next line. + * + * @return bool + */ + private function moveToNextLine() + { + if ($this->currentLineNb >= count($this->lines) - 1) { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + private function moveToPreviousLine() + { + $this->currentLine = $this->lines[--$this->currentLineNb]; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap true if maps should return a stdClass instead of array() + * + * @return mixed A PHP value + * + * @throws ParseException When reference does not exist + */ + private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap) + { + if (0 === strpos($value, '*')) { + if (false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + + if (!array_key_exists($value, $this->refs)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLine); + } + + return $this->refs[$value]; + } + + if (preg_match('/^'.self::FOLDED_SCALAR_PATTERN.'$/', $value, $matches)) { + $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; + + return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers)); + } + + try { + return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a folded scalar. + * + * @param string $separator The separator that was used to begin this folded scalar (| or >) + * @param string $indicator The indicator that was used to begin this folded scalar (+ or -) + * @param int $indentation The indentation that was used to begin this folded scalar + * + * @return string The text value + */ + private function parseFoldedScalar($separator, $indicator = '', $indentation = 0) + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $text = ''; + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $text .= "\n"; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + if (preg_match('/^ +/', $this->currentLine, $matches)) { + $indentation = strlen($matches[0]); + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank || + preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank) { + $text .= substr($this->currentLine, $indentation); + } else { + $text .= $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $text .= "\n"; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $text .= "\n"; + } + + if ($notEOF) { + $this->moveToPreviousLine(); + } + + // replace all non-trailing single newlines with spaces in folded blocks + if ('>' === $separator) { + preg_match('/(\n*)$/', $text, $matches); + $text = preg_replace('/(?getCurrentLineIndentation(); + $EOF = !$this->moveToNextLine(); + + while (!$EOF && $this->isCurrentLineEmpty()) { + $EOF = !$this->moveToNextLine(); + } + + if ($EOF) { + return false; + } + + $ret = false; + if ($this->getCurrentLineIndentation() > $currentIndentation) { + $ret = true; + } + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise + */ + private function isCurrentLineEmpty() + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + /** + * Returns true if the current line is blank. + * + * @return bool Returns true if the current line is blank, false otherwise + */ + private function isCurrentLineBlank() + { + return '' == trim($this->currentLine, ' '); + } + + /** + * Returns true if the current line is a comment line. + * + * @return bool Returns true if the current line is a comment line, false otherwise + */ + private function isCurrentLineComment() + { + //checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = ltrim($this->currentLine, ' '); + + return $ltrimmedLine[0] === '#'; + } + + /** + * Cleanups a YAML string to be parsed. + * + * @param string $value The input YAML string + * + * @return string A cleaned up YAML string + */ + private function cleanup($value) + { + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if ($count == 1) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if ($count == 1) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + + return $value; + } + + /** + * Returns true if the next line starts unindented collection. + * + * @return bool Returns true if the next line starts unindented collection, false otherwise + */ + private function isNextLineUnIndentedCollection() + { + $currentIndentation = $this->getCurrentLineIndentation(); + $notEOF = $this->moveToNextLine(); + + while ($notEOF && $this->isCurrentLineEmpty()) { + $notEOF = $this->moveToNextLine(); + } + + if (false === $notEOF) { + return false; + } + + $ret = false; + if ( + $this->getCurrentLineIndentation() == $currentIndentation + && + $this->isStringUnIndentedCollectionItem($this->currentLine) + ) { + $ret = true; + } + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the string is un-indented collection item. + * + * @return bool Returns true if the string is un-indented collection item, false otherwise + */ + private function isStringUnIndentedCollectionItem() + { + return (0 === strpos($this->currentLine, '- ')); + } +} diff --git a/vendor/symfony/yaml/README.md b/vendor/symfony/yaml/README.md new file mode 100644 index 00000000..85a97867 --- /dev/null +++ b/vendor/symfony/yaml/README.md @@ -0,0 +1,21 @@ +Yaml Component +============== + +YAML implements most of the YAML 1.2 specification. + +```php +use Symfony\Component\Yaml\Yaml; + +$array = Yaml::parse(file_get_contents(filename)); + +print Yaml::dump($array); +``` + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Yaml/ + $ composer install + $ phpunit diff --git a/vendor/symfony/yaml/Tests/DumperTest.php b/vendor/symfony/yaml/Tests/DumperTest.php new file mode 100644 index 00000000..b103b9a1 --- /dev/null +++ b/vendor/symfony/yaml/Tests/DumperTest.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\Yaml\Tests; + +use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Dumper; + +class DumperTest extends \PHPUnit_Framework_TestCase +{ + protected $parser; + protected $dumper; + protected $path; + + protected $array = array( + '' => 'bar', + 'foo' => '#bar', + 'foo\'bar' => array(), + 'bar' => array(1, 'foo'), + 'foobar' => array( + 'foo' => 'bar', + 'bar' => array(1, 'foo'), + 'foobar' => array( + 'foo' => 'bar', + 'bar' => array(1, 'foo'), + ), + ), + ); + + protected function setUp() + { + $this->parser = new Parser(); + $this->dumper = new Dumper(); + $this->path = __DIR__.'/Fixtures'; + } + + protected function tearDown() + { + $this->parser = null; + $this->dumper = null; + $this->path = null; + $this->array = null; + } + + public function testSetIndentation() + { + $this->dumper->setIndentation(7); + + $expected = <<assertEquals($expected, $this->dumper->dump($this->array, 4, 0)); + } + + public function testSpecifications() + { + $files = $this->parser->parse(file_get_contents($this->path.'/index.yml')); + foreach ($files as $file) { + $yamls = file_get_contents($this->path.'/'.$file.'.yml'); + + // split YAMLs documents + foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) { + if (!$yaml) { + continue; + } + + $test = $this->parser->parse($yaml); + if (isset($test['dump_skip']) && $test['dump_skip']) { + continue; + } elseif (isset($test['todo']) && $test['todo']) { + // TODO + } else { + eval('$expected = '.trim($test['php']).';'); + $this->assertSame($expected, $this->parser->parse($this->dumper->dump($expected, 10)), $test['test']); + } + } + } + } + + public function testInlineLevel() + { + $expected = <<assertEquals($expected, $this->dumper->dump($this->array, -10), '->dump() takes an inline level argument'); + $this->assertEquals($expected, $this->dumper->dump($this->array, 0), '->dump() takes an inline level argument'); + + $expected = <<assertEquals($expected, $this->dumper->dump($this->array, 1), '->dump() takes an inline level argument'); + + $expected = <<assertEquals($expected, $this->dumper->dump($this->array, 2), '->dump() takes an inline level argument'); + + $expected = <<assertEquals($expected, $this->dumper->dump($this->array, 3), '->dump() takes an inline level argument'); + + $expected = <<assertEquals($expected, $this->dumper->dump($this->array, 4), '->dump() takes an inline level argument'); + $this->assertEquals($expected, $this->dumper->dump($this->array, 10), '->dump() takes an inline level argument'); + } + + public function testObjectSupportEnabled() + { + $dump = $this->dumper->dump(array('foo' => new A(), 'bar' => 1), 0, 0, false, true); + + $this->assertEquals('{ foo: !!php/object:O:30:"Symfony\Component\Yaml\Tests\A":1:{s:1:"a";s:3:"foo";}, bar: 1 }', $dump, '->dump() is able to dump objects'); + } + + public function testObjectSupportDisabledButNoExceptions() + { + $dump = $this->dumper->dump(array('foo' => new A(), 'bar' => 1)); + + $this->assertEquals('{ foo: null, bar: 1 }', $dump, '->dump() does not dump objects when disabled'); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\DumpException + */ + public function testObjectSupportDisabledWithExceptions() + { + $this->dumper->dump(array('foo' => new A(), 'bar' => 1), 0, 0, true, false); + } + + /** + * @dataProvider getEscapeSequences + */ + public function testEscapedEscapeSequencesInQuotedScalar($input, $expected) + { + $this->assertEquals($expected, $this->dumper->dump($input)); + } + + public function getEscapeSequences() + { + return array( + 'null' => array("\t\\0", '"\t\\\\0"'), + 'bell' => array("\t\\a", '"\t\\\\a"'), + 'backspace' => array("\t\\b", '"\t\\\\b"'), + 'horizontal-tab' => array("\t\\t", '"\t\\\\t"'), + 'line-feed' => array("\t\\n", '"\t\\\\n"'), + 'vertical-tab' => array("\t\\v", '"\t\\\\v"'), + 'form-feed' => array("\t\\f", '"\t\\\\f"'), + 'carriage-return' => array("\t\\r", '"\t\\\\r"'), + 'escape' => array("\t\\e", '"\t\\\\e"'), + 'space' => array("\t\\ ", '"\t\\\\ "'), + 'double-quote' => array("\t\\\"", '"\t\\\\\\""'), + 'slash' => array("\t\\/", '"\t\\\\/"'), + 'backslash' => array("\t\\\\", '"\t\\\\\\\\"'), + 'next-line' => array("\t\\N", '"\t\\\\N"'), + 'non-breaking-space' => array("\t\\�", '"\t\\\\�"'), + 'line-separator' => array("\t\\L", '"\t\\\\L"'), + 'paragraph-separator' => array("\t\\P", '"\t\\\\P"'), + ); + } +} + +class A +{ + public $a = 'foo'; +} diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsAnchorAlias.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsAnchorAlias.yml new file mode 100644 index 00000000..5f9c9427 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsAnchorAlias.yml @@ -0,0 +1,31 @@ +--- %YAML:1.0 +test: Simple Alias Example +brief: > + If you need to refer to the same item of data twice, + you can give that item an alias. The alias is a plain + string, starting with an ampersand. The item may then + be referred to by the alias throughout your document + by using an asterisk before the name of the alias. + This is called an anchor. +yaml: | + - &showell Steve + - Clark + - Brian + - Oren + - *showell +php: | + array('Steve', 'Clark', 'Brian', 'Oren', 'Steve') + +--- +test: Alias of a Mapping +brief: > + An alias can be used on any item of data, including + sequences, mappings, and other complex data types. +yaml: | + - &hello + Meat: pork + Starch: potato + - banana + - *hello +php: | + array(array('Meat'=>'pork', 'Starch'=>'potato'), 'banana', array('Meat'=>'pork', 'Starch'=>'potato')) diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsBasicTests.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsBasicTests.yml new file mode 100644 index 00000000..dfd93021 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsBasicTests.yml @@ -0,0 +1,202 @@ +--- %YAML:1.0 +test: Simple Sequence +brief: | + You can specify a list in YAML by placing each + member of the list on a new line with an opening + dash. These lists are called sequences. +yaml: | + - apple + - banana + - carrot +php: | + array('apple', 'banana', 'carrot') +--- +test: Sequence With Item Being Null In The Middle +brief: | + You can specify a list in YAML by placing each + member of the list on a new line with an opening + dash. These lists are called sequences. +yaml: | + - apple + - + - carrot +php: | + array('apple', null, 'carrot') +--- +test: Sequence With Last Item Being Null +brief: | + You can specify a list in YAML by placing each + member of the list on a new line with an opening + dash. These lists are called sequences. +yaml: | + - apple + - banana + - +php: | + array('apple', 'banana', null) +--- +test: Nested Sequences +brief: | + You can include a sequence within another + sequence by giving the sequence an empty + dash, followed by an indented list. +yaml: | + - + - foo + - bar + - baz +php: | + array(array('foo', 'bar', 'baz')) +--- +test: Mixed Sequences +brief: | + Sequences can contain any YAML data, + including strings and other sequences. +yaml: | + - apple + - + - foo + - bar + - x123 + - banana + - carrot +php: | + array('apple', array('foo', 'bar', 'x123'), 'banana', 'carrot') +--- +test: Deeply Nested Sequences +brief: | + Sequences can be nested even deeper, with each + level of indentation representing a level of + depth. +yaml: | + - + - + - uno + - dos +php: | + array(array(array('uno', 'dos'))) +--- +test: Simple Mapping +brief: | + You can add a keyed list (also known as a dictionary or + hash) to your document by placing each member of the + list on a new line, with a colon separating the key + from its value. In YAML, this type of list is called + a mapping. +yaml: | + foo: whatever + bar: stuff +php: | + array('foo' => 'whatever', 'bar' => 'stuff') +--- +test: Sequence in a Mapping +brief: | + A value in a mapping can be a sequence. +yaml: | + foo: whatever + bar: + - uno + - dos +php: | + array('foo' => 'whatever', 'bar' => array('uno', 'dos')) +--- +test: Nested Mappings +brief: | + A value in a mapping can be another mapping. +yaml: | + foo: whatever + bar: + fruit: apple + name: steve + sport: baseball +php: | + array( + 'foo' => 'whatever', + 'bar' => array( + 'fruit' => 'apple', + 'name' => 'steve', + 'sport' => 'baseball' + ) + ) +--- +test: Mixed Mapping +brief: | + A mapping can contain any assortment + of mappings and sequences as values. +yaml: | + foo: whatever + bar: + - + fruit: apple + name: steve + sport: baseball + - more + - + python: rocks + perl: papers + ruby: scissorses +php: | + array( + 'foo' => 'whatever', + 'bar' => array( + array( + 'fruit' => 'apple', + 'name' => 'steve', + 'sport' => 'baseball' + ), + 'more', + array( + 'python' => 'rocks', + 'perl' => 'papers', + 'ruby' => 'scissorses' + ) + ) + ) +--- +test: Mapping-in-Sequence Shortcut +todo: true +brief: | + If you are adding a mapping to a sequence, you + can place the mapping on the same line as the + dash as a shortcut. +yaml: | + - work on YAML.py: + - work on Store +php: | + array(array('work on YAML.py' => array('work on Store'))) +--- +test: Sequence-in-Mapping Shortcut +todo: true +brief: | + The dash in a sequence counts as indentation, so + you can add a sequence inside of a mapping without + needing spaces as indentation. +yaml: | + allow: + - 'localhost' + - '%.sourceforge.net' + - '%.freepan.org' +php: | + array('allow' => array('localhost', '%.sourceforge.net', '%.freepan.org')) +--- +todo: true +test: Merge key +brief: | + A merge key ('<<') can be used in a mapping to insert other mappings. If + the value associated with the merge key is a mapping, each of its key/value + pairs is inserted into the current mapping. +yaml: | + mapping: + name: Joe + job: Accountant + <<: + age: 38 +php: | + array( + 'mapping' => + array( + 'name' => 'Joe', + 'job' => 'Accountant', + 'age' => 38 + ) + ) diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsBlockMapping.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsBlockMapping.yml new file mode 100644 index 00000000..f7ca469b --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsBlockMapping.yml @@ -0,0 +1,51 @@ +--- +test: One Element Mapping +brief: | + A mapping with one key/value pair +yaml: | + foo: bar +php: | + array('foo' => 'bar') +--- +test: Multi Element Mapping +brief: | + More than one key/value pair +yaml: | + red: baron + white: walls + blue: berries +php: | + array( + 'red' => 'baron', + 'white' => 'walls', + 'blue' => 'berries', + ) +--- +test: Values aligned +brief: | + Often times human editors of documents will align the values even + though YAML emitters generally don't. +yaml: | + red: baron + white: walls + blue: berries +php: | + array( + 'red' => 'baron', + 'white' => 'walls', + 'blue' => 'berries', + ) +--- +test: Colons aligned +brief: | + Spaces can come before the ': ' key/value separator. +yaml: | + red : baron + white : walls + blue : berries +php: | + array( + 'red' => 'baron', + 'white' => 'walls', + 'blue' => 'berries', + ) diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsDocumentSeparator.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsDocumentSeparator.yml new file mode 100644 index 00000000..d9881025 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsDocumentSeparator.yml @@ -0,0 +1,85 @@ +--- %YAML:1.0 +test: Trailing Document Separator +todo: true +brief: > + You can separate YAML documents + with a string of three dashes. +yaml: | + - foo: 1 + bar: 2 + --- + more: stuff +python: | + [ + [ { 'foo': 1, 'bar': 2 } ], + { 'more': 'stuff' } + ] +ruby: | + [ { 'foo' => 1, 'bar' => 2 } ] + +--- +test: Leading Document Separator +todo: true +brief: > + You can explicitly give an opening + document separator to your YAML stream. +yaml: | + --- + - foo: 1 + bar: 2 + --- + more: stuff +python: | + [ + [ {'foo': 1, 'bar': 2}], + {'more': 'stuff'} + ] +ruby: | + [ { 'foo' => 1, 'bar' => 2 } ] + +--- +test: YAML Header +todo: true +brief: > + The opening separator can contain directives + to the YAML parser, such as the version + number. +yaml: | + --- %YAML:1.0 + foo: 1 + bar: 2 +php: | + array('foo' => 1, 'bar' => 2) +documents: 1 + +--- +test: Red Herring Document Separator +brief: > + Separators included in blocks or strings + are treated as blocks or strings, as the + document separator should have no indentation + preceding it. +yaml: | + foo: | + --- +php: | + array('foo' => "---\n") + +--- +test: Multiple Document Separators in Block +brief: > + This technique allows you to embed other YAML + documents within literal blocks. +yaml: | + foo: | + --- + foo: bar + --- + yo: baz + bar: | + fooness +php: | + array( + 'foo' => "---\nfoo: bar\n---\nyo: baz\n", + 'bar' => "fooness\n" + ) diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsErrorTests.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsErrorTests.yml new file mode 100644 index 00000000..e8506fcb --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsErrorTests.yml @@ -0,0 +1,25 @@ +--- +test: Missing value for hash item +todo: true +brief: | + Third item in this hash doesn't have a value +yaml: | + okay: value + also okay: ~ + causes error because no value specified + last key: value okay here too +python-error: causes error because no value specified + +--- +test: Not indenting enough +brief: | + There was a bug in PyYaml where it was off by one + in the indentation check. It was allowing the YAML + below. +# This is actually valid YAML now. Someone should tell showell. +yaml: | + foo: + firstline: 1 + secondline: 2 +php: | + array('foo' => null, 'firstline' => 1, 'secondline' => 2) diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsFlowCollections.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsFlowCollections.yml new file mode 100644 index 00000000..03090e4a --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsFlowCollections.yml @@ -0,0 +1,60 @@ +--- +test: Simple Inline Array +brief: > + Sequences can be contained on a + single line, using the inline syntax. + Separate each entry with commas and + enclose in square brackets. +yaml: | + seq: [ a, b, c ] +php: | + array('seq' => array('a', 'b', 'c')) +--- +test: Simple Inline Hash +brief: > + Mapping can also be contained on + a single line, using the inline + syntax. Each key-value pair is + separated by a colon, with a comma + between each entry in the mapping. + Enclose with curly braces. +yaml: | + hash: { name: Steve, foo: bar } +php: | + array('hash' => array('name' => 'Steve', 'foo' => 'bar')) +--- +test: Multi-line Inline Collections +todo: true +brief: > + Both inline sequences and inline mappings + can span multiple lines, provided that you + indent the additional lines. +yaml: | + languages: [ Ruby, + Perl, + Python ] + websites: { YAML: yaml.org, + Ruby: ruby-lang.org, + Python: python.org, + Perl: use.perl.org } +php: | + array( + 'languages' => array('Ruby', 'Perl', 'Python'), + 'websites' => array( + 'YAML' => 'yaml.org', + 'Ruby' => 'ruby-lang.org', + 'Python' => 'python.org', + 'Perl' => 'use.perl.org' + ) + ) +--- +test: Commas in Values (not in the spec!) +todo: true +brief: > + List items in collections are delimited by commas, but + there must be a space after each comma. This allows you + to add numbers without quoting. +yaml: | + attendances: [ 45,123, 70,000, 17,222 ] +php: | + array('attendances' => array(45123, 70000, 17222)) diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsFoldedScalars.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsFoldedScalars.yml new file mode 100644 index 00000000..a14735a5 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsFoldedScalars.yml @@ -0,0 +1,176 @@ +--- %YAML:1.0 +test: Single ending newline +brief: > + A pipe character, followed by an indented + block of text is treated as a literal + block, in which newlines are preserved + throughout the block, including the final + newline. +yaml: | + --- + this: | + Foo + Bar +php: | + array('this' => "Foo\nBar\n") +--- +test: The '+' indicator +brief: > + The '+' indicator says to keep newlines at the end of text + blocks. +yaml: | + normal: | + extra new lines not kept + + preserving: |+ + extra new lines are kept + + + dummy: value +php: | + array( + 'normal' => "extra new lines not kept\n", + 'preserving' => "extra new lines are kept\n\n\n", + 'dummy' => 'value' + ) +--- +test: Three trailing newlines in literals +brief: > + To give you more control over how space + is preserved in text blocks, YAML has + the keep '+' and chomp '-' indicators. + The keep indicator will preserve all + ending newlines, while the chomp indicator + will strip all ending newlines. +yaml: | + clipped: | + This has one newline. + + + + same as "clipped" above: "This has one newline.\n" + + stripped: |- + This has no newline. + + + + same as "stripped" above: "This has no newline." + + kept: |+ + This has four newlines. + + + + same as "kept" above: "This has four newlines.\n\n\n\n" +php: | + array( + 'clipped' => "This has one newline.\n", + 'same as "clipped" above' => "This has one newline.\n", + 'stripped' => 'This has no newline.', + 'same as "stripped" above' => 'This has no newline.', + 'kept' => "This has four newlines.\n\n\n\n", + 'same as "kept" above' => "This has four newlines.\n\n\n\n" + ) +--- +test: Extra trailing newlines with spaces +todo: true +brief: > + Normally, only a single newline is kept + from the end of a literal block, unless the + keep '+' character is used in combination + with the pipe. The following example + will preserve all ending whitespace + since the last line of both literal blocks + contains spaces which extend past the indentation + level. +yaml: | + --- + this: | + Foo + + + kept: |+ + Foo + + +php: | + array('this' => "Foo\n\n \n", + 'kept' => "Foo\n\n \n" ) + +--- +test: Folded Block in a Sequence +brief: > + A greater-then character, followed by an indented + block of text is treated as a folded block, in + which lines of text separated by a single newline + are concatenated as a single line. +yaml: | + --- + - apple + - banana + - > + can't you see + the beauty of yaml? + hmm + - dog +php: | + array( + 'apple', + 'banana', + "can't you see the beauty of yaml? hmm\n", + 'dog' + ) +--- +test: Folded Block as a Mapping Value +brief: > + Both literal and folded blocks can be + used in collections, as values in a + sequence or a mapping. +yaml: | + --- + quote: > + Mark McGwire's + year was crippled + by a knee injury. + source: espn +php: | + array( + 'quote' => "Mark McGwire's year was crippled by a knee injury.\n", + 'source' => 'espn' + ) +--- +test: Three trailing newlines in folded blocks +brief: > + The keep and chomp indicators can also + be applied to folded blocks. +yaml: | + clipped: > + This has one newline. + + + + same as "clipped" above: "This has one newline.\n" + + stripped: >- + This has no newline. + + + + same as "stripped" above: "This has no newline." + + kept: >+ + This has four newlines. + + + + same as "kept" above: "This has four newlines.\n\n\n\n" +php: | + array( + 'clipped' => "This has one newline.\n", + 'same as "clipped" above' => "This has one newline.\n", + 'stripped' => 'This has no newline.', + 'same as "stripped" above' => 'This has no newline.', + 'kept' => "This has four newlines.\n\n\n\n", + 'same as "kept" above' => "This has four newlines.\n\n\n\n" + ) diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsNullsAndEmpties.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsNullsAndEmpties.yml new file mode 100644 index 00000000..9a5300f2 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsNullsAndEmpties.yml @@ -0,0 +1,45 @@ +--- %YAML:1.0 +test: Empty Sequence +brief: > + You can represent the empty sequence + with an empty inline sequence. +yaml: | + empty: [] +php: | + array('empty' => array()) +--- +test: Empty Mapping +brief: > + You can represent the empty mapping + with an empty inline mapping. +yaml: | + empty: {} +php: | + array('empty' => array()) +--- +test: Empty Sequence as Entire Document +yaml: | + [] +php: | + array() +--- +test: Empty Mapping as Entire Document +yaml: | + {} +php: | + array() +--- +test: Null as Document +yaml: | + ~ +php: | + null +--- +test: Empty String +brief: > + You can represent an empty string + with a pair of quotes. +yaml: | + '' +php: | + '' diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsSpecificationExamples.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsSpecificationExamples.yml new file mode 100644 index 00000000..0a8b5def --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsSpecificationExamples.yml @@ -0,0 +1,1697 @@ +--- %YAML:1.0 +test: Sequence of scalars +spec: 2.1 +yaml: | + - Mark McGwire + - Sammy Sosa + - Ken Griffey +php: | + array('Mark McGwire', 'Sammy Sosa', 'Ken Griffey') +--- +test: Mapping of scalars to scalars +spec: 2.2 +yaml: | + hr: 65 + avg: 0.278 + rbi: 147 +php: | + array('hr' => 65, 'avg' => 0.278, 'rbi' => 147) +--- +test: Mapping of scalars to sequences +spec: 2.3 +yaml: | + american: + - Boston Red Sox + - Detroit Tigers + - New York Yankees + national: + - New York Mets + - Chicago Cubs + - Atlanta Braves +php: | + array('american' => + array( 'Boston Red Sox', 'Detroit Tigers', + 'New York Yankees' ), + 'national' => + array( 'New York Mets', 'Chicago Cubs', + 'Atlanta Braves' ) + ) +--- +test: Sequence of mappings +spec: 2.4 +yaml: | + - + name: Mark McGwire + hr: 65 + avg: 0.278 + - + name: Sammy Sosa + hr: 63 + avg: 0.288 +php: | + array( + array('name' => 'Mark McGwire', 'hr' => 65, 'avg' => 0.278), + array('name' => 'Sammy Sosa', 'hr' => 63, 'avg' => 0.288) + ) +--- +test: Legacy A5 +todo: true +spec: legacy_A5 +yaml: | + ? + - New York Yankees + - Atlanta Braves + : + - 2001-07-02 + - 2001-08-12 + - 2001-08-14 + ? + - Detroit Tigers + - Chicago Cubs + : + - 2001-07-23 +perl-busted: > + YAML.pm will be able to emulate this behavior soon. In this regard + it may be somewhat more correct than Python's native behaviour which + can only use tuples as mapping keys. PyYAML will also need to figure + out some clever way to roundtrip structured keys. +python: | + [ + { + ('New York Yankees', 'Atlanta Braves'): + [yaml.timestamp('2001-07-02'), + yaml.timestamp('2001-08-12'), + yaml.timestamp('2001-08-14')], + ('Detroit Tigers', 'Chicago Cubs'): + [yaml.timestamp('2001-07-23')] + } + ] +ruby: | + { + [ 'New York Yankees', 'Atlanta Braves' ] => + [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ], + [ 'Detroit Tigers', 'Chicago Cubs' ] => + [ Date.new( 2001, 7, 23 ) ] + } +syck: | + struct test_node seq1[] = { + { T_STR, 0, "New York Yankees" }, + { T_STR, 0, "Atlanta Braves" }, + end_node + }; + struct test_node seq2[] = { + { T_STR, 0, "2001-07-02" }, + { T_STR, 0, "2001-08-12" }, + { T_STR, 0, "2001-08-14" }, + end_node + }; + struct test_node seq3[] = { + { T_STR, 0, "Detroit Tigers" }, + { T_STR, 0, "Chicago Cubs" }, + end_node + }; + struct test_node seq4[] = { + { T_STR, 0, "2001-07-23" }, + end_node + }; + struct test_node map[] = { + { T_SEQ, 0, 0, seq1 }, + { T_SEQ, 0, 0, seq2 }, + { T_SEQ, 0, 0, seq3 }, + { T_SEQ, 0, 0, seq4 }, + end_node + }; + struct test_node stream[] = { + { T_MAP, 0, 0, map }, + end_node + }; + +--- +test: Sequence of sequences +spec: 2.5 +yaml: | + - [ name , hr , avg ] + - [ Mark McGwire , 65 , 0.278 ] + - [ Sammy Sosa , 63 , 0.288 ] +php: | + array( + array( 'name', 'hr', 'avg' ), + array( 'Mark McGwire', 65, 0.278 ), + array( 'Sammy Sosa', 63, 0.288 ) + ) +--- +test: Mapping of mappings +todo: true +spec: 2.6 +yaml: | + Mark McGwire: {hr: 65, avg: 0.278} + Sammy Sosa: { + hr: 63, + avg: 0.288 + } +php: | + array( + 'Mark McGwire' => + array( 'hr' => 65, 'avg' => 0.278 ), + 'Sammy Sosa' => + array( 'hr' => 63, 'avg' => 0.288 ) + ) +--- +test: Two documents in a stream each with a leading comment +todo: true +spec: 2.7 +yaml: | + # Ranking of 1998 home runs + --- + - Mark McGwire + - Sammy Sosa + - Ken Griffey + + # Team ranking + --- + - Chicago Cubs + - St Louis Cardinals +ruby: | + y = YAML::Stream.new + y.add( [ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ] ) + y.add( [ 'Chicago Cubs', 'St Louis Cardinals' ] ) +documents: 2 + +--- +test: Play by play feed from a game +todo: true +spec: 2.8 +yaml: | + --- + time: 20:03:20 + player: Sammy Sosa + action: strike (miss) + ... + --- + time: 20:03:47 + player: Sammy Sosa + action: grand slam + ... +perl: | + [ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ] +documents: 2 + +--- +test: Single document with two comments +spec: 2.9 +yaml: | + hr: # 1998 hr ranking + - Mark McGwire + - Sammy Sosa + rbi: + # 1998 rbi ranking + - Sammy Sosa + - Ken Griffey +php: | + array( + 'hr' => array( 'Mark McGwire', 'Sammy Sosa' ), + 'rbi' => array( 'Sammy Sosa', 'Ken Griffey' ) + ) +--- +test: Node for Sammy Sosa appears twice in this document +spec: 2.10 +yaml: | + --- + hr: + - Mark McGwire + # Following node labeled SS + - &SS Sammy Sosa + rbi: + - *SS # Subsequent occurrence + - Ken Griffey +php: | + array( + 'hr' => + array('Mark McGwire', 'Sammy Sosa'), + 'rbi' => + array('Sammy Sosa', 'Ken Griffey') + ) +--- +test: Mapping between sequences +todo: true +spec: 2.11 +yaml: | + ? # PLAY SCHEDULE + - Detroit Tigers + - Chicago Cubs + : + - 2001-07-23 + + ? [ New York Yankees, + Atlanta Braves ] + : [ 2001-07-02, 2001-08-12, + 2001-08-14 ] +ruby: | + { + [ 'Detroit Tigers', 'Chicago Cubs' ] => [ Date.new( 2001, 7, 23 ) ], + [ 'New York Yankees', 'Atlanta Braves' ] => [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ] + } +syck: | + struct test_node seq1[] = { + { T_STR, 0, "New York Yankees" }, + { T_STR, 0, "Atlanta Braves" }, + end_node + }; + struct test_node seq2[] = { + { T_STR, 0, "2001-07-02" }, + { T_STR, 0, "2001-08-12" }, + { T_STR, 0, "2001-08-14" }, + end_node + }; + struct test_node seq3[] = { + { T_STR, 0, "Detroit Tigers" }, + { T_STR, 0, "Chicago Cubs" }, + end_node + }; + struct test_node seq4[] = { + { T_STR, 0, "2001-07-23" }, + end_node + }; + struct test_node map[] = { + { T_SEQ, 0, 0, seq3 }, + { T_SEQ, 0, 0, seq4 }, + { T_SEQ, 0, 0, seq1 }, + { T_SEQ, 0, 0, seq2 }, + end_node + }; + struct test_node stream[] = { + { T_MAP, 0, 0, map }, + end_node + }; + +--- +test: Sequence key shortcut +spec: 2.12 +yaml: | + --- + # products purchased + - item : Super Hoop + quantity: 1 + - item : Basketball + quantity: 4 + - item : Big Shoes + quantity: 1 +php: | + array ( + array ( + 'item' => 'Super Hoop', + 'quantity' => 1, + ), + array ( + 'item' => 'Basketball', + 'quantity' => 4, + ), + array ( + 'item' => 'Big Shoes', + 'quantity' => 1, + ) + ) +perl: | + [ + { item => 'Super Hoop', quantity => 1 }, + { item => 'Basketball', quantity => 4 }, + { item => 'Big Shoes', quantity => 1 } + ] + +ruby: | + [ + { 'item' => 'Super Hoop', 'quantity' => 1 }, + { 'item' => 'Basketball', 'quantity' => 4 }, + { 'item' => 'Big Shoes', 'quantity' => 1 } + ] +python: | + [ + { 'item': 'Super Hoop', 'quantity': 1 }, + { 'item': 'Basketball', 'quantity': 4 }, + { 'item': 'Big Shoes', 'quantity': 1 } + ] +syck: | + struct test_node map1[] = { + { T_STR, 0, "item" }, + { T_STR, 0, "Super Hoop" }, + { T_STR, 0, "quantity" }, + { T_STR, 0, "1" }, + end_node + }; + struct test_node map2[] = { + { T_STR, 0, "item" }, + { T_STR, 0, "Basketball" }, + { T_STR, 0, "quantity" }, + { T_STR, 0, "4" }, + end_node + }; + struct test_node map3[] = { + { T_STR, 0, "item" }, + { T_STR, 0, "Big Shoes" }, + { T_STR, 0, "quantity" }, + { T_STR, 0, "1" }, + end_node + }; + struct test_node seq[] = { + { T_MAP, 0, 0, map1 }, + { T_MAP, 0, 0, map2 }, + { T_MAP, 0, 0, map3 }, + end_node + }; + struct test_node stream[] = { + { T_SEQ, 0, 0, seq }, + end_node + }; + + +--- +test: Literal perserves newlines +todo: true +spec: 2.13 +yaml: | + # ASCII Art + --- | + \//||\/|| + // || ||_ +perl: | + "\\//||\\/||\n// || ||_\n" +ruby: | + "\\//||\\/||\n// || ||_\n" +python: | + [ + flushLeft( + """ + \//||\/|| + // || ||_ + """ + ) + ] +syck: | + struct test_node stream[] = { + { T_STR, 0, "\\//||\\/||\n// || ||_\n" }, + end_node + }; + +--- +test: Folded treats newlines as a space +todo: true +spec: 2.14 +yaml: | + --- + Mark McGwire's + year was crippled + by a knee injury. +perl: | + "Mark McGwire's year was crippled by a knee injury." +ruby: | + "Mark McGwire's year was crippled by a knee injury." +python: | + [ "Mark McGwire's year was crippled by a knee injury." ] +syck: | + struct test_node stream[] = { + { T_STR, 0, "Mark McGwire's year was crippled by a knee injury." }, + end_node + }; + +--- +test: Newlines preserved for indented and blank lines +todo: true +spec: 2.15 +yaml: | + --- > + Sammy Sosa completed another + fine season with great stats. + + 63 Home Runs + 0.288 Batting Average + + What a year! +perl: | + "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" +ruby: | + "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" +python: | + [ + flushLeft( + """ + Sammy Sosa completed another fine season with great stats. + + 63 Home Runs + 0.288 Batting Average + + What a year! + """ + ) + ] +syck: | + struct test_node stream[] = { + { T_STR, 0, "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" }, + end_node + }; + + +--- +test: Indentation determines scope +spec: 2.16 +yaml: | + name: Mark McGwire + accomplishment: > + Mark set a major league + home run record in 1998. + stats: | + 65 Home Runs + 0.278 Batting Average +php: | + array( + 'name' => 'Mark McGwire', + 'accomplishment' => "Mark set a major league home run record in 1998.\n", + 'stats' => "65 Home Runs\n0.278 Batting Average\n" + ) +--- +test: Quoted scalars +todo: true +spec: 2.17 +yaml: | + unicode: "Sosa did fine.\u263A" + control: "\b1998\t1999\t2000\n" + hexesc: "\x0D\x0A is \r\n" + + single: '"Howdy!" he cried.' + quoted: ' # not a ''comment''.' + tie-fighter: '|\-*-/|' +ruby: | + { + "tie-fighter" => "|\\-*-/|", + "control"=>"\0101998\t1999\t2000\n", + "unicode"=>"Sosa did fine." + ["263A".hex ].pack('U*'), + "quoted"=>" # not a 'comment'.", + "single"=>"\"Howdy!\" he cried.", + "hexesc"=>"\r\n is \r\n" + } +--- +test: Multiline flow scalars +todo: true +spec: 2.18 +yaml: | + plain: + This unquoted scalar + spans many lines. + + quoted: "So does this + quoted scalar.\n" +ruby: | + { + 'plain' => 'This unquoted scalar spans many lines.', + 'quoted' => "So does this quoted scalar.\n" + } +--- +test: Integers +spec: 2.19 +yaml: | + canonical: 12345 + decimal: +12,345 + octal: 014 + hexadecimal: 0xC +php: | + array( + 'canonical' => 12345, + 'decimal' => 12345, + 'octal' => 014, + 'hexadecimal' => 0xC + ) +--- +# FIX: spec shows parens around -inf and NaN +test: Floating point +spec: 2.20 +yaml: | + canonical: 1.23015e+3 + exponential: 12.3015e+02 + fixed: 1,230.15 + negative infinity: -.inf + not a number: .NaN + float as whole number: !!float 1 +php: | + array( + 'canonical' => 1230.15, + 'exponential' => 1230.15, + 'fixed' => 1230.15, + 'negative infinity' => log(0), + 'not a number' => -log(0), + 'float as whole number' => (float) 1 + ) +--- +test: Miscellaneous +spec: 2.21 +yaml: | + null: ~ + true: true + false: false + string: '12345' +php: | + array( + '' => null, + 1 => true, + 0 => false, + 'string' => '12345' + ) +--- +test: Timestamps +todo: true +spec: 2.22 +yaml: | + canonical: 2001-12-15T02:59:43.1Z + iso8601: 2001-12-14t21:59:43.10-05:00 + spaced: 2001-12-14 21:59:43.10 -05:00 + date: 2002-12-14 # Time is noon UTC +php: | + array( + 'canonical' => YAML::mktime( 2001, 12, 15, 2, 59, 43, 0.10 ), + 'iso8601' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'spaced' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'date' => Date.new( 2002, 12, 14 ) + ) +--- +test: legacy Timestamps test +todo: true +spec: legacy D4 +yaml: | + canonical: 2001-12-15T02:59:43.00Z + iso8601: 2001-02-28t21:59:43.00-05:00 + spaced: 2001-12-14 21:59:43.00 -05:00 + date: 2002-12-14 +php: | + array( + 'canonical' => Time::utc( 2001, 12, 15, 2, 59, 43, 0 ), + 'iso8601' => YAML::mktime( 2001, 2, 28, 21, 59, 43, 0, "-05:00" ), + 'spaced' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0, "-05:00" ), + 'date' => Date.new( 2002, 12, 14 ) + ) +--- +test: Various explicit families +todo: true +spec: 2.23 +yaml: | + not-date: !str 2002-04-28 + picture: !binary | + R0lGODlhDAAMAIQAAP//9/X + 17unp5WZmZgAAAOfn515eXv + Pz7Y6OjuDg4J+fn5OTk6enp + 56enmleECcgggoBADs= + + application specific tag: !!something | + The semantics of the tag + above may be different for + different documents. + +ruby-setup: | + YAML.add_private_type( "something" ) do |type, val| + "SOMETHING: #{val}" + end +ruby: | + { + 'not-date' => '2002-04-28', + 'picture' => "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236i^\020' \202\n\001\000;", + 'application specific tag' => "SOMETHING: The semantics of the tag\nabove may be different for\ndifferent documents.\n" + } +--- +test: Application specific family +todo: true +spec: 2.24 +yaml: | + # Establish a tag prefix + --- !clarkevans.com,2002/graph/^shape + # Use the prefix: shorthand for + # !clarkevans.com,2002/graph/circle + - !^circle + center: &ORIGIN {x: 73, 'y': 129} + radius: 7 + - !^line # !clarkevans.com,2002/graph/line + start: *ORIGIN + finish: { x: 89, 'y': 102 } + - !^label + start: *ORIGIN + color: 0xFFEEBB + value: Pretty vector drawing. +ruby-setup: | + YAML.add_domain_type( "clarkevans.com,2002", 'graph/shape' ) { |type, val| + if Array === val + val << "Shape Container" + val + else + raise YAML::Error, "Invalid graph of class #{ val.class }: " + val.inspect + end + } + one_shape_proc = Proc.new { |type, val| + scheme, domain, type = type.split( /:/, 3 ) + if val.is_a? ::Hash + val['TYPE'] = "Shape: #{type}" + val + else + raise YAML::Error, "Invalid graph of class #{ val.class }: " + val.inspect + end + } + YAML.add_domain_type( "clarkevans.com,2002", 'graph/circle', &one_shape_proc ) + YAML.add_domain_type( "clarkevans.com,2002", 'graph/line', &one_shape_proc ) + YAML.add_domain_type( "clarkevans.com,2002", 'graph/label', &one_shape_proc ) +ruby: | + [ + { + "radius" => 7, + "center"=> + { + "x" => 73, + "y" => 129 + }, + "TYPE" => "Shape: graph/circle" + }, { + "finish" => + { + "x" => 89, + "y" => 102 + }, + "TYPE" => "Shape: graph/line", + "start" => + { + "x" => 73, + "y" => 129 + } + }, { + "TYPE" => "Shape: graph/label", + "value" => "Pretty vector drawing.", + "start" => + { + "x" => 73, + "y" => 129 + }, + "color" => 16772795 + }, + "Shape Container" + ] +# --- +# test: Unordered set +# spec: 2.25 +# yaml: | +# # sets are represented as a +# # mapping where each key is +# # associated with the empty string +# --- !set +# ? Mark McGwire +# ? Sammy Sosa +# ? Ken Griff +--- +test: Ordered mappings +todo: true +spec: 2.26 +yaml: | + # ordered maps are represented as + # a sequence of mappings, with + # each mapping having one key + --- !omap + - Mark McGwire: 65 + - Sammy Sosa: 63 + - Ken Griffy: 58 +ruby: | + YAML::Omap[ + 'Mark McGwire', 65, + 'Sammy Sosa', 63, + 'Ken Griffy', 58 + ] +--- +test: Invoice +dump_skip: true +spec: 2.27 +yaml: | + --- !clarkevans.com,2002/^invoice + invoice: 34843 + date : 2001-01-23 + bill-to: &id001 + given : Chris + family : Dumars + address: + lines: | + 458 Walkman Dr. + Suite #292 + city : Royal Oak + state : MI + postal : 48046 + ship-to: *id001 + product: + - + sku : BL394D + quantity : 4 + description : Basketball + price : 450.00 + - + sku : BL4438H + quantity : 1 + description : Super Hoop + price : 2392.00 + tax : 251.42 + total: 4443.52 + comments: > + Late afternoon is best. + Backup contact is Nancy + Billsmer @ 338-4338. +php: | + array( + 'invoice' => 34843, 'date' => mktime(0, 0, 0, 1, 23, 2001), + 'bill-to' => + array( 'given' => 'Chris', 'family' => 'Dumars', 'address' => array( 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak', 'state' => 'MI', 'postal' => 48046 ) ) + , 'ship-to' => + array( 'given' => 'Chris', 'family' => 'Dumars', 'address' => array( 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak', 'state' => 'MI', 'postal' => 48046 ) ) + , 'product' => + array( + array( 'sku' => 'BL394D', 'quantity' => 4, 'description' => 'Basketball', 'price' => 450.00 ), + array( 'sku' => 'BL4438H', 'quantity' => 1, 'description' => 'Super Hoop', 'price' => 2392.00 ) + ), + 'tax' => 251.42, 'total' => 4443.52, + 'comments' => "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.\n" + ) +--- +test: Log file +todo: true +spec: 2.28 +yaml: | + --- + Time: 2001-11-23 15:01:42 -05:00 + User: ed + Warning: > + This is an error message + for the log file + --- + Time: 2001-11-23 15:02:31 -05:00 + User: ed + Warning: > + A slightly different error + message. + --- + Date: 2001-11-23 15:03:17 -05:00 + User: ed + Fatal: > + Unknown variable "bar" + Stack: + - file: TopClass.py + line: 23 + code: | + x = MoreObject("345\n") + - file: MoreClass.py + line: 58 + code: |- + foo = bar +ruby: | + y = YAML::Stream.new + y.add( { 'Time' => YAML::mktime( 2001, 11, 23, 15, 01, 42, 00, "-05:00" ), + 'User' => 'ed', 'Warning' => "This is an error message for the log file\n" } ) + y.add( { 'Time' => YAML::mktime( 2001, 11, 23, 15, 02, 31, 00, "-05:00" ), + 'User' => 'ed', 'Warning' => "A slightly different error message.\n" } ) + y.add( { 'Date' => YAML::mktime( 2001, 11, 23, 15, 03, 17, 00, "-05:00" ), + 'User' => 'ed', 'Fatal' => "Unknown variable \"bar\"\n", + 'Stack' => [ + { 'file' => 'TopClass.py', 'line' => 23, 'code' => "x = MoreObject(\"345\\n\")\n" }, + { 'file' => 'MoreClass.py', 'line' => 58, 'code' => "foo = bar" } ] } ) +documents: 3 + +--- +test: Throwaway comments +yaml: | + ### These are four throwaway comment ### + + ### lines (the second line is empty). ### + this: | # Comments may trail lines. + contains three lines of text. + The third one starts with a + # character. This isn't a comment. + + # These are three throwaway comment + # lines (the first line is empty). +php: | + array( + 'this' => "contains three lines of text.\nThe third one starts with a\n# character. This isn't a comment.\n" + ) +--- +test: Document with a single value +todo: true +yaml: | + --- > + This YAML stream contains a single text value. + The next stream is a log file - a sequence of + log entries. Adding an entry to the log is a + simple matter of appending it at the end. +ruby: | + "This YAML stream contains a single text value. The next stream is a log file - a sequence of log entries. Adding an entry to the log is a simple matter of appending it at the end.\n" +--- +test: Document stream +todo: true +yaml: | + --- + at: 2001-08-12 09:25:00.00 Z + type: GET + HTTP: '1.0' + url: '/index.html' + --- + at: 2001-08-12 09:25:10.00 Z + type: GET + HTTP: '1.0' + url: '/toc.html' +ruby: | + y = YAML::Stream.new + y.add( { + 'at' => Time::utc( 2001, 8, 12, 9, 25, 00 ), + 'type' => 'GET', + 'HTTP' => '1.0', + 'url' => '/index.html' + } ) + y.add( { + 'at' => Time::utc( 2001, 8, 12, 9, 25, 10 ), + 'type' => 'GET', + 'HTTP' => '1.0', + 'url' => '/toc.html' + } ) +documents: 2 + +--- +test: Top level mapping +yaml: | + # This stream is an example of a top-level mapping. + invoice : 34843 + date : 2001-01-23 + total : 4443.52 +php: | + array( + 'invoice' => 34843, + 'date' => mktime(0, 0, 0, 1, 23, 2001), + 'total' => 4443.52 + ) +--- +test: Single-line documents +todo: true +yaml: | + # The following is a sequence of three documents. + # The first contains an empty mapping, the second + # an empty sequence, and the last an empty string. + --- {} + --- [ ] + --- '' +ruby: | + y = YAML::Stream.new + y.add( {} ) + y.add( [] ) + y.add( '' ) +documents: 3 + +--- +test: Document with pause +todo: true +yaml: | + # A communication channel based on a YAML stream. + --- + sent at: 2002-06-06 11:46:25.10 Z + payload: Whatever + # Receiver can process this as soon as the following is sent: + ... + # Even if the next message is sent long after: + --- + sent at: 2002-06-06 12:05:53.47 Z + payload: Whatever + ... +ruby: | + y = YAML::Stream.new + y.add( + { 'sent at' => YAML::mktime( 2002, 6, 6, 11, 46, 25, 0.10 ), + 'payload' => 'Whatever' } + ) + y.add( + { "payload" => "Whatever", "sent at" => YAML::mktime( 2002, 6, 6, 12, 5, 53, 0.47 ) } + ) +documents: 2 + +--- +test: Explicit typing +yaml: | + integer: 12 + also int: ! "12" + string: !str 12 +php: | + array( 'integer' => 12, 'also int' => 12, 'string' => '12' ) +--- +test: Private types +todo: true +yaml: | + # Both examples below make use of the 'x-private:ball' + # type family URI, but with different semantics. + --- + pool: !!ball + number: 8 + color: black + --- + bearing: !!ball + material: steel +ruby: | + y = YAML::Stream.new + y.add( { 'pool' => + YAML::PrivateType.new( 'ball', + { 'number' => 8, 'color' => 'black' } ) } + ) + y.add( { 'bearing' => + YAML::PrivateType.new( 'ball', + { 'material' => 'steel' } ) } + ) +documents: 2 + +--- +test: Type family under yaml.org +yaml: | + # The URI is 'tag:yaml.org,2002:str' + - !str a Unicode string +php: | + array( 'a Unicode string' ) +--- +test: Type family under perl.yaml.org +todo: true +yaml: | + # The URI is 'tag:perl.yaml.org,2002:Text::Tabs' + - !perl/Text::Tabs {} +ruby: | + [ YAML::DomainType.new( 'perl.yaml.org,2002', 'Text::Tabs', {} ) ] +--- +test: Type family under clarkevans.com +todo: true +yaml: | + # The URI is 'tag:clarkevans.com,2003-02:timesheet' + - !clarkevans.com,2003-02/timesheet {} +ruby: | + [ YAML::DomainType.new( 'clarkevans.com,2003-02', 'timesheet', {} ) ] +--- +test: URI Escaping +todo: true +yaml: | + same: + - !domain.tld,2002/type\x30 value + - !domain.tld,2002/type0 value + different: # As far as the YAML parser is concerned + - !domain.tld,2002/type%30 value + - !domain.tld,2002/type0 value +ruby-setup: | + YAML.add_domain_type( "domain.tld,2002", "type0" ) { |type, val| + "ONE: #{val}" + } + YAML.add_domain_type( "domain.tld,2002", "type%30" ) { |type, val| + "TWO: #{val}" + } +ruby: | + { 'same' => [ 'ONE: value', 'ONE: value' ], 'different' => [ 'TWO: value', 'ONE: value' ] } +--- +test: URI Prefixing +todo: true +yaml: | + # 'tag:domain.tld,2002:invoice' is some type family. + invoice: !domain.tld,2002/^invoice + # 'seq' is shorthand for 'tag:yaml.org,2002:seq'. + # This does not effect '^customer' below + # because it is does not specify a prefix. + customers: !seq + # '^customer' is shorthand for the full + # notation 'tag:domain.tld,2002:customer'. + - !^customer + given : Chris + family : Dumars +ruby-setup: | + YAML.add_domain_type( "domain.tld,2002", /(invoice|customer)/ ) { |type, val| + if val.is_a? ::Hash + scheme, domain, type = type.split( /:/, 3 ) + val['type'] = "domain #{type}" + val + else + raise YAML::Error, "Not a Hash in domain.tld/invoice: " + val.inspect + end + } +ruby: | + { "invoice"=> { "customers"=> [ { "given"=>"Chris", "type"=>"domain customer", "family"=>"Dumars" } ], "type"=>"domain invoice" } } + +--- +test: Overriding anchors +yaml: | + anchor : &A001 This scalar has an anchor. + override : &A001 > + The alias node below is a + repeated use of this value. + alias : *A001 +php: | + array( 'anchor' => 'This scalar has an anchor.', + 'override' => "The alias node below is a repeated use of this value.\n", + 'alias' => "The alias node below is a repeated use of this value.\n" ) +--- +test: Flow and block formatting +todo: true +yaml: | + empty: [] + flow: [ one, two, three # May span lines, + , four, # indentation is + five ] # mostly ignored. + block: + - First item in top sequence + - + - Subordinate sequence entry + - > + A folded sequence entry + - Sixth item in top sequence +ruby: | + { 'empty' => [], 'flow' => [ 'one', 'two', 'three', 'four', 'five' ], + 'block' => [ 'First item in top sequence', [ 'Subordinate sequence entry' ], + "A folded sequence entry\n", 'Sixth item in top sequence' ] } +--- +test: Complete mapping test +todo: true +yaml: | + empty: {} + flow: { one: 1, two: 2 } + spanning: { one: 1, + two: 2 } + block: + first : First entry + second: + key: Subordinate mapping + third: + - Subordinate sequence + - { } + - Previous mapping is empty. + - A key: value pair in a sequence. + A second: key:value pair. + - The previous entry is equal to the following one. + - + A key: value pair in a sequence. + A second: key:value pair. + !float 12 : This key is a float. + ? > + ? + : This key had to be protected. + "\a" : This key had to be escaped. + ? > + This is a + multi-line + folded key + : Whose value is + also multi-line. + ? this also works as a key + : with a value at the next line. + ? + - This key + - is a sequence + : + - With a sequence value. + ? + This: key + is a: mapping + : + with a: mapping value. +ruby: | + { 'empty' => {}, 'flow' => { 'one' => 1, 'two' => 2 }, + 'spanning' => { 'one' => 1, 'two' => 2 }, + 'block' => { 'first' => 'First entry', 'second' => + { 'key' => 'Subordinate mapping' }, 'third' => + [ 'Subordinate sequence', {}, 'Previous mapping is empty.', + { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' }, + 'The previous entry is equal to the following one.', + { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' } ], + 12.0 => 'This key is a float.', "?\n" => 'This key had to be protected.', + "\a" => 'This key had to be escaped.', + "This is a multi-line folded key\n" => "Whose value is also multi-line.", + 'this also works as a key' => 'with a value at the next line.', + [ 'This key', 'is a sequence' ] => [ 'With a sequence value.' ] } } + # Couldn't recreate map exactly, so we'll do a detailed check to be sure it's entact + obj_y['block'].keys.each { |k| + if Hash === k + v = obj_y['block'][k] + if k['This'] == 'key' and k['is a'] == 'mapping' and v['with a'] == 'mapping value.' + obj_r['block'][k] = v + end + end + } +--- +test: Literal explicit indentation +yaml: | + # Explicit indentation must + # be given in all the three + # following cases. + leading spaces: |2 + This value starts with four spaces. + + leading line break: |2 + + This value starts with a line break. + + leading comment indicator: |2 + # first line starts with a + # character. + + # Explicit indentation may + # also be given when it is + # not required. + redundant: |2 + This value is indented 2 spaces. +php: | + array( + 'leading spaces' => " This value starts with four spaces.\n", + 'leading line break' => "\nThis value starts with a line break.\n", + 'leading comment indicator' => "# first line starts with a\n# character.\n", + 'redundant' => "This value is indented 2 spaces.\n" + ) +--- +test: Chomping and keep modifiers +yaml: | + clipped: | + This has one newline. + + same as "clipped" above: "This has one newline.\n" + + stripped: |- + This has no newline. + + same as "stripped" above: "This has no newline." + + kept: |+ + This has two newlines. + + same as "kept" above: "This has two newlines.\n\n" +php: | + array( + 'clipped' => "This has one newline.\n", + 'same as "clipped" above' => "This has one newline.\n", + 'stripped' => 'This has no newline.', + 'same as "stripped" above' => 'This has no newline.', + 'kept' => "This has two newlines.\n\n", + 'same as "kept" above' => "This has two newlines.\n\n" + ) +--- +test: Literal combinations +todo: true +yaml: | + empty: | + + literal: | + The \ ' " characters may be + freely used. Leading white + space is significant. + + Line breaks are significant. + Thus this value contains one + empty line and ends with a + single line break, but does + not start with one. + + is equal to: "The \\ ' \" characters may \ + be\nfreely used. Leading white\n space \ + is significant.\n\nLine breaks are \ + significant.\nThus this value contains \ + one\nempty line and ends with a\nsingle \ + line break, but does\nnot start with one.\n" + + # Comments may follow a block + # scalar value. They must be + # less indented. + + # Modifiers may be combined in any order. + indented and chomped: |2- + This has no newline. + + also written as: |-2 + This has no newline. + + both are equal to: " This has no newline." +php: | + array( + 'empty' => '', + 'literal' => "The \\ ' \" characters may be\nfreely used. Leading white\n space " + + "is significant.\n\nLine breaks are significant.\nThus this value contains one\n" + + "empty line and ends with a\nsingle line break, but does\nnot start with one.\n", + 'is equal to' => "The \\ ' \" characters may be\nfreely used. Leading white\n space " + + "is significant.\n\nLine breaks are significant.\nThus this value contains one\n" + + "empty line and ends with a\nsingle line break, but does\nnot start with one.\n", + 'indented and chomped' => ' This has no newline.', + 'also written as' => ' This has no newline.', + 'both are equal to' => ' This has no newline.' + ) +--- +test: Folded combinations +todo: true +yaml: | + empty: > + + one paragraph: > + Line feeds are converted + to spaces, so this value + contains no line breaks + except for the final one. + + multiple paragraphs: >2 + + An empty line, either + at the start or in + the value: + + Is interpreted as a + line break. Thus this + value contains three + line breaks. + + indented text: > + This is a folded + paragraph followed + by a list: + * first entry + * second entry + Followed by another + folded paragraph, + another list: + + * first entry + + * second entry + + And a final folded + paragraph. + + above is equal to: | + This is a folded paragraph followed by a list: + * first entry + * second entry + Followed by another folded paragraph, another list: + + * first entry + + * second entry + + And a final folded paragraph. + + # Explicit comments may follow + # but must be less indented. +php: | + array( + 'empty' => '', + 'one paragraph' => 'Line feeds are converted to spaces, so this value'. + " contains no line breaks except for the final one.\n", + 'multiple paragraphs' => "\nAn empty line, either at the start or in the value:\n". + "Is interpreted as a line break. Thus this value contains three line breaks.\n", + 'indented text' => "This is a folded paragraph followed by a list:\n". + " * first entry\n * second entry\nFollowed by another folded paragraph, ". + "another list:\n\n * first entry\n\n * second entry\n\nAnd a final folded paragraph.\n", + 'above is equal to' => "This is a folded paragraph followed by a list:\n". + " * first entry\n * second entry\nFollowed by another folded paragraph, ". + "another list:\n\n * first entry\n\n * second entry\n\nAnd a final folded paragraph.\n" + ) +--- +test: Single quotes +todo: true +yaml: | + empty: '' + second: '! : \ etc. can be used freely.' + third: 'a single quote '' must be escaped.' + span: 'this contains + six spaces + + and one + line break' + is same as: "this contains six spaces\nand one line break" +php: | + array( + 'empty' => '', + 'second' => '! : \\ etc. can be used freely.', + 'third' => "a single quote ' must be escaped.", + 'span' => "this contains six spaces\nand one line break", + 'is same as' => "this contains six spaces\nand one line break" + ) +--- +test: Double quotes +todo: true +yaml: | + empty: "" + second: "! : etc. can be used freely." + third: "a \" or a \\ must be escaped." + fourth: "this value ends with an LF.\n" + span: "this contains + four \ + spaces" + is equal to: "this contains four spaces" +php: | + array( + 'empty' => '', + 'second' => '! : etc. can be used freely.', + 'third' => 'a " or a \\ must be escaped.', + 'fourth' => "this value ends with an LF.\n", + 'span' => "this contains four spaces", + 'is equal to' => "this contains four spaces" + ) +--- +test: Unquoted strings +todo: true +yaml: | + first: There is no unquoted empty string. + + second: 12 ## This is an integer. + + third: !str 12 ## This is a string. + + span: this contains + six spaces + + and one + line break + + indicators: this has no comments. + #:foo and bar# are + both text. + + flow: [ can span + lines, # comment + like + this ] + + note: { one-line keys: but multi-line values } + +php: | + array( + 'first' => 'There is no unquoted empty string.', + 'second' => 12, + 'third' => '12', + 'span' => "this contains six spaces\nand one line break", + 'indicators' => "this has no comments. #:foo and bar# are both text.", + 'flow' => [ 'can span lines', 'like this' ], + 'note' => { 'one-line keys' => 'but multi-line values' } + ) +--- +test: Spanning sequences +todo: true +yaml: | + # The following are equal seqs + # with different identities. + flow: [ one, two ] + spanning: [ one, + two ] + block: + - one + - two +php: | + array( + 'flow' => [ 'one', 'two' ], + 'spanning' => [ 'one', 'two' ], + 'block' => [ 'one', 'two' ] + ) +--- +test: Flow mappings +yaml: | + # The following are equal maps + # with different identities. + flow: { one: 1, two: 2 } + block: + one: 1 + two: 2 +php: | + array( + 'flow' => array( 'one' => 1, 'two' => 2 ), + 'block' => array( 'one' => 1, 'two' => 2 ) + ) +--- +test: Representations of 12 +todo: true +yaml: | + - 12 # An integer + # The following scalars + # are loaded to the + # string value '1' '2'. + - !str 12 + - '12' + - "12" + - "\ + 1\ + 2\ + " + # Strings containing paths and regexps can be unquoted: + - /foo/bar + - d:/foo/bar + - foo/bar + - /a.*b/ +php: | + array( 12, '12', '12', '12', '12', '/foo/bar', 'd:/foo/bar', 'foo/bar', '/a.*b/' ) +--- +test: "Null" +todo: true +yaml: | + canonical: ~ + + english: null + + # This sequence has five + # entries, two with values. + sparse: + - ~ + - 2nd entry + - Null + - 4th entry + - + + four: This mapping has five keys, + only two with values. + +php: | + array ( + 'canonical' => null, + 'english' => null, + 'sparse' => array( null, '2nd entry', null, '4th entry', null ]), + 'four' => 'This mapping has five keys, only two with values.' + ) +--- +test: Omap +todo: true +yaml: | + # Explicitly typed dictionary. + Bestiary: !omap + - aardvark: African pig-like ant eater. Ugly. + - anteater: South-American ant eater. Two species. + - anaconda: South-American constrictor snake. Scary. + # Etc. +ruby: | + { + 'Bestiary' => YAML::Omap[ + 'aardvark', 'African pig-like ant eater. Ugly.', + 'anteater', 'South-American ant eater. Two species.', + 'anaconda', 'South-American constrictor snake. Scary.' + ] + } + +--- +test: Pairs +todo: true +yaml: | + # Explicitly typed pairs. + tasks: !pairs + - meeting: with team. + - meeting: with boss. + - break: lunch. + - meeting: with client. +ruby: | + { + 'tasks' => YAML::Pairs[ + 'meeting', 'with team.', + 'meeting', 'with boss.', + 'break', 'lunch.', + 'meeting', 'with client.' + ] + } + +--- +test: Set +todo: true +yaml: | + # Explicitly typed set. + baseball players: !set + Mark McGwire: + Sammy Sosa: + Ken Griffey: +ruby: | + { + 'baseball players' => YAML::Set[ + 'Mark McGwire', nil, + 'Sammy Sosa', nil, + 'Ken Griffey', nil + ] + } + +--- +test: Boolean +yaml: | + false: used as key + logical: true + answer: false +php: | + array( + false => 'used as key', + 'logical' => true, + 'answer' => false + ) +--- +test: Integer +yaml: | + canonical: 12345 + decimal: +12,345 + octal: 014 + hexadecimal: 0xC +php: | + array( + 'canonical' => 12345, + 'decimal' => 12345, + 'octal' => 12, + 'hexadecimal' => 12 + ) +--- +test: Float +yaml: | + canonical: 1.23015e+3 + exponential: 12.3015e+02 + fixed: 1,230.15 + negative infinity: -.inf + not a number: .NaN +php: | + array( + 'canonical' => 1230.15, + 'exponential' => 1230.15, + 'fixed' => 1230.15, + 'negative infinity' => log(0), + 'not a number' => -log(0) + ) +--- +test: Timestamp +todo: true +yaml: | + canonical: 2001-12-15T02:59:43.1Z + valid iso8601: 2001-12-14t21:59:43.10-05:00 + space separated: 2001-12-14 21:59:43.10 -05:00 + date (noon UTC): 2002-12-14 +ruby: | + array( + 'canonical' => YAML::mktime( 2001, 12, 15, 2, 59, 43, 0.10 ), + 'valid iso8601' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'space separated' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'date (noon UTC)' => Date.new( 2002, 12, 14 ) + ) +--- +test: Binary +todo: true +yaml: | + canonical: !binary "\ + R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\ + OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\ + +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\ + AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=" + base64: !binary | + R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 + OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ + +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC + AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= + description: > + The binary value above is a tiny arrow + encoded as a gif image. +ruby-setup: | + arrow_gif = "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236iiiccc\243\243\243\204\204\204\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371!\376\016Made with GIMP\000,\000\000\000\000\f\000\f\000\000\005, \216\2010\236\343@\024\350i\020\304\321\212\010\034\317\200M$z\357\3770\205p\270\2601f\r\e\316\001\303\001\036\020' \202\n\001\000;" +ruby: | + { + 'canonical' => arrow_gif, + 'base64' => arrow_gif, + 'description' => "The binary value above is a tiny arrow encoded as a gif image.\n" + } + +--- +test: Merge key +todo: true +yaml: | + --- + - &CENTER { x: 1, y: 2 } + - &LEFT { x: 0, y: 2 } + - &BIG { r: 10 } + - &SMALL { r: 1 } + + # All the following maps are equal: + + - # Explicit keys + x: 1 + y: 2 + r: 10 + label: center/big + + - # Merge one map + << : *CENTER + r: 10 + label: center/big + + - # Merge multiple maps + << : [ *CENTER, *BIG ] + label: center/big + + - # Override + << : [ *BIG, *LEFT, *SMALL ] + x: 1 + label: center/big + +ruby-setup: | + center = { 'x' => 1, 'y' => 2 } + left = { 'x' => 0, 'y' => 2 } + big = { 'r' => 10 } + small = { 'r' => 1 } + node1 = { 'x' => 1, 'y' => 2, 'r' => 10, 'label' => 'center/big' } + node2 = center.dup + node2.update( { 'r' => 10, 'label' => 'center/big' } ) + node3 = big.dup + node3.update( center ) + node3.update( { 'label' => 'center/big' } ) + node4 = small.dup + node4.update( left ) + node4.update( big ) + node4.update( { 'x' => 1, 'label' => 'center/big' } ) + +ruby: | + [ + center, left, big, small, node1, node2, node3, node4 + ] + +--- +test: Default key +todo: true +yaml: | + --- # Old schema + link with: + - library1.dll + - library2.dll + --- # New schema + link with: + - = : library1.dll + version: 1.2 + - = : library2.dll + version: 2.3 +ruby: | + y = YAML::Stream.new + y.add( { 'link with' => [ 'library1.dll', 'library2.dll' ] } ) + obj_h = Hash[ 'version' => 1.2 ] + obj_h.default = 'library1.dll' + obj_h2 = Hash[ 'version' => 2.3 ] + obj_h2.default = 'library2.dll' + y.add( { 'link with' => [ obj_h, obj_h2 ] } ) +documents: 2 + +--- +test: Special keys +todo: true +yaml: | + "!": These three keys + "&": had to be quoted + "=": and are normal strings. + # NOTE: the following node should NOT be serialized this way. + encoded node : + !special '!' : '!type' + !special|canonical '&' : 12 + = : value + # The proper way to serialize the above node is as follows: + node : !!type &12 value +ruby: | + { '!' => 'These three keys', '&' => 'had to be quoted', + '=' => 'and are normal strings.', + 'encoded node' => YAML::PrivateType.new( 'type', 'value' ), + 'node' => YAML::PrivateType.new( 'type', 'value' ) } diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsTypeTransfers.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsTypeTransfers.yml new file mode 100644 index 00000000..aac4e680 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/YtsTypeTransfers.yml @@ -0,0 +1,244 @@ +--- %YAML:1.0 +test: Strings +brief: > + Any group of characters beginning with an + alphabetic or numeric character is a string, + unless it belongs to one of the groups below + (such as an Integer or Time). +yaml: | + String +php: | + 'String' +--- +test: String characters +brief: > + A string can contain any alphabetic or + numeric character, along with many + punctuation characters, including the + period, dash, space, quotes, exclamation, and + question mark. +yaml: | + - What's Yaml? + - It's for writing data structures in plain text. + - And? + - And what? That's not good enough for you? + - No, I mean, "And what about Yaml?" + - Oh, oh yeah. Uh.. Yaml for Ruby. +php: | + array( + "What's Yaml?", + "It's for writing data structures in plain text.", + "And?", + "And what? That's not good enough for you?", + "No, I mean, \"And what about Yaml?\"", + "Oh, oh yeah. Uh.. Yaml for Ruby." + ) +--- +test: Indicators in Strings +brief: > + Be careful using indicators in strings. In particular, + the comma, colon, and pound sign must be used carefully. +yaml: | + the colon followed by space is an indicator: but is a string:right here + same for the pound sign: here we have it#in a string + the comma can, honestly, be used in most cases: [ but not in, inline collections ] +php: | + array( + 'the colon followed by space is an indicator' => 'but is a string:right here', + 'same for the pound sign' => 'here we have it#in a string', + 'the comma can, honestly, be used in most cases' => array('but not in', 'inline collections') + ) +--- +test: Forcing Strings +brief: > + Any YAML type can be forced into a string using the + explicit !str method. +yaml: | + date string: !str 2001-08-01 + number string: !str 192 +php: | + array( + 'date string' => '2001-08-01', + 'number string' => '192' + ) +--- +test: Single-quoted Strings +brief: > + You can also enclose your strings within single quotes, + which allows use of slashes, colons, and other indicators + freely. Inside single quotes, you can represent a single + quote in your string by using two single quotes next to + each other. +yaml: | + all my favorite symbols: '#:!/%.)' + a few i hate: '&(*' + why do i hate them?: 'it''s very hard to explain' + entities: '£ me' +php: | + array( + 'all my favorite symbols' => '#:!/%.)', + 'a few i hate' => '&(*', + 'why do i hate them?' => 'it\'s very hard to explain', + 'entities' => '£ me' + ) +--- +test: Double-quoted Strings +brief: > + Enclosing strings in double quotes allows you + to use escapings to represent ASCII and + Unicode characters. +yaml: | + i know where i want my line breaks: "one here\nand another here\n" +php: | + array( + 'i know where i want my line breaks' => "one here\nand another here\n" + ) +--- +test: Multi-line Quoted Strings +todo: true +brief: > + Both single- and double-quoted strings may be + carried on to new lines in your YAML document. + They must be indented a step and indentation + is interpreted as a single space. +yaml: | + i want a long string: "so i'm going to + let it go on and on to other lines + until i end it with a quote." +php: | + array('i want a long string' => "so i'm going to ". + "let it go on and on to other lines ". + "until i end it with a quote." + ) + +--- +test: Plain scalars +todo: true +brief: > + Unquoted strings may also span multiple lines, if they + are free of YAML space indicators and indented. +yaml: | + - My little toe is broken in two places; + - I'm crazy to have skied this way; + - I'm not the craziest he's seen, since there was always the German guy + who skied for 3 hours on a broken shin bone (just below the kneecap); + - Nevertheless, second place is respectable, and he doesn't + recommend going for the record; + - He's going to put my foot in plaster for a month; + - This would impair my skiing ability somewhat for the + duration, as can be imagined. +php: | + array( + "My little toe is broken in two places;", + "I'm crazy to have skied this way;", + "I'm not the craziest he's seen, since there was always ". + "the German guy who skied for 3 hours on a broken shin ". + "bone (just below the kneecap);", + "Nevertheless, second place is respectable, and he doesn't ". + "recommend going for the record;", + "He's going to put my foot in plaster for a month;", + "This would impair my skiing ability somewhat for the duration, ". + "as can be imagined." + ) +--- +test: 'Null' +brief: > + You can use the tilde '~' character for a null value. +yaml: | + name: Mr. Show + hosted by: Bob and David + date of next season: ~ +php: | + array( + 'name' => 'Mr. Show', + 'hosted by' => 'Bob and David', + 'date of next season' => null + ) +--- +test: Boolean +brief: > + You can use 'true' and 'false' for Boolean values. +yaml: | + Is Gus a Liar?: true + Do I rely on Gus for Sustenance?: false +php: | + array( + 'Is Gus a Liar?' => true, + 'Do I rely on Gus for Sustenance?' => false + ) +--- +test: Integers +dump_skip: true +brief: > + An integer is a series of numbers, optionally + starting with a positive or negative sign. Integers + may also contain commas for readability. +yaml: | + zero: 0 + simple: 12 + one-thousand: 1,000 + negative one-thousand: -1,000 +php: | + array( + 'zero' => 0, + 'simple' => 12, + 'one-thousand' => 1000, + 'negative one-thousand' => -1000 + ) +--- +test: Integers as Map Keys +brief: > + An integer can be used a dictionary key. +yaml: | + 1: one + 2: two + 3: three +php: | + array( + 1 => 'one', + 2 => 'two', + 3 => 'three' + ) +--- +test: Floats +dump_skip: true +brief: > + Floats are represented by numbers with decimals, + allowing for scientific notation, as well as + positive and negative infinity and "not a number." +yaml: | + a simple float: 2.00 + larger float: 1,000.09 + scientific notation: 1.00009e+3 +php: | + array( + 'a simple float' => 2.0, + 'larger float' => 1000.09, + 'scientific notation' => 1000.09 + ) +--- +test: Time +todo: true +brief: > + You can represent timestamps by using + ISO8601 format, or a variation which + allows spaces between the date, time and + time zone. +yaml: | + iso8601: 2001-12-14t21:59:43.10-05:00 + space separated: 2001-12-14 21:59:43.10 -05:00 +php: | + array( + 'iso8601' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'space separated' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ) + ) +--- +test: Date +todo: true +brief: > + A date can be represented by its year, + month and day in ISO8601 order. +yaml: | + 1976-07-31 +php: | + date( 1976, 7, 31 ) diff --git a/vendor/symfony/yaml/Tests/Fixtures/embededPhp.yml b/vendor/symfony/yaml/Tests/Fixtures/embededPhp.yml new file mode 100644 index 00000000..ec456ed0 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/embededPhp.yml @@ -0,0 +1 @@ +value: diff --git a/vendor/symfony/yaml/Tests/Fixtures/escapedCharacters.yml b/vendor/symfony/yaml/Tests/Fixtures/escapedCharacters.yml new file mode 100644 index 00000000..09bf86e7 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/escapedCharacters.yml @@ -0,0 +1,147 @@ +test: outside double quotes +yaml: | + \0 \ \a \b \n +php: | + "\\0 \\ \\a \\b \\n" +--- +test: null +yaml: | + "\0" +php: | + "\x00" +--- +test: bell +yaml: | + "\a" +php: | + "\x07" +--- +test: backspace +yaml: | + "\b" +php: | + "\x08" +--- +test: horizontal tab (1) +yaml: | + "\t" +php: | + "\x09" +--- +test: horizontal tab (2) +yaml: | + "\ " +php: | + "\x09" +--- +test: line feed +yaml: | + "\n" +php: | + "\x0a" +--- +test: vertical tab +yaml: | + "\v" +php: | + "\x0b" +--- +test: form feed +yaml: | + "\f" +php: | + "\x0c" +--- +test: carriage return +yaml: | + "\r" +php: | + "\x0d" +--- +test: escape +yaml: | + "\e" +php: | + "\x1b" +--- +test: space +yaml: | + "\ " +php: | + "\x20" +--- +test: slash +yaml: | + "\/" +php: | + "\x2f" +--- +test: backslash +yaml: | + "\\" +php: | + "\\" +--- +test: Unicode next line +yaml: | + "\N" +php: | + "\xc2\x85" +--- +test: Unicode non-breaking space +yaml: | + "\_" +php: | + "\xc2\xa0" +--- +test: Unicode line separator +yaml: | + "\L" +php: | + "\xe2\x80\xa8" +--- +test: Unicode paragraph separator +yaml: | + "\P" +php: | + "\xe2\x80\xa9" +--- +test: Escaped 8-bit Unicode +yaml: | + "\x42" +php: | + "B" +--- +test: Escaped 16-bit Unicode +yaml: | + "\u20ac" +php: | + "\xe2\x82\xac" +--- +test: Escaped 32-bit Unicode +yaml: | + "\U00000043" +php: | + "C" +--- +test: Example 5.13 Escaped Characters +note: | + Currently throws an error parsing first line. Maybe Symfony Yaml doesn't support + continuation of string across multiple lines? Keeping test here but disabled. +todo: true +yaml: | + "Fun with \\ + \" \a \b \e \f \ + \n \r \t \v \0 \ + \ \_ \N \L \P \ + \x41 \u0041 \U00000041" +php: | + "Fun with \x5C\n\x22 \x07 \x08 \x1B \x0C\n\x0A \x0D \x09 \x0B \x00\n\x20 \xA0 \x85 \xe2\x80\xa8 \xe2\x80\xa9\nA A A" +--- +test: Double quotes with a line feed +yaml: | + { double: "some value\n \"some quoted string\" and 'some single quotes one'" } +php: | + array( + 'double' => "some value\n \"some quoted string\" and 'some single quotes one'" + ) diff --git a/vendor/symfony/yaml/Tests/Fixtures/index.yml b/vendor/symfony/yaml/Tests/Fixtures/index.yml new file mode 100644 index 00000000..3216a89e --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/index.yml @@ -0,0 +1,18 @@ +- escapedCharacters +- sfComments +- sfCompact +- sfTests +- sfObjects +- sfMergeKey +- sfQuotes +- YtsAnchorAlias +- YtsBasicTests +- YtsBlockMapping +- YtsDocumentSeparator +- YtsErrorTests +- YtsFlowCollections +- YtsFoldedScalars +- YtsNullsAndEmpties +- YtsSpecificationExamples +- YtsTypeTransfers +- unindentedCollections diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfComments.yml b/vendor/symfony/yaml/Tests/Fixtures/sfComments.yml new file mode 100644 index 00000000..6a7ffeca --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/sfComments.yml @@ -0,0 +1,73 @@ +--- %YAML:1.0 +test: Comments at the end of a line +brief: > + Comments at the end of a line +yaml: | + ex1: "foo # bar" + ex2: "foo # bar" # comment + ex3: 'foo # bar' # comment + ex4: foo # comment +php: | + array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo') +--- +test: Comments in the middle +brief: > + Comments in the middle +yaml: | + foo: + # some comment + # some comment + bar: foo + # some comment + # some comment +php: | + array('foo' => array('bar' => 'foo')) +--- +test: Comments on a hash line +brief: > + Comments on a hash line +yaml: | + foo: # a comment + foo: bar # a comment +php: | + array('foo' => array('foo' => 'bar')) +--- +test: 'Value starting with a #' +brief: > + 'Value starting with a #' +yaml: | + foo: '#bar' +php: | + array('foo' => '#bar') +--- +test: Document starting with a comment and a separator +brief: > + Commenting before document start is allowed +yaml: | + # document comment + --- + foo: bar # a comment +php: | + array('foo' => 'bar') +--- +test: Comment containing a colon on a hash line +brief: > + Comment containing a colon on a scalar line +yaml: 'foo # comment: this is also part of the comment' +php: | + 'foo' +--- +test: 'Hash key containing a #' +brief: > + 'Hash key containing a #' +yaml: 'foo#bar: baz' +php: | + array('foo#bar' => 'baz') +--- +test: 'Hash key ending with a space and a #' +brief: > + 'Hash key ending with a space and a #' +yaml: | + 'foo #': baz +php: | + array('foo #' => 'baz') diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfCompact.yml b/vendor/symfony/yaml/Tests/Fixtures/sfCompact.yml new file mode 100644 index 00000000..1339d23a --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/sfCompact.yml @@ -0,0 +1,159 @@ +--- %YAML:1.0 +test: Compact notation +brief: | + Compact notation for sets of mappings with single element +yaml: | + --- + # products purchased + - item : Super Hoop + - item : Basketball + quantity: 1 + - item: + name: Big Shoes + nick: Biggies + quantity: 1 +php: | + array ( + array ( + 'item' => 'Super Hoop', + ), + array ( + 'item' => 'Basketball', + 'quantity' => 1, + ), + array ( + 'item' => array( + 'name' => 'Big Shoes', + 'nick' => 'Biggies' + ), + 'quantity' => 1 + ) + ) +--- +test: Compact notation combined with inline notation +brief: | + Combinations of compact and inline notation are allowed +yaml: | + --- + items: + - { item: Super Hoop, quantity: 1 } + - [ Basketball, Big Shoes ] +php: | + array ( + 'items' => array ( + array ( + 'item' => 'Super Hoop', + 'quantity' => 1, + ), + array ( + 'Basketball', + 'Big Shoes' + ) + ) + ) +--- %YAML:1.0 +test: Compact notation +brief: | + Compact notation for sets of mappings with single element +yaml: | + --- + # products purchased + - item : Super Hoop + - item : Basketball + quantity: 1 + - item: + name: Big Shoes + nick: Biggies + quantity: 1 +php: | + array ( + array ( + 'item' => 'Super Hoop', + ), + array ( + 'item' => 'Basketball', + 'quantity' => 1, + ), + array ( + 'item' => array( + 'name' => 'Big Shoes', + 'nick' => 'Biggies' + ), + 'quantity' => 1 + ) + ) +--- +test: Compact notation combined with inline notation +brief: | + Combinations of compact and inline notation are allowed +yaml: | + --- + items: + - { item: Super Hoop, quantity: 1 } + - [ Basketball, Big Shoes ] +php: | + array ( + 'items' => array ( + array ( + 'item' => 'Super Hoop', + 'quantity' => 1, + ), + array ( + 'Basketball', + 'Big Shoes' + ) + ) + ) +--- %YAML:1.0 +test: Compact notation +brief: | + Compact notation for sets of mappings with single element +yaml: | + --- + # products purchased + - item : Super Hoop + - item : Basketball + quantity: 1 + - item: + name: Big Shoes + nick: Biggies + quantity: 1 +php: | + array ( + array ( + 'item' => 'Super Hoop', + ), + array ( + 'item' => 'Basketball', + 'quantity' => 1, + ), + array ( + 'item' => array( + 'name' => 'Big Shoes', + 'nick' => 'Biggies' + ), + 'quantity' => 1 + ) + ) +--- +test: Compact notation combined with inline notation +brief: | + Combinations of compact and inline notation are allowed +yaml: | + --- + items: + - { item: Super Hoop, quantity: 1 } + - [ Basketball, Big Shoes ] +php: | + array ( + 'items' => array ( + array ( + 'item' => 'Super Hoop', + 'quantity' => 1, + ), + array ( + 'Basketball', + 'Big Shoes' + ) + ) + ) diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml b/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml new file mode 100644 index 00000000..4b67d341 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml @@ -0,0 +1,58 @@ +--- %YAML:1.0 +test: Simple In Place Substitution +brief: > + If you want to reuse an entire alias, only overwriting what is different + you can use a << in place substitution. This is not part of the official + YAML spec, but a widely implemented extension. See the following URL for + details: http://yaml.org/type/merge.html +yaml: | + foo: &foo + a: Steve + b: Clark + c: Brian + bar: + a: before + d: other + <<: *foo + b: new + x: Oren + c: + foo: bar + foo: ignore + bar: foo + duplicate: + foo: bar + foo: ignore + foo2: &foo2 + a: Ballmer + ding: &dong [ fi, fei, fo, fam] + check: + <<: + - *foo + - *dong + isit: tested + head: + <<: [ *foo , *dong , *foo2 ] + taz: &taz + a: Steve + w: + p: 1234 + nested: + <<: *taz + d: Doug + w: &nestedref + p: 12345 + z: + <<: *nestedref +php: | + array( + 'foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'), + 'bar' => array('a' => 'before', 'd' => 'other', 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'x' => 'Oren'), + 'duplicate' => array('foo' => 'bar'), + 'foo2' => array('a' => 'Ballmer'), + 'ding' => array('fi', 'fei', 'fo', 'fam'), + 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), + 'head' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam'), + 'taz' => array('a' => 'Steve', 'w' => array('p' => 1234)), + 'nested' => array('a' => 'Steve', 'w' => array('p' => 12345), 'd' => 'Doug', 'z' => array('p' => 12345)) + ) diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfObjects.yml b/vendor/symfony/yaml/Tests/Fixtures/sfObjects.yml new file mode 100644 index 00000000..ee124b24 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/sfObjects.yml @@ -0,0 +1,11 @@ +--- %YAML:1.0 +test: Objects +brief: > + Comments at the end of a line +yaml: | + ex1: "foo # bar" + ex2: "foo # bar" # comment + ex3: 'foo # bar' # comment + ex4: foo # comment +php: | + array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo') diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfQuotes.yml b/vendor/symfony/yaml/Tests/Fixtures/sfQuotes.yml new file mode 100644 index 00000000..741f1bef --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/sfQuotes.yml @@ -0,0 +1,33 @@ +--- %YAML:1.0 +test: Some characters at the beginning of a string must be escaped +brief: > + Some characters at the beginning of a string must be escaped +yaml: | + foo: | bar +php: | + array('foo' => '| bar') +--- +test: A key can be a quoted string +brief: > + A key can be a quoted string +yaml: | + "foo1": bar + 'foo2': bar + "foo \" bar": bar + 'foo '' bar': bar + 'foo3: ': bar + "foo4: ": bar + foo5: { "foo \" bar: ": bar, 'foo '' bar: ': bar } +php: | + array( + 'foo1' => 'bar', + 'foo2' => 'bar', + 'foo " bar' => 'bar', + 'foo \' bar' => 'bar', + 'foo3: ' => 'bar', + 'foo4: ' => 'bar', + 'foo5' => array( + 'foo " bar: ' => 'bar', + 'foo \' bar: ' => 'bar', + ), + ) diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfTests.yml b/vendor/symfony/yaml/Tests/Fixtures/sfTests.yml new file mode 100644 index 00000000..7a54f163 --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/sfTests.yml @@ -0,0 +1,135 @@ +--- %YAML:1.0 +test: Multiple quoted string on one line +brief: > + Multiple quoted string on one line +yaml: | + stripped_title: { name: "foo bar", help: "bar foo" } +php: | + array('stripped_title' => array('name' => 'foo bar', 'help' => 'bar foo')) +--- +test: Empty sequence +yaml: | + foo: [ ] +php: | + array('foo' => array()) +--- +test: Empty value +yaml: | + foo: +php: | + array('foo' => null) +--- +test: Inline string parsing +brief: > + Inline string parsing +yaml: | + test: ['complex: string', 'another [string]'] +php: | + array('test' => array('complex: string', 'another [string]')) +--- +test: Boolean +brief: > + Boolean +yaml: | + - false + - true + - null + - ~ + - 'false' + - 'true' + - 'null' + - '~' +php: | + array( + false, + true, + null, + null, + 'false', + 'true', + 'null', + '~', + ) +--- +test: Empty lines in folded blocks +brief: > + Empty lines in folded blocks +yaml: | + foo: + bar: | + foo + + + + bar +php: | + array('foo' => array('bar' => "foo\n\n\n \nbar\n")) +--- +test: IP addresses +brief: > + IP addresses +yaml: | + foo: 10.0.0.2 +php: | + array('foo' => '10.0.0.2') +--- +test: A sequence with an embedded mapping +brief: > + A sequence with an embedded mapping +yaml: | + - foo + - bar: { bar: foo } +php: | + array('foo', array('bar' => array('bar' => 'foo'))) +--- +test: A sequence with an unordered array +brief: > + A sequence with an unordered array +yaml: | + 1: foo + 0: bar +php: | + array(1 => 'foo', 0 => 'bar') +--- +test: Octal +brief: as in spec example 2.19, octal value is converted +yaml: | + foo: 0123 +php: | + array('foo' => 83) +--- +test: Octal strings +brief: Octal notation in a string must remain a string +yaml: | + foo: "0123" +php: | + array('foo' => '0123') +--- +test: Octal strings +brief: Octal notation in a string must remain a string +yaml: | + foo: '0123' +php: | + array('foo' => '0123') +--- +test: Octal strings +brief: Octal notation in a string must remain a string +yaml: | + foo: | + 0123 +php: | + array('foo' => "0123\n") +--- +test: Document as a simple hash +brief: Document as a simple hash +yaml: | + { foo: bar } +php: | + array('foo' => 'bar') +--- +test: Document as a simple array +brief: Document as a simple array +yaml: | + [ foo, bar ] +php: | + array('foo', 'bar') diff --git a/vendor/symfony/yaml/Tests/Fixtures/unindentedCollections.yml b/vendor/symfony/yaml/Tests/Fixtures/unindentedCollections.yml new file mode 100644 index 00000000..0c96108e --- /dev/null +++ b/vendor/symfony/yaml/Tests/Fixtures/unindentedCollections.yml @@ -0,0 +1,82 @@ +--- %YAML:1.0 +test: Unindented collection +brief: > + Unindented collection +yaml: | + collection: + - item1 + - item2 + - item3 +php: | + array('collection' => array('item1', 'item2', 'item3')) +--- +test: Nested unindented collection (two levels) +brief: > + Nested unindented collection +yaml: | + collection: + key: + - a + - b + - c +php: | + array('collection' => array('key' => array('a', 'b', 'c'))) +--- +test: Nested unindented collection (three levels) +brief: > + Nested unindented collection +yaml: | + collection: + key: + subkey: + - one + - two + - three +php: | + array('collection' => array('key' => array('subkey' => array('one', 'two', 'three')))) +--- +test: Key/value after unindented collection (1) +brief: > + Key/value after unindented collection (1) +yaml: | + collection: + key: + - a + - b + - c + foo: bar +php: | + array('collection' => array('key' => array('a', 'b', 'c')), 'foo' => 'bar') +--- +test: Key/value after unindented collection (at the same level) +brief: > + Key/value after unindented collection +yaml: | + collection: + key: + - a + - b + - c + foo: bar +php: | + array('collection' => array('key' => array('a', 'b', 'c'), 'foo' => 'bar')) +--- +test: Shortcut Key after unindented collection +brief: > + Key/value after unindented collection +yaml: | + collection: + - key: foo + foo: bar +php: | + array('collection' => array(array('key' => 'foo', 'foo' => 'bar'))) +--- +test: Shortcut Key after unindented collection with custom spaces +brief: > + Key/value after unindented collection +yaml: | + collection: + - key: foo + foo: bar +php: | + array('collection' => array(array('key' => 'foo', 'foo' => 'bar'))) diff --git a/vendor/symfony/yaml/Tests/InlineTest.php b/vendor/symfony/yaml/Tests/InlineTest.php new file mode 100644 index 00000000..522d7705 --- /dev/null +++ b/vendor/symfony/yaml/Tests/InlineTest.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 Symfony\Component\Yaml\Tests; + +use Symfony\Component\Yaml\Inline; + +class InlineTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTestsForParse + */ + public function testParse($yaml, $value) + { + $this->assertSame($value, Inline::parse($yaml), sprintf('::parse() converts an inline YAML to a PHP structure (%s)', $yaml)); + } + + /** + * @dataProvider getTestsForParseWithMapObjects + */ + public function testParseWithMapObjects($yaml, $value) + { + $actual = Inline::parse($yaml, false, false, true); + + $this->assertSame(serialize($value), serialize($actual)); + } + + /** + * @dataProvider getTestsForDump + */ + public function testDump($yaml, $value) + { + $this->assertEquals($yaml, Inline::dump($value), sprintf('::dump() converts a PHP structure to an inline YAML (%s)', $yaml)); + + $this->assertSame($value, Inline::parse(Inline::dump($value)), 'check consistency'); + } + + public function testDumpNumericValueWithLocale() + { + $locale = setlocale(LC_NUMERIC, 0); + if (false === $locale) { + $this->markTestSkipped('Your platform does not support locales.'); + } + + try { + $requiredLocales = array('fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252'); + if (false === setlocale(LC_NUMERIC, $requiredLocales)) { + $this->markTestSkipped('Could not set any of required locales: '.implode(', ', $requiredLocales)); + } + + $this->assertEquals('1.2', Inline::dump(1.2)); + $this->assertContains('fr', strtolower(setlocale(LC_NUMERIC, 0))); + setlocale(LC_NUMERIC, $locale); + } catch (\Exception $e) { + setlocale(LC_NUMERIC, $locale); + throw $e; + } + } + + public function testHashStringsResemblingExponentialNumericsShouldNotBeChangedToINF() + { + $value = '686e444'; + + $this->assertSame($value, Inline::parse(Inline::dump($value))); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + */ + public function testParseScalarWithIncorrectlyQuotedStringShouldThrowException() + { + $value = "'don't do somthin' like that'"; + Inline::parse($value); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + */ + public function testParseScalarWithIncorrectlyDoubleQuotedStringShouldThrowException() + { + $value = '"don"t do somthin" like that"'; + Inline::parse($value); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + */ + public function testParseInvalidMappingKeyShouldThrowException() + { + $value = '{ "foo " bar": "bar" }'; + Inline::parse($value); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + */ + public function testParseInvalidMappingShouldThrowException() + { + Inline::parse('[foo] bar'); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + */ + public function testParseInvalidSequenceShouldThrowException() + { + Inline::parse('{ foo: bar } bar'); + } + + public function testParseScalarWithCorrectlyQuotedStringShouldReturnString() + { + $value = "'don''t do somthin'' like that'"; + $expect = "don't do somthin' like that"; + + $this->assertSame($expect, Inline::parseScalar($value)); + } + + /** + * @dataProvider getDataForParseReferences + */ + public function testParseReferences($yaml, $expected) + { + $this->assertSame($expected, Inline::parse($yaml, false, false, false, array('var' => 'var-value'))); + } + + public function getDataForParseReferences() + { + return array( + 'scalar' => array('*var', 'var-value'), + 'list' => array('[ *var ]', array('var-value')), + 'list-in-list' => array('[[ *var ]]', array(array('var-value'))), + 'map-in-list' => array('[ { key: *var } ]', array(array('key' => 'var-value'))), + 'embedded-mapping-in-list' => array('[ key: *var ]', array(array('key' => 'var-value'))), + 'map' => array('{ key: *var }', array('key' => 'var-value')), + 'list-in-map' => array('{ key: [*var] }', array('key' => array('var-value'))), + 'map-in-map' => array('{ foo: { bar: *var } }', array('foo' => array('bar' => 'var-value'))), + ); + } + + public function testParseMapReferenceInSequence() + { + $foo = array( + 'a' => 'Steve', + 'b' => 'Clark', + 'c' => 'Brian', + ); + $this->assertSame(array($foo), Inline::parse('[*foo]', false, false, false, array('foo' => $foo))); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + * @expectedExceptionMessage A reference must contain at least one character. + */ + public function testParseUnquotedAsterisk() + { + Inline::parse('{ foo: * }'); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + * @expectedExceptionMessage A reference must contain at least one character. + */ + public function testParseUnquotedAsteriskFollowedByAComment() + { + Inline::parse('{ foo: * #foo }'); + } + + public function getTestsForParse() + { + return array( + array('', ''), + array('null', null), + array('false', false), + array('true', true), + array('12', 12), + array('-12', -12), + array('"quoted string"', 'quoted string'), + array("'quoted string'", 'quoted string'), + array('12.30e+02', 12.30e+02), + array('0x4D2', 0x4D2), + array('02333', 02333), + array('.Inf', -log(0)), + array('-.Inf', log(0)), + array("'686e444'", '686e444'), + array('686e444', 646e444), + array('123456789123456789123456789123456789', '123456789123456789123456789123456789'), + array('"foo\r\nbar"', "foo\r\nbar"), + array("'foo#bar'", 'foo#bar'), + array("'foo # bar'", 'foo # bar'), + array("'#cfcfcf'", '#cfcfcf'), + array('::form_base.html.twig', '::form_base.html.twig'), + + // Pre-YAML-1.2 booleans + array("'y'", 'y'), + array("'n'", 'n'), + array("'yes'", 'yes'), + array("'no'", 'no'), + array("'on'", 'on'), + array("'off'", 'off'), + + array('2007-10-30', mktime(0, 0, 0, 10, 30, 2007)), + array('2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)), + array('2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)), + array('1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)), + array('1730-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 1730)), + + array('"a \\"string\\" with \'quoted strings inside\'"', 'a "string" with \'quoted strings inside\''), + array("'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''), + + // sequences + // urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon + array('[foo, http://urls.are/no/mappings, false, null, 12]', array('foo', 'http://urls.are/no/mappings', false, null, 12)), + array('[ foo , bar , false , null , 12 ]', array('foo', 'bar', false, null, 12)), + array('[\'foo,bar\', \'foo bar\']', array('foo,bar', 'foo bar')), + + // mappings + array('{foo:bar,bar:foo,false:false,null:null,integer:12}', array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)), + array('{ foo : bar, bar : foo, false : false, null : null, integer : 12 }', array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)), + array('{foo: \'bar\', bar: \'foo: bar\'}', array('foo' => 'bar', 'bar' => 'foo: bar')), + array('{\'foo\': \'bar\', "bar": \'foo: bar\'}', array('foo' => 'bar', 'bar' => 'foo: bar')), + array('{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}', array('foo\'' => 'bar', 'bar"' => 'foo: bar')), + array('{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}', array('foo: ' => 'bar', 'bar: ' => 'foo: bar')), + + // nested sequences and mappings + array('[foo, [bar, foo]]', array('foo', array('bar', 'foo'))), + array('[foo, {bar: foo}]', array('foo', array('bar' => 'foo'))), + array('{ foo: {bar: foo} }', array('foo' => array('bar' => 'foo'))), + array('{ foo: [bar, foo] }', array('foo' => array('bar', 'foo'))), + + array('[ foo, [ bar, foo ] ]', array('foo', array('bar', 'foo'))), + + array('[{ foo: {bar: foo} }]', array(array('foo' => array('bar' => 'foo')))), + + array('[foo, [bar, [foo, [bar, foo]], foo]]', array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo'))), + + array('[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]', array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo')))), + + array('[foo, bar: { foo: bar }]', array('foo', '1' => array('bar' => array('foo' => 'bar')))), + array('[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', array('foo', '@foo.baz', array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, '@service_container')), + ); + } + + public function getTestsForParseWithMapObjects() + { + return array( + array('', ''), + array('null', null), + array('false', false), + array('true', true), + array('12', 12), + array('-12', -12), + array('"quoted string"', 'quoted string'), + array("'quoted string'", 'quoted string'), + array('12.30e+02', 12.30e+02), + array('0x4D2', 0x4D2), + array('02333', 02333), + array('.Inf', -log(0)), + array('-.Inf', log(0)), + array("'686e444'", '686e444'), + array('686e444', 646e444), + array('123456789123456789123456789123456789', '123456789123456789123456789123456789'), + array('"foo\r\nbar"', "foo\r\nbar"), + array("'foo#bar'", 'foo#bar'), + array("'foo # bar'", 'foo # bar'), + array("'#cfcfcf'", '#cfcfcf'), + array('::form_base.html.twig', '::form_base.html.twig'), + + array('2007-10-30', mktime(0, 0, 0, 10, 30, 2007)), + array('2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)), + array('2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)), + array('1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)), + array('1730-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 1730)), + + array('"a \\"string\\" with \'quoted strings inside\'"', 'a "string" with \'quoted strings inside\''), + array("'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''), + + // sequences + // urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon + array('[foo, http://urls.are/no/mappings, false, null, 12]', array('foo', 'http://urls.are/no/mappings', false, null, 12)), + array('[ foo , bar , false , null , 12 ]', array('foo', 'bar', false, null, 12)), + array('[\'foo,bar\', \'foo bar\']', array('foo,bar', 'foo bar')), + + // mappings + array('{foo:bar,bar:foo,false:false,null:null,integer:12}', (object) array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)), + array('{ foo : bar, bar : foo, false : false, null : null, integer : 12 }', (object) array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)), + array('{foo: \'bar\', bar: \'foo: bar\'}', (object) array('foo' => 'bar', 'bar' => 'foo: bar')), + array('{\'foo\': \'bar\', "bar": \'foo: bar\'}', (object) array('foo' => 'bar', 'bar' => 'foo: bar')), + array('{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}', (object) array('foo\'' => 'bar', 'bar"' => 'foo: bar')), + array('{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}', (object) array('foo: ' => 'bar', 'bar: ' => 'foo: bar')), + + // nested sequences and mappings + array('[foo, [bar, foo]]', array('foo', array('bar', 'foo'))), + array('[foo, {bar: foo}]', array('foo', (object) array('bar' => 'foo'))), + array('{ foo: {bar: foo} }', (object) array('foo' => (object) array('bar' => 'foo'))), + array('{ foo: [bar, foo] }', (object) array('foo' => array('bar', 'foo'))), + + array('[ foo, [ bar, foo ] ]', array('foo', array('bar', 'foo'))), + + array('[{ foo: {bar: foo} }]', array((object) array('foo' => (object) array('bar' => 'foo')))), + + array('[foo, [bar, [foo, [bar, foo]], foo]]', array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo'))), + + array('[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]', array('foo', (object) array('bar' => 'foo', 'foo' => array('foo', (object) array('bar' => 'foo'))), array('foo', (object) array('bar' => 'foo')))), + + array('[foo, bar: { foo: bar }]', array('foo', '1' => (object) array('bar' => (object) array('foo' => 'bar')))), + array('[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', array('foo', '@foo.baz', (object) array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, '@service_container')), + + array('{}', new \stdClass()), + array('{ foo : bar, bar : {} }', (object) array('foo' => 'bar', 'bar' => new \stdClass())), + array('{ foo : [], bar : {} }', (object) array('foo' => array(), 'bar' => new \stdClass())), + array('{foo: \'bar\', bar: {} }', (object) array('foo' => 'bar', 'bar' => new \stdClass())), + array('{\'foo\': \'bar\', "bar": {}}', (object) array('foo' => 'bar', 'bar' => new \stdClass())), + array('{\'foo\': \'bar\', "bar": \'{}\'}', (object) array('foo' => 'bar', 'bar' => '{}')), + + array('[foo, [{}, {}]]', array('foo', array(new \stdClass(), new \stdClass()))), + array('[foo, [[], {}]]', array('foo', array(array(), new \stdClass()))), + array('[foo, [[{}, {}], {}]]', array('foo', array(array(new \stdClass(), new \stdClass()), new \stdClass()))), + array('[foo, {bar: {}}]', array('foo', '1' => (object) array('bar' => new \stdClass()))), + ); + } + + public function getTestsForDump() + { + return array( + array('null', null), + array('false', false), + array('true', true), + array('12', 12), + array("'quoted string'", 'quoted string'), + array('!!float 1230', 12.30e+02), + array('1234', 0x4D2), + array('1243', 02333), + array('.Inf', -log(0)), + array('-.Inf', log(0)), + array("'686e444'", '686e444'), + array('"foo\r\nbar"', "foo\r\nbar"), + array("'foo#bar'", 'foo#bar'), + array("'foo # bar'", 'foo # bar'), + array("'#cfcfcf'", '#cfcfcf'), + + array("'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''), + + array("'-dash'", '-dash'), + array("'-'", '-'), + + // Pre-YAML-1.2 booleans + array("'y'", 'y'), + array("'n'", 'n'), + array("'yes'", 'yes'), + array("'no'", 'no'), + array("'on'", 'on'), + array("'off'", 'off'), + + // sequences + array('[foo, bar, false, null, 12]', array('foo', 'bar', false, null, 12)), + array('[\'foo,bar\', \'foo bar\']', array('foo,bar', 'foo bar')), + + // mappings + array('{ foo: bar, bar: foo, \'false\': false, \'null\': null, integer: 12 }', array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)), + array('{ foo: bar, bar: \'foo: bar\' }', array('foo' => 'bar', 'bar' => 'foo: bar')), + + // nested sequences and mappings + array('[foo, [bar, foo]]', array('foo', array('bar', 'foo'))), + + array('[foo, [bar, [foo, [bar, foo]], foo]]', array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo'))), + + array('{ foo: { bar: foo } }', array('foo' => array('bar' => 'foo'))), + + array('[foo, { bar: foo }]', array('foo', array('bar' => 'foo'))), + + array('[foo, { bar: foo, foo: [foo, { bar: foo }] }, [foo, { bar: foo }]]', array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo')))), + + array('[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', array('foo', '@foo.baz', array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, '@service_container')), + ); + } +} diff --git a/vendor/symfony/yaml/Tests/ParseExceptionTest.php b/vendor/symfony/yaml/Tests/ParseExceptionTest.php new file mode 100644 index 00000000..e4eb9c98 --- /dev/null +++ b/vendor/symfony/yaml/Tests/ParseExceptionTest.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\Yaml\Tests; + +use Symfony\Component\Yaml\Exception\ParseException; + +class ParseExceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testGetMessage() + { + $exception = new ParseException('Error message', 42, 'foo: bar', '/var/www/app/config.yml'); + if (PHP_VERSION_ID >= 50400) { + $message = 'Error message in "/var/www/app/config.yml" at line 42 (near "foo: bar")'; + } else { + $message = 'Error message in "\\/var\\/www\\/app\\/config.yml" at line 42 (near "foo: bar")'; + } + + $this->assertEquals($message, $exception->getMessage()); + } + + public function testGetMessageWithUnicodeInFilename() + { + $exception = new ParseException('Error message', 42, 'foo: bar', 'äöü.yml'); + if (PHP_VERSION_ID >= 50400) { + $message = 'Error message in "äöü.yml" at line 42 (near "foo: bar")'; + } else { + $message = 'Error message in "\u00e4\u00f6\u00fc.yml" at line 42 (near "foo: bar")'; + } + + $this->assertEquals($message, $exception->getMessage()); + } +} diff --git a/vendor/symfony/yaml/Tests/ParserTest.php b/vendor/symfony/yaml/Tests/ParserTest.php new file mode 100644 index 00000000..08ef43fa --- /dev/null +++ b/vendor/symfony/yaml/Tests/ParserTest.php @@ -0,0 +1,773 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Tests; + +use Symfony\Component\Yaml\Yaml; +use Symfony\Component\Yaml\Parser; + +class ParserTest extends \PHPUnit_Framework_TestCase +{ + protected $parser; + + protected function setUp() + { + $this->parser = new Parser(); + } + + protected function tearDown() + { + $this->parser = null; + } + + /** + * @dataProvider getDataFormSpecifications + */ + public function testSpecifications($file, $expected, $yaml, $comment) + { + $this->assertEquals($expected, var_export($this->parser->parse($yaml), true), $comment); + } + + public function getDataFormSpecifications() + { + $parser = new Parser(); + $path = __DIR__.'/Fixtures'; + + $tests = array(); + $files = $parser->parse(file_get_contents($path.'/index.yml')); + foreach ($files as $file) { + $yamls = file_get_contents($path.'/'.$file.'.yml'); + + // split YAMLs documents + foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) { + if (!$yaml) { + continue; + } + + $test = $parser->parse($yaml); + if (isset($test['todo']) && $test['todo']) { + // TODO + } else { + eval('$expected = '.trim($test['php']).';'); + + $tests[] = array($file, var_export($expected, true), $test['yaml'], $test['test']); + } + } + } + + return $tests; + } + + public function testTabsInYaml() + { + // test tabs in YAML + $yamls = array( + "foo:\n bar", + "foo:\n bar", + "foo:\n bar", + "foo:\n bar", + ); + + foreach ($yamls as $yaml) { + try { + $content = $this->parser->parse($yaml); + + $this->fail('YAML files must not contain tabs'); + } catch (\Exception $e) { + $this->assertInstanceOf('\Exception', $e, 'YAML files must not contain tabs'); + $this->assertEquals('A YAML file cannot contain tabs as indentation at line 2 (near "'.strpbrk($yaml, "\t").'").', $e->getMessage(), 'YAML files must not contain tabs'); + } + } + } + + public function testEndOfTheDocumentMarker() + { + $yaml = <<assertEquals('foo', $this->parser->parse($yaml)); + } + + public function getBlockChompingTests() + { + $tests = array(); + + $yaml = <<<'EOF' +foo: |- + one + two +bar: |- + one + two + +EOF; + $expected = array( + 'foo' => "one\ntwo", + 'bar' => "one\ntwo", + ); + $tests['Literal block chomping strip with single trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: |- + one + two + +bar: |- + one + two + + +EOF; + $expected = array( + 'foo' => "one\ntwo", + 'bar' => "one\ntwo", + ); + $tests['Literal block chomping strip with multiple trailing newlines'] = array($expected, $yaml); + + $yaml = <<<'EOF' +{} + + +EOF; + $expected = array(); + $tests['Literal block chomping strip with multiple trailing newlines after a 1-liner'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: |- + one + two +bar: |- + one + two +EOF; + $expected = array( + 'foo' => "one\ntwo", + 'bar' => "one\ntwo", + ); + $tests['Literal block chomping strip without trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: | + one + two +bar: | + one + two + +EOF; + $expected = array( + 'foo' => "one\ntwo\n", + 'bar' => "one\ntwo\n", + ); + $tests['Literal block chomping clip with single trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: | + one + two + +bar: | + one + two + + +EOF; + $expected = array( + 'foo' => "one\ntwo\n", + 'bar' => "one\ntwo\n", + ); + $tests['Literal block chomping clip with multiple trailing newlines'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: | + one + two +bar: | + one + two +EOF; + $expected = array( + 'foo' => "one\ntwo\n", + 'bar' => "one\ntwo", + ); + $tests['Literal block chomping clip without trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: |+ + one + two +bar: |+ + one + two + +EOF; + $expected = array( + 'foo' => "one\ntwo\n", + 'bar' => "one\ntwo\n", + ); + $tests['Literal block chomping keep with single trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: |+ + one + two + +bar: |+ + one + two + + +EOF; + $expected = array( + 'foo' => "one\ntwo\n\n", + 'bar' => "one\ntwo\n\n", + ); + $tests['Literal block chomping keep with multiple trailing newlines'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: |+ + one + two +bar: |+ + one + two +EOF; + $expected = array( + 'foo' => "one\ntwo\n", + 'bar' => "one\ntwo", + ); + $tests['Literal block chomping keep without trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: >- + one + two +bar: >- + one + two + +EOF; + $expected = array( + 'foo' => 'one two', + 'bar' => 'one two', + ); + $tests['Folded block chomping strip with single trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: >- + one + two + +bar: >- + one + two + + +EOF; + $expected = array( + 'foo' => 'one two', + 'bar' => 'one two', + ); + $tests['Folded block chomping strip with multiple trailing newlines'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: >- + one + two +bar: >- + one + two +EOF; + $expected = array( + 'foo' => 'one two', + 'bar' => 'one two', + ); + $tests['Folded block chomping strip without trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: > + one + two +bar: > + one + two + +EOF; + $expected = array( + 'foo' => "one two\n", + 'bar' => "one two\n", + ); + $tests['Folded block chomping clip with single trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: > + one + two + +bar: > + one + two + + +EOF; + $expected = array( + 'foo' => "one two\n", + 'bar' => "one two\n", + ); + $tests['Folded block chomping clip with multiple trailing newlines'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: > + one + two +bar: > + one + two +EOF; + $expected = array( + 'foo' => "one two\n", + 'bar' => 'one two', + ); + $tests['Folded block chomping clip without trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: >+ + one + two +bar: >+ + one + two + +EOF; + $expected = array( + 'foo' => "one two\n", + 'bar' => "one two\n", + ); + $tests['Folded block chomping keep with single trailing newline'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: >+ + one + two + +bar: >+ + one + two + + +EOF; + $expected = array( + 'foo' => "one two\n\n", + 'bar' => "one two\n\n", + ); + $tests['Folded block chomping keep with multiple trailing newlines'] = array($expected, $yaml); + + $yaml = <<<'EOF' +foo: >+ + one + two +bar: >+ + one + two +EOF; + $expected = array( + 'foo' => "one two\n", + 'bar' => 'one two', + ); + $tests['Folded block chomping keep without trailing newline'] = array($expected, $yaml); + + return $tests; + } + + /** + * @dataProvider getBlockChompingTests + */ + public function testBlockChomping($expected, $yaml) + { + $this->assertSame($expected, $this->parser->parse($yaml)); + } + + /** + * Regression test for issue #7989. + * + * @see https://github.com/symfony/symfony/issues/7989 + */ + public function testBlockLiteralWithLeadingNewlines() + { + $yaml = <<<'EOF' +foo: |- + + + bar + +EOF; + $expected = array( + 'foo' => "\n\nbar", + ); + + $this->assertSame($expected, $this->parser->parse($yaml)); + } + + public function testObjectSupportEnabled() + { + $input = <<assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, false, true), '->parse() is able to parse objects'); + } + + public function testObjectSupportDisabledButNoExceptions() + { + $input = <<assertEquals(array('foo' => null, 'bar' => 1), $this->parser->parse($input), '->parse() does not parse objects'); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + */ + public function testObjectsSupportDisabledWithExceptions() + { + $this->parser->parse('foo: !!php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";}', true, false); + } + + public function testNonUtf8Exception() + { + if (!function_exists('iconv')) { + $this->markTestSkipped('Exceptions for non-utf8 charsets require the iconv() function.'); + + return; + } + + $yamls = array( + iconv('UTF-8', 'ISO-8859-1', "foo: 'äöüß'"), + iconv('UTF-8', 'ISO-8859-15', "euro: '€'"), + iconv('UTF-8', 'CP1252', "cp1252: '©ÉÇáñ'"), + ); + + foreach ($yamls as $yaml) { + try { + $this->parser->parse($yaml); + + $this->fail('charsets other than UTF-8 are rejected.'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Yaml\Exception\ParseException', $e, 'charsets other than UTF-8 are rejected.'); + } + } + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + */ + public function testUnindentedCollectionException() + { + $yaml = <<parser->parse($yaml); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + */ + public function testShortcutKeyUnindentedCollectionException() + { + $yaml = <<parser->parse($yaml); + } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + * @expectedExceptionMessage Multiple documents are not supported. + */ + public function testMultipleDocumentsNotSupportedException() + { + Yaml::parse(<< It is an error for two equal keys to appear in the same mapping node. + * > In such a case the YAML processor may continue, ignoring the second + * > `key: value` pair and issuing an appropriate warning. This strategy + * > preserves a consistent information model for one-pass and random access + * > applications. + * + * @see http://yaml.org/spec/1.2/spec.html#id2759572 + * @see http://yaml.org/spec/1.1/#id932806 + * + * @covers \Symfony\Component\Yaml\Parser::parse + */ + public function testMappingDuplicateKeyBlock() + { + $input = << array( + 'child' => 'first', + ), + ); + $this->assertSame($expected, Yaml::parse($input)); + } + + /** + * @covers \Symfony\Component\Yaml\Inline::parseMapping + */ + public function testMappingDuplicateKeyFlow() + { + $input = << array( + 'child' => 'first', + ), + ); + $this->assertSame($expected, Yaml::parse($input)); + } + + public function testEmptyValue() + { + $input = <<assertEquals(array('hash' => null), Yaml::parse($input)); + } + + public function testStringBlockWithComments() + { + $this->assertEquals(array('content' => << +

    title

    + + +footer # comment3 +EOT + ), Yaml::parse(<< +

    title

    + + + footer # comment3 +EOF + )); + } + + public function testFoldedStringBlockWithComments() + { + $this->assertEquals(array(array('content' => << +

    title

    + + +footer # comment3 +EOT + )), Yaml::parse(<< +

    title

    + + + footer # comment3 +EOF + )); + } + + public function testNestedFoldedStringBlockWithComments() + { + $this->assertEquals(array(array( + 'title' => 'some title', + 'content' => << +

    title

    + + +footer # comment3 +EOT + )), Yaml::parse(<< +

    title

    + + + footer # comment3 +EOF + )); + } + + public function testReferenceResolvingInInlineStrings() + { + $this->assertEquals(array( + 'var' => 'var-value', + 'scalar' => 'var-value', + 'list' => array('var-value'), + 'list_in_list' => array(array('var-value')), + 'map_in_list' => array(array('key' => 'var-value')), + 'embedded_mapping' => array(array('key' => 'var-value')), + 'map' => array('key' => 'var-value'), + 'list_in_map' => array('key' => array('var-value')), + 'map_in_map' => array('foo' => array('bar' => 'var-value')), + ), Yaml::parse(<<assertEquals(array('foo' => 1, 'bar' => 2), $this->parser->parse($yaml)); + } + + public function testFloatKeys() + { + $yaml = << array( + '1.2' => 'bar', + '1.3' => 'baz', + ), + ); + + $this->assertEquals($expected, $this->parser->parse($yaml)); + } +} + +class B +{ + public $b = 'foo'; +} diff --git a/vendor/symfony/yaml/Tests/YamlTest.php b/vendor/symfony/yaml/Tests/YamlTest.php new file mode 100644 index 00000000..8db65e39 --- /dev/null +++ b/vendor/symfony/yaml/Tests/YamlTest.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\Yaml\Tests; + +use Symfony\Component\Yaml\Yaml; + +class YamlTest extends \PHPUnit_Framework_TestCase +{ + public function testParseAndDump() + { + $data = array('lorem' => 'ipsum', 'dolor' => 'sit'); + $yml = Yaml::dump($data); + $parsed = Yaml::parse($yml); + $this->assertEquals($data, $parsed); + } + + /** + * @group legacy + */ + public function testLegacyParseFromFile() + { + $filename = __DIR__.'/Fixtures/index.yml'; + $contents = file_get_contents($filename); + $parsedByFilename = Yaml::parse($filename); + $parsedByContents = Yaml::parse($contents); + $this->assertEquals($parsedByFilename, $parsedByContents); + } +} diff --git a/vendor/symfony/yaml/Unescaper.php b/vendor/symfony/yaml/Unescaper.php new file mode 100644 index 00000000..1b5e5ec2 --- /dev/null +++ b/vendor/symfony/yaml/Unescaper.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 Symfony\Component\Yaml; + +/** + * Unescaper encapsulates unescaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + */ +class Unescaper +{ + /** + * Parser and Inline assume UTF-8 encoding, so escaped Unicode characters + * must be converted to that encoding. + * + * @deprecated since version 2.5, to be removed in 3.0 + * + * @internal + */ + const ENCODING = 'UTF-8'; + + /** + * Regex fragment that matches an escaped character in a double quoted string. + */ + const REGEX_ESCAPED_CHARACTER = "\\\\([0abt\tnvfre \\\"\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})"; + + /** + * Unescapes a single quoted string. + * + * @param string $value A single quoted string. + * + * @return string The unescaped string. + */ + public function unescapeSingleQuotedString($value) + { + return str_replace('\'\'', '\'', $value); + } + + /** + * Unescapes a double quoted string. + * + * @param string $value A double quoted string. + * + * @return string The unescaped string. + */ + public function unescapeDoubleQuotedString($value) + { + $self = $this; + $callback = function ($match) use ($self) { + return $self->unescapeCharacter($match[0]); + }; + + // evaluate the string + return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); + } + + /** + * Unescapes a character that was found in a double-quoted string. + * + * @param string $value An escaped character + * + * @return string The unescaped character + */ + public function unescapeCharacter($value) + { + switch ($value{1}) { + case '0': + return "\x0"; + case 'a': + return "\x7"; + case 'b': + return "\x8"; + case 't': + return "\t"; + case "\t": + return "\t"; + case 'n': + return "\n"; + case 'v': + return "\xB"; + case 'f': + return "\xC"; + case 'r': + return "\r"; + case 'e': + return "\x1B"; + case ' ': + return ' '; + case '"': + return '"'; + case '/': + return '/'; + case '\\': + return '\\'; + case 'N': + // U+0085 NEXT LINE + return "\xC2\x85"; + case '_': + // U+00A0 NO-BREAK SPACE + return "\xC2\xA0"; + case 'L': + // U+2028 LINE SEPARATOR + return "\xE2\x80\xA8"; + case 'P': + // U+2029 PARAGRAPH SEPARATOR + return "\xE2\x80\xA9"; + case 'x': + return self::utf8chr(hexdec(substr($value, 2, 2))); + case 'u': + return self::utf8chr(hexdec(substr($value, 2, 4))); + case 'U': + return self::utf8chr(hexdec(substr($value, 2, 8))); + } + } + + /** + * Get the UTF-8 character for the given code point. + * + * @param int $c The unicode code point + * + * @return string The corresponding UTF-8 character + */ + private static function utf8chr($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); + } +} diff --git a/vendor/symfony/yaml/Yaml.php b/vendor/symfony/yaml/Yaml.php new file mode 100644 index 00000000..b6def517 --- /dev/null +++ b/vendor/symfony/yaml/Yaml.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\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @author Fabien Potencier + * + * @api + */ +class Yaml +{ + /** + * Parses YAML into a PHP array. + * + * The parse method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. + * + * Usage: + * + * $array = Yaml::parse(file_get_contents('config.yml')); + * print_r($array); + * + * + * As this method accepts both plain strings and file names as an input, + * you must validate the input before calling this method. Passing a file + * as an input is a deprecated feature and will be removed in 3.0. + * + * Note: the ability to pass file names to the Yaml::parse method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead. + * + * @param string $input Path to a YAML file or a string containing YAML + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap True if maps should return a stdClass instead of array() + * + * @return array The YAML converted to a PHP array + * + * @throws ParseException If the YAML is not valid + * + * @api + */ + public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) + { + // if input is a file, process it + $file = ''; + if (strpos($input, "\n") === false && is_file($input)) { + @trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED); + + if (false === is_readable($input)) { + throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input)); + } + + $file = $input; + $input = file_get_contents($file); + } + + $yaml = new Parser(); + + try { + return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } catch (ParseException $e) { + if ($file) { + $e->setParsedFile($file); + } + + throw $e; + } + } + + /** + * Dumps a PHP array to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param array $array PHP array + * @param int $inline The level where you switch to inline YAML + * @param int $indent The amount of spaces to use for indentation of nested nodes. + * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport true if object support is enabled, false otherwise + * + * @return string A YAML string representing the original PHP array + * + * @api + */ + public static function dump($array, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false) + { + $yaml = new Dumper(); + $yaml->setIndentation($indent); + + return $yaml->dump($array, $inline, 0, $exceptionOnInvalidType, $objectSupport); + } +} diff --git a/vendor/symfony/yaml/composer.json b/vendor/symfony/yaml/composer.json new file mode 100644 index 00000000..b07b439c --- /dev/null +++ b/vendor/symfony/yaml/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/yaml", + "type": "library", + "description": "Symfony Yaml Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Yaml\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + } +} diff --git a/vendor/symfony/yaml/phpunit.xml.dist b/vendor/symfony/yaml/phpunit.xml.dist new file mode 100644 index 00000000..418b2c6c --- /dev/null +++ b/vendor/symfony/yaml/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + +