diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..eae1e6eff --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +; Ignore modules from make file. +modules/contrib +modules/aegir +themes/contrib diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..0d777cce9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,85 @@ +language: php + +sudo: required + +# Only run test when committing to 1.x branch. +branches: + only: + - 7.x-1.x + +env: + global: + - DEVSHOP_VERSION=1.x + - SITE_HOSTS='dev.drup.devshop.travis dev.projectname.devshop.travis live.projectname.devshop.travis testenv.drpl8.devshop.travis' + + matrix: + - test="Upgrade" + COMMAND="robo up --test-upgrade" + UPGRADE_FROM_VERSION="1.0.0-beta10" + + - test="Install with Docker" + COMMAND="robo up --test" + + - test="Install with Ansible on Ubuntu 14.04 with Apache" + COMMAND="robo up --mode=install.sh --test" + +# - test="Install with Ansible on Ubuntu 14.04 with NGINX" +# COMMAND="robo up --mode=install.sh --test --install-sh-options='--server-webserver=nginx'" + + - test="Install with Ansible on CentOS 7" + COMMAND="robo up --mode=install.sh --install-sh-image=geerlingguy/docker-centos7-ansible --test" + + +services: + - docker + +before_install: + - pwd + - env + + # Install Robo + - wget https://github.com/consolidation/Robo/releases/download/1.0.5/robo.phar + - sudo mv robo.phar /usr/local/bin/robo + - sudo chmod +x /usr/local/bin/robo + + # Install Drush + - wget https://github.com/drush-ops/drush/releases/download/8.1.9/drush.phar + - sudo mv drush.phar /usr/local/bin/drush + - sudo chmod +x /usr/local/bin/drush + + # Clone devshop + - cd .. + - git clone http://github.com/opendevshop/devshop + - cd devshop + - git checkout -qf ${DEVSHOP_VERSION} + - git status + - pwd + - cp build-devmaster-travis-forks.make.yml build-devmaster-dev.make.yml + - cat build-devmaster-dev.make.yml + + # Prepare devshop CLI. + - composer install + +script: + - ${COMMAND} -n --no-dev + +# - container_id=$(mktemp) +# # Run container in detached state +# - 'sudo docker run --detach --name devshop_container --volume="${PWD}/devshop":/usr/share/devshop:rw --volume="${PWD}":/usr/share/devshop/devmaster:rw ${run_opts} -h devshop.travis --add-host "${SITE_HOSTS}":127.0.0.1 ${distribution}-${version}:ansible "${init}" > "${container_id}"' +# +# # Install script. +# - 'sudo docker exec devshop_container env TRAVIS=true TERM=xterm TRAVIS_BRANCH=$TRAVIS_BRANCH TRAVIS_REPO_SLUG=$TRAVIS_REPO_SLUG DEVSHOP_UPGRADE_TO_VERSION=$DEVSHOP_UPGRADE_TO_VERSION TRAVIS_PULL_REQUEST_BRANCH=$TRAVIS_PULL_REQUEST_BRANCH ${install_command} --makefile=/usr/share/devshop/devmaster/build-devmaster-test.make' +# +# # Hostmaster Status +# - 'sudo docker exec devshop_container env TERM=xterm sudo su - aegir -c "drush @hostmaster status"' +# +# # Turn off hosting queued, and the hosting task queue. +# - 'sudo docker exec devshop_container env sudo su - aegir -c "drush @hostmaster dis hosting_queued -y -v"' +# - 'sudo docker exec devshop_container env sudo su - aegir -c "drush @hostmaster vset hosting_queue_tasks_enabled 0 -y"' +# +# # Build and Run Tests +# - 'sudo docker exec devshop_container env TERM=xterm sudo su - -c "cd /usr/share/devshop/tests && composer update"' +# - 'sudo docker exec devshop_container env TERM=xterm sudo su - aegir -c "devshop devmaster:test"' +# +# # Stop container. +# - 'sudo docker stop devshop_container' diff --git a/API.txt b/API.txt new file mode 100644 index 000000000..fbb7fb258 --- /dev/null +++ b/API.txt @@ -0,0 +1,13 @@ +DevShop Provision API +===================== + +Document in Progress. + +Drush Hooks +----------- + +You can add drush hooks to act before or after devshop tasks. + +An example of a post-deploy hook is in the file deploy_hooks_examples/devshop.drush.inc + +Copy this file to sites/all/drush and use it for your specific site. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..76f721ca9 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +DevShop DevMaster +================= + +This is the DevShop web-based front-end, called Devmaster. + +It is a Drupal Install Profile and Makefile, otherwise known as a "Distribution". + +Please fork this repo if you wish to contribute to the Drupal based front-end of DevShop. + +More information about DevShop can be found in the [main project repository](https://github.com/opendevshop/devshop). + +Issues & Development +==================== + +All issues for any DevShop repository are located in the main project: [https://github.com/opendevshop/devshop/issues](https://github.com/opendevshop/devshop/issues) + +To contribute to development, please see the [Development](https://docs.opendevshop.com/development.html) section of the documentation. diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 000000000..525a4bd0e --- /dev/null +++ b/VERSION.txt @@ -0,0 +1 @@ +1.x \ No newline at end of file diff --git a/build-devmaster.make b/build-devmaster.make new file mode 100644 index 000000000..850ba28dc --- /dev/null +++ b/build-devmaster.make @@ -0,0 +1,13 @@ +; +; Loads the DevMaster install profile from drupal.org. +; +; This makefile is used by the DevShop standalone installer to build devmaster. +; + +core = 7.x +api = 2 + +includes[] = drupal-org-core.make +projects[devmaster][type] = profile +projects[devmaster][download][type] = git +projects[devmaster][download][branch] = 7.x-1.x \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..2ec2ec39a --- /dev/null +++ b/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "symfony/process": "^2.7", + "cpliakas/git-wrapper": "~1.0" + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..11e551c5a --- /dev/null +++ b/composer.lock @@ -0,0 +1,182 @@ +{ + "_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": "baae59addf8fe5c626e3b85bc900ba3e", + "packages": [ + { + "name": "cpliakas/git-wrapper", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/cpliakas/git-wrapper.git", + "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cpliakas/git-wrapper/zipball/5b89e9c5b4598c110e2d93d1245b4e26df3855a5", + "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/event-dispatcher": "~2.3|~3.0", + "symfony/process": "~2.3|~3.0" + }, + "require-dev": { + "pdepend/pdepend": "~1.0", + "phploc/phploc": "~2.0", + "phpmd/phpmd": "~1.0", + "phpunit/phpunit": "~3.0", + "psr/log": "~1.0", + "scrutinizer/ocular": "~1.0", + "sebastian/phpcpd": "~2.0", + "symfony/filesystem": "~2.0" + }, + "suggest": { + "monolog/monolog": "Enables logging of executed git commands" + }, + "type": "library", + "autoload": { + "psr-0": { + "GitWrapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Pliakas", + "email": "opensource@chrispliakas.com" + } + ], + "description": "A PHP wrapper around the Git command line utility.", + "homepage": "https://github.com/cpliakas/git-wrapper", + "keywords": [ + "git" + ], + "time": "2015-11-11 15:45:43" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf", + "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2015-10-30 23:35:59" + }, + { + "name": "symfony/process", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/1b988a88e3551102f3c2d9e1d47a18c3a78d6312", + "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2015-11-30 12:35:10" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/devmaster.info b/devmaster.info new file mode 100644 index 000000000..8956db267 --- /dev/null +++ b/devmaster.info @@ -0,0 +1,71 @@ +name = OpenDevShop DevMaster +description = Web interface for OpenDevShop. +core = 7.x + +; Drupal core +dependencies[] = block +dependencies[] = help +dependencies[] = menu +dependencies[] = node +dependencies[] = system +dependencies[] = user +dependencies[] = update + +; Aegir core +dependencies[] = hosting +dependencies[] = hosting_task +dependencies[] = hosting_client +dependencies[] = hosting_db_server +dependencies[] = hosting_site +dependencies[] = hosting_package +dependencies[] = hosting_platform +dependencies[] = hosting_web_server +dependencies[] = hosting_server +dependencies[] = hosting_clone +dependencies[] = hosting_cron +dependencies[] = hosting_migrate + +; DevShop Aegir +dependencies[] = hosting_alias +dependencies[] = hosting_queued +dependencies[] = hosting_http_basic_auth +dependencies[] = hosting_sync + +dependencies[] = hosting_filemanager +dependencies[] = hosting_logs +dependencies[] = hosting_ssl +dependencies[] = hosting_tasks_extra +dependencies[] = hosting_backup_queue +dependencies[] = hosting_site_backup_manager + +;dependencies[] = fix_permissions +;dependencies[] = fix_ownership + +; Drupal contrib +dependencies[] = admin_menu +dependencies[] = admin_menu_toolbar +dependencies[] = betterlogin +dependencies[] = views +dependencies[] = views_bulk_operations +dependencies[] = actions_permissions +dependencies[] = r4032login +dependencies[] = jquery_update +dependencies[] = module_filter + +; This breaks hosting.install. Removing it for now. +;dependencies[] = adminrole + +; DevShop +dependencies[] = devshop_hosting +dependencies[] = devshop_dothooks +dependencies[] = devshop_projects +dependencies[] = devshop_pull +dependencies[] = devshop_github +dependencies[] = devshop_testing +dependencies[] = aegir_ssh +dependencies[] = aegir_download +dependencies[] = aegir_commit +dependencies[] = aegir_update +dependencies[] = aegir_config +dependencies[] = devshop_stats + diff --git a/devmaster.make b/devmaster.make new file mode 100644 index 000000000..b59a66edc --- /dev/null +++ b/devmaster.make @@ -0,0 +1,8 @@ +core = 7.x +api = 2 + +; Includes + +; This makefile will make sure we get the development code from the Aegir +; modules instead of the tagged releases. +includes[devmaster] = drupal-org.make diff --git a/devmaster.profile b/devmaster.profile new file mode 100644 index 000000000..9db5db9af --- /dev/null +++ b/devmaster.profile @@ -0,0 +1,309 @@ +platform->server->http_service_type === 'nginx') { + module_enable(array('hosting_nginx')); + } + + // Bootstrap and create all the initial nodes + devmaster_bootstrap(); + + // Finalize and setup themes, menus, optional modules etc + devmaster_task_finalize(); +} + +function devmaster_bootstrap() { + /* Default node types and default node */ + $types = node_types_rebuild(); + + variable_set('install_profile', 'devmaster'); + + // Initialize the hosting defines + hosting_init(); + + /* Default client */ + $node = new stdClass(); + $node->uid = 1; + $node->type = 'client'; + $node->title = drush_get_option('client_name', 'admin'); + $node->status = 1; + node_save($node); + variable_set('hosting_default_client', $node->nid); + variable_set('hosting_admin_client', $node->nid); + + $client_id = $node->nid; + + /* Default server */ + $node = new stdClass(); + $node->uid = 1; + $node->type = 'server'; + $node->title = php_uname('n'); + $node->status = 1; + $node->hosting_name = 'server_master'; + $node->services = array(); + + /* Make it compatible with more than apache and nginx */ + $master_server = d()->platform->server; + hosting_services_add($node, 'http', $master_server->http_service_type, array( + 'restart_cmd' => $master_server->http_restart_cmd, + 'port' => 80, + 'available' => 1, + )); + + /* examine the db server associated with the hostmaster site */ + $db_server = d()->db_server; + $master_db = parse_url($db_server->master_db); + /* if it's not the same server as the master server, create a new node + * for it */ + if ($db_server->remote_host == $master_server->remote_host) { + $db_node = $node; + } else { + $db_node = new stdClass(); + $db_node->uid = 1; + $db_node->type = 'server'; + $db_node->title = $master_db['host']; + $db_node->status = 1; + $db_node->hosting_name = 'server_' . $db_server->remote_host; + $db_node->services = array(); + } + hosting_services_add($db_node, 'db', $db_server->db_service_type, array( + 'db_type' => $master_db['scheme'], + 'db_user' => urldecode($master_db['user']), + 'db_passwd' => isset($master_db['pass']) ? urldecode($master_db['pass']) : '', + 'port' => 3306, + 'available' => 1, + )); + + drupal_set_message(st('Creating master server node')); + node_save($node); + if ($db_server->remote_host != $master_server->remote_host) { + drupal_set_message(st('Creating db server node')); + node_save($db_node); + } + variable_set('hosting_default_web_server', $node->nid); + variable_set('hosting_own_web_server', $node->nid); + + variable_set('hosting_default_db_server', $db_node->nid); + variable_set('hosting_own_db_server', $db_node->nid); + + // Create the hostmaster platform & packages + $node = new stdClass(); + $node->uid = 1; + $node->title = 'Drupal'; + $node->type = 'package'; + $node->package_type = 'platform'; + $node->short_name = 'drupal'; + $node->old_short_name = 'drupal'; + $node->description = 'Drupal code-base.'; + $node->status = 1; + node_save($node); + $package_id = $node->nid; + + // @TODO: We need to still call these nodes "hostmaster" because the aliases are still @hostmaster and @platform_hostmaster + $node = new stdClass(); + $node->uid = 1; + $node->type = 'platform'; + $node->title = 'hostmaster'; + $node->publish_path = d()->root; + $node->makefile = ''; + $node->verified = 1; + $node->web_server = variable_get('hosting_default_web_server', 2); + $node->platform_status = 1; + $node->status = 1; + $node->make_working_copy = 0; + node_save($node); + $platform_id = $node->nid; + variable_set('hosting_own_platform', $node->nid); + + $instance = new stdClass(); + $instance->rid = $node->nid; + $instance->version = VERSION; + $instance->filename = ''; + $instance->version_code = 1; + //$instance->schema_version = drupal_get_installed_schema_version('system'); + $instance->schema_version = 0; + $instance->package_id = $package_id; + $instance->status = 0; + $instance->platform = $platform_id; + hosting_package_instance_save($instance); + + // Create the hostmaster profile package node + $node = new stdClass(); + $node->uid = 1; + $node->title = 'devmaster'; + $node->type = 'package'; + $node->old_short_name = 'devmaster'; + $node->description = 'The Devmaster profile.'; + $node->package_type = 'profile'; + $node->short_name = 'devmaster'; + $node->status = 1; + node_save($node); + $profile_id = $node->nid; + + $instance = new stdClass(); + $instance->rid = $node->nid; + $instance->version = VERSION; + $instance->filename = ''; + $instance->version_code = 1; + //$instance->schema_version = drupal_get_installed_schema_version('system'); + $instance->schema_version = 0; + $instance->package_id = $profile_id; + $instance->status = 0; + $instance->platform = $platform_id; + hosting_package_instance_save($instance); + + // Create the main Aegir site node + $node = new stdClass(); + $node->uid = 1; + $node->type = 'site'; + $node->title = d()->uri; + $node->platform = $platform_id; + $node->client = $client_id; + $node->db_name = ''; + $node->db_server = $db_node->nid; + $node->profile = $profile_id; + $node->import = true; + $node->hosting_name = 'hostmaster'; + $node->site_status = 1; + $node->verified = 1; + $node->status = 1; + node_save($node); + + // Save the hostmaster site nid. + variable_set('aegir_hostmaster_site_nid', $node->nid); + + // Enable the hosting features of modules that we enable by default. + // The module will already be enabled, + // this makes sure we also set the default permissions. + $default_hosting_features = array( + 'hosting_web_server' => 'web_server', + 'hosting_db_server' => 'db_server', + 'hosting_platform' => 'platform', + 'hosting_client' => 'client', + 'hosting_task' => 'task', + 'hosting_server' => 'server', + 'hosting_package' => 'package', + 'hosting_site' => 'site', + 'hosting' => 'hosting', + ); + hosting_features_enable($default_hosting_features, $rebuild = TRUE, $enable = FALSE); + + // Set the frontpage + variable_set('site_frontpage', 'devshop'); + + // Set the sitename + variable_set('site_name', 'DevShop'); + + // do not allow user registration: the signup form will do that + variable_set('user_register', 0); + + // This is saved because the config generation script is running via drush, and does not have access to this value + variable_set('install_url' , $GLOBALS['base_url']); + + // Disable backup queue for sites by default. + variable_set('hosting_backup_queue_default_enabled', 0); + + // Set hosting_logs default folder. + variable_set('provision_logs_file_path', '/var/log/aegir'); +} + +function devmaster_task_finalize() { + + // Enable "boots" theme. + drupal_set_message(st('Enabling "boots" theme')); + $theme = 'boots'; + theme_enable(array($theme)); + variable_set('theme_default', $theme); + + // Disable the default Bartik theme + theme_disable(array('bartik')); + + drupal_set_message(st('Configuring default blocks')); + devmaster_place_blocks($theme); + + // Save "menu_options" for our content types, so they don't offer to be put in menus. + variable_set('menu_options_client', ''); + variable_set('menu_options_platform', ''); + variable_set('menu_options_server', ''); + variable_set('menu_options_site', ''); + +// drupal_set_message(st('Configuring default blocks')); +// install_add_block('devshop_hosting', 'devshop_tasks', $theme, 1, 5, 'header', 1); +// +// // @TODO: CREATE DEVSHOP ROLES! +// drupal_set_message(st('Configuring roles')); +// install_remove_permissions(install_get_rid('anonymous user'), array('access content', 'access all views')); +// install_remove_permissions(install_get_rid('authenticated user'), array('access content', 'access all views')); +// install_add_permissions(install_get_rid('anonymous user'), array('access disabled sites')); +// install_add_permissions(install_get_rid('authenticated user'), array('access disabled sites')); +// +// // Create administrator role +// $rid = install_add_role('administrator'); +// variable_set('user_admin_role', $rid); + + // Hide errors from the screen. + variable_set('error_level', 0); + + // Disable Aegir's "Welcome" page + variable_set('hosting_welcome_page', 0); + + // Disable menu settings for projects + variable_set('menu_options_project', ''); + + // Force things to delete even if things fail. + variable_set('hosting_delete_force', 1); + + // Make sure blocks are setup properly. +// _block_rehash(); + + // Rebuild node access permissions. + node_access_rebuild(); +} + +/** + * Helper function to place block. + */ +function devmaster_place_blocks($theme) { + $blocks = array( + array( + 'module' => 'devshop_projects', + 'delta' => 'project_nav', + 'theme' => $theme, + 'status' => 1, + 'weight' => -1, + 'region' => 'header', + 'visibility' => 0, + 'pages' => '', + 'cache' => -1, + ), + array( + 'module' => 'devshop_projects', + 'delta' => 'project_create', + 'theme' => $theme, + 'status' => 1, + 'weight' => -1, + 'region' => 'sidebar_first', + 'visibility' => 0, + 'pages' => '', + 'cache' => -1, + ), + ); + + $query = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'visibility', 'pages', 'cache')); + + foreach ($blocks as $block) { + $query->values($block); + } + $query->execute(); + +} diff --git a/drupal-org-core.make b/drupal-org-core.make new file mode 100644 index 000000000..abf6cbeef --- /dev/null +++ b/drupal-org-core.make @@ -0,0 +1,5 @@ +core = 7.x +api = 2 + +projects[drupal][type] = core +projects[drupal][version] = 7.58 diff --git a/drupal-org.make b/drupal-org.make new file mode 100644 index 000000000..9b38f3ba2 --- /dev/null +++ b/drupal-org.make @@ -0,0 +1,90 @@ +core = 7.x +api = 2 + +defaults[projects][subdir] = "contrib" +defaults[projects][type] = "module" + +; Update this with each new release of devshop +projects[devshop_stats][version] = 1.x +projects[devshop_stats][subdir] = "contrib" + +; Aegir Modules +; For development, use latest branch. +; For release, use tagged version +projects[hosting][subdir] = aegir +projects[hosting][version] = "3.141" + +; Aegir Core not included in hosting.module +projects[eldir][type] = theme +projects[eldir][version] = "3.140" + +projects[hosting_git][subdir] = aegir +projects[hosting_git][version] = "3.140" + +projects[hosting_remote_import][subdir] = aegir +projects[hosting_remote_import][version] = "3.140" + +projects[hosting_site_backup_manager][subdir] = aegir +projects[hosting_site_backup_manager][version] = "3.140" + +projects[hosting_tasks_extra][subdir] = aegir +projects[hosting_tasks_extra][version] = "3.140" + +projects[hosting_logs][subdir] = aegir +projects[hosting_logs][version] = "3.140" + +projects[hosting_filemanager][subdir] = aegir +projects[hosting_filemanager][version] = "1.x" + +projects[aegir_ssh][subdir] = aegir +projects[aegir_ssh][version] = 1.0 + +projects[aegir_config][subdir] = aegir +projects[aegir_config][version] = 1.00-beta1 + +; Not working yet. +;projects[hosting_solr][version] = "1" + +; Contrib Modules +projects[sshkey][version] = "2.0" +projects[betterlogin][version] = 1.4 +projects[entity][version] = 1.9 +projects[openidadmin][version] = 1.0 +projects[overlay_paths][version] = 1.3 +projects[r4032login][version] = 1.8 +projects[admin_menu][version] = "3.0-rc5" +projects[adminrole][version] = "1.1" +projects[jquery_update][version] = "3.0-alpha5" +projects[views][version] = "3.18" +projects[views_bulk_operations][version] = "3.4" +projects[ctools][version] = "1.14" +projects[features][version] = "2.10" +projects[distro_update][version] = "1" +projects[module_filter][version] = "2" +projects[intercomio][version] = "1" +projects[libraries][version] = 2.3 + +; Hosting HTTPS with Let's Encrypt! +; @TODO: Update with a new alpha. +; projects[hosting_https][type] = module +; projects[hosting_https][download][type] = git +; projects[hosting_https][download][url] = https://gitlab.com/aegir/hosting_https.git +; projects[hosting_https][download][branch] = master +; projects[hosting_https][subdir] = "aegir" + +; Dehydrated for LetsEncrypt.org +; libraries[dehydrated][download][type] = git +; libraries[dehydrated][download][url] = https://github.com/lukas2511/dehydrated +; libraries[dehydrated][destination] = modules/aegir/hosting_https/submodules/letsencrypt/drush/bin + +; Timeago module +projects[timeago][version] = 2.3 + +; JQuery TimeAgo plugin +libraries[timeago][download][type] = get +libraries[timeago][download][url] = https://raw.githubusercontent.com/rmm5t/jquery-timeago/v1.5.3/jquery.timeago.js +libraries[timeago][destination] = libraries + +; Bootstrap base theme +projects[bootstrap][type] = theme +projects[bootstrap][version] = 3.19 diff --git a/logo.png b/logo.png new file mode 100644 index 000000000..13c356bc0 Binary files /dev/null and b/logo.png differ diff --git a/modules/devshop/README.txt b/modules/devshop/README.txt new file mode 100644 index 000000000..95fcdd2c7 --- /dev/null +++ b/modules/devshop/README.txt @@ -0,0 +1,59 @@ + +DevShop Hosting +=============== +Drupal development infrastructure made easy. + +This module provides the front-end interface needed to +deploy and manage sites using the DevShop git and features +based development workflow. + +About DevShop +------------- +The goals of DevShop are... + +1. to simplify management of multiple environments for multiple Drupal projects. +2. to provide web-based tools that streamline the Drupal site building workflow. +3. to provide a common, open-source infrastructure for Drupal development shops. + + +Installation +------------ +For installation instructions, see INSTALL.txt. + + +DevShop Projects +---------------- +DevShop functionality centers around "Projects". Aegir Project nodes store a +Git URL, the code path, the "base url", and the branches of the remote +repository. + +DevShop allows multiple platforms and sites (for dev, test, or live purposes) +to be created very easily. Platforms can be easily created from existing +branches of your git repositories. + + +Creating Projects +----------------- + +To create a new project, visit either the Projects page or click +"Create Content" > "DevShop Project". + +### Step 1: Git URL and project name. + +Enter your project's Git URL and Project Name. + +NOTE: Your project's git repo must be a complete drupal core file set. It +should match the structure of Drupal core's git repository, and can be a clone +of http://git.drupalcode.org/project/drupal.git + +### Step 2: File Path and Base URL + +Enter the base path to the Project's code. Recommended is /var/aegir/projects + +Enter the base URL for the project. All Project Sites will be on a subdomain of this base URL. + +### Step 3: Choose Platforms + +To complete this step, the Verify Project task must finish. + +On this page, you choose if you want dev, test, or live platforms and what branches each should live on. You can also choose the branches of your git repository you wish to create platforms and sites for. diff --git a/modules/devshop/aegir_commit/aegir_commit.info b/modules/devshop/aegir_commit/aegir_commit.info new file mode 100644 index 000000000..9ad032226 --- /dev/null +++ b/modules/devshop/aegir_commit/aegir_commit.info @@ -0,0 +1,4 @@ +name = Aegir Commit +description = Task for committing changes to a site. +core = 7.x +dependencies[] = hosting_site \ No newline at end of file diff --git a/modules/devshop/aegir_commit/aegir_commit.module b/modules/devshop/aegir_commit/aegir_commit.module new file mode 100644 index 000000000..81160defb --- /dev/null +++ b/modules/devshop/aegir_commit/aegir_commit.module @@ -0,0 +1,182 @@ + t('Commit Code'), + 'description' => t('Commit all or some of the changed files to the git repository.'), + 'dialog' => TRUE, + 'icon' => 'save' + ); + return $tasks; +} + +/** + * Implements hook_perm() + * @return array + */ +function aegir_commit_permission() { + return array( + 'create commit task' => array( + 'title' => t('create commit task'), + 'description' => t('Create "commit code" task.'), + ), + ); +} + +/** + * @return mixed + */ +function hosting_task_commit_form($node) { + + global $user; + $environment = $node->environment; + $options = array(); + + try { + $wrapper = new GitWrapper(); + $git = $wrapper->workingCopy($environment->repo_path); + $diff_stat = array_filter(explode(PHP_EOL,$wrapper->git('diff --numstat', $environment->repo_path))); + $status = array_filter(explode(PHP_EOL, $git->getStatus())); + + // Generate list of files + foreach ($status as &$line) { + $items = array_filter(explode(' ', trim($line))); + $file = array_combine(array('status', 'path'), $items); + + if ($file['status'] == 'A') { + $options[$file['path']] = 'Added'; + } + elseif ($file['status'] == 'M') { + $options[$file['path']] = 'Modified'; + } + elseif ($file['status'] == 'D') { + $options[$file['path']] = 'Deleted'; + } + elseif ($file['status'] == '??') { + $options[$file['path']] = 'New'; + } + $options[$file['path']] .= "{$file['path']}"; + } + } + catch (GitException $e) { + drupal_set_message("Cannot Commit Changes: Unable to load git repository at path '{$environment->repo_path}'. Something is wrong: either the path in the database is wrong, or the files are missing from their intended location.", 'error'); + drupal_set_message($e->getMessage(), 'error'); + drupal_goto("node/{$environment->project_nid}"); + } + $form['title'] = array( + '#markup' => '

' . t('Commit Changes') . '

', + ); + $form['git_info'] = array( + '#type' => 'fieldset', + '#title' => t('Git Status & Diff'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#weight' => 100, + '#prefix' => '
', + '#suffix' => t('It is recommended to read the git diff before you commit.') . '
', + ); + + // Limit status to 1000 lines + $lines = explode("\n", $environment->git_status); + $count = count($lines); + if ($count > 100) { + $lines = array_slice($lines, 0, 100); + $lines[] = "# STATUS TRUNCATED. SHOWING 100 of $count LINES."; + } + $environment->git_status = implode("\n", $lines); + + // Get git diff. + $environment->git_diff = trim(shell_exec("cd {$environment->repo_path}; git -c color.ui=always diff")); + + $form['git_info']['status'] = array( + '#type' => 'markup', + '#markup' => theme('devshop_ascii', array( + 'output' => $environment->git_status + )), + ); + $form['git_info']['diff'] = array( + '#type' => 'markup', + '#markup' => theme('devshop_ascii', array( + 'output' => $environment->git_diff + )), + ); + $form['files'] = array( + '#description' => t('Check the boxes next to the files you wish to commit. If you leave all boxes unchecked, all new and changed files will be committed.'), + '#type' => 'checkboxes', + '#options' => $options, + '#element_validate' => array('aegir_commit_validate_files'), + ); + $form['message'] = array( + '#title' => t('Message'), + '#rows' => 2, + '#type' => 'textarea', + '#description' => t('A commit message. A small automated timestamp will be added to indicate this was committed by devshop.'), + ); + $form['push'] = array( + '#title' => t('Push code after committing.'), + '#type' => 'checkbox', + '#default_value' => 1, + ); + $form['name'] = array( + '#type' => 'value', + '#value' => $user->name, + ); + $form['mail'] = array( + '#type' => 'value', + '#value' => $user->mail, + ); + return $form; +} + + +/** + * Converts list of files to json encode for hosting task arguments table. + * @param $element + * @param $form_state + */ +function aegir_commit_validate_files($element, &$form_state) { + if (!empty($element['#value'])) { + $values = json_encode(array_filter(array_values($element['#value']))); + form_set_value($element, $values, $form_state); + } +} + +/** + * Implements hook_devshop_environment_menu(). + * + * Defines the list of tasks that appear under the gear icon. + */ +function aegir_commit_devshop_environment_menu($environment) { + + if ($environment->site && $environment->site_status == HOSTING_SITE_ENABLED) { + $items[] = 'commit'; + } + return $items; +} + +/** + * Implements drush_hook_pre_hosting_task() + * + * Runs before the command "hosting-task" is run. Passes "task_args" from + * the task node into "$task->options" which become drush options. + */ +function drush_aegir_commit_pre_hosting_task() +{ + $task =& drush_get_context('HOSTING_TASK'); + + if ($task->task_type != 'commit') { + return; + } + $task->options['files'] = $task->task_args['files']; + $task->options['message'] = $task->task_args['message']; + $task->options['push'] = $task->task_args['push']; + $task->options['name'] = $task->task_args['name']; + $task->options['mail'] = $task->task_args['mail']; +} + diff --git a/modules/devshop/aegir_commit/hosting.feature.aegir_commit.inc b/modules/devshop/aegir_commit/hosting.feature.aegir_commit.inc new file mode 100644 index 000000000..dae2e5b67 --- /dev/null +++ b/modules/devshop/aegir_commit/hosting.feature.aegir_commit.inc @@ -0,0 +1,16 @@ + t('Commit Code'), + 'description' => t('Commit all or some of the changed files to the git repository.'), + 'status' => HOSTING_FEATURE_DISABLED, + 'module' => 'aegir_commit', + 'group' => 'advanced', + ); + return $features; +} diff --git a/modules/devshop/aegir_download/aegir_download.info b/modules/devshop/aegir_download/aegir_download.info new file mode 100644 index 000000000..6a3ff40ed --- /dev/null +++ b/modules/devshop/aegir_download/aegir_download.info @@ -0,0 +1,4 @@ +name = Aegir Download +description = Adds the ability to download packages to aegir hosted sites. +core = 7.x +dependencies[] = hosting_site \ No newline at end of file diff --git a/modules/devshop/aegir_download/aegir_download.module b/modules/devshop/aegir_download/aegir_download.module new file mode 100644 index 000000000..9c2e6ada7 --- /dev/null +++ b/modules/devshop/aegir_download/aegir_download.module @@ -0,0 +1,84 @@ + t('Download Modules'), + 'description' => t('Add modules or themes to your git repository.'), + 'dialog' => TRUE, + 'icon' => 'download' + ); + return $tasks; +} + +/** + * Implements hook_permission(). + * @return array + */ +function aegir_download_permission() { + return array( + 'create download task' => array( + 'title' => t('create download task'), + 'description' => t('Create "download" task.'), + ), + ); +} + +/** + * @return mixed + */ +function hosting_task_download_form() { + + $form['packages'] = array( + '#type' => 'textarea', + '#title' => t('Drupal modules or themes to download'), + '#description' => '

' . t('Enter the names of the drupal module or themes you would like to download to your project. The names must match the package system name: If you want the module from http://drupal.org/project/views, enter "views" into this field. Separate multiple packages with a space.') . '

' . t('If you enter the name of an existing module, it will overwrite your old version. This is a good way to update your modules. Run Update.php to ensure smooth deployment.') . '

', + ); + $form['commit'] = array( + '#title' => t('Commit to git.'), + '#type' => 'checkbox', + '#default_value' => 1, + ); + $form['message'] = array( + '#type' => 'textarea', + '#title' => t('Git Commit Message'), + '#description' => t('Enter a message to use in the git commit message.'), + ); + $form['update'] = array( + '#title' => t('Run Database Updates'), + '#type' => 'checkbox', + '#default_value' => 1, + '#description' => t('If module updates occur, run update.php to update the database. Only use if you trust the modules you are downloading.'), + ); + return $form; +} + +/** + * Implements hook_devshop_environment_menu(). + * + * Defines the list of tasks that appear under the gear icon. + */ +function aegir_download_devshop_environment_menu($environment) { + + if ($environment->site && $environment->site_status == HOSTING_SITE_ENABLED) { + $items[] = 'download'; + } + return $items; +} + +function drush_aegir_download_pre_hosting_task() +{ + $task =& drush_get_context('HOSTING_TASK'); + if ($task->task_type != 'download') { + return; + } + + drush_log('[AEGIR] Download package enabled...', 'ok'); + + $task->options['packages'] = $task->task_args['packages']; + $task->options['commit'] = $task->task_args['commit']; + $task->options['message'] = $task->task_args['message']; + $task->options['update'] = $task->task_args['update']; +} diff --git a/modules/devshop/aegir_download/hosting.feature.aegir_download.inc b/modules/devshop/aegir_download/hosting.feature.aegir_download.inc new file mode 100644 index 000000000..c5d501920 --- /dev/null +++ b/modules/devshop/aegir_download/hosting.feature.aegir_download.inc @@ -0,0 +1,16 @@ + t('Download Modules'), + 'description' => t('Add modules or themes to your git repository.'), + 'status' => HOSTING_FEATURE_ENABLED, + 'module' => 'aegir_download', + 'group' => 'advanced', + ); + return $features; +} diff --git a/modules/devshop/aegir_update/aegir_update.info b/modules/devshop/aegir_update/aegir_update.info new file mode 100644 index 000000000..7482630bc --- /dev/null +++ b/modules/devshop/aegir_update/aegir_update.info @@ -0,0 +1,4 @@ +name = Aegir Update +description = Adds the ability to update your Drupal site using Aegir. +core = 7.x +dependencies[] = hosting_site \ No newline at end of file diff --git a/modules/devshop/aegir_update/aegir_update.module b/modules/devshop/aegir_update/aegir_update.module new file mode 100644 index 000000000..70a4bc6d7 --- /dev/null +++ b/modules/devshop/aegir_update/aegir_update.module @@ -0,0 +1,84 @@ + t('Update Drupal'), + 'description' => t('Upgrades drupal core and contrib to the latest versions.'), + 'dialog' => TRUE, + 'icon' => 'wrench' + ); + return $tasks; +} + +/** + * Implements hook_permission(). + * @return array + */ +function aegir_update_permission() { + return array( + 'create update_drupal task' => array( + 'title' => t('create update_drupal task'), + 'description' => t('Create "Update Drupal" task.'), + ), + ); +} + +/** + * @return mixed + */ +function hosting_task_update_drupal_form() { + $form = array(); + $form['note'] = array( + '#markup' => t('This will run the command drush pm-update on your Drupal site.'), + '#prefix' => '

', + '#suffix' => '

', + ); + $form['warning'] = array( + '#markup' => t('Running this task may have unexpected consequences. It is not recommended to run on a production site.'), + '#prefix' => '
', + '#suffix' => '
', + ); + $form['commit'] = array( + '#type' => 'checkbox', + '#title' => t('Commit & Push all changes'), + '#description' => t('After running the update, commit and push all changes.'), + '#default_value' => 1, + ); + return $form; +} + +/** + * Implements hook_post_hosting_TASK_TYPE_task(). + * + * Trigger a verify task for the site and the platform. + */ +function aegir_update_post_hosting_update_drupal_task($task, $data) { + + $account = user_load($task->uid); + if (isset($task->task_args['commit']) && $task->task_args['commit']) { + hosting_add_task($task->ref->nid, 'commit', array( + 'push' => 1, + 'name' => $account->name, + 'mail' => $account->mail, + )); + } + + hosting_add_task($task->ref->platform, 'verify'); + hosting_add_task($task->ref->nid, 'verify'); +} + +/** + * Implements hook_devshop_environment_menu(). + * + * Defines the list of tasks that appear under the gear icon. + */ +function aegir_update_devshop_environment_menu($environment) { + + if ($environment->site && $environment->site_status == HOSTING_SITE_ENABLED) { + $items[] = 'update_drupal'; + } + return $items; +} diff --git a/modules/devshop/aegir_update/drush/aegir_update.drush.inc b/modules/devshop/aegir_update/drush/aegir_update.drush.inc new file mode 100644 index 000000000..42911c424 --- /dev/null +++ b/modules/devshop/aegir_update/drush/aegir_update.drush.inc @@ -0,0 +1,55 @@ + 'Updates drupal and contributed packages.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + ); + return $items; +} + +/** + * Implements the provision-update command. + */ +function drush_aegir_update_provision_update_drupal() { + drush_errors_on(); + + if (drush_drupal_major_version(d()->root) == 8) { +// drush_log('Drush command provision-update_drupal is not tet compatible with Drupal 8. Run the command `composer update drupal/core webflo/drupal-core-require-dev symfony/* --with-dependencies'); + + $path = d()->platform->repo_path? d()->platform->repo_path: d()->platform->root; + if (file_exists($path . '/composer.json')) { + devshop_process('composer update drupal/core webflo/drupal-core-require-dev symfony/* --with-dependencies', $path); + } + else { + drush_set_error('PROVISION_ERROR', dt('Drupal 8 was detected, but a composer.json file was not found in %path. Updating Drupal 8 automatically requires using a composer-based platform. See https://github.com/drupal-composer/drupal-project for the starting template.', [ + '%path' => $path, + ])); + } + } + elseif (drush_drupal_major_version(d()->root) <= 7) { + provision_backend_invoke(d()->name, 'pm-update'); + } + drush_log(dt('Drush pm-update task completed')); +} + +/** + * Map values of site node into command line arguments. + */ +function drush_aegir_update_pre_hosting_task($task) { + $task = &drush_get_context('HOSTING_TASK'); + if ($task->task_type == 'update_drupal' && !empty($task->task_args['commit'])) { + // Pass the argument provision_git expects. + $task->options['commit'] = $task->task_args['commit']; + } +} diff --git a/modules/devshop/aegir_update/hosting.feature.aegir_update.inc b/modules/devshop/aegir_update/hosting.feature.aegir_update.inc new file mode 100644 index 000000000..d6d4e07ec --- /dev/null +++ b/modules/devshop/aegir_update/hosting.feature.aegir_update.inc @@ -0,0 +1,16 @@ + t('Update Drupal'), + 'description' => t('Upgrades drupal core and contrib to the latest versions.'), + 'status' => HOSTING_FEATURE_DISABLED, + 'module' => 'aegir_update', + 'group' => 'advanced', + ); + return $features; +} diff --git a/modules/devshop/composer.json b/modules/devshop/composer.json new file mode 100644 index 000000000..221f7b526 --- /dev/null +++ b/modules/devshop/composer.json @@ -0,0 +1,9 @@ +{ + "require": { + "symfony/yaml": "^2.7", + "symfony/process": "^2.7", + "sensiolabs/ansi-to-html": "^1.1", + "knplabs/github-api": "~1.2", + "cpliakas/git-wrapper": "~1.0" + } +} \ No newline at end of file diff --git a/modules/devshop/composer.lock b/modules/devshop/composer.lock new file mode 100644 index 000000000..85352317a --- /dev/null +++ b/modules/devshop/composer.lock @@ -0,0 +1,432 @@ +{ + "_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": "b56d168cb201a1fe6c8f6e91f1403d54", + "content-hash": "38100c4c011afc9bd07907655e2e0e58", + "packages": [ + { + "name": "cpliakas/git-wrapper", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/cpliakas/git-wrapper.git", + "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cpliakas/git-wrapper/zipball/5b89e9c5b4598c110e2d93d1245b4e26df3855a5", + "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/event-dispatcher": "~2.3|~3.0", + "symfony/process": "~2.3|~3.0" + }, + "require-dev": { + "pdepend/pdepend": "~1.0", + "phploc/phploc": "~2.0", + "phpmd/phpmd": "~1.0", + "phpunit/phpunit": "~3.0", + "psr/log": "~1.0", + "scrutinizer/ocular": "~1.0", + "sebastian/phpcpd": "~2.0", + "symfony/filesystem": "~2.0" + }, + "suggest": { + "monolog/monolog": "Enables logging of executed git commands" + }, + "type": "library", + "autoload": { + "psr-0": { + "GitWrapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Pliakas", + "email": "opensource@chrispliakas.com" + } + ], + "description": "A PHP wrapper around the Git command line utility.", + "homepage": "https://github.com/cpliakas/git-wrapper", + "keywords": [ + "git" + ], + "time": "2015-11-11 15:45:43" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-03-18 18:23:50" + }, + { + "name": "knplabs/github-api", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/php-github-api.git", + "reference": "832b7be695ed2733741cd5c79166b4a88fb50786" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/832b7be695ed2733741cd5c79166b4a88fb50786", + "reference": "832b7be695ed2733741cd5c79166b4a88fb50786", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "guzzle/guzzle": "~3.7", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "knplabs/gaufrette": "Needed for optional Gaufrette cache" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Github\\": "lib/Github/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thibault Duplessis", + "email": "thibault.duplessis@gmail.com", + "homepage": "http://ornicar.github.com" + }, + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + } + ], + "description": "GitHub API v3 client", + "homepage": "https://github.com/KnpLabs/php-github-api", + "keywords": [ + "api", + "gh", + "gist", + "github" + ], + "time": "2015-10-11 02:38:28" + }, + { + "name": "sensiolabs/ansi-to-html", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/sensiolabs/ansi-to-html.git", + "reference": "02598b975c510e9e7d07d0be0a89c7a6b43464d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sensiolabs/ansi-to-html/zipball/02598b975c510e9e7d07d0be0a89c7a6b43464d6", + "reference": "02598b975c510e9e7d07d0be0a89c7a6b43464d6", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "suggest": { + "twig/twig": "Provides nice templating features" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-0": { + "SensioLabs\\AnsiConverter": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A library to convert a text with ANSI codes to HTML", + "time": "2015-07-22 03:07:58" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "ee278f7c851533e58ca307f66305ccb9188aceda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ee278f7c851533e58ca307f66305ccb9188aceda", + "reference": "ee278f7c851533e58ca307f66305ccb9188aceda", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2016-01-13 10:28:07" + }, + { + "name": "symfony/process", + "version": "v2.8.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac", + "reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2016-01-06 09:59:23" + }, + { + "name": "symfony/yaml", + "version": "v2.8.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/34c8a4b51e751e7ea869b8262f883d008a2b81b8", + "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-01-13 10:28:07" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/modules/devshop/devshop.css b/modules/devshop/devshop.css new file mode 100644 index 000000000..31fd15076 --- /dev/null +++ b/modules/devshop/devshop.css @@ -0,0 +1,130 @@ +div#header div.logo a { + text-indent: -999px; + overflow: hidden; + height: 72px !important; + width: 72px !important; + margin: 10px; +} + +#devshop-project-create-step-environments .platform { + clear: both; + border-bottom: 1px solid #e8e8e8; +} +#devshop-project-create-step-environments .platform .form-item { + float: left; + width: 46%; + clear: none; + height: 2.0em; + border-bottom: none; +} +body.aegir #page div.node div.content fieldset.project-environments div.form-item { + padding: 5px; +} + +fieldset.project-environments { + clear: both; +} + +#project-settings-table .description, +#project-settings-table .form-item-labeled label +{ + display: none; +} + +textarea#rsa { + display: none; +} + +.form-submit { + margin-top: 10px; +} +#edit-cancel { + float: right; +} +.command input { + font-family: Courier New, monospace; + width: 100%; +} +#footer { + height: 100px !important; + line-height: inherit !important; +} +#footer h2 { + border: none; + font-weight: normal; + text-transform: uppercase; +} +#footer .block { + float: left; + margin-right: 20px; + +} + +#block-system-0.block { + float: right; +} + +body.node-type-project a.hosting-goto-site-link { + background: none; + padding: inherit; +} + +/** + * Task Block! + */ +#block-devshop_hosting-devshop_tasks { + position: absolute; + top: 0px; + margin: 0px auto; + width: 940px; +} + + +#block-devshop_hosting-devshop_tasks .content { + position: absolute; + right: 0px; + top: 40px; +} + +#block-devshop_hosting-devshop_tasks #devshop-task-status { + font: bold 13px/20px "Helvetica Neue",Arial,sans-serif; + border: 0px; + padding: 5px 10px 5px 30px; + color: #fff; + background: #666; + cursor: pointer; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + width: 200px; +} + +#block-devshop_hosting-devshop_tasks #devshop-task-status.active { + background: #666 5px no-repeat url('devshop_projects/images/spinner-white.gif'); +} + +#block-devshop_hosting-devshop_tasks #devshop-task-status.inactive { + background: #666; +} + + +#hosting-task-queue-block { + display: none; + width: 96%; + margin: 0px auto; +} + + +#block-devshop_hosting-devshop_tasks:hover #hosting-task-queue-block { + display: block; +} + + +#block-devshop_hosting-devshop_tasks .task-logs-link { + display: block; + padding: 5px 10px; + background: #999; + color: white; + text-align: center; + font-size: 10pt; + text-transform: uppercase; +} diff --git a/modules/devshop/devshop_acquia/README.md b/modules/devshop/devshop_acquia/README.md new file mode 100644 index 000000000..b9257134a --- /dev/null +++ b/modules/devshop/devshop_acquia/README.md @@ -0,0 +1,50 @@ +Acquia Cloud Hooks Integration +============================== + +DevShop now supports firing Acquia Cloud Hooks when deploying to environments. + +The code in acquia.drush.inc will detect an acquia repo and trigger your hooks +to run. + +See the Acquia Cloud Hooks repository for more information and samples: https://github.com/acquia/cloud-hooks + +Currently, only the `post-code-update` hook is supported. More to come. + +Hooks +===== + +### post-code-deploy + +Changing branch or tag manually. + +DevShop Equivalent: provision-devshop-deploy when triggered from front-end. + +``` +post-code-deploy site target-env source-branch deployed-tag repo-url repo-type +``` + +### post-code-update + +Deploying code to a branch environment automatically. + +DevShop Equivalent: provision-devshop-deploy + +``` +post-code-deploy site target-env source-branch deployed-tag repo-url repo-type +``` + +### post-db-copy + +**Coming Soon...* + +``` +post-db-copy site target-env db-name source-env +``` + +### post-files-copy + +**Coming Soon...* + +``` +post-files-copy mysite prod dev +``` \ No newline at end of file diff --git a/modules/devshop/devshop_acquia/devshop_acquia.info b/modules/devshop/devshop_acquia/devshop_acquia.info new file mode 100644 index 000000000..46150d210 --- /dev/null +++ b/modules/devshop/devshop_acquia/devshop_acquia.info @@ -0,0 +1,5 @@ +name = DevShop Acquia Integration +description = Allows for Acquia Cloud Hooks to be used as deploy hooks in DevShop. +core = 7.x +package = DevShop +dependencies[] = devshop_hosting diff --git a/modules/devshop/devshop_acquia/devshop_acquia.module b/modules/devshop/devshop_acquia/devshop_acquia.module new file mode 100644 index 000000000..a0e8600ac --- /dev/null +++ b/modules/devshop/devshop_acquia/devshop_acquia.module @@ -0,0 +1,151 @@ +ref->type != 'site') { + return; + } + + // If this is a deploy task, and acquia hooks is not configured to run, return. + if ($task->task_type == 'devshop-deploy' && (!isset($task->task_args['acquia_hooks']) || $task->task_args['acquia_hooks'] != 1)) { + return; + } + + $environment = (object) $task->ref->environment; + $project = (object) $task->ref->project; + + // If project has no path to drupal, we know it's not acquia. + if ($project->drupal_path != 'docroot' || !file_exists($environment->repo_path . '/hooks')) { + drush_log('Acquia Cloud Hooks error', 'devshop_command'); + drush_log('./docroot or ./hooks folder is missing, but Acquia hooks are configured to run. Please create the hooks or turn off Acquia Cloud Hooks for the project.', 'devshop_error'); + return drush_set_error('DRUSH_ERROR'); + } + + // Determine the hook to run + $hooks = array(); + + // If "manual" deployment, it's a post-code-deploy + if (isset($task->task_args['manual']) && $task->task_args['manual'] == 1) { + $hooks[] = 'post-code-deploy'; + } + // If it is automatic, it's a "post-code-update" + elseif ($task->task_type == 'devshop_deploy') { + $hooks[] = 'post-code-update'; + } + // If task is sync... + elseif ($task->task_type == 'sync') { + // If database is copied, run post-db-copy hook. + if ($task->task_args['database']) { + $hooks[] = 'post-db-copy'; + } + // If files are copied, run post-files-copy hook. + if ($task->task_args['files']) { + $hooks[] = 'post-files-copy'; + } + } + + foreach ($hooks as $hook) { + + drush_log("[DEVSHOP] Invoking acquia hooks for hook $hook in environment {$environment->name} ... Scanning {$environment->repo_path}/hooks/common/{$hook} & {$environment->repo_path}/hooks/{$environment->name}/{$hook} ", 'ok'); + + // Collect cloud hook scripts to run for all environments "common". + $files = scandir("{$environment->repo_path}/hooks/common/{$hook}"); + if (empty($files)) $files = array(); + + // The list of files we will run. + $scripts = array(); + + // Remove incorrect entries from scandir. + $files = array_diff($files, array('..', '.', '.gitignore')); + + // Collect the full path to each script. + foreach ($files as $script) { + $scripts[] = realpath("{$environment->repo_path}/hooks/common/{$hook}/{$script}"); + } + + // Collect cloud hook scripts to run for this environment. + if (file_exists("{$environment->repo_path}/hooks/{$environment->name}/{$hook}")) { + + $files = scandir("{$environment->repo_path}/hooks/{$environment->name}/{$hook}"); + + // Remove incorrect entries from scandir. + $files= array_diff($files, array('..', '.', '.gitignore')); + + // Collect the full path to each script. + foreach ($files as $script) { + $scripts[] = realpath("{$environment->repo_path}/hooks/{$environment->name}/{$hook}/{$script}"); + } + } + + // Notify logs how many scripts we've found + $count = count($scripts); + drush_log("[DEVSHOP] Found $count scripts to run...", 'ok'); + + // Run Scripts + // @TODO: Implement using symfony:Process component. + // Usage: post-code-deploy site target-env source-branch deployed-tag repo-url repo-type + foreach ($scripts as $file) { + drush_log('[DEVSHOP] Running Acquia Cloud Hook: ' . $file, 'ok'); + $hook_path = str_replace($environment->repo_path, '', $file); + drush_log("[Acquia Cloud Hook $hook] $hook_path", 'devshop_command'); + + // This is a trick. DevShop doesn't have aliases like $project.$environment. + // When Acquia cloud hook writers create a script, they will use something like: + // + // drush_alias=$site'.'$target_env + // drush @$drush_alias cc all + // + // So, by passing "environment->name" as $site, and project.hostname as $target_env, + // we can trick the Acquia cloud script to use the right alias. + $alias = explode('.', $environment->drush_alias); + $environment_name = substr($alias[0], 1); + + $command = "sh $file {$environment_name} {$project->base_url} {$environment_name} old_branch {$environment->git_ref} repo_url repo_type"; + + drush_log('[Running] ' . $command, 'devshop_log'); + + $process = new Process($command); + $process->setTimeout(NULL); + $exit_code = $process->run(function ($type, $buffer) { + if (Process::ERR === $type) { + drush_log($buffer, 'devshop_ok'); + } + else { + drush_log($buffer, 'devshop_ok'); + } + }); + + // check exit code + if ($exit_code === 0) { + drush_log("[.hooks] Command executed successfully.", 'ok'); + } + else { + drush_log("[.hooks] Hook command failed: $command", 'error'); + drush_log($command, 'devshop_error'); + } + } + } +} diff --git a/modules/devshop/devshop_acquia/example.slack.sh b/modules/devshop/devshop_acquia/example.slack.sh new file mode 100644 index 000000000..3c4f848d1 --- /dev/null +++ b/modules/devshop/devshop_acquia/example.slack.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# +# Altered from https://github.com/acquia/cloud-hooks/tree/master/samples/slack +# Requires a /var/aegir/slack_settings file that contains: +# +# SLACK_WEBHOOK_URL=https://example.slack.com/services/hooks/incoming-webhook?token=TOKEN +# +# Cloud Hook: post-code-deploy +# +# The post-code-deploy hook is run whenever you use the Workflow page to +# deploy new code to an environment, either via drag-drop or by selecting +# an existing branch or tag from the Code drop-down list. See +# ../README.md for details. +# +# Usage: post-code-deploy site target-env source-branch deployed-tag repo-url +# repo-type + +site="$1" +target_env="$2" +source_branch="$3" +deployed_tag="$4" +repo_url="$5" +repo_type="$6" +source="$7" +site_url="$8" + +# Load the Slack webhook URL (which is not stored in this repo). +. $HOME/slack_settings + +if [ $source = 'devshop' ]; then + source="DevShop" + image="https://www.drupal.org/files/project-images/devshop-icon.png" +else + source="Acquia Cloud" + image="https://pbs.twimg.com/profile_images/1901642489/cloud_icon_150.png" +fi + +# Post deployment notice to Slack +curl -X POST --data-urlencode "payload={\"channel\": \"#dev-engageny\", \"username\": \"$source\", \"text\": \"Git branch \`$deployed_tag\` updated on *$target_env*: $site_url.\", \"icon_url\": \"$image\"}" $SLACK_WEBHOOK_URL diff --git a/modules/devshop/devshop_bitbucket/devshop_bitbucket.drush.inc b/modules/devshop/devshop_bitbucket/devshop_bitbucket.drush.inc new file mode 100644 index 000000000..cb5400855 --- /dev/null +++ b/modules/devshop/devshop_bitbucket/devshop_bitbucket.drush.inc @@ -0,0 +1,122 @@ +task_type == 'clone') { +// $platform = node_load($task->task_args['target_platform']); +// $environment = $platform->environment; +// $project = $platform->project; +// } +// // Subsequent tasks are "deploy" and "verify" +// elseif (($task->task_type == 'verify' && $task->ref->type == 'site') || $task->task_type == 'devshop-deploy' || $task->task_type == 'test') { +// $environment = $task->ref->environment; +// $project = $task->ref->project; +// } +// else { +// return; +// } +// +// // If a pull request object is available... +// if (isset($environment->bitbucket_pull_request->pull_request_object->deployment)) { +// +// // Create a deployment status +// $owner = $project->bitbucket_owner; +// $repo = $project->bitbucket_repo; +// $deployment_id = $environment->bitbucket_pull_request->pull_request_object->deployment->id; +// +// try { +// $token = variable_get('devshop_bitbucket_token', ''); +// $client = new \bitbucket\Client(); +// $client->authenticate($token, bitbucket\Client::AUTH_HTTP_TOKEN); +// +// $params = new stdClass(); +// if ($status == HOSTING_TASK_SUCCESS || $status == HOSTING_TASK_WARNING) { +// $params->state = $state = 'success'; +// } +// else { +// $params->state = $state = 'failure'; +// } +// +// // If task is a test run, only submit a commit status for devshop/tests context. +// if ($task->task_type == 'test') { +// $sha = $environment->bitbucket_pull_request->pull_request_object->head->sha; +// +// $params = new stdClass(); +// $params->state = $state; +// $params->target_url = url("node/{$task->nid}/revisions/{$task->vid}/view",array('absolute' => TRUE)); +// +// if ($status == HOSTING_TASK_WARNING) { +// $params->description = t('DevShop: Tests passed with warnings'); +// } +// else { +// $params->description = t('DevShop: Tests !status!', array('!status' => $state)); +// } +// $params->context = 'devshop/tests'; +// +// $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// drush_log('Commit status created for devshop/tests!', 'success'); +// } +// // Otherwise we create a deployment and a commit status. +// else { +// +// $params->target_url = $environment->url; +// $params->description = t('Visit !url', array('!url' => $task->ref->environment->url)); +// $post_url = "/repos/$owner/$repo/deployments/{$deployment_id}/statuses"; +// +// drush_log('Attempting to create bitbucket deployment status: ' . $post_url, 'success'); +// +// $deployment_status = $client->getHttpClient()->post($post_url, json_encode($params)); +// drush_log('Deployment status created!', 'success'); +// +// +// // Update Status API +// +// // Create a status +// $sha = $environment->bitbucket_pull_request->pull_request_object->head->sha; +// +// $params = new stdClass(); +// $params->state = $state; +// $params->target_url = url("node/{$task->nid}", array('absolute' => TRUE));; +// +// if ($status == HOSTING_TASK_WARNING) { +// $params->description = t('DevShop: Deploy success with warnings. [!url]', array( +// '!url' => $environment->url, +// )); +// } +// else { +// $params->description = t('DevShop: Deploy !status [!url]', array( +// '!status' => $state, +// '!url' => $environment->url, +// )); +// } +// $params->context = 'devshop/deploy'; +// +// $deployment_status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// drush_log('Commit status created!', 'success'); +// +// // If deploy task fails, tests won't run. +// if ($environment->settings->deploy['test'] && $status == HOSTING_TASK_ERROR) { +// +// $params = new stdClass(); +// $params->state = $state; +// $params->description = t('DevShop: Tests not run due to Deploy Fail'); +// $params->context = 'devshop/tests'; +// +// $deployment_status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// drush_log('Commit status created for devshop/tests', 'success'); +// } +// } +// } catch (bitbucket\Exception\RuntimeException $e) { +// drush_log('bitbucket API Error: ' . $e->getMessage(), 'error'); +// } +// } +//} diff --git a/modules/devshop/devshop_bitbucket/devshop_bitbucket.info b/modules/devshop/devshop_bitbucket/devshop_bitbucket.info new file mode 100644 index 000000000..648e8f885 --- /dev/null +++ b/modules/devshop/devshop_bitbucket/devshop_bitbucket.info @@ -0,0 +1,7 @@ +name = DevShop BitBucket +description = Support for BitBucket Webhooks +core = 7.x +package = DevShop +files[] = includes/add-key.inc +files[] = includes/admin.inc +dependencies[] = devshop_projects diff --git a/modules/devshop/devshop_bitbucket/devshop_bitbucket.install b/modules/devshop/devshop_bitbucket/devshop_bitbucket.install new file mode 100644 index 000000000..cee08e732 --- /dev/null +++ b/modules/devshop/devshop_bitbucket/devshop_bitbucket.install @@ -0,0 +1,95 @@ + array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Pull Request ID', + ), + 'number' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Pull Request Number', + ), + 'project_nid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => "The project's Node ID.", + ), + 'environment_name' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => 64, + 'default' => '', + 'description' => 'Environment name for this pull request environment.', + ), + 'pull_request_object' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + 'description' => 'A serialized array of settings for this environment.', + ), + ), + 'primary key' => array('id'), + ); + return $schema; +} + +/** + * Implements hook_install(). + */ +function devshop_bitbucket_install() { + + // Push devshop_bitbucket's system weight to 1. + db_update('system') + ->fields(array( + 'weight' => 2 + )) + ->condition('name', 'devshop_bitbucket') + ->execute(); + + // Display a message about setting a bitbucket personal token. + drupal_set_message(t('DevShop bitbucket module has been enabled. You must add an access token to enable full functionality at !link.', array( + '!link' => l(t('the settings page'), 'admin/devshop/bitbucket'), + ))); +} + +/** + * Set a weight higher than devshop_project so our form doesn't get obliterated by + * devshop_projects_form_project_node_form_alter() + */ +function devshop_bitbucket_update_7000() { + db_update('system') + ->fields(array( + 'weight' => 1 + )) + ->condition('name', 'devshop_bitbucket') + ->execute(); +} + +/** + * Set a weight higher than devshop_project so our form doesn't get obliterated by + * devshop_projects_form_project_node_form_alter() + */ +function devshop_bitbucket_update_7001() { + db_update('system') + ->fields(array( + 'weight' => 2 + )) + ->condition('name', 'devshop_bitbucket') + ->execute(); +} diff --git a/modules/devshop/devshop_bitbucket/devshop_bitbucket.module b/modules/devshop/devshop_bitbucket/devshop_bitbucket.module new file mode 100644 index 000000000..d7284e800 --- /dev/null +++ b/modules/devshop/devshop_bitbucket/devshop_bitbucket.module @@ -0,0 +1,744 @@ + 'BitBucket', +// 'description' => 'DevShop BitBucket Integration Settings', +// 'page callback' => 'drupal_get_form', +// 'page arguments' => array('devshop_bitbucket_settings_form'), +// 'access arguments' => array('administer projects'), +// 'file' => 'admin.inc', +// 'file path' => drupal_get_path('module', 'devshop_bitbucket') . '/includes', +// 'type' => MENU_LOCAL_TASK, +// ); +// $items['admin/devshop/bitbucket/add-key'] = array( +// 'title' => 'Add public key to bitbucket Account', +// 'page callback' => 'drupal_get_form', +// 'page arguments' => array('devshop_bitbucket_add_key_to_account'), +// 'access arguments' => array('administer projects'), +// 'file' => 'add-key.inc', +// 'file path' => drupal_get_path('module', 'devshop_bitbucket') . '/includes', +// 'type' => MENU_CALLBACK, +// ); + return $items; +} + +/** + * Implements hook_form_FORM_ID_alter() for project_node_form(). + */ +function devshop_bitbucket_form_project_node_form_alter(&$form, &$form_state, $form_id) { + $node = $form['#node']; + + if ($node->project->git_provider != 'bitbucket') { + return; + } + + //All settings git pull in project page + $form['project']['settings']['bitbucket'] = array( + '#type' => 'fieldset', + '#group' => 'project_settings', + '#collapsible' => TRUE, + '#collapsed' => arg(1) != $node->nid, + '#title' => t('BitBucket Integration'), + ); + + // Pull Requests create environments? + // $form['bitbucket']['pull_request_environments'] = array( + $form['project']['settings']['bitbucket']['pull_request_environments'] = array( + '#type' => 'checkbox', + '#title' => t('Create Environments for Pull Requests'), + '#default_value' => isset($node->project->settings->bitbucket) ? $node->project->settings->bitbucket['pull_request_environments'] : FALSE, + '#description' => t('If using bitbucket, create a new environment when a new Pull Request is created.'), + ); + + // Delete Pull Request environments? + // $form['bitbucket']['pull_request_environments_delete'] = array( + $form['project']['settings']['bitbucket']['pull_request_environments_delete'] = array( + '#type' => 'checkbox', + '#title' => t('Delete Pull Request Environments'), + '#default_value' => isset($node->project->settings->bitbucket) ? $node->project->settings->bitbucket['pull_request_environments_delete'] : FALSE, + '#description' => t('When Pull Requests are closed, delete the environment.'), + ); + + // Pull Request Environment method. + // $form['bitbucket']['pull_request_environments_method'] = array( + + $environments = array_keys($node->project->environments); + $options = array( + t('Install Drupal') => array( + 'devshop__bitbucket__install' => empty($node->project->install_profile)? t('Default install profile.'): $node->project->install_profile, + ), + t('Clone another environment') => array(), + ); + + if (empty($environments)) { + $options[t('Clone another environment')][] = t('No environments available.'); + } + else { + $options[t('Clone another environment')] = array_combine($environments, $environments); + } + $form['project']['settings']['bitbucket']['pull_request_environments_method'] = array( + '#type' => 'select', + '#title' => t('Pull Request Environment Creation Method'), + '#default_value' => isset($node->project->settings->bitbucket) ? + $node->project->settings->bitbucket['pull_request_environments_method'] : 'devshop__bitbucket__install', + '#description' => t('Select the method for creating the pull request environments.'), + '#options' => $options, + ); +} + +/** + * Implements hook_form_alter(). + */ +function devshop_bitbucket_form_devshop_project_create_step_git_alter(&$form, &$form_state, $form_id) { + + // Look for Token +// $token = variable_get('devshop_bitbucket_token', ''); +// +// if (empty($token)) { +// $form['connect']['#description'] = '
' . t('bitbucket API Token was not found.') . ' ' . l(t('Configure DevShop bitbucket Settings'), 'admin/devshop/bitbucket') . '
'; +// return; +// } +// +// if (devshop_bitbucket_check_key()) { +// $form['connect']['#description'] = '
' . t('The DevShop Public SSH Key has been found in your bitbucket account.') . '
'; +// } +// else { +// $form['connect']['#description'] = t('The DevShop Public SSH Key was not found in your account.') . ' '; +// +// $form['connect']['#description'] .= l(t('Add devshop public key to your bitbucket account'), 'admin/devshop/bitbucket/add-key', array( +// 'query' => array( +// 'destination' => $_GET['q'], +// ), +// 'attributes' => array( +// 'class' => 'btn btn-success' +// ), +// )) . ''; +// $form['connect']['#prefix'] = '
'; +// $form['connect']['#suffix'] = '
'; +// } +} + + +/** + * Implements hook_form_FORM_ID_alter() for project_node_form(). + */ +function devshop_bitbucket_form_devshop_project_create_step_sites_alter(&$form, &$form_state) { +// +// // Return if project isn't ready. +// if (empty($form['install_profile'])) { +// return; +// } +// +// // Return if there is no bitbucket token +// $bitbucket_token = variable_get('devshop_bitbucket_token', ''); +// if (empty($bitbucket_token)) { +// return; +// } +// +// // Load project and bitbucket +// $project_node = node_load($form['nid']['#value']); +// $project = $project_node->project; +// +// // Return if provider is not bitbucket. +// if ($project->git_provider != 'bitbucket') { +// return; +// } +// +// // Return if deploy method is not webhook. +// if ($project->settings->deploy['method'] != 'webhook') { +// return; +// } +// +// $repo = $project->bitbucket_owner . '/' . $project->bitbucket_repo; +// $repo_url = "http://bitbucket.com/" . $repo; +// +// $form['bitbucket_webhook'] = array( +// '#title' => t('Setup bitbucket Webhook'), +// '#description' => t('Leave this box checked to automatically add a webhook to your bitbucket Repository !link.', array( +// '!link' => l($repo, $repo_url, array( +// 'attributes' => array( +// 'target' => '_blank', +// ), +// )) +// )), +// '#type' => 'checkbox', +// '#default_value' => 1, +// ); +// +// $form['#validate'][] = 'devshop_bitbucket_project_create_webhook'; +// $form['#submit'][] = 'devshop_bitbucket_project_create_webhook'; +} + +/** + * Extra submit hook for last step of project create form. + */ +function devshop_bitbucket_project_create_webhook($form, $form_state) { +// +// // Return if box is not checked. +// if (empty($form_state['values']['bitbucket_webhook'])) { +// return; +// } +// +// // Get Project +// $project_node = node_load($form['nid']['#value']); +// $project = $project_node->project; +// +// // Get bitbucket client +// $client = new bitbucket\Client(); +// $bitbucket_token = variable_get('devshop_bitbucket_token', ''); +// $client->authenticate($bitbucket_token, bitbucket\Client::AUTH_HTTP_TOKEN); +// +// // Create the webhook. +// try { +// $hook = $client->repo()->hooks()->create($project->bitbucket_owner, $project->bitbucket_repo, array( +// 'name' => 'web', +// 'active' => true, +// 'events' => array( +// 'push', +// 'pull_request', +// 'delete', +// 'release', +// ), +// 'config' => array( +// 'url' => $project->webhook_url, +// 'content_type' => 'json', +// 'insecure_ssl' => '1', +// ), +// )); +// } +// catch (bitbucket\Exception\ValidationFailedException $e) { +// // For some reason, bitbucket always returns an exception here when there is already webhooks on the project. +// if ($e->getMessage() == 'Validation Failed: Hook already exists on this repository') { +// drupal_set_message(t("bitbucket webhook added, but there is already an existing webhook. Please check your repository's !link.", array( +// '!link' => l(t('Webhook Settings'), $project->git_repo_url . '/settings/hooks'), +// )), 'warning'); +// } +// else { +// drupal_set_message(t('bitbucket Validation Exception: !exception', array('!exception' => $e->getMessage())), 'error'); +// } +// } +// catch (bitbucket\Exception\RuntimeException $e) { +// drupal_set_message(t('bitbucket Runtime Exception: !exception', array('!exception' => $e->getMessage())), 'error'); +// } +} + +/** + * Implements hook_node_load(). + */ +function devshop_bitbucket_node_load($nodes, $types) { + if (count(array_intersect(array('project'), $types))) { + foreach ($nodes as $nid => $node) { + + // Look for a pull request object. + $pull_requests = db_query('SELECT * FROM {hosting_devshop_bitbucket_pull_requests} WHERE project_nid = :project_nid', array(':project_nid' => $nid)); + foreach ($pull_requests as $pull_request) { + if (!empty($pull_request->pull_request_object)) { + $pull_request->pull_request_object = unserialize($pull_request->pull_request_object); + if (isset($node->project->environments[$pull_request->environment_name])) { + $node->project->environments[$pull_request->environment_name]->bitbucket_pull_request = $pull_request; + } + } + } + + // Parse bitbucket owner and repo. + if (isset($node->project) && $node->project->git_provider == 'bitbucket') { + $node->project->bitbucket_owner = + $parts = explode('/', parse_url($node->project->git_repo_url, PHP_URL_PATH)); + $node->project->bitbucket_owner = $parts[1]; + $node->project->bitbucket_repo = $parts[2]; + } + } + } + +} + +/** + * Implements hook_node_update() for task insert. + * + * If task is a test run, send a "pending" commit status. + */ +function devshop_bitbucket_node_update($node) { +// +// // Only act on test triggers. +// if ($node->type != 'task' || $node->type == 'task' && ($node->task_type != 'test' || $node->task_type != 'deploy')) { +// return; +// } +// +// // Load the site and check for environment. +// $site = node_load($node->rid); +// if (empty($site->environment) || empty($site->environment->bitbucket_pull_request) || empty($site->environment->settings->deploy['test'])) { +// return; +// } +// +// try { +// $token = variable_get('devshop_bitbucket_token', ''); +// $client = new \bitbucket\Client(); +// $client->authenticate($token, bitbucket\Client::AUTH_HTTP_TOKEN); +// +// // Create a deployment status +// $project = $site->project; +// $owner = $project->bitbucket_owner; +// $repo = $project->bitbucket_repo; +// $sha = $site->environment->bitbucket_pull_request->pull_request_object->head->sha; +// +// $params = new stdClass(); +// $params->state = 'pending'; +// +// if ($node->task_type != 'test') { +// $params->target_url = url( +// "node/{$node->nid}", +// array('absolute' => true) +// ); +// $params->description = t('DevShop: Run Tests'); +// $params->context = 'devshop/tests'; +// } +// elseif ($node->task_type != 'deploy') { +// $params->target_url = $site->environment->url; +// $params->description = t('DevShop: Deploy'); +// $params->context = 'devshop/deploy'; +// } +// +// // Post status to bitbucket +// $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// +// watchdog('devshop_bitbucket','Test run initiated. bitbucket has been notified.'); +// } catch (bitbucket\Exception\RuntimeException $e) { +// watchdog('devshop_bitbucket', 'bitbucket Runtime Error in devshop_bitbucket_node_update(): ' . $e->getMessage()); +// } +} + + +/** + * + */ +function devshop_bitbucket_comment($task, $status) { + + $output = array(); + $output[] = '> **DEVSHOP**'; + $output[] = '> ' . ucfirst($task->task_type) . ": " . _hosting_parse_error_code($status); + $output[] = '> Site: ' . $task->ref->environment->url; + $output[] = '> Project: ' . url("node/{$task->ref->project->nid}", array('absolute' => TRUE)); + + if ($task->task_type == 'test') { + $output[] = 'Results: ' . url("node/{$task->nid}", array('absolute' => TRUE)); + } + + if ($task->task_type == 'import') { + $output[] = t('Your environment is now available.'); + } + + return implode("\n", $output); +} + + + +/** + * bitbucket action to take on webhook init + */ +function devshop_bitbucket_webhook($project_node) { + $headers = getallheaders(); + $project = $project_node->project; + + // Create a bitbucket deployment +// require_once 'vendor/autoload.php'; + + // @TODO: Handle form content from bitbucket as well. + if ($headers['Content-Type'] == 'application/json' || $headers['content-type'] == 'application/json') { + $data = json_decode(file_get_contents('php://input')); + +// watchdog('debug', $GLOBALS['HTTP_RAW_POST_DATA']); +// watchdog('debug', print_r($data, 1)); + + $args = array(); + $args['cache'] = 1; + + if (isset($headers['X-Event-Key'])) { + $bitbucket_event = $headers['X-Event-Key']; + } + else { + $bitbucket_event = ''; + } + + switch ($bitbucket_event) { + case 'ping': + $message = 'Pong!'; + break; + case 'repo:push': + + // If push is for a deleted branch, don't do anything. +// if ($data->deleted && $data->after == "0000000000000000000000000000000000000000") { +// $message = 'Deleted ref detected.'; +// break; +// } + + // Limit "Deploy" tasks to only run for the branches we have new code for.. + $git_ref = $data->push->changes[0]->new->name; + + // Check for environments set to pull + $environments_to_pull = array(); + foreach ($project->environments as $environment_name => $environment) { + + // Only pull if deploy is not disabled or if environment is tracking a tag. + if ($git_ref == $environment->git_ref && !$environment->settings->pull_disabled && !in_array($environment->git_ref, $project->settings->git['tags'])) { + $environments_to_pull[] = $environment->name; + + + // Default args to the environments deploy settings. + $args = $environment->settings->deploy; + $args['git_ref'] = $environment->git_ref; + + if (isset($environment->site) && $node = node_load($environment->site)) { + hosting_add_task($environment->site, 'devshop-deploy', $args); + } + } + } + + $message = "Push Received for git ref $git_ref. Deploying code to environments: " . implode(', ', $environments_to_pull); + break; + + case 'pullrequest:created': + case 'pullrequest:updated': + case 'pullrequest:declined': + case 'pullrequest:merged': + + // If pull request environments is enabled... + if ($project->settings->bitbucket['pull_request_environments']) { + $message = 'Pull Request Received.'; + + // @TODO: Handle forks? + $branch = $data->pullrequest->source->branch->name; + + // Determine environment branch. + // @TODO: Make Configurable, allow branch names to be env name + // $environment_name = "pr" . $data->pull_request->number; + $environment_name = 'branch_' . strtr($branch, array( + '-' => '_', + '/' => '_', + )); + $already_have_pr_info = FALSE; + + // When PR is opened... create new environment. +// if ($data->action == 'opened' || $data->action == 'reopened') { + if ($data->pullrequest->state == 'OPEN') { + $message = "Detected Pull Request creation for $branch \n"; + if (isset($project->environments[$environment_name])) { + $message = "Environment $environment_name already exists! Not creating one... \n"; + + // @TODO: Check for environments that are being deleted. + if (isset($project->environments[$environment_name]->bitbucket_pull_request)) { + $message .= "Already have a PR for $environment_name ... not inserting."; + $already_have_pr_info = TRUE; + } + } + else { + // If method is "install"... + if ($project->settings->bitbucket['pull_request_environments_method'] == 'devshop__bitbucket__install') { + hosting_create_environment($project, $environment_name, $branch); + $message .= "Environment $environment_name created for $project_node->title via installation profile.\n"; + } + // Otherwise, it is a clone from live. + elseif (isset($project->environments[$project->settings->bitbucket['pull_request_environments_method']] )) { + $source_env = $project->settings->bitbucket['pull_request_environments_method']; + hosting_create_environment($project, $environment_name, $branch, $source_env); + $message .= "Environment $environment_name created for $project_node->title via cloning $source_env \n"; + } + else { + $message .= 'Unable to determine what to do! Check "Pull Request Environment Method" setting.'; + } + } +// +// $owner = $project->bitbucket_owner; +// $repo = $project->bitbucket_repo; +// $message .= "About to try to create a deployment for $owner/$repo... \n"; +// +// // Send a "deployment" to bitbucket. +// try { +// $token = variable_get('devshop_bitbucket_token', ''); +// $client = new \bitbucket\Client(); +// $client->authenticate($token, bitbucket\Client::AUTH_HTTP_TOKEN); +// +// $sha = $data->pull_request->head->sha; +// $environment_name_url = str_replace('_', '-', $environment_name); +// $url = "http://{$environment_name_url}.{$project->base_url}"; +// +// $params = new stdClass(); +// $params->ref = $sha; +// $params->environment = $url; +// $params->required_contexts = array(); +// $post_url = "/repos/$owner/$repo/deployments"; +// $deployment = json_decode($client->getHttpClient()->post($post_url, json_encode($params))->getBody(TRUE)); +// +// // Save deployment to pull request data for later access. +// $data->pull_request->deployment = $deployment; +// +// $message .= " Deployment Created! \n"; +// +// // Create deployment status +// $params = new stdClass(); +// $params->state = 'pending'; +// $params->target_url = $url; +// $params->description = t('New environment is being created. Please stand by.'); +// $deployment_status = $client->getHttpClient()->post("/repos/$owner/$repo/deployments/{$deployment->id}/statuses", json_encode($params)); +// +// $message .= " Deployment Status Created! \n"; +// +// // Set a commit status for this REF for devshop/deploy context +// $sha = $data->pull_request->head->sha; +// +// $params = new stdClass(); +// $params->state = 'pending'; +// $params->target_url = url("node/$project->nid", array('absolute' => TRUE)); +// $params->description = t('DevShop: Deploy'); +// $params->context = 'devshop/deploy'; +// +// // Post status to bitbucket +// $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// $message .= " Commit Status Created! \n"; +// +// // Determine if we are going to run tests. +// // For now it is using the "live environment" setting. +// // @TODO: Once we add "deploy hooks" to "Project Settings: Environment Defaults" we will have to change this. +// +// // If live environment is set to run tests on deploy... +// $live_environment = $project->settings->live['live_environment']; +// if ($project->environments[$live_environment]->settings->deploy['test']) { +// $params = new stdClass(); +// $params->state = 'pending'; +// $params->target_url = url( +// "node/$project->nid", +// array('absolute' => true) +// ); +// $params->description = t('DevShop: Run Tests'); +// $params->context = 'devshop/tests'; +// +// // Post status to bitbucket +// $status = $client->getHttpClient()->post( +// "/repos/$owner/$repo/statuses/$sha", +// json_encode($params) +// ); +// $message .= " Commit Status for pending test run Created! \n"; +// } +// +// } catch (bitbucket\Exception\RuntimeException $e) { +// watchdog('devshop_bitbucket', 'bitbucket Runtime Error: ' . $e->getMessage()); +// $message .= 'bitbucket RunTimeException during PR Create: ' . $e->getMessage() . $e->getCode(); +// if ($e->getCode() == '409') { +// $message .= "\n Branch is out of date! alert the developer!"; +// +// // Send a failed commit status to alert to developer +// $params = new stdClass(); +// $params->state = 'failure'; +// $params->target_url = $project->git_repo_url; +// $params->description = t('Branch is out of date! Merge from default branch.'); +// $params->context = 'devshop/merge'; +// +// // Post status to bitbucket +// $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// } +// +// } catch (bitbucket\Exception\ValidationFailedException $e) { +// watchdog('devshop_bitbucket', 'bitbucket Validation Failed Error: ' . $e->getMessage()); +// $message .= 'bitbucket ValidationFailedException Error: ' . $e->getMessage(); +// } + + // Insert PR record + if (!$already_have_pr_info) { + $info = new stdClass(); + $info->id = $data->pullrequest->id; + $info->number = $data->pullrequest->id; + $info->project_nid = $project->nid; + $info->environment_name = $environment_name; + $info->pull_request_object = serialize($data->pullrequest); + + // Save environment record. + + // @TODO : We have to alter our schema. BitBucket doesn't offer unique PR Ids +// if (drupal_write_record('hosting_devshop_bitbucket_pull_requests', $info)) { +// $message .= ' ' . t('Pull Request info saved to DevShop.'); +// } + } + } + + // When PR is updated, send a new deployment status environment. + elseif ($data->action == 'synchronize') { +// +// // Create a new deployment +// $owner = $project->bitbucket_owner; +// $repo = $project->bitbucket_repo; +// $message .= "About to set deployment status for $owner/$repo... \n"; +// +// try { +// $token = variable_get('devshop_bitbucket_token', ''); +// $client = new \bitbucket\Client(); +// $client->authenticate($token, bitbucket\Client::AUTH_HTTP_TOKEN); +// +// $sha = $data->pull_request->head->sha; +// $environment = (object) $project->environments[$environment_name]; +// +// $params = new stdClass(); +// $params->ref = $sha; +// $params->environment = $environment->url; +// $params->required_contexts = array(); +// $post_url = "/repos/$owner/$repo/deployments"; +// $deployment = json_decode($client->getHttpClient()->post($post_url, json_encode($params))->getBody(TRUE)); +// +// // Save deployment to pull request data for later access. +// $data->pull_request->deployment = $deployment; +// +// $message .= " Deployment Created! \n"; +// +// // Create deployment status +// $deployment_id = $deployment->id; +// +// $params = new stdClass(); +// $params->state = 'pending'; +// $params->target_url = $environment->url; +// $params->description = t('Code is being deployed. Please stand by.'); +// +// $post_url = "/repos/$owner/$repo/deployments/{$deployment_id}/statuses"; +// $message .= "Attempting to create deployment status: $post_url \n"; +// +// $deployment_status = $client->getHttpClient()->post($post_url, json_encode($params)); +// +// $message .= " Deployment Status Created! \n"; +// +// // Set a commit status for this REF for devshop/deploy context +// $sha = $data->pull_request->head->sha; +// +// $params = new stdClass(); +// $params->state = 'pending'; +// $params->target_url = url("node/$project->nid", array('absolute' => TRUE)); +// $params->description = t('DevShop: Deploy'); +// $params->context = 'devshop/deploy'; +// +// // Post status to bitbucket +// $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// +// $message .= " Commit Status Created! \n"; +// +// // If environment is configured to run tests, add another status. +// if (!empty($environment->settings->deploy['test'])) { +// $params = new stdClass(); +// $params->state = 'pending'; +// +// // @TODO: Add the link to the last "test" task here instead of the project. +// $params->target_url = url("node/$project->nid", array('absolute' => TRUE)); +// $params->description = t('DevShop: Run Tests'); +// $params->context = 'devshop/tests'; +// +// // Post status to bitbucket +// $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// +// $message .= " Commit Status Created for test runs! \n"; +// } +// +// } catch (bitbucket\Exception\RuntimeException $e) { +// $log = 'bitbucket API Error during PR Syncronize: ' . $e->getMessage() . $e->getCode(); +// watchdog('devshop_bitbucket', $log); +// +// if ($e->getCode() == '409') { +// $log .= "\n Out of date! alert the developer!"; +// +// // Send a failed commit status to alert to developer +// $params = new stdClass(); +// $params->state = 'failure'; +// $params->target_url = $project->git_repo_url; +// $params->description = t('Branch is out of date! Merge from default branch.'); +// $params->context = 'devshop/merge'; +// +// // Post status to bitbucket +// $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// } +// $message .= $log . "\n"; +// } + + + // Update the PR record + $info = new stdClass(); + $info->id = $data->pullrequest->id; + $info->number = $data->pullrequest->id; + $info->project_nid = $project->nid; + $info->environment_name = $environment_name; + $info->pull_request_object = serialize($data->pullrequest); + + // Save environment record. + if (isset($project->environments[$environment_name]->bitbucket_pull_request)) { + $update = array('id'); + } + else { + $update = array(); + } + + // @TODO: We have to alter our schema. +// if (drupal_write_record('hosting_devshop_bitbucket_pull_requests', $info, $update)) { +// $message .= ' ' . t('Pull Request info saved to DevShop.'); +// } + } + // When PR is closed, delete environment. + elseif ($data->pullrequest->state == 'rejected' || $data->pullrequest->state == 'fulfilled') { + + $message .= "Pull Request Closed \n"; + if ($project->settings->bitbucket['pull_request_environments_delete']) { + + // If environment has a site... trigger it's deletion. + // Platform deletion triggers after site deletion completes. + if ($project->environments[$environment_name]->site) { + hosting_add_task($project->environments[$environment_name]->site, 'delete'); + $message .= "Environment $environment_name (Site Node: {$project->environments[$environment_name]->site}) scheduled for deletion."; + } + // If environment has a platform... trigger it's deletion. + elseif ($project->environments[$environment_name]->platform) { + hosting_add_task($project->environments[$environment_name]->platform, 'delete'); + $message .= "Environment $environment_name (Platform Node: {$project->environments[$environment_name]->platform}) scheduled for deletion."; + } + } + } + } + break; + } + + } + else { + $message = 'bitbucket Request Received, but not in JSON. Please make sure to configure the webhook to use Payload version: application/vnd.bitbucket.v3+json'; + } + return $message; +} + +/** + * Check the bitbucket account for an SSH key. + * + * @return bool + */ +function devshop_bitbucket_check_key() { +// $devshop_key = variable_get('devshop_public_key', ''); +// $token = variable_get('devshop_bitbucket_token', ''); +// +// $client = new \bitbucket\Client(); +// $client->authenticate($token, bitbucket\Client::AUTH_HTTP_TOKEN); +// +// try { +// $keys = $client->currentUser()->keys()->all(); +// } +// // Happens when user has no public keys. +// catch (\bitbucket\Exception\RuntimeException $e) { +// return FALSE; +// } +// +// foreach ($keys as $key) { +// if (strpos($devshop_key, $key['key']) === 0) { +// return TRUE; +// } +// } +} diff --git a/modules/devshop/devshop_bitbucket/includes/add-key.inc b/modules/devshop/devshop_bitbucket/includes/add-key.inc new file mode 100644 index 000000000..79c696fa7 --- /dev/null +++ b/modules/devshop/devshop_bitbucket/includes/add-key.inc @@ -0,0 +1,52 @@ + $devshop_key, + 'title' => t('Added by DevShop from !url', array( + '!url' => $_SERVER['HTTP_HOST'], + )), + ); + + // Create public key + $client = new \bitbucket\Client(); + $client->authenticate($token, bitbucket\Client::AUTH_HTTP_TOKEN); + try { + $return = $client->currentUser()->keys()->create($params); + } + catch (\bitbucket\Exception\ValidationFailedException $e) { + drupal_set_message($e->getMessage(), 'error'); + drupal_set_message('Code ' . $e->getCode(), 'error'); + } + + if ($return['verified']) { + drupal_set_message('SSH Key added to your account.'); + drupal_goto('admin/devshop/bitbucket'); + } else { + drupal_set_message('Something went wrong. SSH Key was not added to your account.', 'error'); + drupal_goto('admin/devshop/bitbucket'); + } +} diff --git a/modules/devshop/devshop_bitbucket/includes/admin.inc b/modules/devshop/devshop_bitbucket/includes/admin.inc new file mode 100644 index 000000000..bf5d52a23 --- /dev/null +++ b/modules/devshop/devshop_bitbucket/includes/admin.inc @@ -0,0 +1,59 @@ + t('bitbucket API Token'), + '#type' => 'password', + '#description' => t('A bitbucket API Token or Personal Access Token'), + '#default_value' => variable_get('devshop_bitbucket_token', ''), + ); + $bitbucket_url = "https://bitbucket.com/settings/tokens"; + $form['help'] = array( + '#markup' => t('Manage your tokens under account settings at !link.', array( + '!link' => l($bitbucket_url, $bitbucket_url, array('attributes' => array('target' => '_blank'))), + )) . '

' . t('Be sure to select the following Oauth Scopes: repo, repo:status, repo:deployment, public_repo, write:public_key, write_repo_hook'), + '#prefix' => '

', + '#suffix' => '

', + ); + + + // Warn the user if this is empty. + $token = variable_get('devshop_bitbucket_token', ''); + if (empty($token)) { + $form['devshop_bitbucket_token']['#description'] .= ' ' . t('No Token was found.') . ''; + $form['devshop_bitbucket_token']['#description'] .= ' ' . l(t('Create a new token'), 'https://bitbucket.com/settings/tokens/new', array('attributes' => array('target' => '_blank'))); + } + else { + $form['devshop_bitbucket_token']['#suffix'] .= ' ' . t('Your token is saved.') . ''; + } + + // Test for public key access. + if (!empty($token)) { + + $form['bitbucket_public_keys'] = array( + '#markup' => '', + ); + + if (devshop_bitbucket_check_key()) { + $form['bitbucket_public_keys']['#markup'] = '
' . t('The DevShop Public SSH Key has been found in your bitbucket account.') . '
'; + } + else { + $form['bitbucket_public_keys']['#markup'] = t('The DevShop Public SSH Key was not found in your account.') . ' '; + + $form['bitbucket_public_keys']['#markup'] .= ''; + $form['bitbucket_public_keys']['#prefix'] = '
'; + $form['bitbucket_public_keys']['#suffix'] = l(' ' . t('Add devshop public key to your bitbucket account'), 'admin/devshop/bitbucket/add-key', array( + 'attributes' => array( + 'class' => 'btn btn-primary btn-large' + ), + 'html' => TRUE, + )) . '
'; + } + } + + return system_settings_form($form); +} diff --git a/modules/devshop/devshop_cloud/drush/LICENSE b/modules/devshop/devshop_cloud/drush/LICENSE deleted file mode 100644 index d6a93266f..000000000 --- a/modules/devshop/devshop_cloud/drush/LICENSE +++ /dev/null @@ -1,340 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider.php deleted file mode 100644 index d22ffecb4..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider.php +++ /dev/null @@ -1,50 +0,0 @@ -setProperty('provider'); - $context->setProperty('provider_options'); - $context->setProperty('provider_data'); - $context->setProperty('provider_server_identifier'); - } - - /** - * Stub for init_server(); - * - * Call from child classes: - * parent::init_server(); - * - * This function is called many times during a server verify. - * Use sparingly. - */ - function init_server() { - } - - /** - * Saves server options to drush options so they will be picked up by - * devshop_cloud_post_hosting_verify_task() - */ - function verify_server_cmd() { - drush_set_option('provider_data', $this->server->provider_data); - drush_set_option('provider_server_identifier', $this->server->provider_server_identifier); - - if (!empty($this->server->ip_addresses)) { - drush_set_option('ip_addresses', $this->server->ip_addresses); - } - } - - static function option_documentation() { - return array( - '--provider' => 'The provider of this server. Must match an available Provision_Service_provider', - '--provider_options' => 'An array of options to send to the provider.', - ); - } -} diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/.gitignore b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/.gitignore deleted file mode 100644 index be7384552..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.DS_Store -*.swp -Thumbs.db -.svn -._* diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/LICENSE.textile b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/LICENSE.textile deleted file mode 100644 index cd0f96d85..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/LICENSE.textile +++ /dev/null @@ -1,9 +0,0 @@ -Copyright (c) 2009 - 2010, "SoftLayer Technologies, Inc.":http://www.softlayer.com/ All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither SoftLayer Technologies, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/README.textile b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/README.textile deleted file mode 100644 index b3f1851af..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/README.textile +++ /dev/null @@ -1,98 +0,0 @@ -h1. A SoftLayer API PHP client. - -h2. Overview - -The SoftLayer API PHP client classes provide a simple method for connecting to and making calls from the SoftLayer API and provides support for many of the SoftLayer API's features. Method calls and client management are handled by the PHP SOAP and XML-RPC extensions. - -Making API calls using the SoftLayer_SoapClient or SoftLayer_XmlrpcClient classes is done in the following steps: - -# Instantiate a new SoftLayer_SoapClient or SoftLayer_XmlrpcClient object using the SoftLayer_SoapClient::getClient() or SoftLayer_XmlrpcClient::getClient() methods. Provide the name of the service that you wish to query, an optional id number of the object that you wish to instantiate, your SoftLayer API username, your SoftLayer API key, and an optional API endpoint base URL. The client classes default to connect over the public Internet. Enter SoftLayer_SoapClient::API_PRIVATE_ENDPOINT or SoftLayer_XmlrpcClient::API_PRIVATE_ENDPOINT to connect to the API over SoftLayer's private network. The system making API calls must be connected to SoftLayer's private network (eg. purchased from SoftLayer or connected via VPN) in order to use the private network API endpoints. -# Define and add optional headers to the client, such as object masks and result limits. -# Call the API method you wish to call as if it were local to your client object. This class throws exceptions if it's unable to execute a query, so it's best to place API method calls in try / catch statements for proper error handling. - -Once your method is executed you may continue using the same client if you need to connect to the same service or define another client object if you wish to work with multiple services at once. - -The most up to date version of this library can be found on the SoftLayer github public repositories: "http://github.com/softlayer/":http://github.com/softlayer/ . Please post to the SoftLayer forums <"http://forums.softlayer.com/":http://forums.softlayer.com/> or open a support ticket in the SoftLayer customer portal if you have any questions regarding use of this library. - -h2. System Requirements - -The SoftLayer_SoapClient class requires at least PHP 5.2.3 and the PHP SOAP enxtension installed. The SoftLayer_Xmlrpc class requires PHP at least PHP 5 and the PHP XML-RPC extension installed. - -A valid API username and key are required to call the SoftLayer API. A connection to the SoftLayer private network is required to connect to SoftLayer's private network API endpopints. - -h2. Installation - -Download and copy the SoftLayer API client to a directory local to your PHP project or a path within your PHP installation's include_path. - -h2. Usage - -These examples use the SoftLayer_SoapClient class. If you wish to use the XML-RPC API then replace mentions of SoapClient.class.php with XmlrpcClient.class.php and SoftLayer_SoapClient with SoftLayer_XmlrpcClient. - -Here's a simple usage example that retrieves account information by calling the "getObject()":http://sldn.softlayer.com/reference/services/SoftLayer_Account/getObject method in the "SoftLayer_Account":http://sldn.softlayer.com/reference/services/SoftLayer_Account service: - -

-getObject();
-    print_r($account);
-} catch (Exception $e) {
-    die('Unable to retrieve account information: ' . $e->getMessage());
-}
-
- -For a more complex example we'll retrieve a support ticket with id 123456 along with the ticket's updates, the user it's assigned to, the servers attached to it, and the datacenter those servers are in. We'll retrieve our extra information using a nested object mask. After we have the ticket we'll update it with the text 'Hello!'. - -

-updates;
-$objectMask->assignedUser;
-$objectMask->attachedHardware->datacenter;
-$client->setObjectMask($objectMask);
-
-// Retrieve the ticket record
-try {
-    $ticket = $client->getObject();
-} catch (Exception $e) {
-    die('Unable to retrieve ticket record: ' . $e->getMessage());
-}
-
-// Update the ticket
-$update = new stdClass();
-$update->entry = 'Hello!';
-
-try {
-    $update = $client->addUpdate($update);
-    echo "Updated ticket 123456. The new update's id is " . $update[0]->id . '.');
-} catch (Exception $e) {
-    die('Unable to update ticket: ' . $e->getMessage());
-}
-
- -h2. Author - -This software is written by the SoftLayer Development Team <"sldn@softlayer.com":mailto:sldn@softlayer.com>. - -h2. Copyright - -This software is Copyright (c) 2009 - 2010 "SoftLayer Technologies, Inc":http://www.softlayer.com/. See the bundled LICENSE.textile file for more information. diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/Common/ObjectMask.class.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/Common/ObjectMask.class.php deleted file mode 100644 index b470c6df2..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/Common/ObjectMask.class.php +++ /dev/null @@ -1,84 +0,0 @@ -datacenter = new StdClass(); - * $objectMask->serverRoom = new StdClass(); - * $objectMask->provisionDate = new StdClass(); - * $objectMask->softwareComponents = new StdClass(); - * $objectMask->softwareComponents->passwords = new StdClass(); - * - * Building an object mask using SoftLayer_ObjectMask is a bit easier to - * type: - * - * $objectMask = new SoftLayer_ObjectMask(); - * $objectMask->datacenter; - * $objectMask->serverRoom; - * $objectMask->provisionDate; - * $objectMask->sofwareComponents->passwords; - * - * Use SoftLayer_SoapClient::setObjectMask() to set these object masks before - * making your SoftLayer API calls. - * - * For more on object mask usage in the SoftLayer API please see - * http://sldn.softlayer.com/article/Using_Object_Masks_in_the_SoftLayer_API . - * - * The most up to date version of this library can be found on the SoftLayer - * github public repositories: http://github.com/softlayer/ . Please post to - * the SoftLayer forums or open a support ticket - * in the SoftLayer customer portal if you have any questions regarding use of - * this library. - * - * @author SoftLayer Technologies, Inc. - * @copyright Copyright (c) 2009 - 2010, Softlayer Technologies, Inc - * @license http://sldn.softlayer.com/article/License - * @see SoftLayer_SoapClient::setObjectMask() - * @see SoftLayer_XmlrpcClient::setObjectMask() - */ -class SoftLayer_ObjectMask -{ - /** - * Define an object mask value - * - * @param string $var - */ - public function __get($var) - { - $this->{$var} = new SoftLayer_ObjectMask(); - - return $this->{$var}; - } -} diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient.class.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient.class.php deleted file mode 100644 index 68b3c9700..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient.class.php +++ /dev/null @@ -1,503 +0,0 @@ - or open a support ticket - * in the SoftLayer customer portal if you have any questions regarding use of - * this library. - * - * @author SoftLayer Technologies, Inc. - * @copyright Copyright (c) 2009 - 2010, Softlayer Technologies, Inc - * @license http://sldn.softlayer.com/article/License - * @link http://sldn.softlayer.com/article/The_SoftLayer_API The SoftLayer API - * @see SoftLayer_SoapClient_AsynchronousAction - */ -class Softlayer_SoapClient extends SoapClient -{ - /** - * Your SoftLayer API username. You may overide this value when calling - * getClient(). - * - * @var string - */ - const API_USER = 'set me'; - - /** - * Your SoftLayer API user's authentication key. You may overide this value - * when calling getClient(). - * - * @link https://manage.softlayer.com/Administrative/apiKeychain API key management in the SoftLayer customer portal - * @var string - */ - const API_KEY = 'set me'; - - /** - * The base URL of the SoftLayer SOAP API's WSDL files over the public - * Internet. - * - * @var string - */ - const API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/soap/v3/'; - - /** - * The base URL of the SoftLayer SOAP API's WSDL files over SoftLayer's - * private network. - * - * @var string - */ - const API_PRIVATE_ENDPOINT = 'http://api.service.softlayer.com/soap/v3/'; - - /** - * The namespace to use for calls to the API - * - * $var string - */ - const DEFAULT_NAMESPACE = 'http://api.service.softlayer.com/soap/v3/'; - - - /** - * The API endpoint base URL used by the client. - * - * @var string - */ - const API_BASE_URL = SoftLayer_SoapClient::API_PUBLIC_ENDPOINT; - - /** - * An optional SOAP timeout if you want to set a timeout independent of - * PHP's socket timeout. - * - * @var int - */ - const SOAP_TIMEOUT = null; - - /** - * The SOAP headers to send along with a SoftLayer API call - * - * @var array - */ - protected $_headers = array(); - - /** - * The name of the SoftLayer API service you wish to query. - * - * @link http://sldn.softlayer.com/reference/services A list of SoftLayer API services - * @var string - */ - protected $_serviceName; - - /** - * The base URL to the SoftLayer API's WSDL files being used by this - * client. - * - * @var string - */ - protected $_endpointUrl; - - /** - * Whether or not the current call is an asynchronous call. - * - * @var bool - */ - protected $_asynchronous = false; - - /** - * The object that handles asynchronous calls if the current call is an - * asynchronous call. - * - * @var SoftLayer_SoapClient_AsynchronousAction - */ - private $_asyncAction = null; - - /** - * If making an asynchronous call, then this is the name of the function - * we're calling. - * - * @var string - */ - public $asyncFunctionName = null; - - /** - * If making an asynchronous call, then this is the result of an - * asynchronous call as retuned from the - * SoftLayer_SoapClient_AsynchronousAction class. - * - * @var object - */ - private $_asyncResult = null; - - /** - * Used when making asynchronous calls. - * - * @var bool - */ - public $oneWay; - - /** - * Execute a SoftLayer API method - * - * @return object - */ - public function __call($functionName, $arguments = null) - { - // Determine if we shoud be making an asynchronous call. If so strip - // "Async" from the end of the method name. - if ($this->_asyncResult == null) { - $this->_asynchronous = false; - $this->_asyncAction = null; - - if (preg_match('/Async$/', $functionName) == 1) { - $this->_asynchronous = true; - $functionName = str_replace('Async', '', $functionName); - - $this->asyncFunctionName = $functionName; - } - } - - try { - $result = parent::__call($functionName, $arguments, null, $this->_headers, null); - } catch (SoapFault $e) { - throw new Exception('There was an error querying the SoftLayer API: ' . $e->getMessage()); - } - - if ($this->_asynchronous == true) { - return $this->_asyncAction; - } - - // remove the resultLimit header if they set it - $this->removeHeader('resultLimit'); - - return $result; - } - - /** - * Create a SoftLayer API SOAP Client - * - * Retrieve a new SoftLayer_SoapClient object for a specific SoftLayer API - * service using either the class' constants API_USER and API_KEY or a - * custom username and API key for authentication. Provide an optional id - * value if you wish to instantiate a particular SoftLayer API object. - * - * @param string $serviceName The name of the SoftLayer API service you wish to query - * @param int $id An optional object id if you're instantiating a particular SoftLayer API object. Setting an id defines this client's initialization parameter header. - * @param string $username An optional API username if you wish to bypass SoftLayer_SoapClient's built-in username. - * @param string $username An optional API key if you wish to bypass SoftLayer_SoapClient's built-in API key. - * @param string $endpointUrl The API endpoint base URL you wish to connect to. Set this to SoftLayer_SoapClient::API_PRIVATE_ENDPOINT to connect via SoftLayer's private network. - * @return SoftLayer_SoapClient - */ - public static function getClient($serviceName, $id = null, $username = null, $apiKey = null, $endpointUrl = null) - { - $serviceName = trim($serviceName); - - if ($serviceName == null) { - throw new Exception('Please provide a SoftLayer API service name.'); - } - - /* - * Default to use the public network API endpoint, otherwise use the - * endpoint defined in API_PUBLIC_ENDPOINT, otherwise use the one - * provided by the user. - */ - if (isset($endpointUrl)) { - $endpointUrl = trim($endpointUrl); - - if ($endpointUrl == null) { - throw new Exception('Please provide a valid API endpoint.'); - } - } elseif (self::API_BASE_URL != null) { - $endpointUrl = self::API_BASE_URL; - } else { - $endpointUrl = SoftLayer_SoapClient::API_PUBLIC_ENDPOINT; - } - - if (is_null(self::SOAP_TIMEOUT)) { - $soapClient = new SoftLayer_SoapClient($endpointUrl . $serviceName . '?wsdl'); - } else { - $soapClient = new SoftLayer_SoapClient($endpointUrl . $serviceName . '?wsdl', array('connection_timeout' => self::SOAP_TIMEOUT)); - } - - $soapClient->_serviceName = $serviceName; - $soapClient->_endpointUrl = $endpointUrl; - - if ($username != null && $apiKey != null) { - $soapClient->setAuthentication($username, $apiKey); - } else { - $soapClient->setAuthentication(self::API_USER, self::API_KEY); - } - - if ($id !== null) { - $soapClient->setInitParameter($id); - } - - return $soapClient; - } - - /** - * Set a SoftLayer API call header - * - * Every header defines a customization specific to an SoftLayer API call. - * Most API calls require authentication and initialization parameter - * headers, but can also include optional headers such as object masks and - * result limits if they're supported by the API method you're calling. - * - * @see removeHeader() - * @param string $name The name of the header you wish to set - * @param object $value The object you wish to set in this header - * @return SoftLayer_SoapClient - */ - public function addHeader($name, $value) - { - $this->_headers[$name] = new SoapHeader(self::DEFAULT_NAMESPACE, $name, $value); - return $this; - } - - /** - * Remove a SoftLayer API call header - * - * Removing headers may cause API queries to fail. - * - * @see addHeader() - * @param string $name The name of the header you wish to remove - * @return SoftLayer_SoapClient - */ - public function removeHeader($name) - { - unset($this->_headers[$name]); - return $this; - } - - /** - * Set a user and key to authenticate a SoftLayer API call - * - * Use this method if you wish to bypass the API_USER and API_KEY class - * constants and set custom authentication per API call. - * - * @link https://manage.softlayer.com/Administrative/apiKeychain API key management in the SoftLayer customer portal - * @param string $username - * @param string $apiKey - * @return SoftLayer_SoapClient - */ - public function setAuthentication($username, $apiKey) - { - $username = trim($username); - $apiKey = trim($apiKey); - - if ($username == null) { - throw new Exception('Please provide a SoftLayer API username.'); - } - - if ($apiKey == null) { - throw new Exception('Please provide a SoftLayer API key.'); - } - - $header = new stdClass(); - $header->username = $username; - $header->apiKey = $apiKey; - - $this->addHeader('authenticate', $header); - return $this; - } - - - /** - * Set an initialization parameter header on a SoftLayer API call - * - * Initialization parameters instantiate a SoftLayer API service object to - * act upon during your API method call. For instance, if your account has a - * server with id number 1234, then setting an initialization parameter of - * 1234 in the SoftLayer_Hardware_Server Service instructs the API to act on - * server record 1234 in your method calls. - * - * @link http://sldn.softlayer.com/article/Using_Initialization_Parameters_in_the_SoftLayer_API Using Initialization Parameters in the SoftLayer API - * @param int $id The ID number of the SoftLayer API object you wish to instantiate. - * @return SoftLayer_SoapClient - */ - public function setInitParameter($id) - { - $id = trim($id); - - if (!is_null($id)) { - $initParameters = new stdClass(); - $initParameters->id = $id; - $this->addHeader($this->_serviceName . 'InitParameters', $initParameters); - } - - return $this; - } - - /** - * Set an object mask to a SoftLayer API call - * - * Use an object mask to retrieve data related your API call's result. - * Object masks are skeleton objects or strings that define nested relational - * properties to retrieve along with an object's local properties. - * - * @see SoftLayer_ObjectMask - * @link http://sldn.softlayer.com/article/Using-Object-Masks-SoftLayer-API Using object masks in the SoftLayer API - * @param object $mask The object mask you wish to define - * @return SoftLayer_SoapClient - */ - public function setObjectMask($mask) - { - if (!is_null($mask)) { - $header = 'SoftLayer_ObjectMask'; - - if ($mask instanceof SoftLayer_ObjectMask) { - $header = sprintf('%sObjectMask', $this->_serviceName); - } - - $objectMask = new stdClass(); - $objectMask->mask = $mask; - $this->addHeader($header, $objectMask); - } - - return $this; - } - - /** - * Set an object filter to a SoftLayer API call - * - * Use an object filter to limit what data you get back - * from the API. Very similar to objectMasks - * - * @see SoftLayer_ObjectMask - * @param object $filter The object filter you wish to define - * @return SoftLayer_SoapClient - */ - public function setObjectFilter($objectFilter) - { - if (!is_null($objectFilter)) { - $header = sprintf('%sObjectFilter', $this->_serviceName); - $this->addHeader($header, $objectFilter); - } - return $this; - } - /** - * Set a result limit on a SoftLayer API call - * - * Many SoftLayer API methods return a group of results. These methods - * support a way to limit the number of results retrieved from the SoftLayer - * API in a way akin to an SQL LIMIT statement. - * - * @link http://sldn.softlayer.com/article/Using_Result_Limits_in_the_SoftLayer_API Using Result Limits in the SoftLayer API - * @param int $limit The number of results to limit your SoftLayer API call to. - * @param int $offset An optional offset to begin your SoftLayer API call's returned result set at. - * @return SoftLayer_SoapClient - */ - public function setResultLimit($limit, $offset = 0) - { - $resultLimit = new stdClass(); - $resultLimit->limit = intval($limit); - $resultLimit->offset = intval($offset); - - $this->addHeader('resultLimit', $resultLimit); - return $this; - } - - /** - * Process a SOAP request - * - * We've overwritten the PHP SoapClient's __doRequest() to allow processing - * asynchronous SOAP calls. If an asynchronous call was deected in the - * __call() method then send processing to the - * SoftLayer_SoapClient_AsynchronousAction class. Otherwise use the - * SoapClient's built-in __doRequest() method. The results of this method - * are sent back to __call() for post-processing. Asynchronous calls use - * handleAsyncResult() to send he results of the call back to __call(). - * - * @return object - */ - public function __doRequest($request, $location, $action, $version, $one_way = false) - { - // Don't make a call if we already have an asynchronous result. - if ($this->_asyncResult != null) { - $result = $this->_asyncResult; - unset($this->_asyncResult); - - return $result; - } - - if ($this->oneWay == true) { - $one_way = true; - $this->oneWay = false; - } - - // Use either the SoapClient or SoftLayer_SoapClient_AsynchronousAction - // class to handle the call. - if ($this->_asynchronous == false) { - $result = parent::__doRequest($request, $location, $action, $version, $one_way); - - return $result; - } else { - $this->_asyncAction = new SoftLayer_SoapClient_AsynchronousAction($this, $this->asyncFunctionName, $request, $location, $action); - return ''; - } - } - - /** - * Process the results of an asynchronous call. - * - * The SoftLayer_SoapClient_AsynchronousAction class uses - * handleAsyncResult() to return it's call resuls back to this classes' - * __call() method for post-pocessing. - * - * @param string $functionName The name of the SOAP method called. - * @param string $result The raw SOAP XML output from a SOAP call - * @return object - */ - public function handleAsyncResult($functionName, $result) - { - $this->_asynchronous = false; - $this->_asyncResult = $result; - - return $this->__call($functionName, array()); - } -} diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient/AsynchronousAction.class.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient/AsynchronousAction.class.php deleted file mode 100644 index 416ad3a24..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient/AsynchronousAction.class.php +++ /dev/null @@ -1,224 +0,0 @@ - receive style of transmission. Response - * time for a call is dependent on the latency between the SOAP client and SOAP - * server and the time required by the server to process the SOAP call. Sending - * multiple SOAP calls in serial over the public Internet can be a time - * consuming process. - * - * Asynchronous calls allow you to send multiple SOAP calls in parallel to the - * SoftLayer API. Parallel calls reduce the latency involved handling multiple - * calls to the time it takes for the longest SOAP call to execute, dramatically - * reducing the time it takes to send multiple SOAP calls in most cases. - * - * Asynchronous calls are handled identically to standard API calls with two - * differences: - * - * 1) The SoftLayer_SoapClient class knows to make an asynchronous call when the - * method called ends with "Async". For example, to make a standard call to the - * method getObject() you would execute $client->geObject(). It's asynchronous - * counterpart is execued with the code $client->getObjectAsync(). Once the - * asynchronous call is made the results of your API command are sent to this - * classes' socket property. - * - * 2) The results of an asynchronous method call are stored in a - * SoftLayer_SoapClient_AsynchronousAction object. Use the wait() method to - * retrieve data off the internal socket and return the result back to the - * SoftLayer_SoapClient for processing. For example if you wish to retrieve the - * results of the method getObject() execute the following statements: - * - * $result = $client->getObjectAsync(); // Make the call and start geting data back. - * $result = $result->wait(); // Return the results of the API call - * - * To chain multiple asynchronous requests together call multiple Async requests - * in succession then call their associated wait() methods in succession. - * - * Here's a simple usage example that retrieves account information, a PDF of an - * account's next invoice and enables VLAN spanning on that same account by - * calling three methods in the SoftLayer_Account service in parallel: - * - * ---------- - * - * // Initialize an API client for the SoftLayer_Account service. - * $client = SoftLayer_SoapClient::getClient('SoftLayer_Account'); - * - * try { - * // Request our account information. - * $account = $client->getObjectAsync(); - * - * // Request a PDF of our next invoice. This can take much longer than - * // getting simple account information. - * $nextInvoicePdf = $client->getNextInvoicePdfAsync(); - * - * // While we're at it we'll enable VLAN spanning on our account. - * $vlanSpanResult = $client->setVlanSpanAsync(true); - * - * // The three requests are now processing in parallel. Use the wait() - * // method to retrieve the resuls of our requests. The wait time involved - * // is roughly the same time as the longest API call. - * $account = $account->wait(); - * $nextInvoicePdf = $nextInvoicePdf->wait(); - * $vlanSpanResult = $vlanSpanResult->wait(); - * - * // Finally, display our results. - * var_dump($account); - * var_dump($nextInvoicePdf); - * var_dump($vlanSpanResult); - * } catch (Exception $e) { - * die('Unable to retrieve account information: ' . $e->getMessage()); - * } - * - * ---------- - * - * The most up to date version of this library can be found on the SoftLayer - * github public repositories: http://github.com/softlayer/ . Please post to - * the SoftLayer forums or open a support ticket - * in the SoftLayer customer portal if you have any questions regarding use of - * this library. - * - * @author SoftLayer Technologies, Inc. - * @copyright Copyright (c) 2009 - 2010, Softlayer Technologies, Inc - * @license http://sldn.softlayer.com/article/License - * @see SoftLayer_SoapClient - */ -class SoftLayer_SoapClient_AsynchronousAction -{ - /** - * The SoftLayer SOAP client making an asynchronous call - * - * @var SoftLayer_SoapClient - */ - protected $_soapClient; - - /** - * The name of the function we're calling - * - * @var string - */ - protected $_functionName; - - /** - * A socket connection to the SoftLayer SOAP API - * - * @var resource - */ - protected $_socket; - - /** - * Perform an asynchgronous SoftLayer SOAP call - * - * Create a raw socket connection to the URL specified by the - * SoftLayer_SoapClient class and send SOAP HTTP headers and request XML to - * that socket. Throw exceptions if we're unable to make the socket - * connection or send data to that socket. - * - * @param SoftLayer_SoapClient $soapClient The SoftLayer SOAP client making the asynchronous call. - * @param string $functionName The name of the function we're calling. - * @param string $request The full XML SOAP request we wish to make. - * @param string $location The URL of the web service we wish to call. - * @param string $action The value of the HTTP SOAPAction header in our SOAP call. - */ - public function __construct($soapClient, $functionName, $request, $location, $action) - { - preg_match('%^(http(?:s)?)://(.*?)(/.*?)$%', $location, $matches); - - $this->_soapClient = $soapClient; - $this->_functionName = $functionName; - - $protocol = $matches[1]; - $host = $matches[2]; - $endpoint = $matches[3]; - - $headers = array( - 'POST ' . $endpoint . ' HTTP/1.1', - 'Host: ' . $host, - 'User-Agent: PHP-SOAP/' . phpversion(), - 'Content-Type: text/xml; charset=utf-8', - 'SOAPAction: "' . $action . '"', - 'Content-Length: ' . strlen($request), - 'Connection: close', - ); - - if ($protocol == 'https') { - $host = 'ssl://' . $host; - $port = 443; - } else { - $port = 80; - } - - $data = implode("\r\n", $headers) . "\r\n\r\n" . $request . "\r\n"; - $this->_socket = fsockopen($host, $port, $errorNumber, $errorMessage); - - if ($this->_socket === false) { - $this->_socket = null; - throw new Exception('Unable to make an asynchronous SoftLayer API call: ' . $errorNumber . ': ' . $errorMessage); - } - - if (fwrite($this->_socket, $data) === false) { - throw new Exception('Unable to write data to an asynchronous SoftLayer API call.'); - } - } - - /** - * Process and return the results of an asyncrhonous SoftLayer API call - * - * Read data from our socket and process the raw SOAP result from the - * SoftLayer_SoapClient instance that made the asynchronous call. wait() - * *must* be called in order to recieve the results from your API call. - * - * @return object - */ - public function wait() - { - $soapResult = ''; - - while (!feof($this->_socket)) { - $soapResult .= fread($this->_socket, 8192); - } - - // separate the SOAP result into headers and data. - list($headers, $data) = explode("\r\n\r\n", $soapResult); - - return $this->_soapClient->handleAsyncResult($this->_functionName, $data); - } - - /** - * Close the socket created when the SOAP request was created. - */ - public function __destruct() - { - if ($this->_socket != null) { - fclose($this->_socket); - } - } -} diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/XmlrpcClient.class.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/XmlrpcClient.class.php deleted file mode 100644 index 31db1313e..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/XmlrpcClient.class.php +++ /dev/null @@ -1,437 +0,0 @@ - or open a support ticket - * in the SoftLayer customer portal if you have any questions regarding use of - * this library. - * - * @author SoftLayer Technologies, Inc. - * @copyright Copyright (c) 2009 - 2010, Softlayer Technologies, Inc - * @license http://sldn.softlayer.com/article/License - * @link http://sldn.softlayer.com/article/The_SoftLayer_API The SoftLayer API - */ -class Softlayer_XmlrpcClient -{ - /** - * Your SoftLayer API username. You may overide this value when calling - * getClient(). - * - * @var string - */ - const API_USER = 'set me'; - - /** - * Your SoftLayer API user's authentication key. You may overide this value - * when calling getClient(). - * - * @link https://manage.softlayer.com/Administrative/apiKeychain API key management in the SoftLayer customer portal - * @var string - */ - const API_KEY = 'set me'; - - /** - * The base URL of SoftLayer XML-RPC API's public network endpoints. - * - * @var string - */ - const API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3/'; - - /** - * The base URL of SoftLayer XML-RPC API's private network endpoints. - * - * @var string - */ - const API_PRIVATE_ENDPOINT = 'http://api.service.softlayer.com/xmlrpc/v3/'; - - /** - * The API endpoint base URL used by the client. - * - * @var string - */ - const API_BASE_URL = SoftLayer_XmlrpcClient::API_PUBLIC_ENDPOINT; - - /** - * The headers to send along with a SoftLayer API call - * - * @var array - */ - protected $_headers = array(); - - /** - * The name of the SoftLayer API service you wish to query. - * - * @link http://sldn.softlayer.com/reference/services A list of SoftLayer API services - * @var string - */ - protected $_serviceName; - - /** - * The base URL of SoftLayer XML-RPC API's endpoints used by this client. - * - * @var string - */ - protected $_endpointUrl; - - /** - * Execute a SoftLayer API method - * - * @return object - */ - public function __call($functionName, $arguments = null) - { - $request = array(); - $request[0] = array('headers' => $this->_headers); - $request = array_merge($request, $arguments); - - try { - $encodedRequest = xmlrpc_encode_request($functionName, $request); - - // Making the XML-RPC call and interpreting the response is adapted - // from the PHP manual: - // http://www.php.net/manual/en/function.xmlrpc-encode-request.php - $context = stream_context_create(array( - 'http' => array( - 'method' => 'POST', - 'header' => 'Content-Type: text/xml', - 'content' => $encodedRequest - ))); - - $file = file_get_contents($this->_endpointUrl . $this->_serviceName, false, $context); - - if ($file === false) { - throw new Exception('Unable to contact the SoftLayer API at ' . $this->_endpointUrl . $serviceName . '.'); - } - - $result = xmlrpc_decode($file); - } catch (Exception $e) { - throw new Exception('There was an error querying the SoftLayer API: ' . $e->getMessage()); - } - - if (is_array($result) && xmlrpc_is_fault($result)) { - throw new Exception('There was an error querying the SoftLayer API: ' . $result['faultString']); - } - - // remove the resultLimit header if they set it - $this->removeHeader('resultLimit'); - - return self::_convertToObject(self::_convertXmlrpcTypes($result)); - } - - /** - * Create a SoftLayer API XML-RPC Client - * - * Retrieve a new SoftLayer_XmlrpcClient object for a specific SoftLayer API - * service using either the class' constants API_USER and API_KEY or a - * custom username and API key for authentication. Provide an optional id - * value if you wish to instantiate a particular SoftLayer API object. - * - * @param string $serviceName The name of the SoftLayer API service you wish to query - * @param int $id An optional object id if you're instantiating a particular SoftLayer API object. Setting an id defines this client's initialization parameter header. - * @param string $username An optional API username if you wish to bypass SoftLayer_XmlrpcClient's built-in username. - * @param string $username An optional API key if you wish to bypass SoftLayer_XmlrpcClient's built-in API key. - * @param string $endpointUrl The API endpoint base URL you wish to connect to. Set this to SoftLayer_XmlrpcClient::API_PRIVATE_ENDPOINT to connect via SoftLayer's private network. - * @return SoftLayer_XmlrpcClient - */ - public static function getClient($serviceName, $id = null, $username = null, $apiKey = null, $endpointUrl = null) - { - $serviceName = trim($serviceName); - $id = trim($id); - $username = trim($username); - $apiKey = trim($apiKey); - - if ($serviceName == null) { - throw new Exception('Please provide a SoftLayer API service name.'); - } - - $client = new Softlayer_XmlrpcClient(); - - /* - * Default to use the public network API endpoint, otherwise use the - * endpoint defined in API_PUBLIC_ENDPOINT, otherwise use the one - * provided by the user. - */ - if (isset($endpointUrl)) { - $endpointUrl = trim($endpointUrl); - - if ($endpointUrl == null) { - throw new Exception('Please provide a valid API endpoint.'); - } - - $client->_endpointUrl = $endpointUrl; - } elseif (self::API_BASE_URL != null) { - $client->_endpointUrl = self::API_BASE_URL; - } else { - $client->_endpointUrl = SoftLayer_XmlrpcClient::API_PUBLIC_ENDPOINT; - } - - if ($username != null && $apiKey != null) { - $client->setAuthentication($username, $apiKey); - } else { - $client->setAuthentication(self::API_USER, self::API_KEY); - } - - $client->_serviceName = $serviceName; - - if ($id != null) { - $client->setInitParameter($id); - } - - return $client; - } - - /** - * Set a SoftLayer API call header - * - * Every header defines a customization specific to an SoftLayer API call. - * Most API calls require authentication and initialization parameter - * headers, but can also include optional headers such as object masks and - * result limits if they're supported by the API method you're calling. - * - * @see removeHeader() - * @param string $name The name of the header you wish to set - * @param object $value The object you wish to set in this header - * @return SoftLayer_XmlrpcClient - */ - public function addHeader($name, $value) - { - if (is_object($value)) { - $value = (array)$value; - } - - $this->_headers[$name] = $value; - return $this; - } - - /** - * Remove a SoftLayer API call header - * - * Removing headers may cause API queries to fail. - * - * @see addHeader() - * @param string $name The name of the header you wish to remove - * @return SoftLayer_XmlrpcClient - */ - public function removeHeader($name) - { - unset($this->_headers[$name]); - return $this; - } - - /** - * Set a user and key to authenticate a SoftLayer API call - * - * Use this method if you wish to bypass the API_USER and API_KEY class - * constants and set custom authentication per API call. - * - * @link https://manage.softlayer.com/Administrative/apiKeychain API key management in the SoftLayer customer portal - * @param string $username - * @param string $apiKey - * @return SoftLayer_XmlrpcClient - */ - public function setAuthentication($username, $apiKey) - { - $username = trim($username); - $apiKey = trim($apiKey); - - if ($username == null) { - throw new Exception('Please provide a SoftLayer API username.'); - } - - if ($apiKey == null) { - throw new Exception('Please provide a SoftLayer API key.'); - } - - $header = new stdClass(); - $header->username = $username; - $header->apiKey = $apiKey; - - $this->addHeader('authenticate', $header); - return $this; - } - - /** - * Set an initialization parameter header on a SoftLayer API call - * - * Initialization parameters instantiate a SoftLayer API service object to - * act upon during your API method call. For instance, if your account has a - * server with id number 1234, then setting an initialization parameter of - * 1234 in the SoftLayer_Hardware_Server Service instructs the API to act on - * server record 1234 in your method calls. - * - * @link http://sldn.softlayer.com/article/Using_Initialization_Parameters_in_the_SoftLayer_API Using Initialization Parameters in the SoftLayer API - * @param int $id The ID number of the SoftLayer API object you wish to instantiate. - * @return SoftLayer_XmlrpcClient - */ - public function setInitParameter($id) - { - $id = trim($id); - - if (!is_null($id)) { - $initParameters = new stdClass(); - $initParameters->id = $id; - $this->addHeader($this->_serviceName . 'InitParameters', $initParameters); - } - - return $this; - } - - /** - * Set an object mask to a SoftLayer API call - * - * Use an object mask to retrieve data related your API call's result. - * Object masks are skeleton objects or strings that define nested relational - * properties to retrieve along with an object's local properties. - * - * @see SoftLayer_ObjectMask - * @link http://sldn.softlayer.com/article/Using-Object-Masks-SoftLayer-API Using object masks in the SoftLayer API - * @param object $mask The object mask you wish to define - * @return SoftLayer_SoapClient - */ - public function setObjectMask($mask) - { - if (!is_null($mask)) { - $header = 'SoftLayer_ObjectMask'; - - if ($mask instanceof SoftLayer_ObjectMask) { - $header = sprintf('%sObjectMask', $this->_serviceName); - } - - $objectMask = new stdClass(); - $objectMask->mask = $mask; - $this->addHeader($header, $objectMask); - } - - return $this; - } - - /** - * Set a result limit on a SoftLayer API call - * - * Many SoftLayer API methods return a group of results. These methods - * support a way to limit the number of results retrieved from the SoftLayer - * API in a way akin to an SQL LIMIT statement. - * - * @link http://sldn.softlayer.com/article/Using_Result_Limits_in_the_SoftLayer_API Using Result Limits in the SoftLayer API - * @param int $limit The number of results to limit your SoftLayer API call to. - * @param int $offset An optional offset to begin your SoftLayer API call's returned result set at. - * @return SoftLayer_XmlrpcClient - */ - public function setResultLimit($limit, $offset = 0) - { - $resultLimit = new stdClass(); - $resultLimit->limit = intval($limit); - $resultLimit->offset = intval($offset); - - $this->addHeader('resultLimit', $resultLimit); - return $this; - } - - /** - * Remove PHP xmlrpc type definition structures from a decoded request array - * - * Certain xmlrpc types like base64 are decoded in PHP to a stdClass with a - * scalar property containing the decoded value of the xmlrpc member and an - * xmlrpc_type property describing which xmlrpc type is being described. This - * function removes xmlrpc_type data and moves the scalar value into the root of - * the xmlrpc value for known xmlrpc types. - * - * @param mixed $result The decoded xmlrpc request to process - * @return mixed - */ - private static function _convertXmlrpcTypes($result) { - if (is_array($result)) { - - // Return case 1: The result is an empty array. Return the empty - // array. - if (count($result) == 0) { - return $result; - } else { - - // Return case 2: The result is a non-empty array. Loop through - // array elements and recursively translate every element. - // Return the fully translated array. - foreach ($result as $key => $value) { - $result[$key] = self::_convertXmlrpcTypes($value); - } - - return $result; - } - - // Return case 3: The result is an xmlrpc scalar. Convert it to a normal - // variable and return it. - } elseif (is_object($result) && $result->scalar != null && $result->xmlrpc_type != null) { - - // Convert known xmlrpc types, otherwise unset the value. - switch ($result->xmlrpc_type) { - case 'base64': - return $result->scalar; - break; - default: - return null; - break; - } - - // Return case 4: Otherwise the result is a non-array and non xml-rpc - // scalar variable. Return it unmolested. - } else { - return $result; - } - } - - /** - * Recursively convert an array to an object - * - * Since xmlrpc_decode_result returns an array, but we want an object - * result, so cast all array parts in our result set as objects. - * - * @param mixed $result A result or portion of a result to convert - * @return mixed - */ - private static function _convertToObject($result) { - return is_array($result) ? (object) array_map('SoftLayer_XmlrpcClient::_convertToObject', $result) : $result; - } -} diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/example.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/example.php deleted file mode 100644 index 9c4665257..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/example.php +++ /dev/null @@ -1,113 +0,0 @@ -, [username], [API key]); - * - * API Service: The name of the API service you wish to connect to. - * id: An optional id to initialize your API service with, if you're - * interacting with a specific object. If you don't need to specify - * an id then pass null to the client. - * username: Your SoftLayer API username. - * API key: Your SoftLayer API key, - */ -$client = SoftLayer_SoapClient::getClient('SoftLayer_Account', null, $apiUsername, $apiKey); - -/** - * Once your client object is created you can call API methods for that service - * directly against your client object. A call may throw an exception on error, - * so it's best to try your call and catch exceptions. - * - * This example calls the getObject() method in the SoftLayer_Account API - * service. - * It retrieves basic account information, and is a great way to test your API - * account and connectivity. - */ -try { - print_r($client->getObject()); -} catch (Exception $e) { - die($e->getMessage()); -} - -/** - * For a more complex example we’ll retrieve a support ticket with id 123456 - * along with the ticket’s updates, the user it’s assigned to, the servers - * attached to it, and the datacenter those servers are in. We’ll retrieve our - * extra information using a nested object mask. After we have the ticket we’ll - * update it with the text ‘Hello!’. - */ - -// Declare an API client to connect to the SoftLayer_Ticket API service. -$client = SoftLayer_SoapClient::getClient('SoftLayer_Ticket', 123456, $apiUsername, $apiKey); - -// Assign an object mask to our API client: -$objectMask = new SoftLayer_ObjectMask(); -$objectMask->updates; -$objectMask->assignedUser; -$objectMask->attachedHardware->datacenter; -$client->setObjectMask($objectMask); - -// Retrieve the ticket record. -try { - $ticket = $client->getObject(); - print_r($ticket); -} catch (Exception $e) { - die('Unable to retrieve ticket record: ' . $e->getMessage()); -} - -// Now update the ticket. -$update = new stdClass(); -$update->entry = 'Hello!'; - -try { - $update = $client->addUpdate($update); - echo "Updated ticket 123456. The new update's id is " . $update[0]->id . '.'; -} catch (Exception $e) { - die('Unable to update ticket: ' . $e->getMessage()); -} diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/digitalocean.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/digitalocean.php deleted file mode 100644 index 48921b85b..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/digitalocean.php +++ /dev/null @@ -1,42 +0,0 @@ -server: Provision_Context_server - */ - function save_server() { - - // Look for provider_server_identifier - $server_identifier = $this->server->provider_server_identifier; - - // If server ID is already found, move on. - if (!empty($server_identifier)) { - drush_log('[DEVSHOP] Server Identifier Found: ' . $server_identifier . ' Not creating new server.', 'ok'); - } - // If there is no server ID, create the server. - else { - - drush_log('[DEVSHOP] Server Identifier not found. Creating new server!', 'ok'); - - // Faking our provider_data response. - $this->server->setProperty('provider_data', array( - 'hello' => 'do', - 'fake data' => 'from digitalocean', - )); - - // Faking our provider server identifier. - $this->server->setProperty('provider_server_identifier', '123456789'); - - $this->server->setProperty('ip_addresses', array( - '1.2.3.4' - )); - - drush_log('[DEVSHOP] Server Identifier found: 123456789. Assumed server was created.', 'ok'); - } - } -} \ No newline at end of file diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/rackspace.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/rackspace.php deleted file mode 100644 index e4641db63..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/rackspace.php +++ /dev/null @@ -1,137 +0,0 @@ -server->rackspace_id = drush_get_option('rackspace_id', ''); -// $this->server->rackspace_image = drush_get_option('rackspace_image', ''); -// $this->server->rackspace_flavor = drush_get_option('rackspace_flavor', ''); -// $this->server->attributes_json = drush_get_option('attributes_json', ''); -// $this->server->role = drush_get_option('role', ''); -// -// } -// -// /** -// * This is run immediately after provision saves the server config files. -// * -// * -// * Provision client home path /var/aegir/clients is writable. -// * [DEVUDO] Verifying Server anotherfaker -// */ -// function verify_server_cmd() { -// drush_log('[DEVUDO] Verifying Server ' . d()->remote_host, 'ok'); -// -// $server_fqdn = d()->remote_host; -// $role = $this->server->role; -// $rackspace_flavor = $this->server->rackspace_flavor; // 2 -// $rackspace_image = $this->server->rackspace_image; -// $rackspace_id = $this->server->rackspace_id; -// $attributes = $this->server->attributes_json; -// -// $ips = array(); -// -// // Look for this chef node on Chef Server -// drush_log("[DEVUDO] Looking for chef node $server_fqdn on chef server", 'ok'); -// $chef_node = shop_get_server($server_fqdn); -// -// // If shop_get_server() returns a string, knife node show didn't work. -// if (is_string($chef_node)) { -// -// // If the error is NOT object not found, there was a more serious error -// if (strpos($chef_node, 'ERROR: The object you are looking for could not be found') !== 0){ -// return drush_set_error(DRUSH_DEVUDO_ERROR, '[DEVUDO] knife failed: ' . $chef_node); -// } -// // Otherwise, we just don't have a chef node of that name yet. -// // So, create a new server. -// drush_log("[DEVUDO] Chef Node not found. Creating server...", 'ok'); -// -// drush_log("[DEVUDO] Running: drush server-create $server_fqdn --role=$role --rackspace_flavor=$rackspace_flavor --rackspace_image=$rackspace_image --attributes=$attributes", 'ok'); -// -// drush_set_option('rackspace_flavor', $rackspace_flavor); -// drush_set_option('rackspace_image', $rackspace_image); -// drush_set_option('role', $role); -// drush_set_option('attributes', $attributes); -// -// $data = drush_shop_provision_server_create($server_fqdn); -// $ips[] = $data['Public IP Address']; -// $ips[] = $data['Private IP Address']; -// -// // Save for shop_hosting_post_hosting_verify_task() -// drush_set_option('rackspace_id', $data['Instance ID']); -// drush_set_option('ip_addresses', $ips); -// } -// // If we got a server node... run chef-client to update it. -// else { -// $ip = $chef_node->automatic->ipaddress; -// drush_log("[DEVUDO] Chef node found with name:$server_fqdn ip:$ip Preparing attributes...", 'ok'); -// -// // @TODO: Copy the attributes file and run chef-client again. -// // Save new json data to file -// -// $json_path = "/tmp/$server_fqdn.json"; -// $attributes_json = $attributes; -// file_put_contents($json_path, $attributes_json); -// -// // Sync file to server -// // Use IP in case something is wrong with DNS -// if (!empty($ip)){ -// $host = $ip; -// } -// else { -// $host = $server_fqdn; -// } -// -// // @TODO: This line implies that aegir already has ssh access to devudo@host -// shop_exec("scp $json_path devudo@$host:~/attributes.json"); -// -// // Run chef-client to update the server itself. -// $chef_client_cmd = "sudo /usr/bin/chef-client -j attributes.json"; -// $chef_client_cmd_exec = escapeshellarg($chef_client_cmd); -// drush_log("[DEVUDO] Running chef-client on $server_fqdn:", 'ok'); -// shop_exec("knife ssh name:$server_fqdn -x devudo $chef_client_cmd_exec -a ipaddress"); -// } -// parent::verify_server_cmd(); -// } -// -// -// -// function config_data($config = null, $class = null) { -// $data = parent::config_data($config, $class); -// $data['rackspace_id'] = $this->server->rackspace_id; -// $data['rackspace_image'] = $this->server->rackspace_image; -// $data['rackspace_flavor'] = $this->server->rackspace_flavor; -// $data['role'] = $this->server->role; -// $data['attributes_json'] = $this->server->attributes_json; -// return $data; -// } -// -// static function option_documentation() { -// return array( -// '--rackspace_id' => 'The unique rackspace server ID.', -// '--rackspace_image' => 'The rackspace server image.', -// '--rackspace_flavor' => 'The rackspace server flavor.', -// '--role' => 'The chef role.', -// '--attributes_json' => 'JSON encoded attributes.', -// ); -// } -// -// /** -// * Ask the web server to check for and load configuration changes. -// */ -// function parse_configs() { -// return TRUE; -// } -//} diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/softlayer.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/softlayer.php deleted file mode 100644 index 0782615a9..000000000 --- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/softlayer.php +++ /dev/null @@ -1,88 +0,0 @@ -server->provider_server_identifier; - - // If server ID is already found, move on. - if ($server_identifier) { - drush_log('[DEVSHOP] Server Identifier Found. Not creating new server.', 'ok'); - } - // If there is no server ID, create the server. - else { - - drush_log('[DEVSHOP] Server Identifier not found. Creating new server!', 'ok'); - - $server_fqdn = d()->remote_host; - - drush_log('[DEVSHOP|softlayer] Creating Server ' . $server_fqdn . '...', 'notice'); - - // Initialize an API client for the SoftLayer_Account service. - $virtual_guest = $this->softlayer_client('SoftLayer_Virtual_Guest'); - $provider_options = $this->prepare_provider_options(); - - // Retrieve our account record - try { - - // @TODO: Add more robust simulation. - //$server = array( - // 'id' => '00000', - // 'stuff' => 'from softlayer', - //); - $server = (array) $virtual_guest->createObject($provider_options); - - drush_log('[DEVSHOP|softlayer] Created server in softlayer: ' . $server['id'], 'ok'); - } catch (Exception $e) { - return drush_set_error('DEVSHOP_CLOUD_API_ACCESS_DENIED', $e->getMessage()); - } - - $provider_data = (array) $server; - $this->server->setProperty('provider_data', $provider_data); - $this->server->setProperty('provider_server_identifier', $provider_data['id']); - } - } - - function prepare_provider_options() { - $devshop_cloud_provider_options = (object) drush_get_option('provider_options', ''); - - // Break up title into hostname (subdomain) and domain. - $provider_options = new stdClass(); - $domain = explode('.', d()->remote_host); - $provider_options->hostname = $domain[0]; - $provider_options->domain = implode('.', array_slice($domain, 1)); - $provider_options->startCpus = $devshop_cloud_provider_options->processors; - $provider_options->maxMemory = $devshop_cloud_provider_options->memory; - $provider_options->hourlyBillingFlag = TRUE; - $provider_options->localDiskFlag = TRUE; - $provider_options->dedicatedAccountHostOnlyFlag = FALSE; - $provider_options->operatingSystemReferenceCode = $devshop_cloud_provider_options->operatingSystems; - - $provider_options->datacenter = new stdClass(); - $provider_options->datacenter->name = $devshop_cloud_provider_options->datacenter; - - return $provider_options; - } - - /** - * Helper for getting a softlayer client. - * @param $service - * @return \Softlayer_SoapClient - */ - private function softlayer_client($service, $id = null) { - $api_key = drush_get_option('softlayer_api_key'); - $username = drush_get_option('softlayer_api_username'); - - // Initialize an API client for the SoftLayer_Account service. - $client = SoftLayer_SoapClient::getClient($service, $id, $username, $api_key); - return $client; - } -} \ No newline at end of file diff --git a/modules/devshop/devshop_cloud/drush/README.md b/modules/devshop/devshop_cloud/drush/README.md deleted file mode 100644 index 591c74eb7..000000000 --- a/modules/devshop/devshop_cloud/drush/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# devshop_cloud -Cloud server provisioning for devshop. diff --git a/modules/devshop/devshop_cloud/drush/devshop_cloud.drush.inc b/modules/devshop/devshop_cloud/drush/devshop_cloud.drush.inc deleted file mode 100644 index e44fe5fb5..000000000 --- a/modules/devshop/devshop_cloud/drush/devshop_cloud.drush.inc +++ /dev/null @@ -1,43 +0,0 @@ - NULL); -} diff --git a/modules/devshop/devshop_cloud/drush/tools.inc b/modules/devshop/devshop_cloud/drush/tools.inc deleted file mode 100644 index bce0161d3..000000000 --- a/modules/devshop/devshop_cloud/drush/tools.inc +++ /dev/null @@ -1,45 +0,0 @@ - 'devshop_dothooks')); +} diff --git a/modules/devshop/devshop_dothooks/devshop_dothooks.module b/modules/devshop/devshop_dothooks/devshop_dothooks.module new file mode 100644 index 000000000..a837d33b7 --- /dev/null +++ b/modules/devshop/devshop_dothooks/devshop_dothooks.module @@ -0,0 +1,144 @@ +settings->deploy['dothooks']) || empty($environment->settings->deploy['dothooks'])){ + return; + } + + if (file_exists($environment->repo_path . '/.hooks.yaml')) { + $hooks_file = '.hooks.yaml'; + $hooks_path = $environment->repo_path . '/.hooks.yaml'; + } + elseif (file_exists($environment->repo_path . '/.hooks.yml')) { + $hooks_file = '.hooks.yml'; + $hooks_path = $environment->repo_path . '/.hooks.yml'; + } + elseif (file_exists($environment->repo_path . '/.hooks')) { + $hooks_file = '.hooks'; + $hooks_path = $environment->repo_path . '/.hooks'; + } + + $environment->dothooks_file_name = $hooks_file; + $environment->dothooks_file_path = $hooks_path; + + // Attempt to parse + if (!empty($environment->dothooks_file_name)) { + try { + $environment->dothooks = $yaml->parse(file_get_contents($hooks_path)); + } catch (\Symfony\Component\Yaml\Exception\ParseException $e) { + $environment->warnings[] = array( + 'text' => t('Invalid YAML in !file: !message', array( + '!file' => $hooks_path, + '!message' => $e->getMessage(), + )), + 'type' => 'error', + ); + } + } +} + +/** + * Implements hook_help() + * @param $path + * @param $arg + * @return string + */ +function devshop_dothooks_help($path, $arg) +{ + switch ($path) { + // Main module help for the block module + case 'admin/help#devshop_dothooks': + $note = t( + 'You can control what happens on deploy through a .hooks file in your repository.' + ); + + return <<$note

+
+# Fires after an environment is installed.
+install: |
+  drush {{alias}} vset site_name "Hooks Hooks Hooks"
+
+# Fires after code is deployed. A "deployment" happens when you push to your
+# git repository or select a new branch or tag for your environment.
+deploy: |
+  drush {{alias}} updb -y
+  drush {{alias}} cc all
+
+# Fires after "verify" task.
+verify: |
+  drush {{alias}} status
+
+# Fires after "Run Tests" task.
+test: |
+  drush {{alias}} uli
+
+
+# Fires after "Deploy Data (Sync)" task.
+sync: |
+  drush {{alias}} en devel -y
+
+
+HTML; + + } +} + + +/** + * Runs a hook for a task. + * @param $hook + * @param $task + */ +function devshop_dothooks_run_hook($hook, $environment) { + + // Respect drush option, but default to environment settings. + if (!drush_get_option('dothooks', $environment->settings->deploy['dothooks'])) { + drush_log('[.hooks] Environment not configured to run .hooks commands.', 'info'); + return; + } + + // If no dothooks file is found, throw an error. + if (empty($environment->dothooks)) { + drush_log(dt('Hook file not found, but the project is configured to use them. Create a .hooks file or turn off "Run deploy commands in the .hooks file".'), 'notice'); + return; + } + + drush_log('[.hooks] Hook file found: ' . $environment->dothooks_file_name, 'ok'); + + // Allow big string or lists of commands. + if (is_array($environment->dothooks[$hook])) { + $hooks = array_filter($environment->dothooks[$hook]); + } + else { + $hooks = array_filter(explode("\n", $environment->dothooks[$hook])); + } + + // Prepare and run each command. + foreach ($hooks as $hook_line) { + $hook_line = strtr($hook_line, array( + '{{alias}}' => $environment->system_alias, + )); + devshop_process($hook_line, $environment->repo_path, 'DevShop .hooks.yml'); + } +} + diff --git a/modules/devshop/devshop_dothooks/hooks.php b/modules/devshop/devshop_dothooks/hooks.php new file mode 100644 index 000000000..ccc9830ce --- /dev/null +++ b/modules/devshop/devshop_dothooks/hooks.php @@ -0,0 +1,53 @@ +ref->type == 'site') { + devshop_dothooks_run_hook('verify', $task->ref->environment); + } +} + +/** + * Implementation of hook_post_hosting_TASK_TYPE_task() + * for DevShop Deploy tasks. + * + * Runs the "deploy" dotHook + */ +function devshop_dothooks_post_hosting_devshop_deploy_task($task, $data) { + devshop_dothooks_run_hook('deploy', $task->ref->environment); +} + +/** + * Implementation of hook_post_hosting_TASK_TYPE_task() + * for Run Tests tasks. + * + * Runs the "test" dotHook + */ +function devshop_dothooks_post_hosting_test_task($task, $data) { + devshop_dothooks_run_hook('test', $task->ref->environment); +} + +/** + * Implementation of hook_post_hosting_TASK_TYPE_task() + * for Sync tasks. + * + * Runs the "sync" dotHook + */ +function devshop_dothooks_post_hosting_sync_task($task, $data) { + devshop_dothooks_run_hook('sync', $task->ref->environment); +} + +/** + * Implementation of hook_post_hosting_TASK_TYPE_task() + * for Install tasks. + * + * Runs the "install" dotHook + */ +function devshop_dothooks_post_hosting_install_task($task, $data) { + devshop_dothooks_run_hook('install', $task->ref->environment); +} \ No newline at end of file diff --git a/modules/devshop/devshop_extra_users/README.md b/modules/devshop/devshop_extra_users/README.md new file mode 100644 index 000000000..9abb25762 --- /dev/null +++ b/modules/devshop/devshop_extra_users/README.md @@ -0,0 +1,18 @@ +DevShop Extras: Users +===================== + +This module provides an example of how to use the DevShop front-end to take action +after an environment is installed. + +Enable it, then visit the "Create Environment" form for a project. + +You will see a form field for "Manager Email". This field gets saved into the environment settings automatically. + +Then, in `devshop_extras_users.drush.inc` during the `hook_post_hosting_TASKTYPE_task` +hook, this module creates a new user in your drupal site immediately after the +installation. + +Nothing else happens here. Typically you would want to set a role or send an +email. + +Use this code as an example for extending your own environments. \ No newline at end of file diff --git a/modules/devshop/devshop_extra_users/devshop_extra_users.drush.inc b/modules/devshop/devshop_extra_users/devshop_extra_users.drush.inc new file mode 100644 index 000000000..9b9b75e27 --- /dev/null +++ b/modules/devshop/devshop_extra_users/devshop_extra_users.drush.inc @@ -0,0 +1,34 @@ +ref->type == 'site' && isset($task->ref->environment->settings->manager_email)) { + + // Create an extra user. + $email = $task->ref->environment->settings->manager_email; + $password = provision_password(); + $arguments = array('name' => $email); + $data = array( + 'mail' => $email, + 'password' => $password, + ); + + drush_log('Manager email is found! running user-create...', 'ok'); + provision_backend_invoke($task->ref->title, 'user-create', $arguments, $data); + + drush_log(dt('User !user has been created with password !password', array( + '!user' => $email, + '!password' => $password, + )), 'ok'); + + drush_log('No email has been sent! Please notify your new user.'); + } +} diff --git a/modules/devshop/devshop_extra_users/devshop_extra_users.info b/modules/devshop/devshop_extra_users/devshop_extra_users.info new file mode 100644 index 000000000..bcc11ca1d --- /dev/null +++ b/modules/devshop/devshop_extra_users/devshop_extra_users.info @@ -0,0 +1,5 @@ +name = DevShop Extra Install +description = Example module for loading extra info for an install profile. +core = 7.x +package = DevShop + diff --git a/modules/devshop/devshop_extra_users/devshop_extra_users.module b/modules/devshop/devshop_extra_users/devshop_extra_users.module new file mode 100644 index 000000000..e1d13d86a --- /dev/null +++ b/modules/devshop/devshop_extra_users/devshop_extra_users.module @@ -0,0 +1,15 @@ + 'textfield', + '#title' => t('Manager Email'), + '#description' => t('Enter an email address and a user will be created.'), + ); + } +} diff --git a/modules/devshop/devshop_github/.gitignore b/modules/devshop/devshop_github/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/modules/devshop/devshop_github/composer.json b/modules/devshop/devshop_github/composer.json new file mode 100644 index 000000000..56e09bfdb --- /dev/null +++ b/modules/devshop/devshop_github/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "knplabs/github-api": "~1.2" + } +} \ No newline at end of file diff --git a/modules/devshop/devshop_github/composer.lock b/modules/devshop/devshop_github/composer.lock new file mode 100644 index 000000000..4883279ed --- /dev/null +++ b/modules/devshop/devshop_github/composer.lock @@ -0,0 +1,233 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "a7ca813c3521e13be685f99e073d2773", + "packages": [ + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-03-18 18:23:50" + }, + { + "name": "knplabs/github-api", + "version": "1.4.7", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/php-github-api.git", + "reference": "d2729cf6b9d5b6fa340b1ff001dd89e319741baa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/d2729cf6b9d5b6fa340b1ff001dd89e319741baa", + "reference": "d2729cf6b9d5b6fa340b1ff001dd89e319741baa", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "guzzle/guzzle": "~3.7", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "knplabs/gaufrette": "Needed for optional Gaufrette cache" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Github\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thibault Duplessis", + "email": "thibault.duplessis@gmail.com", + "homepage": "http://ornicar.github.com" + }, + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + } + ], + "description": "GitHub API v3 client", + "homepage": "https://github.com/KnpLabs/php-github-api", + "keywords": [ + "api", + "gh", + "gist", + "github" + ], + "time": "2015-04-07 20:24:33" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.6.6", + "target-dir": "Symfony/Component/EventDispatcher", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher.git", + "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/70f7c8478739ad21e3deef0d977b38c77f1fb284", + "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5", + "symfony/dependency-injection": "~2.6", + "symfony/expression-language": "~2.6", + "symfony/phpunit-bridge": "~2.7", + "symfony/stopwatch": "~2.3" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "http://symfony.com", + "time": "2015-03-13 17:37:22" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/modules/devshop/devshop_github/devshop_github.drush.inc b/modules/devshop/devshop_github/devshop_github.drush.inc new file mode 100644 index 000000000..7e493e98d --- /dev/null +++ b/modules/devshop/devshop_github/devshop_github.drush.inc @@ -0,0 +1,120 @@ +task_type, $types) && isset($task->ref->project) && !empty($task->ref->project) && isset($task->ref->environment) && !empty($task->ref->environment)) { + $project = $task->ref->project; + $environment = $task->ref->environment; + } + else { + return; + } + + // If a pull request object is available... + if (isset($environment->github_pull_request->pull_request_object->deployment)) { + + // If project is configured to reinstall every time, only react on "install" tasks. Otherwise, we get two github deployments because both a "deploy" (git pull) and a "install" task are run on each git push. + if ($project->settings->github['pull_request_reinstall'] && $task->task_type == 'devshop-deploy') { + return; + } + + // Create a deployment status + $owner = $project->github_owner; + $repo = $project->github_repo; + $deployment_id = $environment->github_pull_request->pull_request_object->deployment->id; + + try { + $token = variable_get('devshop_github_token', ''); + $client = new \Github\Client(); + $client->authenticate($token, Github\Client::AUTH_HTTP_TOKEN); + + $params = new stdClass(); + if ($status == HOSTING_TASK_SUCCESS || $status == HOSTING_TASK_WARNING) { + $params->state = $state = 'success'; + } + else { + $params->state = $state = 'failure'; + } + + // If task is a test run, only submit a commit status for devshop/tests context. + if ($task->task_type == 'test') { + $sha = $environment->github_pull_request->pull_request_object->head->sha; + + $params = new stdClass(); + $params->state = $state; + $params->target_url = url("node/{$task->nid}/view", array('absolute' => TRUE)); + + if ($status == HOSTING_TASK_WARNING) { + $params->description = t('DevShop: Tests passed with warnings'); + } + else { + $params->description = t('DevShop: Tests !status!', array('!status' => $state)); + } + $params->context = 'devshop/tests'; + + $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); + drush_log('Commit status created for devshop/tests!', 'success'); + } + // Otherwise we create a deployment and a commit status. + else { + + $params->target_url = $environment->url; + $params->description = t('Visit !url', array('!url' => $task->ref->environment->url)); + $post_url = "/repos/$owner/$repo/deployments/{$deployment_id}/statuses"; + + drush_log('Attempting to create github deployment status: ' . $post_url, 'success'); + + $deployment_status = $client->getHttpClient()->post($post_url, json_encode($params)); + drush_log('Deployment status created!', 'success'); + + + // Update Status API + + // Create a status + $sha = $environment->github_pull_request->pull_request_object->head->sha; + + $params = new stdClass(); + $params->state = $state; + $params->target_url = url("node/{$task->nid}", array('absolute' => TRUE));; + + if ($status == HOSTING_TASK_WARNING) { + $params->description = t('DevShop: Deploy success with warnings. [!url]', array( + '!url' => $environment->url, + )); + } + else { + $params->description = t('DevShop: Deploy !status [!url]', array( + '!status' => $state, + '!url' => $environment->url, + )); + } + $params->context = 'devshop/deploy'; + + $deployment_status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); + drush_log('Commit status created!', 'success'); + + // If deploy task fails, tests won't run. + if ($environment->settings->deploy['test'] && $status == HOSTING_TASK_ERROR) { + + $params = new stdClass(); + $params->state = $state; + $params->description = t('DevShop: Tests not run due to Deploy Fail'); + $params->context = 'devshop/tests'; + + $deployment_status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); + drush_log('Commit status created for devshop/tests', 'success'); + } + } + } catch (Github\Exception\RuntimeException $e) { + drush_log('GitHub API Error: ' . $e->getMessage(), 'error'); + } + } +} diff --git a/modules/devshop/devshop_github/devshop_github.info b/modules/devshop/devshop_github/devshop_github.info new file mode 100644 index 000000000..6900a4293 --- /dev/null +++ b/modules/devshop/devshop_github/devshop_github.info @@ -0,0 +1,7 @@ +name = DevShop GitHub +description = Integration with GitHub +core = 7.x +package = DevShop +files[] = includes/add-key.inc +files[] = includes/admin.inc +dependencies[] = devshop_projects diff --git a/modules/devshop/devshop_github/devshop_github.install b/modules/devshop/devshop_github/devshop_github.install new file mode 100644 index 000000000..23b3482ec --- /dev/null +++ b/modules/devshop/devshop_github/devshop_github.install @@ -0,0 +1,94 @@ + array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Pull Request ID', + ), + 'number' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Pull Request Number', + ), + 'project_nid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => "The project's Node ID.", + ), + 'environment_name' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => 64, + 'default' => '', + 'description' => 'Environment name for this pull request environment.', + ), + 'pull_request_object' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + 'description' => 'A serialized array of settings for this environment.', + ), + ), + 'primary key' => array('id'), + ); + return $schema; +} + +/** + * Implements hook_install(). + */ +function devshop_github_install() { + + // Push devshop_github's system weight to 1. + db_update('system') + ->fields(array( + 'weight' => 2 + )) + ->condition('name', 'devshop_github') + ->execute(); + + // Display a message about setting a github personal token. + drupal_set_message(t('DevShop GitHub module has been enabled. You must add an access token to enable full functionality at !link.', array( + '!link' => l(t('the settings page'), 'admin/devshop/github'), + ))); +} + +/** + * Set a weight higher than devshop_project so our form doesn't get obliterated by + * devshop_projects_form_project_node_form_alter() + */ +function devshop_github_update_7000() { + db_update('system') + ->fields(array( + 'weight' => 1 + )) + ->condition('name', 'devshop_github') + ->execute(); +} + +/** + * Set a weight higher than devshop_project module. + */ +function devshop_github_update_7001() { + db_update('system') + ->fields(array( + 'weight' => 2 + )) + ->condition('name', 'devshop_github') + ->execute(); +} \ No newline at end of file diff --git a/modules/devshop/devshop_github/devshop_github.module b/modules/devshop/devshop_github/devshop_github.module new file mode 100644 index 000000000..ce42ee6c9 --- /dev/null +++ b/modules/devshop/devshop_github/devshop_github.module @@ -0,0 +1,928 @@ + 'GitHub', + 'description' => 'DevShop GitHub Integration Settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('devshop_github_settings_form'), + 'access arguments' => array('administer projects'), + 'file' => 'admin.inc', + 'file path' => drupal_get_path('module', 'devshop_github') . '/includes', + 'type' => MENU_LOCAL_TASK, + ); + $items['admin/devshop/github/add-key'] = array( + 'title' => 'Add public key to GitHub Account', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('devshop_github_add_key_to_account'), + 'access arguments' => array('administer projects'), + 'file' => 'add-key.inc', + 'file path' => drupal_get_path('module', 'devshop_github') . '/includes', + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Implements hook_form_FORM_ID_alter() for project_node_form(). + */ +function devshop_github_form_project_node_form_alter(&$form, &$form_state, $form_id) { + $node = $form['#node']; + + if ($node->project->git_provider != 'github') { + return; + } + + //All settings git pull in project page + $form['project']['settings']['github'] = array( + '#type' => 'fieldset', + '#group' => 'project_settings', + '#collapsible' => TRUE, + '#collapsed' => arg(1) != $node->nid, + '#title' => t('GitHub Integration'), + ); + + // Pull Requests create environments? + // $form['github']['pull_request_environments'] = array( + $form['project']['settings']['github']['pull_request_environments'] = array( + '#type' => 'checkbox', + '#title' => t('Create Environments for Pull Requests'), + '#default_value' => isset($node->project->settings->github) ? $node->project->settings->github['pull_request_environments'] : FALSE, + '#description' => t('If using GitHub, create a new environment when a new Pull Request is created.'), + ); + + // Delete Pull Request environments? + // $form['github']['pull_request_environments_delete'] = array( + $form['project']['settings']['github']['pull_request_environments_delete'] = array( + '#type' => 'checkbox', + '#title' => t('Delete Pull Request Environments'), + '#default_value' => isset($node->project->settings->github) ? $node->project->settings->github['pull_request_environments_delete'] : FALSE, + '#description' => t('When Pull Requests are closed, delete the environment.'), + '#states' => array( + 'visible' => array( + ':input[name="project[settings][github][pull_request_environments]"]' => array('checked' => TRUE), + ), + ), + ); + + // Pull Request Environment method. + // $form['github']['pull_request_environments_method'] = array( + + $environments = array_keys($node->project->environments); + $options = array( + t('Install Drupal') => array( + 'devshop__github__install' => empty($node->project->install_profile)? t('Default install profile.'): $node->project->install_profile, + ), + t('Clone another environment') => array(), + ); + + if (empty($environments)) { + $options[t('Clone another environment')][] = t('No environments available. Check Project settings when you have one.'); + } + else { + $options[t('Clone another environment')] = array_combine($environments, $environments); + } + $form['project']['settings']['github']['pull_request_environments_method'] = array( + '#type' => 'select', + '#title' => t('Pull Request Environment Creation Method'), + '#default_value' => isset($node->project->settings->github) ? + $node->project->settings->github['pull_request_environments_method'] : 'devshop__github__install', + '#description' => t('Select the method for creating the pull request environments.'), + '#options' => $options, + '#states' => array( + 'visible' => array( + ':input[name="project[settings][github][pull_request_environments]"]' => array('checked' => TRUE), + ), + ), + ); + $form['project']['settings']['github']['pull_request_reinstall'] = array( + '#type' => 'checkbox', + '#title' => t('Reinstall Pull Request Environments on every git push.'), + '#default_value' => isset($node->project->settings->github) ? + $node->project->settings->github['pull_request_reinstall'] : 0, + '#description' => t('Destroy and reinstall Pull Request environments on every code push. All data in environments created via Pull Request will be destroyed.'), + '#states' => array( + 'visible' => array( + ':input[name="project[settings][github][pull_request_environments]"]' => array('checked' => TRUE), + ), + ), + ); +} + +/** + * Implements hook_form_alter(). + */ +function devshop_github_form_devshop_project_create_step_git_alter(&$form, &$form_state, $form_id) { + + // Look for Token + $token = variable_get('devshop_github_token', ''); + + if (empty($token)) { + $form['connect']['#description'] = '
' . t('GitHub API Token was not found.') . ' ' . l(t('Configure DevShop GitHub Settings'), 'admin/devshop/github') . '
'; + return; + } + + if (devshop_github_check_key()) { + $form['connect']['#description'] = '
' . t('The DevShop Public SSH Key has been found in your GitHub account.') . '
'; + } + else { + $form['connect']['#description'] = t('The DevShop Public SSH Key was not found in your account.') . ' '; + + $form['connect']['#description'] .= l(t('Add devshop public key to your GitHub account'), 'admin/devshop/github/add-key', array( + 'query' => array( + 'destination' => $_GET['q'], + ), + 'attributes' => array( + 'class' => 'btn btn-success' + ), + )) . ''; + $form['connect']['#prefix'] = '
'; + $form['connect']['#suffix'] = '
'; + } +} + + +/** + * Implements hook_form_FORM_ID_alter() for project_node_form(). + */ +function devshop_github_form_devshop_project_create_step_sites_alter(&$form, &$form_state) { + + // Return if project isn't ready. + if (empty($form['install_profile'])) { + return; + } + + // Return if there is no github token + $github_token = variable_get('devshop_github_token', ''); + if (empty($github_token)) { + return; + } + + // Load project and github + $project_node = node_load($form['nid']['#value']); + $project = $project_node->project; + + // Return if provider is not github. + if ($project->git_provider != 'github') { + return; + } + + // Return if deploy method is not webhook. + if ($project->settings->deploy['method'] != 'webhook') { + return; + } + + $repo = $project->github_owner . '/' . $project->github_repo; + $repo_url = "http://github.com/" . $repo; + + $form['github_webhook'] = array( + '#title' => t('Setup GitHub Webhook'), + '#description' => t('Leave this box checked to automatically add a webhook to your GitHub Repository !link.', array( + '!link' => l($repo, $repo_url, array( + 'attributes' => array( + 'target' => '_blank', + ), + )) + )), + '#type' => 'checkbox', + '#default_value' => 1, + ); + + $form['#validate'][] = 'devshop_github_project_create_webhook'; + $form['#submit'][] = 'devshop_github_project_create_webhook'; +} + +/** + * Extra submit hook for last step of project create form. + */ +function devshop_github_project_create_webhook($form, $form_state) { + + // Return if box is not checked. + if (empty($form_state['values']['github_webhook'])) { + return; + } + + // Get Project + $project_node = node_load($form['nid']['#value']); + $project = $project_node->project; + + // Get GitHub client + $client = new Github\Client(); + $github_token = variable_get('devshop_github_token', ''); + $client->authenticate($github_token, Github\Client::AUTH_HTTP_TOKEN); + + // Create the webhook. + try { + $hook = $client->repo()->hooks()->create($project->github_owner, $project->github_repo, array( + 'name' => 'web', + 'active' => true, + 'events' => array( + 'push', + 'pull_request', + 'delete', + 'release', + ), + 'config' => array( + 'url' => $project->webhook_url, + 'content_type' => 'json', + 'insecure_ssl' => '1', + ), + )); + } + catch (Github\Exception\ValidationFailedException $e) { + // For some reason, github always returns an exception here when there is already webhooks on the project. + if ($e->getMessage() == 'Validation Failed: Hook already exists on this repository') { + drupal_set_message(t("GitHub webhook added, but there is already an existing webhook. Please check your repository's !link.", array( + '!link' => l(t('Webhook Settings'), $project->git_repo_url . '/settings/hooks'), + )), 'warning'); + } + else { + drupal_set_message(t('GitHub Validation Exception: !exception', array('!exception' => $e->getMessage())), 'error'); + } + } + catch (Github\Exception\RuntimeException $e) { + drupal_set_message(t('GitHub Runtime Exception: !exception', array('!exception' => $e->getMessage())), 'error'); + } +} + +/** + * Implements hook_node_load(). + */ +function devshop_github_node_load($nodes, $types) { + if (count(array_intersect(array('project'), $types))) { + foreach ($nodes as $nid => $node) { + + // Look for a pull request object. + $pull_requests = db_query('SELECT * FROM {hosting_devshop_github_pull_requests} WHERE project_nid = :project_nid', array(':project_nid' => $nid)); + foreach ($pull_requests as $pull_request) { + if (!empty($pull_request->pull_request_object)) { + $pull_request->pull_request_object = unserialize($pull_request->pull_request_object); + if (isset($node->project->environments[$pull_request->environment_name])) { + $node->project->environments[$pull_request->environment_name]->github_pull_request = $pull_request; + } + } + } + + // Parse github owner and repo. + if (isset($node->project) && $node->project->git_provider == 'github') { + $node->project->github_owner = + $parts = explode('/', parse_url($node->project->git_repo_url, PHP_URL_PATH)); + $node->project->github_owner = $parts[1]; + $node->project->github_repo = $parts[2]; + } + } + } + +} + +/** + * Implements hook_node_update() for task insert. + * + * If task is a test run, send a "pending" commit status. + */ +function devshop_github_node_update($node) { + + // Only act on test triggers. + if ($node->type != 'task' || $node->type == 'task' && ($node->task_type != 'test' || $node->task_type != 'deploy')) { + return; + } + + // Load the site and check for environment. + $site = node_load($node->rid); + if (empty($site->environment) || empty($site->environment->github_pull_request) || empty($site->environment->settings->deploy['test'])) { + return; + } + + try { + $token = variable_get('devshop_github_token', ''); + $client = new \Github\Client(); + $client->authenticate($token, Github\Client::AUTH_HTTP_TOKEN); + + // Create a deployment status + $project = $site->project; + $owner = $project->github_owner; + $repo = $project->github_repo; + $sha = $site->environment->github_pull_request->pull_request_object->head->sha; + + $params = new stdClass(); + $params->state = 'pending'; + + if ($node->task_type != 'test') { + $params->target_url = url( + "node/{$node->nid}", + array('absolute' => true) + ); + $params->description = t('DevShop: Run Tests'); + $params->context = 'devshop/tests'; + } + elseif ($node->task_type != 'deploy') { + $params->target_url = $site->environment->url; + $params->description = t('DevShop: Deploy'); + $params->context = 'devshop/deploy'; + } + + // Post status to github + $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); + + watchdog('devshop_github','Test run initiated. GitHub has been notified.'); + } catch (Github\Exception\RuntimeException $e) { + watchdog('devshop_github', 'GitHub Runtime Error in devshop_github_node_update(): ' . $e->getMessage()); + } +} + + +/** +* Implements hook_hosting_task_update_status() +*/ +// function devshop_github_hosting_task_update_status($task, $status) { +// +// $task_types = array( +// 'test', +// 'import', +// 'devshop-deploy', +// ); +// +// if (in_array($task->task_type, $task_types) && $task->ref->type == 'site' && isset($task->ref->environment->github_pull_request)) { +// +// // If autoloader is not available, return. +// if (!file_exists(__DIR__ . '/vendor/autoload.php')) { +// return; +// } +// +// // If project is not from github, return. +// if ($task->ref->project->git_provider != 'github') { +// return; +// } +// +// // Include vendors +// require_once 'vendor/autoload.php'; +// +// drush_log('===========================================', 'ok'); +// drush_log('Notifying github...', 'ok'); +// +// // Extract username and repo +// list($s, $owner, $repo) = explode('/', parse_url($task->ref->project->git_repo_url, PHP_URL_PATH)); +// +// try { +// $token = variable_get('devshop_github_token', ''); +// $client = new \Github\Client(); +// $client->authenticate($token, Github\Client::AUTH_HTTP_TOKEN); +// +// // Create a status +// $sha = $task->ref->environment->github_pull_request->pull_request_object->head->sha; +// +// if ($task->task_type == 'devshop-deploy'){ +// $description = t('Deployed to Environment: ') . _hosting_parse_error_code($status); +// $url = $task->ref->environment->url; +// } +// elseif ($task->task_type == 'test') { +// $description = t('Tests: ') . _hosting_parse_error_code($status); +// $url = url("devshop_tests/{$task->nid}/{$task->vid}", array('absolute' => TRUE)); +// } +// else { +// $description = 'Something happened...'; +// $url = $task->ref->environment->url; +// } +// +// if ($status == HOSTING_TASK_ERROR) { +// $state = 'error'; +// } +// elseif ($status == HOSTING_TASK_PROCESSING) { +// $state = 'pending'; +// } +// elseif ($status == HOSTING_TASK_SUCCESS || $status == HOSTING_TASK_WARNING) { +// $state = 'success'; +// } +// else { +// $state = 'error'; +// } +// +// $params = new stdClass(); +// $params->state = $state; +// $params->target_url = $url; +// $params->description = $description; +// $params->context = 'devshop/' . $task->task_type; +// +// $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); +// +// drush_log('Status posted! ', 'ok'); +// } catch (Github\Exception\RuntimeException $e) { +// drush_log('GitHub API Error: ' . $e->getMessage(), 'error'); +// drush_log(l(t('Configure GitHub API'), 'admin/devshop/github'), 'error'); +// } catch (Github\Exception\ValidationFailedException $e) { +// drush_log('GitHub API Error: ' . $e->getMessage(), 'error'); +// } +// +// drush_log('done trying... ', 'ok'); +// +// } +// } + +/** + * + */ +function devshop_github_comment($task, $status) { + + $output = array(); + $output[] = '> **DEVSHOP**'; + $output[] = '> ' . ucfirst($task->task_type) . ": " . _hosting_parse_error_code($status); + $output[] = '> Site: ' . $task->ref->environment->url; + $output[] = '> Project: ' . url("node/{$task->ref->project->nid}", array('absolute' => TRUE)); + + if ($task->task_type == 'test') { + $output[] = 'Results: ' . url("node/{$task->nid}", array('absolute' => TRUE)); + } + + if ($task->task_type == 'import') { + $output[] = t('Your environment is now available.'); + } + + return implode("\n", $output); +} + + + +/** + * GitHub action to take on webhook init + */ +function devshop_github_webhook($project_node) { + $headers = getallheaders(); + $project = $project_node->project; + + // Create a github deployment + require_once 'vendor/autoload.php'; + + // @TODO: Handle form content from github as well. + if ($headers['Content-Type'] == 'application/json' || $headers['content-type'] == 'application/json') { + $data = json_decode(file_get_contents('php://input')); + + $args = array(); + $args['cache'] = 1; + + if (isset($headers['X-Github-Event'])) { + $github_event = $headers['X-Github-Event']; + } + elseif (isset($headers['X-GitHub-Event'])) { + $github_event = $headers['X-GitHub-Event']; + } + else { + $github_event = ''; + } + + switch ($github_event) { + case 'ping': + $message = 'Pong!'; + break; + case 'push': + + // If push is for a deleted branch, don't do anything. + if ($data->deleted && $data->after == "0000000000000000000000000000000000000000") { + $message = 'Deleted ref detected.'; + break; + } + + // Limit "Deploy" tasks to only run for the branches we have new code for.. + $git_ref = strtr($data->ref, array('refs/tags/' => '', 'refs/heads/' => '')); + + // Check for environments set to pull + $environments_to_pull = array(); + $message = "Push Received for git ref $git_ref."; + + foreach ($project->environments as $environment_name => $environment) { + + // Only pull if deploy is not disabled or if environment is tracking a tag. + if ( +// @TODO: Improve this. If a site install failed, it shows up as "disabled", so we should still do a git pull. + $git_ref == $environment->git_ref && + !$environment->settings->pull_disabled && + !in_array($environment->git_ref, $project->settings->git['tags']) + ) { + $environments_to_pull[] = $environment->name; + + if (isset($environment->site) && $node = node_load($environment->site)) { + + // If project is set to reinstall pull request environments, and this is a pull request environment, run a forced install task. + if (isset($project->settings->github['pull_request_reinstall']) && $project->settings->github['pull_request_reinstall'] && isset($environment->github_pull_request)) { + + // Trigger deploy task to get latest code. + // Pass no args to skip DB updates, cache clearing, etc. + hosting_add_task($environment->site, 'devshop-deploy', array( + 'git_ref' => $environment->git_ref, + )); + + // Trigger "install" with force-reinstall + hosting_add_task($environment->site, 'install', array('force-reinstall' => 1)); + } + + // Otherwise, just run a "deploy" task. + else { + + // Default args to the environments deploy settings. + $args = $environment->settings->deploy; + $args['git_ref'] = $environment->git_ref; + hosting_add_task($environment->site, 'devshop-deploy', $args); + } + } + } + } + + if (empty($environments_to_pull)) { + $message .= "No environments tracking this branch. "; + } + else { + $message .= "Deploying code to environments: " . implode(', ', $environments_to_pull); + } + break; + + case 'pull_request': + // If pull request environments is enabled... + if ($project->settings->github['pull_request_environments']) { + $message = 'Pull Request Received.'; + + // @TODO: Handle forks? + $branch = $data->pull_request->head->ref; + + // Determine environment branch. + // @TODO: Make Configurable, allow branch names to be env name + $environment_name = "pr" . $data->pull_request->number; +// $environment_name = 'branch_' . str_replace('-', '_', $branch); + $already_have_pr_info = FALSE; + + // When PR is opened... create new environment. + if ($data->action == 'opened' || $data->action == 'reopened') { + $message = "Detected Pull Request creation for $branch \n"; + if (isset($project->environments[$environment_name])) { + $message = "Environment $environment_name already exists! Not creating one... \n"; + + // @TODO: Check for environments that are being deleted. + if (isset($project->environments[$environment_name]->github_pull_request)) { + $message .= "Already have a PR for $environment_name ... not inserting."; + $already_have_pr_info = TRUE; + } + } + else { + // If method is "install"... + if ($project->settings->github['pull_request_environments_method'] == 'devshop__github__install') { + hosting_create_environment($project, $environment_name, $branch); + $message .= "Environment $environment_name created for $project_node->title via installation profile.\n"; + } + // Otherwise, it is a clone from live. + elseif (isset($project->environments[$project->settings->github['pull_request_environments_method']] )) { + $source_env = $project->settings->github['pull_request_environments_method']; + hosting_create_environment($project, $environment_name, $branch, $source_env); + $message .= "Environment $environment_name created for $project_node->title via cloning $source_env \n"; + } + else { + $message .= 'Unable to determine what to do! Check "Pull Request Environment Method" setting.'; + } + } + + $project_node = node_load($project->nid); + $project = $project_node->project; + $environment = $project->environments[$environment_name]; + + $owner = $project->github_owner; + $repo = $project->github_repo; + $message .= "\n About to try to create a deployment for $owner/$repo... \n"; + + // Send a "deployment" to github. + try { + $token = variable_get('devshop_github_token', ''); + $client = new \Github\Client(); + $client->authenticate($token, Github\Client::AUTH_HTTP_TOKEN); + + $sha = $data->pull_request->head->sha; + + $params = new stdClass(); + $params->ref = $sha; + + // In GitHub's API, "environment" is just a small string it displays on the pull request: + $params->environment = $environment_name; + devshop_environment_url($project, $environment_name) + ; + $params->required_contexts = array(); + + // $message .= "\nEnvironment: " . print_r($environment, 1); + $message .= "\nDeployment Params: " . print_r($params, 1); + + $post_url = "/repos/$owner/$repo/deployments"; + $deployment = json_decode($client->getHttpClient()->post($post_url, json_encode($params))->getBody(TRUE)); + + // Save deployment to pull request data for later access. + $data->pull_request->deployment = $deployment; + + $message .= " Deployment Created! \n"; + + // Create deployment status + $params = new stdClass(); + $params->state = 'pending'; + $params->target_url = $environment->url;; + $params->description = t('New environment is being created. Please stand by.'); + $deployment_status = $client->getHttpClient()->post("/repos/$owner/$repo/deployments/{$deployment->id}/statuses", json_encode($params)); + + $message .= " Deployment Status Created! \n"; + + // Set a commit status for this REF for devshop/deploy context + $sha = $data->pull_request->head->sha; + + $params = new stdClass(); + $params->state = 'pending'; + $params->target_url = url("node/$environment->site", array('absolute' => TRUE)); + $params->description = t('DevShop: Deploy'); + $params->context = 'devshop/deploy'; + + // Post status to github + $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); + $message .= " Commit Status Created! \n"; + + // Determine if we are going to run tests. + // For now it is using the "live environment" setting. + // @TODO: Once we add "deploy hooks" to "Project Settings: Environment Defaults" we will have to change this. + +// // If live environment is set to run tests on deploy... +// $live_environment = $project->settings->live['live_environment']; +// if ($project->environments[$live_environment]->settings->deploy['test']) { +// $params = new stdClass(); +// $params->state = 'pending'; +// $params->target_url = url( +// "node/$environment->site", +// array('absolute' => true) +// ); +// $params->description = t('DevShop: Run Tests'); +// $params->context = 'devshop/tests'; +// +// // Post status to github +// $status = $client->getHttpClient()->post( +// "/repos/$owner/$repo/statuses/$sha", +// json_encode($params) +// ); +// $message .= " Commit Status for pending test run Created! \n"; +// } + + } catch (Github\Exception\RuntimeException $e) { + watchdog('devshop_github', 'GitHub Runtime Error: ' . $e->getMessage()); + $message .= 'GitHub RunTimeException during PR Create: ' . $e->getMessage() . $e->getCode(); + + if ($e->getCode() == '409') { + $message .= "\n Branch is out of date! alert the developer!"; + + // Send a failed commit status to alert to developer + $params = new stdClass(); + $params->state = 'failure'; + $params->target_url = $project->git_repo_url; + $params->description = t('Branch is out of date! Merge from default branch.'); + $params->context = 'devshop/merge'; + + // Post status to github + $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); + } + else { + http_response_code(500); + } + + } catch (Github\Exception\ValidationFailedException $e) { + watchdog('devshop_github', 'GitHub Validation Failed Error: ' . $e->getMessage()); + $message .= 'GitHub ValidationFailedException Error: ' . $e->getMessage(); + } + + // Insert PR record + if (!$already_have_pr_info) { + $info = new stdClass(); + $info->id = $data->pull_request->id; + $info->number = $data->pull_request->number; + $info->project_nid = $project->nid; + $info->environment_name = $environment_name; + $info->pull_request_object = serialize($data->pull_request); + + // Last minute check for existing PR info + $results = db_select('hosting_devshop_github_pull_requests', 'pr') + ->condition('id', $data->pull_request->id) + ->fields('pr', array('id')) + ->execute() + ->fetchAssoc(); + + // If there are results, save $update param for drupal_write_record(). + if (empty($results)) { + $update = array(); + } + else { + $update = array('id'); + } + + // Save environment record. + try { + drupal_write_record('hosting_devshop_github_pull_requests', $info, $update); + $message .= ' ' . t('Pull Request info saved to DevShop.'); + } + catch (\PDOException $e) { + $message .= 'Saving PR record failed: ' . $e->getMessage(); + } + } + } + + // When PR is updated, send a new deployment status environment. + elseif ($data->action == 'synchronize') { + + // Create a new deployment + $owner = $project->github_owner; + $repo = $project->github_repo; + $environment = $project->environments[$environment_name]; + + // Environment might have been deleted. + if (empty($environment)) { + $message .= "Environment $environment_name not found in project $project->name."; + continue; + } + + $message .= "About to set deployment status for repo $owner/$repo using environment $environment_name \n"; + $message .= "Environment: " . print_r($environment, 1); + + try { + $token = variable_get('devshop_github_token', ''); + $client = new \Github\Client(); + $client->authenticate($token, Github\Client::AUTH_HTTP_TOKEN); + + $sha = $data->pull_request->head->sha; + + $params = new stdClass(); + $params->ref = $sha; + $params->environment = $environment->url; + $params->required_contexts = array(); + $post_url = "/repos/$owner/$repo/deployments"; + $deployment = json_decode($client->getHttpClient()->post($post_url, json_encode($params))->getBody(TRUE)); + + // Save deployment to pull request data for later access. + $data->pull_request->deployment = $deployment; + + $message .= " Deployment Created! \n"; + + // Create deployment status + $deployment_id = $deployment->id; + + $params = new stdClass(); + $params->state = 'pending'; + $params->target_url = $environment->url; + $params->description = t('Code is being deployed. Please stand by.'); + + $post_url = "/repos/$owner/$repo/deployments/{$deployment_id}/statuses"; + $message .= "Attempting to create deployment status: $post_url \n"; + + $deployment_status = $client->getHttpClient()->post($post_url, json_encode($params)); + + $message .= " Deployment Status Created! \n"; + + // Set a commit status for this REF for devshop/deploy context + $sha = $data->pull_request->head->sha; + + $params = new stdClass(); + $params->state = 'pending'; + $params->target_url = url("node/$project->nid", array('absolute' => TRUE)); + $params->description = t('DevShop: Deploy'); + $params->context = 'devshop/deploy'; + + // Post status to github + $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); + + $message .= " Commit Status Created! \n"; + + // If environment is configured to run tests, add another status. + if (!empty($environment->settings->deploy['test'])) { + $params = new stdClass(); + $params->state = 'pending'; + + // @TODO: Add the link to the last "test" task here instead of the project. + $params->target_url = url("node/$project->nid", array('absolute' => TRUE)); + $params->description = t('DevShop: Run Tests'); + $params->context = 'devshop/tests'; + + // Post status to github + $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); + + $message .= " Commit Status Created for test runs! \n"; + } + + } catch (Github\Exception\RuntimeException $e) { + $log = 'GitHub API Error during PR Syncronize: ' . $e->getMessage() . $e->getCode(); + watchdog('devshop_github', $log); + + if ($e->getCode() == '409') { + $log .= "\n Out of date! alert the developer!"; + + // Send a failed commit status to alert to developer + $params = new stdClass(); + $params->state = 'failure'; + $params->target_url = $project->git_repo_url; + $params->description = t('Branch is out of date! Merge from default branch.'); + $params->context = 'devshop/merge'; + + // Post status to github + $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params)); + } + else { + http_response_code(500); + } + $message .= $log . "\n"; + } + + + // Update the PR record + $info = new stdClass(); + $info->id = $data->pull_request->id; + $info->number = $data->pull_request->number; + $info->project_nid = $project->nid; + $info->environment_name = $environment_name; + $info->pull_request_object = serialize($data->pull_request); + + // Save environment record. + $results = db_select('hosting_devshop_github_pull_requests', 'pr') + ->condition('id', $data->pull_request->id) + ->fields('pr', array('id')) + ->execute() + ->fetchAssoc(); + + // If there are results, save $update param for drupal_write_record(). + if (empty($results)) { + $update = array(); + } + else { + $update = array('id'); + } + + // Save environment record. + try { + drupal_write_record('hosting_devshop_github_pull_requests', $info, $update); + $message .= ' ' . t('Pull Request info saved to DevShop.'); + } + catch (\PDOException $e) { + $message .= 'Saving PR record failed: ' . $e->getMessage(); + } + } + // When PR is closed, delete environment. + elseif ($data->action == 'closed') { + $message .= "Pull Request Closed \n"; + if ($project->settings->github['pull_request_environments_delete']) { + + // If environment has a site... trigger it's deletion. + // Platform deletion triggers after site deletion completes. + if ($project->environments[$environment_name]->site) { + hosting_add_task($project->environments[$environment_name]->site, 'delete', array('force' => 1)); + $message .= "Environment $environment_name (Site Node: {$project->environments[$environment_name]->site}) scheduled for deletion."; + } + // If environment has a platform... trigger it's deletion. + elseif ($project->environments[$environment_name]->platform) { + hosting_add_task($project->environments[$environment_name]->platform, 'delete'); + $message .= "Environment $environment_name (Platform Node: {$project->environments[$environment_name]->platform}) scheduled for deletion."; + } + } + } + } + break; + } + + } + else { + $message = 'GitHub Request Received, but not in JSON. Please make sure to configure the webhook to use Payload version: application/vnd.github.v3+json'; + } + return $message; +} + +/** + * Check the GitHub account for an SSH key. + * + * @return bool + */ +function devshop_github_check_key() { + $devshop_key = variable_get('devshop_public_key', ''); + $token = variable_get('devshop_github_token', ''); + + $client = new \Github\Client(); + $client->authenticate($token, Github\Client::AUTH_HTTP_TOKEN); + + try { + $keys = $client->currentUser()->keys()->all(); + } + // Happens when user has no public keys. + catch (\GitHub\Exception\RuntimeException $e) { + return FALSE; + } + $devshop_sshkey = sshkey_parse($devshop_key); + $devshop_fingerprint = theme('sshkey_fingerprint', $devshop_sshkey); + foreach ($keys as $key) { + $ssh_key = sshkey_parse($key['key']); + $fingerprint = theme('sshkey_fingerprint', $ssh_key); + if ($fingerprint == $devshop_fingerprint) { + return TRUE; + } + } +} diff --git a/modules/devshop/devshop_github/includes/add-key.inc b/modules/devshop/devshop_github/includes/add-key.inc new file mode 100644 index 000000000..89da4fdda --- /dev/null +++ b/modules/devshop/devshop_github/includes/add-key.inc @@ -0,0 +1,56 @@ + $devshop_key, + 'title' => t('Added by DevShop from !url', array( + '!url' => $_SERVER['HTTP_HOST'], + )), + ); + + // Create public key + $client = new \Github\Client(); + $client->authenticate($token, Github\Client::AUTH_HTTP_TOKEN); + try { + $return = $client->currentUser()->keys()->create($params); + } + catch (\Github\Exception\RuntimeException $e) { + drupal_set_message($e->getMessage(), 'error'); + drupal_set_message('Code ' . $e->getCode(), 'error'); + } + catch (\GitHub\Exception\ValidationFailedException $e) { + drupal_set_message($e->getMessage(), 'error'); + drupal_set_message('Code ' . $e->getCode(), 'error'); + } + + if ($return['verified']) { + drupal_set_message('SSH Key added to your account.'); + drupal_goto('admin/devshop/github'); + } else { + drupal_set_message('Something went wrong. SSH Key was not added to your account.', 'error'); + drupal_goto('admin/devshop/github'); + } +} diff --git a/modules/devshop/devshop_github/includes/admin.inc b/modules/devshop/devshop_github/includes/admin.inc new file mode 100644 index 000000000..a788f887c --- /dev/null +++ b/modules/devshop/devshop_github/includes/admin.inc @@ -0,0 +1,59 @@ + t('GitHub API Token'), + '#type' => 'password', + '#description' => t('A GitHub API Token or Personal Access Token'), + '#default_value' => variable_get('devshop_github_token', ''), + ); + $github_url = "https://github.com/settings/tokens"; + $form['help'] = array( + '#markup' => t('Manage your tokens under account settings at !link.', array( + '!link' => l($github_url, $github_url, array('attributes' => array('target' => '_blank'))), + )) . '

' . t('Be sure to select the following Oauth Scopes: repo, repo:status, repo:deployment, public_repo, write:public_key, write_repo_hook'), + '#prefix' => '

', + '#suffix' => '

', + ); + + + // Warn the user if this is empty. + $token = variable_get('devshop_github_token', ''); + if (empty($token)) { + $form['devshop_github_token']['#description'] .= ' ' . t('No Token was found.') . ''; + $form['devshop_github_token']['#description'] .= ' ' . l(t('Create a new token'), 'https://github.com/settings/tokens/new', array('attributes' => array('target' => '_blank'))); + } + else { + $form['devshop_github_token']['#suffix'] .= ' ' . t('Your token is saved.') . ''; + } + + // Test for public key access. + if (!empty($token)) { + + $form['github_public_keys'] = array( + '#markup' => '', + ); + + if (devshop_github_check_key()) { + $form['github_public_keys']['#markup'] = '
' . t('The DevShop Public SSH Key has been found in your GitHub account.') . '
'; + } + else { + $form['github_public_keys']['#markup'] = t('The DevShop Public SSH Key was not found in your account.') . ' '; + + $form['github_public_keys']['#markup'] .= ''; + $form['github_public_keys']['#prefix'] = '
'; + $form['github_public_keys']['#suffix'] = l(' ' . t('Add devshop public key to your GitHub account'), 'admin/devshop/github/add-key', array( + 'attributes' => array( + 'class' => 'btn btn-primary btn-large' + ), + 'html' => TRUE, + )) . '
'; + } + } + + return system_settings_form($form); +} diff --git a/modules/devshop/devshop_github/vendor/autoload.php b/modules/devshop/devshop_github/vendor/autoload.php new file mode 100644 index 000000000..6bba741a6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + private $classMapAuthoritative = false; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-0 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/modules/devshop/devshop_github/vendor/composer/autoload_classmap.php b/modules/devshop/devshop_github/vendor/composer/autoload_classmap.php new file mode 100644 index 000000000..7a91153b0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/event-dispatcher'), + 'Guzzle\\Tests' => array($vendorDir . '/guzzle/guzzle/tests'), + 'Guzzle' => array($vendorDir . '/guzzle/guzzle/src'), + 'Github\\' => array($vendorDir . '/knplabs/github-api/lib'), +); diff --git a/modules/devshop/devshop_github/vendor/composer/autoload_psr4.php b/modules/devshop/devshop_github/vendor/composer/autoload_psr4.php new file mode 100644 index 000000000..b265c64a2 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/composer/autoload_psr4.php @@ -0,0 +1,9 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + return $loader; + } +} + +function composerRequirec023ec1f16bdfa3119c161ea844cfa9e($file) +{ + require $file; +} diff --git a/modules/devshop/devshop_github/vendor/composer/installed.json b/modules/devshop/devshop_github/vendor/composer/installed.json new file mode 100644 index 000000000..0dc9fd353 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/composer/installed.json @@ -0,0 +1,223 @@ +[ + { + "name": "symfony/event-dispatcher", + "version": "v2.6.6", + "version_normalized": "2.6.6.0", + "target-dir": "Symfony/Component/EventDispatcher", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher.git", + "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/70f7c8478739ad21e3deef0d977b38c77f1fb284", + "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5", + "symfony/dependency-injection": "~2.6", + "symfony/expression-language": "~2.6", + "symfony/phpunit-bridge": "~2.7", + "symfony/stopwatch": "~2.3" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2015-03-13 17:37:22", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "http://symfony.com" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "version_normalized": "3.9.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "time": "2015-03-18 18:23:50", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ] + }, + { + "name": "knplabs/github-api", + "version": "1.4.7", + "version_normalized": "1.4.7.0", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/php-github-api.git", + "reference": "d2729cf6b9d5b6fa340b1ff001dd89e319741baa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/d2729cf6b9d5b6fa340b1ff001dd89e319741baa", + "reference": "d2729cf6b9d5b6fa340b1ff001dd89e319741baa", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "guzzle/guzzle": "~3.7", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "knplabs/gaufrette": "Needed for optional Gaufrette cache" + }, + "time": "2015-04-07 20:24:33", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Github\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thibault Duplessis", + "email": "thibault.duplessis@gmail.com", + "homepage": "http://ornicar.github.com" + }, + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + } + ], + "description": "GitHub API v3 client", + "homepage": "https://github.com/KnpLabs/php-github-api", + "keywords": [ + "api", + "gh", + "gist", + "github" + ] + } +] diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/.gitignore b/modules/devshop/devshop_github/vendor/guzzle/guzzle/.gitignore new file mode 100644 index 000000000..893035d5b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/.gitignore @@ -0,0 +1,27 @@ +# Ingore common cruft +.DS_STORE +coverage +.idea + +# Ignore binary files +guzzle.phar +guzzle-min.phar + +# Ignore potentially sensitive phpunit file +phpunit.xml + +# Ignore composer generated files +composer.phar +composer.lock +composer-test.lock +vendor/ + +# Ignore build files +build/ +phing/build.properties + +# Ignore subsplit working directory +.subsplit + +docs/_build +docs/*.pyc diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/.travis.yml b/modules/devshop/devshop_github/vendor/guzzle/guzzle/.travis.yml new file mode 100644 index 000000000..209e05cd6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/.travis.yml @@ -0,0 +1,17 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm + +before_script: + - curl --version + - pecl install uri_template-beta || echo "pecl uri_template not available" + - composer self-update + - composer install --no-interaction --prefer-source --dev + - ~/.nvm/nvm.sh install v0.6.14 + +script: composer test diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/CHANGELOG.md b/modules/devshop/devshop_github/vendor/guzzle/guzzle/CHANGELOG.md new file mode 100644 index 000000000..f0dc5444a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/CHANGELOG.md @@ -0,0 +1,751 @@ +# CHANGELOG + +## 3.9.3 - 2015-03-18 + +* Ensuring Content-Length is not stripped from a request when it is `0`. +* Added more information to stream wrapper exceptions. +* Message parser will no longer throw warnings for malformed messages. +* Giving a valid cache TTL when max-age is 0. + +## 3.9.2 - 2014-09-10 + +* Retrying "Connection died, retrying a fresh connect" curl errors. +* Automatically extracting the cacert from the phar in client constructor. +* Added EntityBody support for OPTIONS requests. + +## 3.9.1 - 2014-05-07 + +* Added a fix to ReadLimitEntityBody to ensure it doesn't infinitely loop. +* Added a fix to the stream checksum function so that when the first read + returns a falsey value, it still continues to consume the stream until EOF. + +## 3.9.0 - 2014-04-23 + +* `null`, `false`, and `"_guzzle_blank_"` all now serialize as an empty value + with no trailing "=". See dc1d824277. +* No longer performing an MD5 check on the cacert each time the phar is used, + but rather copying the cacert to the temp directory. +* `"0"` can now be added as a URL path +* Deleting cookies that are set to empty +* If-Modified-Since is no longer unnecessarily added to the CachePlugin +* Cookie path matching now follows RFC 6265 s5.1.4 +* Updated service descriptions are now added to a service client's composite + factory. +* MockPlugin now throws an exception if the queue is empty. +* Properly parsing URLs that start with "http" but are not absolute +* Added the ability to configure the curl_multi_select timeout setting +* OAuth parameters are now sorted using lexicographical byte value ordering +* Fixing invalid usage of an out of range PHP feature in the ErrorResponsePlugin + +## 3.8.1 -2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events accross the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it'sdeprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 3.4.0 - 2013-04-11 + +* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289 +* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 +* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 +* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added ``getParamNames()`` and ``hasParam()`` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to ``Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`` +* Bug: ``+`` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using ``curl.`` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/LICENSE b/modules/devshop/devshop_github/vendor/guzzle/guzzle/LICENSE new file mode 100644 index 000000000..d51aa6986 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011 Michael Dowling, https://github.com/mtdowling + +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/modules/devshop/devshop_github/vendor/guzzle/guzzle/README.md b/modules/devshop/devshop_github/vendor/guzzle/guzzle/README.md new file mode 100644 index 000000000..6be06bf47 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/README.md @@ -0,0 +1,57 @@ +Guzzle, PHP HTTP client and webservice framework +================================================ + +# This is an old version of Guzzle + +This repository is for Guzzle 3.x. Guzzle 5.x, the new version of Guzzle, has +been released and is available at +[https://github.com/guzzle/guzzle](https://github.com/guzzle/guzzle). The +documentation for Guzzle version 5+ can be found at +[http://guzzlephp.org](http://guzzlephp.org). + +Guzzle 3 is only maintained for bug and security fixes. Guzzle 3 will be EOL +at some point in late 2015. + +### About Guzzle 3 + +[![Composer Downloads](https://poser.pugx.org/guzzle/guzzle/d/total.png)](https://packagist.org/packages/guzzle/guzzle) + [![Build Status](https://secure.travis-ci.org/guzzle/guzzle3.png?branch=master)](http://travis-ci.org/guzzle/guzzle3) + +- Extremely powerful API provides all the power of cURL with a simple interface. +- Truly take advantage of HTTP/1.1 with persistent connections, connection pooling, and parallel requests. +- Service description DSL allows you build awesome web service clients faster. +- Symfony2 event-based plugin system allows you to completely modify the behavior of a request. + +Get answers with: [Documentation](http://guzzle3.readthedocs.org/en/latest/), [Forums](https://groups.google.com/forum/?hl=en#!forum/guzzle), IRC ([#guzzlephp](irc://irc.freenode.net/#guzzlephp) @ irc.freenode.net) + +### Installing via Composer + +The recommended way to install Guzzle is through [Composer](http://getcomposer.org). + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php + +# Add Guzzle as a dependency +php composer.phar require guzzle/guzzle:~3.9 +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` +## Known Issues + +1. Problem following a specific redirect: https://github.com/guzzle/guzzle/issues/385. + This has been fixed in Guzzle 4/5. +2. Root XML attributes not serialized in a service description: https://github.com/guzzle/guzzle3/issues/5. + This has been fixed in Guzzle 4/5. +3. Accept-Encoding not preserved when following redirect: https://github.com/guzzle/guzzle3/issues/9 + Fixed in Guzzle 4/5. +4. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10 + Fixed in Guzzle 4/5. +5. Recursive model references with array items: https://github.com/guzzle/guzzle3/issues/13 + Fixed in Guzzle 4/5 +6. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10 + Fixed in Guzzle 4/5. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/UPGRADING.md b/modules/devshop/devshop_github/vendor/guzzle/guzzle/UPGRADING.md new file mode 100644 index 000000000..f58bf1171 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/UPGRADING.md @@ -0,0 +1,537 @@ +Guzzle Upgrade Guide +==================== + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +3.3 to 3.4 +---------- + +Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/build.xml b/modules/devshop/devshop_github/vendor/guzzle/guzzle/build.xml new file mode 100644 index 000000000..2aa62ba9a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/build.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/composer.json new file mode 100644 index 000000000..59424b39b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/composer.json @@ -0,0 +1,82 @@ +{ + "name": "guzzle/guzzle", + "type": "library", + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + + "require": { + "php": ">=5.3.3", + "ext-curl": "*", + "symfony/event-dispatcher": "~2.1" + }, + + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + + "scripts": { + "test": "phpunit" + }, + + "require-dev": { + "doctrine/cache": "~1.3", + "symfony/class-loader": "~2.1", + "monolog/monolog": "~1.0", + "psr/log": "~1.0", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3", + "phpunit/phpunit": "3.7.*" + }, + + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/Makefile b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/Makefile new file mode 100644 index 000000000..d92e03f95 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/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/Guzzle.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Guzzle.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/Guzzle" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Guzzle" + @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/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json new file mode 100644 index 000000000..81683026b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json @@ -0,0 +1,176 @@ +{ + "additionalProperties": true, + "name": { + "type": "string", + "description": "Name of the web service" + }, + "apiVersion": { + "type": ["string", "number"], + "description": "Version identifier that the service description is compatible with" + }, + "baseUrl": { + "type": "string", + "description": "Base URL of the web service. Any relative URI specified in an operation will be merged with the baseUrl using the process defined in RFC 2396" + }, + "basePath": { + "type": "string", + "description": "Alias of baseUrl" + }, + "_description": { + "type": "string", + "description": "Short summary of the web service. This is actually called 'description' but this JSON schema wont validate using just description." + }, + "operations": { + "description": "Operations of the web service", + "type": "object", + "properties": { + "extends": { + "type": "string", + "description": "Extend from another operation by name. The parent operation must be defined before the child." + }, + "httpMethod": { + "type": "string", + "description": "HTTP method used with the operation (e.g. GET, POST, PUT, DELETE, PATCH, etc)" + }, + "uri": { + "type": "string", + "description": "URI of the operation. The uri attribute can contain URI templates. The variables of the URI template are parameters of the operation with a location value of uri" + }, + "summary": { + "type": "string", + "description": "Short summary of what the operation does" + }, + "class": { + "type": "string", + "description": "Custom class to instantiate instead of the default Guzzle\\Service\\Command\\OperationCommand" + }, + "responseClass": { + "type": "string", + "description": "This is what is returned from the method. Can be a primitive, class name, or model name." + }, + "responseNotes": { + "type": "string", + "description": "A description of the response returned by the operation" + }, + "responseType": { + "type": "string", + "description": "The type of response that the operation creates. If not specified, this value will be automatically inferred based on whether or not there is a model matching the name, if a matching class name is found, or set to 'primitive' by default.", + "enum": [ "primitive", "class", "model", "documentation" ] + }, + "deprecated": { + "type": "boolean", + "description": "Whether or not the operation is deprecated" + }, + "errorResponses": { + "description": "Errors that could occur while executing the operation", + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "number", + "description": "HTTP response status code of the error" + }, + "reason": { + "type": "string", + "description": "Response reason phrase or description of the error" + }, + "class": { + "type": "string", + "description": "A custom exception class that would be thrown if the error is encountered" + } + } + } + }, + "data": { + "type": "object", + "additionalProperties": "true" + }, + "parameters": { + "$ref": "parameters", + "description": "Parameters of the operation. Parameters are used to define how input data is serialized into a HTTP request." + }, + "additionalParameters": { + "$ref": "parameters", + "description": "Validation and serialization rules for any parameter supplied to the operation that was not explicitly defined." + } + } + }, + "models": { + "description": "Schema models that can be referenced throughout the service description. Models can be used to define how an HTTP response is parsed into a Guzzle\\Service\\Resource\\Model object.", + "type": "object", + "properties": { + "$ref": "parameters", + "description": "Parameters of the model. When a model is referenced in a responseClass attribute of an operation, parameters define how a HTTP response message is parsed into a Guzzle\\Service\\Resource\\Model." + } + }, + "includes": { + "description": "Service description files to include and extend from (can be a .json, .js, or .php file)", + "type": "array", + "items": { + "type": "string", + "pattern": ".+\\.(js|json|php)$" + } + }, + "definitions": { + "parameters": { + "extends": "http://json-schema.org/schema", + "id": "parameters", + "name": { + "type": "string", + "description": "Unique name of the parameter" + }, + "type": { + "type": ["string", "array"], + "description": "Type of variable (string, number, integer, boolean, object, array, numeric, null, any). Types are using for validation and determining the structure of a parameter. You can use a union type by providing an array of simple types. If one of the union types matches the provided value, then the value is valid." + }, + "instanceOf": { + "type": "string", + "description": "When the type is an object, you can specify the class that the object must implement" + }, + "required": { + "type": "boolean", + "description": "Whether or not the parameter is required" + }, + "default": { + "description": "Default value to use if no value is supplied" + }, + "static": { + "type": "bool", + "description": "Set to true to specify that the parameter value cannot be changed from the default setting" + }, + "description": { + "type": "string", + "description": "Documentation of the parameter" + }, + "location": { + "type": "string", + "description": "The location of a request used to apply a parameter. Custom locations can be registered with a command, but the defaults are uri, query, statusCode, reasonPhrase, header, body, json, xml, postField, postFile, responseBody" + }, + "sentAs": { + "type": "string", + "description": "Specifies how the data being modeled is sent over the wire. For example, you may wish to include certain headers in a response model that have a normalized casing of FooBar, but the actual header is x-foo-bar. In this case, sentAs would be set to x-foo-bar." + }, + "filters": { + "type": "array", + "description": "Array of static method names to to run a parameter value through. Each value in the array must be a string containing the full class path to a static method or an array of complex filter information. You can specify static methods of classes using the full namespace class name followed by ‘::’ (e.g. FooBar::baz()). Some filters require arguments in order to properly filter a value. For complex filters, use a hash containing a ‘method’ key pointing to a static method, and an ‘args’ key containing an array of positional arguments to pass to the method. Arguments can contain keywords that are replaced when filtering a value: '@value‘ is replaced with the value being validated, '@api‘ is replaced with the Parameter object.", + "items": { + "type": ["string", { + "object": { + "properties": { + "method": { + "type": "string", + "description": "PHP function to call", + "required": true + }, + "args": { + "type": "array" + } + } + } + }] + } + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png new file mode 100644 index 000000000..f1017f7e6 Binary files /dev/null and b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png differ diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/homepage.css b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/homepage.css new file mode 100644 index 000000000..70c46d8d5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/homepage.css @@ -0,0 +1,122 @@ +/* Hero unit on homepage */ + +.hero-unit h1 { + font-size: 49px; + margin-bottom: 12px; +} + +.hero-unit { + padding: 40px; +} + +.hero-unit p { + font-size: 17px; +} + +.masthead img { + float: left; + margin-right: 17px; +} + +.hero-unit ul li { + margin-left: 220px; +} + +.hero-unit .buttons { + text-align: center; +} + +.jumbotron { + position: relative; + padding: 40px 0; + color: #fff; + text-shadow: 0 1px 3px rgba(0,0,0,.4), 0 0 30px rgba(0,0,0,.075); + background: #00312F; + background: -moz-linear-gradient(45deg, #002F31 0%, #335A6D 100%); + background: -webkit-gradient(linear, left bottom, right top, color-stop(0%,#00312D), color-stop(100%,#33566D)); + background: -webkit-linear-gradient(45deg, #020031 0%,#334F6D 100%); + background: -o-linear-gradient(45deg, #002D31 0%,#334D6D 100%); + background: -ms-linear-gradient(45deg, #002F31 0%,#33516D 100%); + background: linear-gradient(45deg, #020031 0%,#33516D 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#020031', endColorstr='#6d3353',GradientType=1 ); + -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, .2), inset 0 -3px 7px rgba(0, 0, 0, .2); + -moz-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2); + box-shadow: inset 0 3px 7px rgba(0, 0, 0, .2), inset 0 -3px 7px rgba(0, 0, 0, .2); +} + +.jumbotron h1 { + font-size: 80px; + font-weight: bold; + letter-spacing: -1px; + line-height: 1; +} + +.jumbotron p { + font-size: 24px; + font-weight: 300; + line-height: 1.25; + margin-bottom: 30px; +} + +.masthead { + padding: 40px 0 30px; + margin-bottom: 0; + color: #fff; + margin-top: -19px; +} + +.masthead h1 { + display: none; +} + +.masthead p { + font-size: 40px; + font-weight: 200; + line-height: 1.25; + margin: 12px 0 0 0; +} + +.masthead .btn { + padding: 19px 24px; + font-size: 24px; + font-weight: 200; + border: 0; +} + +/* Social bar on homepage */ + +.social { + padding: 2px 0; + text-align: center; + background-color: #f5f5f5; + border-top: 1px solid #fff; + border-bottom: 1px solid #ddd; + margin: 0 0 20px 0; +} + +.social ul { + margin-top: 0; +} + +.social-buttons { + margin-left: 0; + margin-bottom: 0; + padding-left: 0; + list-style: none; +} + +.social-buttons li { + display: inline-block; + padding: 5px 8px; + line-height: 1; + *display: inline; + *zoom: 1; +} + +.center-announcement { + padding: 10px; + background-color: rgb(238, 243, 255); + border-radius: 8px; + text-align: center; + margin: 24px 0; +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/logo.png b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/logo.png new file mode 100644 index 000000000..965a4ef41 Binary files /dev/null and b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/logo.png differ diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.css b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.css new file mode 100644 index 000000000..4d410b12e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.css @@ -0,0 +1,41 @@ +.com { + color: #93A1A1; +} +.lit { + color: #195F91; +} +.pun, .opn, .clo { + color: #93A1A1; +} +.fun { + color: #DC322F; +} +.str, .atv { + color: #DD1144; +} +.kwd, .linenums .tag { + color: #1E347B; +} +.typ, .atn, .dec, .var { + color: teal; +} +.pln { + color: #48484C; +} +.prettyprint { + background-color: #F7F7F9; + border: 1px solid #E1E1E8; + padding: 8px; +} +.prettyprint.linenums { + box-shadow: 40px 0 0 #FBFBFC inset, 41px 0 0 #ECECF0 inset; +} +ol.linenums { + margin: 0 0 0 33px; +} +ol.linenums li { + color: #BEBEC5; + line-height: 18px; + padding-left: 12px; + text-shadow: 0 1px 0 #FFFFFF; +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.js b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.js new file mode 100644 index 000000000..eef5ad7e6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.js @@ -0,0 +1,28 @@ +var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; +(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= +[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), +l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, +q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, +q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, +"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), +a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} +for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], +"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], +H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], +J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ +I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), +["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", +/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), +["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", +hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= +!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p + + + +
+
+ guzzle +

Guzzle

+

Guzzle is a PHP HTTP client
& framework for building RESTful web service clients.

+

+ View Guzzle on GitHub + Read the docs +

+
+
+ + + +
+ +

Introducing Guzzle

+ +

Guzzle takes the pain out of sending HTTP requests and the redundancy out of creating web service clients. It's + a framework that includes the tools needed to create a robust web service client, including: + Service descriptions for defining the inputs and outputs of an API, resource iterators for traversing + paginated resources, batching for sending a large number of requests as efficiently as possible.

+ +
    +
  • All the power of cURL with a simple interface.
  • +
  • Persistent connections and parallel requests.
  • +
  • Streams request and response bodies
  • +
  • Service descriptions for quickly building clients.
  • +
  • Powered by the Symfony2 EventDispatcher.
  • +
  • Use all of the code or only specific components.
  • +
  • Plugins for caching, logging, OAuth, mocks, and more
  • +
  • Includes a custom node.js webserver to test your clients.
  • +
+ +
+ Guzzle is now part of Drupal 8 core and powers the official AWS SDK for PHP +
+ +

GitHub Example

+ +
<?php
+require_once 'vendor/autoload.php';
+use Guzzle\Http\Client;
+
+// Create a client and provide a base URL
+$client = new Client('https://api.github.com');
+// Create a request with basic Auth
+$request = $client->get('/user')->setAuth('user', 'pass');
+// Send the request and get the response
+$response = $request->send();
+echo $response->getBody();
+// >>> {"type":"User", ...
+echo $response->getHeader('Content-Length');
+// >>> 792
+
+ +

Twitter Example

+
<?php
+// Create a client to work with the Twitter API
+$client = new Client('https://api.twitter.com/{version}', array(
+    'version' => '1.1'
+));
+
+// Sign all requests with the OauthPlugin
+$client->addSubscriber(new Guzzle\Plugin\Oauth\OauthPlugin(array(
+    'consumer_key'  => '***',
+    'consumer_secret' => '***',
+    'token'       => '***',
+    'token_secret'  => '***'
+)));
+
+echo $client->get('statuses/user_timeline.json')->send()->getBody();
+// >>> {"public_gists":6,"type":"User" ...
+
+// Create a tweet using POST
+$request = $client->post('statuses/update.json', null, array(
+    'status' => 'Tweeted with Guzzle, http://guzzlephp.org'
+));
+
+// Send the request and parse the JSON response into an array
+$data = $request->send()->json();
+echo $data['text'];
+// >>> Tweeted with Guzzle, http://t.co/kngJMfRk
+
+
+ + diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/leftbar.html b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/leftbar.html new file mode 100644 index 000000000..e69de29bb diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/nav_links.html b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/nav_links.html new file mode 100644 index 000000000..d4f2165bb --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/nav_links.html @@ -0,0 +1,5 @@ +
  • Docs
  • +
  • API
  • +
  • GitHub
  • +
  • Forum
  • +
  • IRC
  • diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/batching/batching.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/batching/batching.rst new file mode 100644 index 000000000..57f04d80f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/batching/batching.rst @@ -0,0 +1,183 @@ +======== +Batching +======== + +Guzzle provides a fairly generic and very customizable batching framework that allows developers to efficiently +transfer requests in parallel. + +Sending requests and commands in parallel +----------------------------------------- + +You can send HTTP requests in parallel by passing an array of ``Guzzle\Http\Message\RequestInterface`` objects to +``Guzzle\Http\Client::send()``: + +.. code-block:: php + + $responses = $client->send(array( + $client->get('http://www.example.com/foo'), + $client->get('http://www.example.com/baz') + $client->get('http://www.example.com/bar') + )); + +You can send commands in parallel by passing an array of ``Guzzle\Service\Command\CommandInterface`` objects +``Guzzle\Service\Client::execute()``: + +.. code-block:: php + + $commands = $client->execute(array( + $client->getCommand('foo'), + $client->getCommand('baz'), + $client->getCommand('bar') + )); + +These approaches work well for most use-cases. When you need more control over the requests that are sent in +parallel or you need to send a large number of requests, you need to use the functionality provided in the +``Guzzle\Batch`` namespace. + +Batching overview +----------------- + +The batch object, ``Guzzle\Batch\Batch``, is a queue. You add requests to the queue until you are ready to transfer +all of the requests. In order to efficiently transfer the items in the queue, the batch object delegates the +responsibility of dividing the queue into manageable parts to a divisor (``Guzzle\Batch\BatchDivisorInterface``). +The batch object then iterates over each array of items created by the divisor and sends them to the batch object's +``Guzzle\Batch\BatchTransferInterface``. + +.. code-block:: php + + use Guzzle\Batch\Batch; + use Guzzle\Http\BatchRequestTransfer; + + // BatchRequestTransfer acts as both the divisor and transfer strategy + $transferStrategy = new BatchRequestTransfer(10); + $divisorStrategy = $transferStrategy; + + $batch = new Batch($transferStrategy, $divisorStrategy); + + // Add some requests to the batch queue + $batch->add($request1) + ->add($request2) + ->add($request3); + + // Flush the queue and retrieve the flushed items + $arrayOfTransferredRequests = $batch->flush(); + +.. note:: + + You might find that your transfer strategy will need to act as both the divisor and transfer strategy. + +Using the BatchBuilder +---------------------- + +The ``Guzzle\Batch\BatchBuilder`` makes it easier to create batch objects. The batch builder also provides an easier +way to add additional behaviors to your batch object. + +Transferring requests +~~~~~~~~~~~~~~~~~~~~~ + +The ``Guzzle\Http\BatchRequestTransfer`` class efficiently transfers HTTP requests in parallel by grouping batches of +requests by the curl_multi handle that is used to transfer the requests. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferRequests(10) + ->build(); + +Transferring commands +~~~~~~~~~~~~~~~~~~~~~ + +The ``Guzzle\Service\Command\BatchCommandTransfer`` class efficiently transfers service commands by grouping commands +by the client that is used to transfer them. You can add commands to a batch object that are transferred by different +clients, and the batch will handle the rest. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferCommands(10) + ->build(); + + $batch->add($client->getCommand('foo')) + ->add($client->getCommand('baz')) + ->add($client->getCommand('bar')); + + $commands = $batch->flush(); + +Batch behaviors +--------------- + +You can add various behaviors to your batch that allow for more customizable transfers. + +Automatically flushing a queue +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``Guzzle\Batch\FlushingBatch`` decorator when you want to pump a large number of items into a batch queue and +have the queue automatically flush when the size of the queue reaches a certain threshold. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferRequests(10) + ->autoFlushAt(10) + ->build(); + +Batch builder method: ``autoFlushAt($threshold)`` + +Notifying on flush +~~~~~~~~~~~~~~~~~~ + +Use the ``Guzzle\Batch\NotifyingBatch`` decorator if you want a function to be notified each time the batch queue is +flushed. This is useful when paired with the flushing batch decorator. Pass a callable to the ``notify()`` method of +a batch builder to use this decorator with the builder. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferRequests(10) + ->autoFlushAt(10) + ->notify(function (array $transferredItems) { + echo 'Transferred ' . count($transferredItems) . "items\n"; + }) + ->build(); + +Batch builder method:: ``notify(callable $callback)`` + +Keeping a history +~~~~~~~~~~~~~~~~~ + +Use the ``Guzzle\Batch\HistoryBatch`` decorator if you want to maintain a history of all the items transferred with +the batch queue. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferRequests(10) + ->keepHistory() + ->build(); + +After transferring items, you can use the ``getHistory()`` of a batch to retrieve an array of transferred items. Be +sure to periodically clear the history using ``clearHistory()``. + +Batch builder method: ``keepHistory()`` + +Exception buffering +~~~~~~~~~~~~~~~~~~~ + +Use the ``Guzzle\Batch\ExceptionBufferingBatch`` decorator to buffer exceptions during a transfer so that you can +transfer as many items as possible then deal with the errored batches after the transfer completes. After transfer, +use the ``getExceptions()`` method of a batch to retrieve an array of +``Guzzle\Batch\Exception\BatchTransferException`` objects. You can use these exceptions to attempt to retry the +failed batches. Be sure to clear the buffered exceptions when you are done with them by using the +``clearExceptions()`` method. + +Batch builder method: ``bufferExceptions()`` diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/conf.py b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/conf.py new file mode 100644 index 000000000..92bc46bb5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/conf.py @@ -0,0 +1,94 @@ +import sys, os +from sphinx.highlighting import lexers +from pygments.lexers.web import PhpLexer + +lexers['php'] = PhpLexer(startinline=True, linenos=1) +lexers['php-annotations'] = PhpLexer(startinline=True, linenos=1) +primary_domain = 'php' + +# -- General configuration ----------------------------------------------------- + +extensions = [] +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' + +project = u'Guzzle' +copyright = u'2012, Michael Dowling' +version = '3.0.0' +release = '3.0.0' + +exclude_patterns = ['_build'] + +# -- Options for HTML output --------------------------------------------------- + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = "Guzzle documentation" +html_short_title = "Guzzle" + +# 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'] + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + '**': ['localtoc.html', 'leftbar.html', 'searchbox.html'] +} + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Guzzledoc' + +# -- Guzzle Sphinx theme setup ------------------------------------------------ + +sys.path.insert(0, '/Users/dowling/projects/guzzle_sphinx_theme') + +import guzzle_sphinx_theme +html_translator_class = 'guzzle_sphinx_theme.HTMLTranslator' +html_theme_path = guzzle_sphinx_theme.html_theme_path() +html_theme = 'guzzle_sphinx_theme' + +# Guzzle theme options (see theme.conf for more information) +html_theme_options = { + "index_template": "index.html", + "project_nav_name": "Guzzle", + "github_user": "guzzle", + "github_repo": "guzzle", + "disqus_comments_shortname": "guzzle", + "google_analytics_account": "UA-22752917-1" +} + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = {} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Guzzle.tex', u'Guzzle Documentation', + u'Michael Dowling', 'manual'), +] + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'guzzle', u'Guzzle Documentation', + [u'Michael Dowling'], 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', 'Guzzle', u'Guzzle Documentation', + u'Michael Dowling', 'Guzzle', 'One line description of project.', + 'Miscellaneous'), +] diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/docs.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/docs.rst new file mode 100644 index 000000000..cf87908bd --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/docs.rst @@ -0,0 +1,73 @@ +.. title:: Guzzle | PHP HTTP client and framework for consuming RESTful web services + +==================== +Guzzle Documentation +==================== + +Getting started +--------------- + +.. toctree:: + :maxdepth: 1 + + getting-started/overview + getting-started/installation + getting-started/faq + +The HTTP client +--------------- + +.. toctree:: + :maxdepth: 2 + + http-client/client + http-client/request + http-client/response + http-client/entity-bodies + http-client/http-redirects + http-client/uri-templates + +Plugins +------- + +.. toctree:: + :maxdepth: 1 + + plugins/plugins-overview + plugins/creating-plugins + plugins/async-plugin + plugins/backoff-plugin + plugins/cache-plugin + plugins/cookie-plugin + plugins/curl-auth-plugin + plugins/history-plugin + plugins/log-plugin + plugins/md5-validator-plugin + plugins/mock-plugin + plugins/oauth-plugin + +The web service client +---------------------- + +.. toctree:: + :maxdepth: 1 + + webservice-client/webservice-client + webservice-client/using-the-service-builder + webservice-client/guzzle-service-descriptions + batching/batching + iterators/resource-iterators + iterators/guzzle-iterators + +Testing +------- + +.. toctree:: + :maxdepth: 2 + + testing/unit-testing + +API Docs +-------- + +`Read the API docs `_ diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/getting-started/faq.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/getting-started/faq.rst new file mode 100644 index 000000000..a0a3fdbb6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/getting-started/faq.rst @@ -0,0 +1,29 @@ +=== +FAQ +=== + +What should I do if I get this error: Fatal error: Maximum function nesting level of '100' reached, aborting! +------------------------------------------------------------------------------------------------------------- + +You could run into this error if you have the XDebug extension installed and you execute a lot of requests in +callbacks. This error message comes specifically from the XDebug extension. PHP itself does not have a function +nesting limit. Change this setting in your php.ini to increase the limit:: + + xdebug.max_nesting_level = 1000 + +[`source `_] + +How can I speed up my client? +----------------------------- + +There are several things you can do to speed up your client: + +1. Utilize a C based HTTP message parser (e.g. ``Guzzle\Parser\Message\PeclHttpMessageParser``) +2. Disable operation validation by setting the ``command.disable_validation`` option to true on a command + +Why am I getting a 417 error response? +-------------------------------------- + +This can occur for a number of reasons, but if you are sending PUT, POST, or PATCH requests with an +``Expect: 100-Continue`` header, a server that does not support this header will return a 417 response. You can work +around this by calling ``$request->removeHeader('Expect');`` after setting the entity body of a request. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/getting-started/installation.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/getting-started/installation.rst new file mode 100644 index 000000000..77d400131 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/getting-started/installation.rst @@ -0,0 +1,154 @@ +============ +Installation +============ + +Requirements +------------ + +#. PHP 5.3.3+ compiled with the cURL extension +#. A recent version of cURL 7.16.2+ compiled with OpenSSL and zlib + +Installing Guzzle +----------------- + +Composer +~~~~~~~~ + +The recommended way to install Guzzle is with `Composer `_. Composer is a dependency +management tool for PHP that allows you to declare the dependencies your project needs and installs them into your +project. + +.. code-block:: bash + + # Install Composer + curl -sS https://getcomposer.org/installer | php + + # Add Guzzle as a dependency + php composer.phar require guzzle/guzzle:~3.9 + +After installing, you need to require Composer's autoloader: + +.. code-block:: php + + require 'vendor/autoload.php'; + +You can find out more on how to install Composer, configure autoloading, and other best-practices for defining +dependencies at `getcomposer.org `_. + +Using only specific parts of Guzzle +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +While you can always just rely on ``guzzle/guzzle``, Guzzle provides several smaller parts of Guzzle as individual +packages available through Composer. + ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| Package name | Description | ++===============================================================================================+==========================================+ +| `guzzle/common `_ | Provides ``Guzzle\Common`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/http `_ | Provides ``Guzzle\Http`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/parser `_ | Provides ``Guzzle\Parser`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/batch `_ | Provides ``Guzzle\Batch`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/cache `_ | Provides ``Guzzle\Cache`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/inflection `_ | Provides ``Guzzle\Inflection`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/iterator `_ | Provides ``Guzzle\Iterator`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/log `_ | Provides ``Guzzle\Log`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin `_ | Provides ``Guzzle\Plugin`` (all plugins) | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-async `_ | Provides ``Guzzle\Plugin\Async`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-backoff `_ | Provides ``Guzzle\Plugin\BackoffPlugin`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-cache `_ | Provides ``Guzzle\Plugin\Cache`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-cookie `_ | Provides ``Guzzle\Plugin\Cookie`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-error-response `_ | Provides ``Guzzle\Plugin\ErrorResponse`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-history `_ | Provides ``Guzzle\Plugin\History`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-log `_ | Provides ``Guzzle\Plugin\Log`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-md5 `_ | Provides ``Guzzle\Plugin\Md5`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-mock `_ | Provides ``Guzzle\Plugin\Mock`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-oauth `_ | Provides ``Guzzle\Plugin\Oauth`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/service `_ | Provides ``Guzzle\Service`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/stream `_ | Provides ``Guzzle\Stream`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ + +Bleeding edge +^^^^^^^^^^^^^ + +During your development, you can keep up with the latest changes on the master branch by setting the version +requirement for Guzzle to ``dev-master``. + +.. code-block:: js + + { + "require": { + "guzzle/guzzle": "dev-master" + } + } + +PEAR +~~~~ + +Guzzle can be installed through PEAR: + +.. code-block:: bash + + pear channel-discover guzzlephp.org/pear + pear install guzzle/guzzle + +You can install a specific version of Guzzle by providing a version number suffix: + +.. code-block:: bash + + pear install guzzle/guzzle-3.9.0 + +Contributing to Guzzle +---------------------- + +In order to contribute, you'll need to checkout the source from GitHub and install Guzzle's dependencies using +Composer: + +.. code-block:: bash + + git clone https://github.com/guzzle/guzzle.git + cd guzzle && curl -s http://getcomposer.org/installer | php && ./composer.phar install --dev + +Guzzle is unit tested with PHPUnit. You will need to create your own phpunit.xml file in order to run the unit tests +(or just copy phpunit.xml.dist to phpunit.xml). Run the tests using the vendored PHPUnit binary: + +.. code-block:: bash + + vendor/bin/phpunit + +You'll need to install node.js v0.5.0 or newer in order to test the cURL implementation. + +Framework integrations +---------------------- + +Using Guzzle with Symfony +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Bundles are available on GitHub: + +- `DdeboerGuzzleBundle `_ for Guzzle 2 +- `MisdGuzzleBundle `_ for Guzzle 3 + +Using Guzzle with Silex +~~~~~~~~~~~~~~~~~~~~~~~ + +A `Guzzle Silex service provider `_ is available on GitHub. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/getting-started/overview.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/getting-started/overview.rst new file mode 100644 index 000000000..505b40978 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/getting-started/overview.rst @@ -0,0 +1,85 @@ +================= +Welcome to Guzzle +================= + +What is Guzzle? +~~~~~~~~~~~~~~~ + +Guzzle is a PHP HTTP client and framework for building web service clients. Guzzle takes the pain out of sending HTTP +requests and the redundancy out of creating web service clients. + +Features at a glance +-------------------- + +- All the power of cURL with a simple interface. +- Persistent connections and parallel requests. +- Streams request and response bodies +- Service descriptions for quickly building clients. +- Powered by the Symfony2 EventDispatcher. +- Use all of the code or only specific components. +- Plugins for caching, logging, OAuth, mocks, and more +- Includes a custom node.js webserver to test your clients. +- Service descriptions for defining the inputs and outputs of an API +- Resource iterators for traversing paginated resources +- Batching for sending a large number of requests as efficiently as possible + +.. code-block:: php + + // Really simple using a static facade + Guzzle\Http\StaticClient::mount(); + $response = Guzzle::get('http://guzzlephp.org'); + + // More control using a client class + $client = new \Guzzle\Http\Client('http://guzzlephp.org'); + $request = $client->get('/'); + $response = $request->send(); + +License +------- + +Licensed using the `MIT license `_. + + Copyright (c) 2013 Michael Dowling + + 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. + +Contributing +------------ + +Guidelines +~~~~~~~~~~ + +This is still a work in progress, but there are only a few rules: + +1. Guzzle follows PSR-0, PSR-1, and PSR-2 +2. All pull requests must include unit tests to ensure the change works as expected and to prevent future regressions + +Reporting a security vulnerability +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We want to ensure that Guzzle is a secure HTTP client library for everyone. If you've discovered a security +vulnerability in Guzzle, we appreciate your help in disclosing it to us in a +`responsible manner `_. + +Publicly disclosing a vulnerability can put the entire community at risk. If you've discovered a security concern, +please email us at security@guzzlephp.org. We'll work with you to make sure that we understand the scope of the issue, +and that we fully address your concern. We consider correspondence sent to security@guzzlephp.org our highest priority, +and work to address any issues that arise as quickly as possible. + +After a security vulnerability has been corrected, a security hotfix release will be deployed as soon as possible. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/client.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/client.rst new file mode 100644 index 000000000..723d729db --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/client.rst @@ -0,0 +1,569 @@ +====================== +The Guzzle HTTP client +====================== + +Guzzle gives PHP developers complete control over HTTP requests while utilizing HTTP/1.1 best practices. Guzzle's HTTP +functionality is a robust framework built on top of the `PHP libcurl bindings `_. + +The three main parts of the Guzzle HTTP client are: + ++--------------+-------------------------------------------------------------------------------------------------------+ +| Clients | ``Guzzle\Http\Client`` (creates and sends requests, associates a response with a request) | ++--------------+-------------------------------------------------------------------------------------------------------+ +| Requests | ``Guzzle\Http\Message\Request`` (requests with no body), | +| | ``Guzzle\Http\Message\EntityEnclosingRequest`` (requests with a body) | ++--------------+-------------------------------------------------------------------------------------------------------+ +| Responses | ``Guzzle\Http\Message\Response`` | ++--------------+-------------------------------------------------------------------------------------------------------+ + +Creating a Client +----------------- + +Clients create requests, send requests, and set responses on a request object. When instantiating a client object, +you can pass an optional "base URL" and optional array of configuration options. A base URL is a +:doc:`URI template ` that contains the URL of a remote server. When creating requests with a relative +URL, the base URL of a client will be merged into the request's URL. + +.. code-block:: php + + use Guzzle\Http\Client; + + // Create a client and provide a base URL + $client = new Client('https://api.github.com'); + + $request = $client->get('/user'); + $request->setAuth('user', 'pass'); + echo $request->getUrl(); + // >>> https://api.github.com/user + + // You must send a request in order for the transfer to occur + $response = $request->send(); + + echo $response->getBody(); + // >>> {"type":"User", ... + + echo $response->getHeader('Content-Length'); + // >>> 792 + + $data = $response->json(); + echo $data['type']; + // >>> User + +Base URLs +~~~~~~~~~ + +Notice that the URL provided to the client's ``get()`` method is relative. Relative URLs will always merge into the +base URL of the client. There are a few rules that control how the URLs are merged. + +.. tip:: + + Guzzle follows `RFC 3986 `_ when merging base URLs and + relative URLs. + +In the above example, we passed ``/user`` to the ``get()`` method of the client. This is a relative URL, so it will +merge into the base URL of the client-- resulting in the derived URL of ``https://api.github.com/users``. + +``/user`` is a relative URL but uses an absolute path because it contains the leading slash. Absolute paths will +overwrite any existing path of the base URL. If an absolute path is provided (e.g. ``/path/to/something``), then the +path specified in the base URL of the client will be replaced with the absolute path, and the query string provided +by the relative URL will replace the query string of the base URL. + +Omitting the leading slash and using relative paths will add to the path of the base URL of the client. So using a +client base URL of ``https://api.twitter.com/v1.1`` and creating a GET request with ``statuses/user_timeline.json`` +will result in a URL of ``https://api.twitter.com/v1.1/statuses/user_timeline.json``. If a relative path and a query +string are provided, then the relative path will be appended to the base URL path, and the query string provided will +be merged into the query string of the base URL. + +If an absolute URL is provided (e.g. ``http://httpbin.org/ip``), then the request will completely use the absolute URL +as-is without merging in any of the URL parts specified in the base URL. + +Configuration options +~~~~~~~~~~~~~~~~~~~~~ + +The second argument of the client's constructor is an array of configuration data. This can include URI template data +or special options that alter the client's behavior: + ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``request.options`` | Associative array of :ref:`Request options ` to apply to every | +| | request created by the client. | ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``redirect.disable`` | Disable HTTP redirects for every request created by the client. | ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``curl.options`` | Associative array of cURL options to apply to every request created by the client. | +| | if either the key or value of an entry in the array is a string, Guzzle will | +| | attempt to find a matching defined cURL constant automatically (e.g. | +| | "CURLOPT_PROXY" will be converted to the constant ``CURLOPT_PROXY``). | ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``ssl.certificate_authority`` | Set to true to use the Guzzle bundled SSL certificate bundle (this is used by | +| | default, 'system' to use the bundle on your system, a string pointing to a file to | +| | use a specific certificate file, a string pointing to a directory to use multiple | +| | certificates, or ``false`` to disable SSL validation (not recommended). | +| | | +| | When using Guzzle inside of a phar file, the bundled SSL certificate will be | +| | extracted to your system's temp folder, and each time a client is created an MD5 | +| | check will be performed to ensure the integrity of the certificate. | ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``command.params`` | When using a ``Guzzle\Service\Client`` object, this is an associative array of | +| | default options to set on each command created by the client. | ++-------------------------------+-------------------------------------------------------------------------------------+ + +Here's an example showing how to set various configuration options, including default headers to send with each request, +default query string parameters to add to each request, a default auth scheme for each request, and a proxy to use for +each request. Values can be injected into the client's base URL using variables from the configuration array. + +.. code-block:: php + + use Guzzle\Http\Client; + + $client = new Client('https://api.twitter.com/{version}', array( + 'version' => 'v1.1', + 'request.options' => array( + 'headers' => array('Foo' => 'Bar'), + 'query' => array('testing' => '123'), + 'auth' => array('username', 'password', 'Basic|Digest|NTLM|Any'), + 'proxy' => 'tcp://localhost:80' + ) + )); + +Setting a custom User-Agent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default Guzzle User-Agent header is ``Guzzle/ curl/ PHP/``. You can +customize the User-Agent header of a client by calling the ``setUserAgent()`` method of a Client object. + +.. code-block:: php + + // Completely override the default User-Agent + $client->setUserAgent('Test/123'); + + // Prepend a string to the default User-Agent + $client->setUserAgent('Test/123', true); + +Creating requests with a client +------------------------------- + +A Client object exposes several methods used to create Request objects: + +* Create a custom HTTP request: ``$client->createRequest($method, $uri, array $headers, $body, $options)`` +* Create a GET request: ``$client->get($uri, array $headers, $options)`` +* Create a HEAD request: ``$client->head($uri, array $headers, $options)`` +* Create a DELETE request: ``$client->delete($uri, array $headers, $body, $options)`` +* Create a POST request: ``$client->post($uri, array $headers, $postBody, $options)`` +* Create a PUT request: ``$client->put($uri, array $headers, $body, $options)`` +* Create a PATCH request: ``$client->patch($uri, array $headers, $body, $options)`` + +.. code-block:: php + + use Guzzle\Http\Client; + + $client = new Client('http://baseurl.com/api/v1'); + + // Create a GET request using Relative to base URL + // URL of the request: http://baseurl.com/api/v1/path?query=123&value=abc) + $request = $client->get('path?query=123&value=abc'); + $response = $request->send(); + + // Create HEAD request using a relative URL with an absolute path + // URL of the request: http://baseurl.com/path?query=123&value=abc + $request = $client->head('/path?query=123&value=abc'); + $response = $request->send(); + + // Create a DELETE request using an absolute URL + $request = $client->delete('http://www.example.com/path?query=123&value=abc'); + $response = $request->send(); + + // Create a PUT request using the contents of a PHP stream as the body + // Specify custom HTTP headers + $request = $client->put('http://www.example.com/upload', array( + 'X-Header' => 'My Header' + ), fopen('http://www.test.com/', 'r')); + $response = $request->send(); + + // Create a POST request and add the POST files manually + $request = $client->post('http://localhost:8983/solr/update') + ->addPostFiles(array('file' => '/path/to/documents.xml')); + $response = $request->send(); + + // Check if a resource supports the DELETE method + $supportsDelete = $client->options('/path')->send()->isMethodAllowed('DELETE'); + $response = $request->send(); + +Client objects create Request objects using a request factory (``Guzzle\Http\Message\RequestFactoryInterface``). +You can inject a custom request factory into the Client using ``$client->setRequestFactory()``, but you can typically +rely on a Client's default request factory. + +Static clients +-------------- + +You can use Guzzle's static client facade to more easily send simple HTTP requests. + +.. code-block:: php + + // Mount the client so that you can access it at \Guzzle + Guzzle\Http\StaticClient::mount(); + $response = Guzzle::get('http://guzzlephp.org'); + +Each request method of the static client (e.g. ``get()``, ``post()`, ``put()``, etc) accepts an associative array of request +options to apply to the request. + +.. code-block:: php + + $response = Guzzle::post('http://test.com', array( + 'headers' => array('X-Foo' => 'Bar'), + 'body' => array('Test' => '123'), + 'timeout' => 10 + )); + +.. _request-options: + +Request options +--------------- + +Request options can be specified when creating a request or in the ``request.options`` parameter of a client. These +options can control various aspects of a request including: headers to send, query string data, where the response +should be downloaded, proxies, auth, etc. + +headers +~~~~~~~ + +Associative array of headers to apply to the request. When specified in the ``$options`` argument of a client creational +method (e.g. ``get()``, ``post()``, etc), the headers in the ``$options`` array will overwrite headers specified in the +``$headers`` array. + +.. code-block:: php + + $request = $client->get($url, array(), array( + 'headers' => array('X-Foo' => 'Bar') + )); + +Headers can be specified on a client to add default headers to every request sent by a client. + +.. code-block:: php + + $client = new Guzzle\Http\Client(); + + // Set a single header using path syntax + $client->setDefaultOption('headers/X-Foo', 'Bar'); + + // Set all headers + $client->setDefaultOption('headers', array('X-Foo' => 'Bar')); + +.. note:: + + In addition to setting request options when creating requests or using the ``setDefaultOption()`` method, any + default client request option can be set using a client's config object: + + .. code-block:: php + + $client->getConfig()->setPath('request.options/headers/X-Foo', 'Bar'); + +query +~~~~~ + +Associative array of query string parameters to the request. When specified in the ``$options`` argument of a client +creational method, the query string parameters in the ``$options`` array will overwrite query string parameters +specified in the `$url`. + +.. code-block:: php + + $request = $client->get($url, array(), array( + 'query' => array('abc' => '123') + )); + +Query string parameters can be specified on a client to add default query string parameters to every request sent by a +client. + +.. code-block:: php + + $client = new Guzzle\Http\Client(); + + // Set a single query string parameter using path syntax + $client->setDefaultOption('query/abc', '123'); + + // Set an array of default query string parameters + $client->setDefaultOption('query', array('abc' => '123')); + +body +~~~~ + +Sets the body of a request. The value supplied to the body option can be a ``Guzzle\Http\EntityBodyInterface``, string, +fopen resource, or array when sending POST requests. When a ``body`` request option is supplied, the option value will +overwrite the ``$body`` argument of a client creational method. + +auth +~~~~ + +Specifies and array of HTTP authorization parameters parameters to use with the request. The array must contain the +username in index [0], the password in index [1], and can optionally contain the authentication type in index [2]. +The available authentication types are: "Basic" (default), "Digest", "NTLM", or "Any". + +.. code-block:: php + + $request = $client->get($url, array(), array( + 'auth' => array('username', 'password', 'Digest') + )); + + // You can add auth headers to every request of a client + $client->setDefaultOption('auth', array('username', 'password', 'Digest')); + +cookies +~~~~~~~ + +Specifies an associative array of cookies to add to the request. + +allow_redirects +~~~~~~~~~~~~~~~ + +Specifies whether or not the request should follow redirects. Requests will follow redirects by default. Set +``allow_redirects`` to ``false`` to disable redirects. + +save_to +~~~~~~~ + +The ``save_to`` option specifies where the body of a response is downloaded. You can pass the path to a file, an fopen +resource, or a ``Guzzle\Http\EntityBodyInterface`` object. + +See :ref:`Changing where a response is downloaded ` for more information on setting the +`save_to` option. + +events +~~~~~~ + +The `events` option makes it easy to attach listeners to the various events emitted by a request object. The `events` +options must be an associative array mapping an event name to a Closure or array the contains a Closure and the +priority of the event. + +.. code-block:: php + + $request = $client->get($url, array(), array( + 'events' => array( + 'request.before_send' => function (\Guzzle\Common\Event $e) { + echo 'About to send ' . $e['request']; + } + ) + )); + + // Using the static client: + Guzzle::get($url, array( + 'events' => array( + 'request.before_send' => function (\Guzzle\Common\Event $e) { + echo 'About to send ' . $e['request']; + } + ) + )); + +plugins +~~~~~~~ + +The `plugins` options makes it easy to attach an array of plugins to a request. + +.. code-block:: php + + // Using the static client: + Guzzle::get($url, array( + 'plugins' => array( + new Guzzle\Plugin\Cache\CachePlugin(), + new Guzzle\Plugin\Cookie\CookiePlugin() + ) + )); + +exceptions +~~~~~~~~~~ + +The `exceptions` option can be used to disable throwing exceptions for unsuccessful HTTP response codes +(e.g. 404, 500, etc). Set `exceptions` to false to not throw exceptions. + +params +~~~~~~ + +The `params` options can be used to specify an associative array of data parameters to add to a request. Note that +these are not query string parameters. + +timeout / connect_timeout +~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can specify the maximum number of seconds to allow for an entire transfer to take place before timing out using +the `timeout` request option. You can specify the maximum number of seconds to wait while trying to connect using the +`connect_timeout` request option. Set either of these options to 0 to wait indefinitely. + +.. code-block:: php + + $request = $client->get('http://www.example.com', array(), array( + 'timeout' => 20, + 'connect_timeout' => 1.5 + )); + +verify +~~~~~~ + +Set to true to enable SSL certificate validation (the default), false to disable SSL certificate validation, or supply +the path to a CA bundle to enable verification using a custom certificate. + +cert +~~~~ + +The `cert` option lets you specify a PEM formatted SSL client certificate to use with servers that require one. If the +certificate requires a password, provide an array with the password as the second item. + +This would typically be used in conjunction with the `ssl_key` option. + +.. code-block:: php + + $request = $client->get('https://www.example.com', array(), array( + 'cert' => '/etc/pki/client_certificate.pem' + ) + + $request = $client->get('https://www.example.com', array(), array( + 'cert' => array('/etc/pki/client_certificate.pem', 's3cr3tp455w0rd') + ) + +ssl_key +~~~~~~~ + +The `ssl_key` option lets you specify a file containing your PEM formatted private key, optionally protected by a password. +Note: your password is sensitive, keep the PHP script containing it safe. + +This would typically be used in conjunction with the `cert` option. + +.. code-block:: php + + $request = $client->get('https://www.example.com', array(), array( + 'ssl_key' => '/etc/pki/private_key.pem' + ) + + $request = $client->get('https://www.example.com', array(), array( + 'ssl_key' => array('/etc/pki/private_key.pem', 's3cr3tp455w0rd') + ) + +proxy +~~~~~ + +The `proxy` option is used to specify an HTTP proxy (e.g. `http://username:password@192.168.16.1:10`). + +debug +~~~~~ + +The `debug` option is used to show verbose cURL output for a transfer. + +stream +~~~~~~ + +When using a static client, you can set the `stream` option to true to return a `Guzzle\Stream\Stream` object that can +be used to pull data from a stream as needed (rather than have cURL download the entire contents of a response to a +stream all at once). + +.. code-block:: php + + $stream = Guzzle::get('http://guzzlephp.org', array('stream' => true)); + while (!$stream->feof()) { + echo $stream->readLine(); + } + +Sending requests +---------------- + +Requests can be sent by calling the ``send()`` method of a Request object, but you can also send requests using the +``send()`` method of a Client. + +.. code-block:: php + + $request = $client->get('http://www.amazon.com'); + $response = $client->send($request); + +Sending requests in parallel +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Client's ``send()`` method accept a single ``Guzzle\Http\Message\RequestInterface`` object or an array of +RequestInterface objects. When an array is specified, the requests will be sent in parallel. + +Sending many HTTP requests serially (one at a time) can cause an unnecessary delay in a script's execution. Each +request must complete before a subsequent request can be sent. By sending requests in parallel, a pool of HTTP +requests can complete at the speed of the slowest request in the pool, significantly reducing the amount of time +needed to execute multiple HTTP requests. Guzzle provides a wrapper for the curl_multi functions in PHP. + +Here's an example of sending three requests in parallel using a client object: + +.. code-block:: php + + use Guzzle\Common\Exception\MultiTransferException; + + try { + $responses = $client->send(array( + $client->get('http://www.google.com/'), + $client->head('http://www.google.com/'), + $client->get('https://www.github.com/') + )); + } catch (MultiTransferException $e) { + + echo "The following exceptions were encountered:\n"; + foreach ($e as $exception) { + echo $exception->getMessage() . "\n"; + } + + echo "The following requests failed:\n"; + foreach ($e->getFailedRequests() as $request) { + echo $request . "\n\n"; + } + + echo "The following requests succeeded:\n"; + foreach ($e->getSuccessfulRequests() as $request) { + echo $request . "\n\n"; + } + } + +If the requests succeed, an array of ``Guzzle\Http\Message\Response`` objects are returned. A single request failure +will not cause the entire pool of requests to fail. Any exceptions thrown while transferring a pool of requests will +be aggregated into a ``Guzzle\Common\Exception\MultiTransferException`` exception. + +Plugins and events +------------------ + +Guzzle provides easy to use request plugins that add behavior to requests based on signal slot event notifications +powered by the +`Symfony2 Event Dispatcher component `_. Any +event listener or subscriber attached to a Client object will automatically be attached to each request created by the +client. + +Using the same cookie session for each request +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Attach a ``Guzzle\Plugin\Cookie\CookiePlugin`` to a client which will in turn add support for cookies to every request +created by a client, and each request will use the same cookie session: + +.. code-block:: php + + use Guzzle\Plugin\Cookie\CookiePlugin; + use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar; + + // Create a new cookie plugin + $cookiePlugin = new CookiePlugin(new ArrayCookieJar()); + + // Add the cookie plugin to the client + $client->addSubscriber($cookiePlugin); + +.. _client-events: + +Events emitted from a client +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``Guzzle\Http\Client`` object emits the following events: + ++------------------------------+--------------------------------------------+------------------------------------------+ +| Event name | Description | Event data | ++==============================+============================================+==========================================+ +| client.create_request | Called when a client creates a request | * client: The client | +| | | * request: The created request | ++------------------------------+--------------------------------------------+------------------------------------------+ + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Http\Client; + + $client = new Client(); + + // Add a listener that will echo out requests as they are created + $client->getEventDispatcher()->addListener('client.create_request', function (Event $e) { + echo 'Client object: ' . spl_object_hash($e['client']) . "\n"; + echo "Request object: {$e['request']}\n"; + }); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst new file mode 100644 index 000000000..823b0c022 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst @@ -0,0 +1,151 @@ +=========================== +Request and response bodies +=========================== + +`Entity body `_ is the term used for the body of an HTTP +message. The entity body of requests and responses is inherently a +`PHP stream `_ in Guzzle. The body of the request can be either a string or +a PHP stream which are converted into a ``Guzzle\Http\EntityBody`` object using its factory method. When using a +string, the entity body is stored in a `temp PHP stream `_. The use of +temp PHP streams helps to protect your application from running out of memory when sending or receiving large entity +bodies in your messages. When more than 2MB of data is stored in a temp stream, it automatically stores the data on +disk rather than in memory. + +EntityBody objects provide a great deal of functionality: compression, decompression, calculate the Content-MD5, +calculate the Content-Length (when the resource is repeatable), guessing the Content-Type, and more. Guzzle doesn't +need to load an entire entity body into a string when sending or retrieving data; entity bodies are streamed when +being uploaded and downloaded. + +Here's an example of gzip compressing a text file then sending the file to a URL: + +.. code-block:: php + + use Guzzle\Http\EntityBody; + + $body = EntityBody::factory(fopen('/path/to/file.txt', 'r+')); + echo $body->read(1024); + $body->seek(0, SEEK_END); + $body->write('foo'); + echo $body->ftell(); + $body->rewind(); + + // Send a request using the body + $response = $client->put('http://localhost:8080/uploads', null, $body)->send(); + +The body of the request can be specified in the ``Client::put()`` or ``Client::post()`` method, or, you can specify +the body of the request by calling the ``setBody()`` method of any +``Guzzle\Http\Message\EntityEnclosingRequestInterface`` object. + +Compression +----------- + +You can compress the contents of an EntityBody object using the ``compress()`` method. The compress method accepts a +filter that must match to one of the supported +`PHP stream filters `_ on your system (e.g. `zlib.deflate`, +``bzip2.compress``, etc). Compressing an entity body will stream the entire entity body through a stream compression +filter into a temporary PHP stream. You can uncompress an entity body using the ``uncompress()`` method and passing +the PHP stream filter to use when decompressing the stream (e.g. ``zlib.inflate``). + +.. code-block:: php + + use Guzzle\Http\EntityBody; + + $body = EntityBody::factory(fopen('/tmp/test.txt', 'r+')); + echo $body->getSize(); + // >>> 1048576 + + // Compress using the default zlib.deflate filter + $body->compress(); + echo $body->getSize(); + // >>> 314572 + + // Decompress the stream + $body->uncompress(); + echo $body->getSize(); + // >>> 1048576 + +Decorators +---------- + +Guzzle provides several EntityBody decorators that can be used to add functionality to an EntityBody at runtime. + +IoEmittingEntityBody +~~~~~~~~~~~~~~~~~~~~ + +This decorator will emit events when data is read from a stream or written to a stream. Add an event subscriber to the +entity body's ``body.read`` or ``body.write`` methods to receive notifications when data data is transferred. + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Http\EntityBody; + use Guzzle\Http\IoEmittingEntityBody; + + $original = EntityBody::factory(fopen('/tmp/test.txt', 'r+')); + $body = new IoEmittingEntityBody($original); + + // Listen for read events + $body->getEventDispatcher()->addListener('body.read', function (Event $e) { + // Grab data from the event + $entityBody = $e['body']; + // Amount of data retrieved from the body + $lengthOfData = $e['length']; + // The actual data that was read + $data = $e['read']; + }); + + // Listen for write events + $body->getEventDispatcher()->addListener('body.write', function (Event $e) { + // Grab data from the event + $entityBody = $e['body']; + // The data that was written + $data = $e['write']; + // The actual amount of data that was written + $data = $e['read']; + }); + +ReadLimitEntityBody +~~~~~~~~~~~~~~~~~~~ + +The ReadLimitEntityBody decorator can be used to transfer a subset or slice of an existing EntityBody object. This can +be useful for breaking a large file into smaller pieces to be sent in chunks (e.g. Amazon S3's multipart upload API). + +.. code-block:: php + + use Guzzle\Http\EntityBody; + use Guzzle\Http\ReadLimitEntityBody; + + $original = EntityBody::factory(fopen('/tmp/test.txt', 'r+')); + echo $original->getSize(); + // >>> 1048576 + + // Limit the size of the body to 1024 bytes and start reading from byte 2048 + $body = new ReadLimitEntityBody($original, 1024, 2048); + echo $body->getSize(); + // >>> 1024 + echo $body->ftell(); + // >>> 0 + +CachingEntityBody +~~~~~~~~~~~~~~~~~ + +The CachingEntityBody decorator is used to allow seeking over previously read bytes on non-seekable read streams. This +can be useful when transferring a non-seekable entity body fails due to needing to rewind the stream (for example, +resulting from a redirect). Data that is read from the remote stream will be buffered in a PHP temp stream so that +previously read bytes are cached first in memory, then on disk. + +.. code-block:: php + + use Guzzle\Http\EntityBody; + use Guzzle\Http\CachingEntityBody; + + $original = EntityBody::factory(fopen('http://www.google.com', 'r')); + $body = new CachingEntityBody($original); + + $body->read(1024); + echo $body->ftell(); + // >>> 1024 + + $body->seek(0); + echo $body->ftell(); + // >>> 0 diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/http-redirects.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/http-redirects.rst new file mode 100644 index 000000000..32ba26891 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/http-redirects.rst @@ -0,0 +1,99 @@ +============== +HTTP redirects +============== + +By default, Guzzle will automatically follow redirects using the non-RFC compliant implementation used by most web +browsers. This means that redirects for POST requests are followed by a GET request. You can force RFC compliance by +enabling the strict mode on a request's parameter object: + +.. code-block:: php + + // Set per request + $request = $client->post(); + $request->getParams()->set('redirect.strict', true); + + // You can set globally on a client so all requests use strict redirects + $client->getConfig()->set('request.params', array( + 'redirect.strict' => true + )); + +By default, Guzzle will redirect up to 5 times before throwing a ``Guzzle\Http\Exception\TooManyRedirectsException``. +You can raise or lower this value using the ``redirect.max`` parameter of a request object: + +.. code-block:: php + + $request->getParams()->set('redirect.max', 2); + +Redirect history +---------------- + +You can get the number of redirects of a request using the resulting response object's ``getRedirectCount()`` method. +Similar to cURL's ``effective_url`` property, Guzzle provides the effective URL, or the last redirect URL that returned +the request, in a response's ``getEffectiveUrl()`` method. + +When testing or debugging, it is often useful to see a history of redirects for a particular request. This can be +achieved using the HistoryPlugin. + +.. code-block:: php + + $request = $client->get('/'); + $history = new Guzzle\Plugin\History\HistoryPlugin(); + $request->addSubscriber($history); + $response = $request->send(); + + // Get the last redirect URL or the URL of the request that received + // this response + echo $response->getEffectiveUrl(); + + // Get the number of redirects + echo $response->getRedirectCount(); + + // Iterate over each sent request and response + foreach ($history->getAll() as $transaction) { + // Request object + echo $transaction['request']->getUrl() . "\n"; + // Response object + echo $transaction['response']->getEffectiveUrl() . "\n"; + } + + // Or, simply cast the HistoryPlugin to a string to view each request and response + echo $history; + +Disabling redirects +------------------- + +You can disable redirects on a client by passing a configuration option in the client's constructor: + +.. code-block:: php + + $client = new Client(null, array('redirect.disable' => true)); + +You can also disable redirects per request: + +.. code-block:: php + + $request = $client->get($url, array(), array('allow_redirects' => false)); + +Redirects and non-repeatable streams +------------------------------------ + +If you are redirected when sending data from a non-repeatable stream and some of the data has been read off of the +stream, then you will get a ``Guzzle\Http\Exception\CouldNotRewindStreamException``. You can get around this error by +adding a custom rewind method to the entity body object being sent in the request. + +.. code-block:: php + + $request = $client->post( + 'http://httpbin.com/redirect/2', + null, + fopen('http://httpbin.com/get', 'r') + ); + + // Add a custom function that can be used to rewind the stream + // (reopen in this example) + $request->getBody()->setRewindFunction(function ($body) { + $body->setStream(fopen('http://httpbin.com/get', 'r')); + return true; + ); + + $response = $client->send(); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/request.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/request.rst new file mode 100644 index 000000000..a8387a915 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/request.rst @@ -0,0 +1,667 @@ +===================== +Using Request objects +===================== + +HTTP request messages +--------------------- + +Request objects are all about building an HTTP message. Each part of an HTTP request message can be set individually +using methods on the request object or set in bulk using the ``setUrl()`` method. Here's the format of an HTTP request +with each part of the request referencing the method used to change it:: + + PUT(a) /path(b)?query=123(c) HTTP/1.1(d) + X-Header(e): header + Content-Length(e): 4 + + data(f) + ++-------------------------+---------------------------------------------------------------------------------+ +| a. **Method** | The request method can only be set when instantiating a request | ++-------------------------+---------------------------------------------------------------------------------+ +| b. **Path** | ``$request->setPath('/path');`` | ++-------------------------+---------------------------------------------------------------------------------+ +| c. **Query** | ``$request->getQuery()->set('query', '123');`` | ++-------------------------+---------------------------------------------------------------------------------+ +| d. **Protocol version** | ``$request->setProtocolVersion('1.1');`` | ++-------------------------+---------------------------------------------------------------------------------+ +| e. **Header** | ``$request->setHeader('X-Header', 'header');`` | ++-------------------------+---------------------------------------------------------------------------------+ +| f. **Entity Body** | ``$request->setBody('data'); // Only available with PUT, POST, PATCH, DELETE`` | ++-------------------------+---------------------------------------------------------------------------------+ + +Creating requests with a client +------------------------------- + +Client objects are responsible for creating HTTP request objects. + +GET requests +~~~~~~~~~~~~ + +`GET requests `_ are the most common form of HTTP +requests. When you visit a website in your browser, the HTML of the website is downloaded using a GET request. GET +requests are idempotent requests that are typically used to download content (an entity) identified by a request URL. + +.. code-block:: php + + use Guzzle\Http\Client; + + $client = new Client(); + + // Create a request that has a query string and an X-Foo header + $request = $client->get('http://www.amazon.com?a=1', array('X-Foo' => 'Bar')); + + // Send the request and get the response + $response = $request->send(); + +You can change where the body of a response is downloaded on any request using the +``$request->setResponseBody(string|EntityBodyInterface|resource)`` method of a request. You can also set the ``save_to`` +option of a request: + +.. code-block:: php + + // Send the response body to a file + $request = $client->get('http://test.com', array(), array('save_to' => '/path/to/file')); + + // Send the response body to an fopen resource + $request = $client->get('http://test.com', array(), array('save_to' => fopen('/path/to/file', 'w'))); + +HEAD requests +~~~~~~~~~~~~~ + +`HEAD requests `_ work exactly like GET requests except +that they do not actually download the response body (entity) of the response message. HEAD requests are useful for +retrieving meta information about an entity identified by a Request-URI. + +.. code-block:: php + + $client = new Guzzle\Http\Client(); + $request = $client->head('http://www.amazon.com'); + $response = $request->send(); + echo $response->getContentLength(); + // >>> Will output the Content-Length header value + +DELETE requests +~~~~~~~~~~~~~~~ + +A `DELETE method `_ requests that the origin server +delete the resource identified by the Request-URI. + +.. code-block:: php + + $client = new Guzzle\Http\Client(); + $request = $client->delete('http://example.com'); + $response = $request->send(); + +POST requests +~~~~~~~~~~~~~ + +While `POST requests `_ can be used for a number of +reasons, POST requests are often used when submitting HTML form data to a website. POST requests can include an entity +body in the HTTP request. + +POST requests in Guzzle are sent with an ``application/x-www-form-urlencoded`` Content-Type header if POST fields are +present but no files are being sent in the POST. If files are specified in the POST request, then the Content-Type +header will become ``multipart/form-data``. + +The ``post()`` method of a client object accepts four arguments: the URL, optional headers, post fields, and an array of +request options. To send files in the POST request, prepend the ``@`` symbol to the array value (just like you would if +you were using the PHP ``curl_setopt`` function). + +Here's how to create a multipart/form-data POST request containing files and fields: + +.. code-block:: php + + $request = $client->post('http://httpbin.org/post', array(), array( + 'custom_field' => 'my custom value', + 'file_field' => '@/path/to/file.xml' + )); + + $response = $request->send(); + +.. note:: + + Remember to **always** sanitize user input when sending POST requests: + + .. code-block:: php + + // Prevent users from accessing sensitive files by sanitizing input + $_POST = array('firstname' => '@/etc/passwd'); + $request = $client->post('http://www.example.com', array(), array ( + 'firstname' => str_replace('@', '', $_POST['firstname']) + )); + +You can alternatively build up the contents of a POST request. + +.. code-block:: php + + $request = $client->post('http://httpbin.org/post') + ->setPostField('custom_field', 'my custom value') + ->addPostFile('file', '/path/to/file.xml'); + + $response = $request->send(); + +Raw POST data +^^^^^^^^^^^^^ + +POST requests can also contain raw POST data that is not related to HTML forms. + +.. code-block:: php + + $request = $client->post('http://httpbin.org/post', array(), 'this is the body'); + $response = $request->send(); + +You can set the body of POST request using the ``setBody()`` method of the +``Guzzle\Http\Message\EntityEnclosingRequest`` object. This method accepts a string, a resource returned from +``fopen``, or a ``Guzzle\Http\EntityBodyInterface`` object. + +.. code-block:: php + + $request = $client->post('http://httpbin.org/post'); + // Set the body of the POST to stream the contents of /path/to/large_body.txt + $request->setBody(fopen('/path/to/large_body.txt', 'r')); + $response = $request->send(); + +PUT requests +~~~~~~~~~~~~ + +The `PUT method `_ requests that the enclosed entity be +stored under the supplied Request-URI. PUT requests are similar to POST requests in that they both can send an entity +body in the request message. + +The body of a PUT request (any any ``Guzzle\Http\Message\EntityEnclosingRequestInterface`` object) is always stored as +a ``Guzzle\Http\Message\EntityBodyInterface`` object. This allows a great deal of flexibility when sending data to a +remote server. For example, you can stream the contents of a stream returned by fopen, stream the contents of a +callback function, or simply send a string of data. + +.. code-block:: php + + $request = $client->put('http://httpbin.org/put', array(), 'this is the body'); + $response = $request->send(); + +Just like with POST, PATH, and DELETE requests, you can set the body of a PUT request using the ``setBody()`` method. + +.. code-block:: php + + $request = $client->put('http://httpbin.org/put'); + $request->setBody(fopen('/path/to/large_body.txt', 'r')); + $response = $request->send(); + +PATCH requests +~~~~~~~~~~~~~~ + +`PATCH requests `_ are used to modify a resource. + +.. code-block:: php + + $request = $client->patch('http://httpbin.org', array(), 'this is the body'); + $response = $request->send(); + +OPTIONS requests +~~~~~~~~~~~~~~~~ + +The `OPTIONS method `_ represents a request for +information about the communication options available on the request/response chain identified by the Request-URI. + +.. code-block:: php + + $request = $client->options('http://httpbin.org'); + $response = $request->send(); + + // Check if the PUT method is supported by this resource + var_export($response->isMethodAllows('PUT')); + +Custom requests +~~~~~~~~~~~~~~~ + +You can create custom HTTP requests that use non-standard HTTP methods using the ``createRequest()`` method of a +client object. + +.. code-block:: php + + $request = $client->createRequest('COPY', 'http://example.com/foo', array( + 'Destination' => 'http://example.com/bar', + 'Overwrite' => 'T' + )); + $response = $request->send(); + +Query string parameters +----------------------- + +Query string parameters of a request are owned by a request's ``Guzzle\Http\Query`` object that is accessible by +calling ``$request->getQuery()``. The Query class extends from ``Guzzle\Common\Collection`` and allows you to set one +or more query string parameters as key value pairs. You can set a parameter on a Query object using the +``set($key, $value)`` method or access the query string object like an associative array. Any previously specified +value for a key will be overwritten when using ``set()``. Use ``add($key, $value)`` to add a value to query string +object, and in the event of a collision with an existing value at a specific key, the value will be converted to an +array that contains all of the previously set values. + +.. code-block:: php + + $request = new Guzzle\Http\Message\Request('GET', 'http://www.example.com?foo=bar&abc=123'); + + $query = $request->getQuery(); + echo "{$query}\n"; + // >>> foo=bar&abc=123 + + $query->remove('abc'); + echo "{$query}\n"; + // >>> foo=bar + + $query->set('foo', 'baz'); + echo "{$query}\n"; + // >>> foo=baz + + $query->add('foo', 'bar'); + echo "{$query}\n"; + // >>> foo%5B0%5D=baz&foo%5B1%5D=bar + +Whoah! What happened there? When ``foo=bar`` was added to the existing ``foo=baz`` query string parameter, the +aggregator associated with the Query object was used to help convert multi-value query string parameters into a string. +Let's disable URL-encoding to better see what's happening. + +.. code-block:: php + + $query->useUrlEncoding(false); + echo "{$query}\n"; + // >>> foo[0]=baz&foo[1]=bar + +.. note:: + + URL encoding can be disabled by passing false, enabled by passing true, set to use RFC 1738 by passing + ``Query::FORM_URLENCODED`` (internally uses PHP's ``urlencode`` function), or set to RFC 3986 by passing + ``Query::RFC_3986`` (this is the default and internally uses PHP's ``rawurlencode`` function). + +As you can see, the multiple values were converted into query string parameters following the default PHP convention of +adding numerically indexed square bracket suffixes to each key (``foo[0]=baz&foo[1]=bar``). The strategy used to convert +multi-value parameters into a string can be customized using the ``setAggregator()`` method of the Query class. Guzzle +ships with the following query string aggregators by default: + +1. ``Guzzle\Http\QueryAggregator\PhpAggregator``: Aggregates using PHP style brackets (e.g. ``foo[0]=baz&foo[1]=bar``) +2. ``Guzzle\Http\QueryAggregator\DuplicateAggregator``: Performs no aggregation and allows for key value pairs to be + repeated in a URL (e.g. ``foo=baz&foo=bar``) +3. ``Guzzle\Http\QueryAggregator\CommaAggregator``: Aggregates using commas (e.g. ``foo=baz,bar``) + +.. _http-message-headers: + +HTTP Message Headers +-------------------- + +HTTP message headers are case insensitive, multiple occurrences of any header can be present in an HTTP message +(whether it's valid or not), and some servers require specific casing of particular headers. Because of this, request +and response headers are stored in ``Guzzle\Http\Message\Header`` objects. The Header object can be cast as a string, +counted, or iterated to retrieve each value from the header. Casting a Header object to a string will return all of +the header values concatenated together using a glue string (typically ", "). + +A request (and response) object have several methods that allow you to retrieve and modify headers. + +* ``getHeaders()``: Get all of the headers of a message as a ``Guzzle\Http\Message\Header\HeaderCollection`` object. +* ``getHeader($header)``: Get a specific header from a message. If the header exists, you'll get a + ``Guzzle\Http\Message\Header`` object. If the header does not exist, this methods returns ``null``. +* ``hasHeader($header)``: Returns true or false based on if the message has a particular header. +* ``setHeader($header, $value)``: Set a header value and overwrite any previously set value for this header. +* ``addHeader($header, $value)``: Add a header with a particular name. If a previous value was already set by the same, + then the header will contain multiple values. +* ``removeHeader($header)``: Remove a header by name from the message. + +.. code-block:: php + + $request = new Request('GET', 'http://httpbin.com/cookies'); + // addHeader will set and append to any existing header values + $request->addHeader('Foo', 'bar'); + $request->addHeader('foo', 'baz'); + // setHeader overwrites any existing values + $request->setHeader('Test', '123'); + + // Request headers can be cast as a string + echo $request->getHeader('Foo'); + // >>> bar, baz + echo $request->getHeader('Test'); + // >>> 123 + + // You can count the number of headers of a particular case insensitive name + echo count($request->getHeader('foO')); + // >>> 2 + + // You can iterate over Header objects + foreach ($request->getHeader('foo') as $header) { + echo $header . "\n"; + } + + // You can get all of the request headers as a Guzzle\Http\Message\Header\HeaderCollection object + $headers = $request->getHeaders(); + + // Missing headers return NULL + var_export($request->getHeader('Missing')); + // >>> null + + // You can see all of the different variations of a header by calling raw() on the Header + var_export($request->getHeader('foo')->raw()); + +Setting the body of a request +----------------------------- + +Requests that can send a body (e.g. PUT, POST, DELETE, PATCH) are instances of +``Guzzle\Http\Message\EntityEnclosingRequestInterface``. Entity enclosing requests contain several methods that allow +you to specify the body to send with a request. + +Use the ``setBody()`` method of a request to set the body that will be sent with a request. This method accepts a +string, a resource returned by ``fopen()``, an array, or an instance of ``Guzzle\Http\EntityBodyInterface``. The body +will then be streamed from the underlying ``EntityBodyInterface`` object owned by the request. When setting the body +of the request, you can optionally specify a Content-Type header and whether or not to force the request to use +chunked Transfer-Encoding. + +.. code-block:: php + + $request = $client->put('/user.json'); + $request->setBody('{"foo":"baz"}', 'application/json'); + +Content-Type header +~~~~~~~~~~~~~~~~~~~ + +Guzzle will automatically add a Content-Type header to a request if the Content-Type can be guessed based on the file +extension of the payload being sent or the file extension present in the path of a request. + +.. code-block:: php + + $request = $client->put('/user.json', array(), '{"foo":"bar"}'); + // The Content-Type was guessed based on the path of the request + echo $request->getHeader('Content-Type'); + // >>> application/json + + $request = $client->put('/user.json'); + $request->setBody(fopen('/tmp/user_data.json', 'r')); + // The Content-Type was guessed based on the path of the entity body + echo $request->getHeader('Content-Type'); + // >>> application/json + +Transfer-Encoding: chunked header +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When sending HTTP requests that contain a payload, you must let the remote server know how to determine when the entire +message has been sent. This usually is done by supplying a ``Content-Length`` header that tells the origin server the +size of the body that is to be sent. In some cases, the size of the payload being sent in a request cannot be known +before initiating the transfer. In these cases (when using HTTP/1.1), you can use the ``Transfer-Encoding: chunked`` +header. + +If the Content-Length cannot be determined (i.e. using a PHP ``http://`` stream), then Guzzle will automatically add +the ``Transfer-Encoding: chunked`` header to the request. + +.. code-block:: php + + $request = $client->put('/user.json'); + $request->setBody(fopen('http://httpbin.org/get', 'r')); + + // The Content-Length could not be determined + echo $request->getHeader('Transfer-Encoding'); + // >>> chunked + +See :doc:`/http-client/entity-bodies` for more information on entity bodies. + +Expect: 100-Continue header +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Expect: 100-Continue`` header is used to help a client prevent sending a large payload to a server that will +reject the request. This allows clients to fail fast rather than waste bandwidth sending an erroneous payload. Guzzle +will automatically add the ``Expect: 100-Continue`` header to a request when the size of the payload exceeds 1MB or if +the body of the request is not seekable (this helps to prevent errors when a non-seekable body request is redirected). + +.. note:: + + If you find that your larger requests are taking too long to complete, you should first check if the + ``Expect: 100-Continue`` header is being sent with the request. Some servers do not respond well to this header, + which causes cURL to sleep for `1 second `_. + +POST fields and files +~~~~~~~~~~~~~~~~~~~~~ + +Any entity enclosing request can send POST style fields and files. This includes POST, PUT, PATCH, and DELETE requests. +Any request that has set POST fields or files will use cURL's POST message functionality. + +.. code-block:: php + + $request = $client->post('/post'); + // Set an overwrite any previously specified value + $request->setPostField('foo', 'bar'); + // Append a value to any existing values + $request->getPostFields()->add('foo', 'baz'); + // Remove a POST field by name + $request->removePostField('fizz'); + + // Add a file to upload (forces multipart/form-data) + $request->addPostFile('my_file', '/path/to/file', 'plain/text'); + // Remove a POST file by POST key name + $request->removePostFile('my_other_file'); + +.. tip:: + + Adding a large number of POST fields to a POST request is faster if you use the ``addPostFields()`` method so that + you can add and process multiple fields with a single call. Adding multiple POST files is also faster using + ``addPostFiles()``. + +Working with cookies +-------------------- + +Cookies can be modified and retrieved from a request using the following methods: + +.. code-block:: php + + $request->addCookie($name, $value); + $request->removeCookie($name); + $value = $request->getCookie($name); + $valueArray = $request->getCookies(); + +Use the :doc:`cookie plugin ` if you need to reuse cookies between requests. + +.. _request-set-response-body: + +Changing where a response is downloaded +---------------------------------------- + +When a request is sent, the body of the response will be stored in a PHP temp stream by default. You can change the +location in which the response will be downloaded using ``$request->setResponseBody($body)`` or the ``save_to`` request +option. This can be useful for downloading the contents of a URL to a specific file. + +Here's an example of using request options: + +.. code-block:: php + + $request = $this->client->get('http://example.com/large.mov', array(), array( + 'save_to' => '/tmp/large_file.mov' + )); + $request->send(); + var_export(file_exists('/tmp/large_file.mov')); + // >>> true + +Here's an example of using ``setResponseBody()``: + +.. code-block:: php + + $body = fopen('/tmp/large_file.mov', 'w'); + $request = $this->client->get('http://example.com/large.mov'); + $request->setResponseBody($body); + + // You can more easily specify the name of a file to save the contents + // of the response to by passing a string to ``setResponseBody()``. + + $request = $this->client->get('http://example.com/large.mov'); + $request->setResponseBody('/tmp/large_file.mov'); + +Custom cURL options +------------------- + +Most of the functionality implemented in the libcurl bindings has been simplified and abstracted by Guzzle. Developers +who need access to `cURL specific functionality `_ can still add cURL handle +specific behavior to Guzzle HTTP requests by modifying the cURL options collection of a request: + +.. code-block:: php + + $request->getCurlOptions()->set(CURLOPT_LOW_SPEED_LIMIT, 200); + +Other special options that can be set in the ``curl.options`` array include: + ++-------------------------+---------------------------------------------------------------------------------+ +| debug | Adds verbose cURL output to a temp stream owned by the cURL handle object | ++-------------------------+---------------------------------------------------------------------------------+ +| progress | Instructs cURL to emit events when IO events occur. This allows you to be | +| | notified when bytes are transferred over the wire by subscribing to a request's | +| | ``curl.callback.read``, ``curl.callback.write``, and ``curl.callback.progress`` | +| | events. | ++-------------------------+---------------------------------------------------------------------------------+ + +Request options +--------------- + +Requests options can be specified when creating a request or in the ``request.options`` parameter of a client. These +options can control various aspects of a request including: headers to send, query string data, where the response +should be downloaded, proxies, auth, etc. + +.. code-block:: php + + $request = $client->get($url, $headers, array('proxy' => 'http://proxy.com')); + +See :ref:`Request options ` for more information. + +Working with errors +------------------- + +HTTP errors +~~~~~~~~~~~ + +Requests that receive a 4xx or 5xx response will throw a ``Guzzle\Http\Exception\BadResponseException``. More +specifically, 4xx errors throw a ``Guzzle\Http\Exception\ClientErrorResponseException``, and 5xx errors throw a +``Guzzle\Http\Exception\ServerErrorResponseException``. You can catch the specific exceptions or just catch the +BadResponseException to deal with either type of error. Here's an example of catching a generic BadResponseException: + +.. code-block:: php + + try { + $response = $client->get('/not_found.xml')->send(); + } catch (Guzzle\Http\Exception\BadResponseException $e) { + echo 'Uh oh! ' . $e->getMessage(); + echo 'HTTP request URL: ' . $e->getRequest()->getUrl() . "\n"; + echo 'HTTP request: ' . $e->getRequest() . "\n"; + echo 'HTTP response status: ' . $e->getResponse()->getStatusCode() . "\n"; + echo 'HTTP response: ' . $e->getResponse() . "\n"; + } + +Throwing an exception when a 4xx or 5xx response is encountered is the default behavior of Guzzle requests. This +behavior can be overridden by adding an event listener with a higher priority than -255 that stops event propagation. +You can subscribe to ``request.error`` to receive notifications any time an unsuccessful response is received. + +You can change the response that will be associated with the request by calling ``setResponse()`` on the +``$event['request']`` object passed into your listener, or by changing the ``$event['response']`` value of the +``Guzzle\Common\Event`` object that is passed to your listener. Transparently changing the response associated with a +request by modifying the event allows you to retry failed requests without complicating the code that uses the client. +This might be useful for sending requests to a web service that has expiring auth tokens. When a response shows that +your token has expired, you can get a new token, retry the request with the new token, and return the successful +response to the user. + +Here's an example of retrying a request using updated authorization credentials when a 401 response is received, +overriding the response of the original request with the new response, and still allowing the default exception +behavior to be called when other non-200 response status codes are encountered: + +.. code-block:: php + + // Add custom error handling to any request created by this client + $client->getEventDispatcher()->addListener('request.error', function(Event $event) { + + if ($event['response']->getStatusCode() == 401) { + + $newRequest = $event['request']->clone(); + $newRequest->setHeader('X-Auth-Header', MyApplication::getNewAuthToken()); + $newResponse = $newRequest->send(); + + // Set the response object of the request without firing more events + $event['response'] = $newResponse; + + // You can also change the response and fire the normal chain of + // events by calling $event['request']->setResponse($newResponse); + + // Stop other events from firing when you override 401 responses + $event->stopPropagation(); + } + + }); + +cURL errors +~~~~~~~~~~~ + +Connection problems and cURL specific errors can also occur when transferring requests using Guzzle. When Guzzle +encounters cURL specific errors while transferring a single request, a ``Guzzle\Http\Exception\CurlException`` is +thrown with an informative error message and access to the cURL error message. + +A ``Guzzle\Http\Exception\MultiTransferException`` exception is thrown when a cURL specific error occurs while +transferring multiple requests in parallel. You can then iterate over all of the exceptions encountered during the +transfer. + +Plugins and events +------------------ + +Guzzle request objects expose various events that allow you to hook in custom logic. A request object owns a +``Symfony\Component\EventDispatcher\EventDispatcher`` object that can be accessed by calling +``$request->getEventDispatcher()``. You can use the event dispatcher to add listeners (a simple callback function) or +event subscribers (classes that listen to specific events of a dispatcher). You can add event subscribers to a request +directly by just calling ``$request->addSubscriber($mySubscriber);``. + +.. _request-events: + +Events emitted from a request +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``Guzzle\Http\Message\Request`` and ``Guzzle\Http\Message\EntityEnclosingRequest`` object emit the following events: + ++------------------------------+--------------------------------------------+------------------------------------------+ +| Event name | Description | Event data | ++==============================+============================================+==========================================+ +| request.before_send | About to send request | * request: Request to be sent | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.sent | Sent the request | * request: Request that was sent | +| | | * response: Received response | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.complete | Completed a full HTTP transaction | * request: Request that was sent | +| | | * response: Received response | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.success | Completed a successful request | * request: Request that was sent | +| | | * response: Received response | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.error | Completed an unsuccessful request | * request: Request that was sent | +| | | * response: Received response | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.exception | An unsuccessful response was | * request: Request | +| | received. | * response: Received response | +| | | * exception: BadResponseException | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.receive.status_line | Received the start of a response | * line: Full response start line | +| | | * status_code: Status code | +| | | * reason_phrase: Reason phrase | +| | | * previous_response: (e.g. redirect) | ++------------------------------+--------------------------------------------+------------------------------------------+ +| curl.callback.progress | cURL progress event (only dispatched when | * handle: CurlHandle | +| | ``emit_io`` is set on a request's curl | * download_size: Total download size | +| | options) | * downloaded: Bytes downloaded | +| | | * upload_size: Total upload bytes | +| | | * uploaded: Bytes uploaded | ++------------------------------+--------------------------------------------+------------------------------------------+ +| curl.callback.write | cURL event called when data is written to | * request: Request | +| | an outgoing stream | * write: Data being written | ++------------------------------+--------------------------------------------+------------------------------------------+ +| curl.callback.read | cURL event called when data is written to | * request: Request | +| | an incoming stream | * read: Data being read | ++------------------------------+--------------------------------------------+------------------------------------------+ + +Creating a request event listener +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here's an example that listens to the ``request.complete`` event of a request and prints the request and response. + +.. code-block:: php + + use Guzzle\Common\Event; + + $request = $client->get('http://www.google.com'); + + // Echo out the response that was received + $request->getEventDispatcher()->addListener('request.complete', function (Event $e) { + echo $e['request'] . "\n\n"; + echo $e['response']; + }); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/response.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/response.rst new file mode 100644 index 000000000..ba487316f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/response.rst @@ -0,0 +1,141 @@ +====================== +Using Response objects +====================== + +Sending a request will return a ``Guzzle\Http\Message\Response`` object. You can view the raw HTTP response message by +casting the Response object to a string. Casting the response to a string will return the entity body of the response +as a string too, so this might be an expensive operation if the entity body is stored in a file or network stream. If +you only want to see the response headers, you can call ``getRawHeaders()``. + +Response status line +-------------------- + +The different parts of a response's `status line `_ +(the first line of the response HTTP message) are easily retrievable. + +.. code-block:: php + + $response = $client->get('http://www.amazon.com')->send(); + + echo $response->getStatusCode(); // >>> 200 + echo $response->getReasonPhrase(); // >>> OK + echo $response->getProtocol(); // >>> HTTP + echo $response->getProtocolVersion(); // >>> 1.1 + +You can determine the type of the response using several helper methods: + +.. code-block:: php + + $response->isSuccessful(); // true + $response->isInformational(); + $response->isRedirect(); + $response->isClientError(); + $response->isServerError(); + +Response headers +---------------- + +The Response object contains helper methods for retrieving common response headers. These helper methods normalize the +variations of HTTP response headers. + +.. code-block:: php + + $response->getCacheControl(); + $response->getContentType(); + $response->getContentLength(); + $response->getContentEncoding(); + $response->getContentMd5(); + $response->getEtag(); + // etc... There are methods for every known response header + +You can interact with the Response headers using the same exact methods used to interact with Request headers. See +:ref:`http-message-headers` for more information. + +.. code-block:: php + + echo $response->getHeader('Content-Type'); + echo $response->getHeader('Content-Length'); + echo $response->getHeaders()['Content-Type']; // PHP 5.4 + +Response body +------------- + +The entity body object of a response can be retrieved by calling ``$response->getBody()``. The response EntityBody can +be cast to a string, or you can pass ``true`` to this method to retrieve the body as a string. + +.. code-block:: php + + $request = $client->get('http://www.amazon.com'); + $response = $request->send(); + echo $response->getBody(); + +See :doc:`/http-client/entity-bodies` for more information on entity bodies. + +JSON Responses +~~~~~~~~~~~~~~ + +You can easily parse and use a JSON response as an array using the ``json()`` method of a response. This method will +always return an array if the response is valid JSON or if the response body is empty. You will get an exception if you +call this method and the response is not valid JSON. + +.. code-block:: php + + $data = $response->json(); + echo gettype($data); + // >>> array + +XML Responses +~~~~~~~~~~~~~ + +You can easily parse and use a XML response as SimpleXMLElement object using the ``xml()`` method of a response. This +method will always return a SimpleXMLElement object if the response is valid XML or if the response body is empty. You +will get an exception if you call this method and the response is not valid XML. + +.. code-block:: php + + $xml = $response->xml(); + echo $xml->foo; + // >>> Bar! + +Streaming responses +------------------- + +Some web services provide streaming APIs that allow a client to keep a HTTP request open for an extended period of +time while polling and reading. Guzzle provides a simple way to convert HTTP request messages into +``Guzzle\Stream\Stream`` objects so that you can send the initial headers of a request, read the response headers, and +pull in the response body manually as needed. + +Here's an example using the Twitter Streaming API to track the keyword "bieber": + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Stream\PhpStreamRequestFactory; + + $client = new Client('https://stream.twitter.com/1'); + + $request = $client->post('statuses/filter.json', null, array( + 'track' => 'bieber' + )); + + $request->setAuth('myusername', 'mypassword'); + + $factory = new PhpStreamRequestFactory(); + $stream = $factory->fromRequest($request); + + // Read until the stream is closed + while (!$stream->feof()) { + // Read a line from the stream + $line = $stream->readLine(); + // JSON decode the line of data + $data = json_decode($line, true); + } + +You can use the ``stream`` request option when using a static client to more easily create a streaming response. + +.. code-block:: php + + $stream = Guzzle::get('http://guzzlephp.org', array('stream' => true)); + while (!$stream->feof()) { + echo $stream->readLine(); + } diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/uri-templates.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/uri-templates.rst new file mode 100644 index 000000000..c18ac3e8d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/http-client/uri-templates.rst @@ -0,0 +1,52 @@ +============= +URI templates +============= + +The ``$uri`` passed to one of the client's request creational methods or the base URL of a client can utilize URI +templates. Guzzle supports the entire `URI templates RFC `_. URI templates add a +special syntax to URIs that replace template place holders with user defined variables. + +Every request created by a Guzzle HTTP client passes through a URI template so that URI template expressions are +automatically expanded: + +.. code-block:: php + + $client = new Guzzle\Http\Client('https://example.com/', array('a' => 'hi')); + $request = $client->get('/{a}'); + +Because of URI template expansion, the URL of the above request will become ``https://example.com/hi``. Notice that +the template was expanded using configuration variables of the client. You can pass in custom URI template variables +by passing the URI of your request as an array where the first index of the array is the URI template and the second +index of the array are template variables that are merged into the client's configuration variables. + +.. code-block:: php + + $request = $client->get(array('/test{?a,b}', array('b' => 'there'))); + +The URL for this request will become ``https://test.com?a=hi&b=there``. URI templates aren't limited to just simple +variable replacements; URI templates can provide an enormous amount of flexibility when creating request URIs. + +.. code-block:: php + + $request = $client->get(array('http://example.com{+path}{/segments*}{?query,data*}', array( + 'path' => '/foo/bar', + 'segments' => array('one', 'two'), + 'query' => 'test', + 'data' => array( + 'more' => 'value' + ) + ))); + +The resulting URL would become ``http://example.com/foo/bar/one/two?query=test&more=value``. + +By default, URI template expressions are enclosed in an opening and closing brace (e.g. ``{var}``). If you are working +with a web service that actually uses braces (e.g. Solr), then you can specify a custom regular expression to use to +match URI template expressions. + +.. code-block:: php + + $client->getUriTemplate()->setRegex('/\<\$(.+)\>/'); + $client->get('/<$a>'); + +You can learn about all of the different features of URI templates by reading the +`URI templates RFC `_. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/index.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/index.rst new file mode 100644 index 000000000..f76f3bbe6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/index.rst @@ -0,0 +1,5 @@ +.. title:: Guzzle | PHP HTTP client and framework for consuming RESTful web services +.. toctree:: + :hidden: + + docs.rst diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst new file mode 100644 index 000000000..a5c7fd33f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst @@ -0,0 +1,97 @@ +================ +Guzzle iterators +================ + +Guzzle provides several SPL iterators that can be used with other SPL iterators, including Guzzle resource iterators. +Guzzle's ``guzzle/iterator`` component can also be used independently of the rest of Guzzle through Packagist and +Composer: https://packagist.org/packages/guzzle/iterator + +ChunkedIterator +--------------- + +Pulls out multiple values from an inner iterator and yields and array of values for each outer iteration -- essentially +pulling out chunks of values from the inner iterator. + +.. code-block:: php + + use Guzzle\Iterator\ChunkedIterator; + + $inner = new ArrayIterator(range(0, 8)); + $chunkedIterator = new ChunkedIterator($inner, 2); + + foreach ($chunkedIterator as $chunk) { + echo implode(', ', $chunk) . "\n"; + } + + // >>> 0, 1 + // >>> 2, 3 + // >>> 4, 5 + // >>> 6, 7 + // >>> 8 + +FilterIterator +-------------- + +This iterator is used to filter values out of the inner iterator. This iterator can be used when PHP 5.4's +CallbackFilterIterator is not available. + +.. code-block:: php + + use Guzzle\Iterator\FilterIterator; + + $inner = new ArrayIterator(range(1, 10)); + $filterIterator = new FilterIterator($inner, function ($value) { + return $value % 2; + }); + + foreach ($filterIterator as $value) { + echo $value . "\n"; + } + + // >>> 2 + // >>> 4 + // >>> 6 + // >>> 8 + // >>> 10 + +MapIterator +----------- + +This iterator modifies the values of the inner iterator before yielding. + +.. code-block:: php + + use Guzzle\Iterator\MapIterator; + + $inner = new ArrayIterator(range(0, 3)); + + $mapIterator = new MapIterator($inner, function ($value) { + return $value * 10; + }); + + foreach ($mapIterator as $value) { + echo $value . "\n"; + } + + // >>> 0 + // >>> 10 + // >>> 20 + // >>> 30 + +MethodProxyIterator +------------------- + +This decorator is useful when you need to expose a specific method from an inner iterator that might be wrapper +by one or more iterator decorators. This decorator proxies missing method calls to each inner iterator until one +of the inner iterators can fulfill the call. + +.. code-block:: php + + use Guzzle\Iterator\MethodProxyIterator; + + $inner = new \ArrayIterator(); + $proxy = new MethodProxyIterator($inner); + + // Proxy method calls to the ArrayIterator + $proxy->append('a'); + $proxy->append('b'); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst new file mode 100644 index 000000000..ce0bee59f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst @@ -0,0 +1,149 @@ +================== +Resource iterators +================== + +Web services often implement pagination in their responses which requires the end-user to issue a series of consecutive +requests in order to fetch all of the data they asked for. Users of your web service client should not be responsible +for implementing the logic involved in iterating through pages of results. Guzzle provides a simple resource iterator +foundation to make it easier on web service client developers to offer a useful abstraction layer. + +Getting an iterator from a client +--------------------------------- + + ResourceIteratorInterface Guzzle\Service\Client::getIterator($command [, array $commandOptions, array $iteratorOptions ]) + +The ``getIterator`` method of a ``Guzzle\Service\ClientInterface`` object provides a convenient interface for +instantiating a resource iterator for a specific command. This method implicitly uses a +``Guzzle\Service\Resource\ResourceIteratorFactoryInterface`` object to create resource iterators. Pass an +instantiated command object or the name of a command in the first argument. When passing the name of a command, the +command factory of the client will create the command by name using the ``$commandOptions`` array. The third argument +may be used to pass an array of options to the constructor of the instantiated ``ResourceIteratorInterface`` object. + +.. code-block:: php + + $iterator = $client->getIterator('get_users'); + + foreach ($iterator as $user) { + echo $user['name'] . ' age ' . $user['age'] . PHP_EOL; + } + +The above code sample might execute a single request or a thousand requests. As a consumer of a web service, I don't +care. I just want to iterate over all of the users. + +Iterator options +~~~~~~~~~~~~~~~~ + +The two universal options that iterators should support are ``limit`` and ``page_size``. Using the ``limit`` option +tells the resource iterator to attempt to limit the total number of iterated resources to a specific amount. Keep in +mind that this is not always possible due to limitations that may be inherent to a web service. The ``page_size`` +option is used to tell a resource iterator how many resources to request per page of results. Much like the ``limit`` +option, you can not rely on getting back exactly the number of resources your specify in the ``page_size`` option. + +.. note:: + + The ``limit`` and ``page_size`` options can also be specified on an iterator using the ``setLimit($limit)`` and + ``setPageSize($pageSize)`` methods. + +Resolving iterator class names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default resource iterator factory of a client object expects that your iterators are stored under the ``Model`` +folder of your client and that an iterator is names after the CamelCase name of a command followed by the word +"Iterator". For example, if you wanted to create an iterator for the ``get_users`` command, then your iterator class +would be ``Model\GetUsersIterator`` and would be stored in ``Model/GetUsersIterator.php``. + +Creating an iterator +-------------------- + +While not required, resource iterators in Guzzle typically iterate using a ``Guzzle\Service\Command\CommandInterface`` +object. ``Guzzle\Service\Resource\ResourceIterator``, the default iterator implementation that you should extend, +accepts a command object and array of iterator options in its constructor. The command object passed to the resource +iterator is expected to be ready to execute and not previously executed. The resource iterator keeps a reference of +this command and clones the original command each time a subsequent request needs to be made to fetch more data. + +Implement the sendRequest method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The most important thing (and usually the only thing) you need to do when creating a resource iterator is to implement +the ``sendRequest()`` method of the resource iterator. The ``sendRequest()`` method is called when you begin +iterating or if there are no resources left to iterate and it you expect to retrieve more resources by making a +subsequent request. The ``$this->command`` property of the resource iterator is updated with a cloned copy of the +original command object passed into the constructor of the iterator. Use this command object to issue your subsequent +requests. + +The ``sendRequest()`` method must return an array of the resources you retrieved from making the subsequent call. +Returning an empty array will stop the iteration. If you suspect that your web service client will occasionally return +an empty result set but still requires further iteration, then you must implement a sort of loop in your +``sendRequest()`` method that will continue to issue subsequent requests until your reach the end of the paginated +result set or until additional resources are retrieved from the web service. + +Update the nextToken property +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Beyond fetching more results, the ``sendRequest()`` method is responsible for updating the ``$this->nextToken`` +property of the iterator. Setting this property to anything other than null tells the iterator that issuing a +subsequent request using the nextToken value will probably return more results. You must continually update this +value in your ``sendRequest()`` method as each response is received from the web service. + +Example iterator +---------------- + +Let's say you want to implement a resource iterator for the ``get_users`` command of your web service. The +``get_users`` command receives a response that contains a list of users, and if there are more pages of results to +retrieve, returns a value called ``next_user``. This return value is known as the **next token** and should be used to +issue subsequent requests. + +Assume the response to a ``get_users`` command returns JSON data that looks like this: + +.. code-block:: javascript + + { + "users": [ + { "name": "Craig Johnson", "age": 10 }, + { "name": "Tom Barker", "age": 20 }, + { "name": "Bob Mitchell", "age": 74 } + ], + "next_user": "Michael Dowling" + } + +Assume that because there is a ``next_user`` value, there will be more users if a subsequent request is issued. If the +``next_user`` value is missing or null, then we know there are no more results to fetch. Let's implement a resource +iterator for this command. + +.. code-block:: php + + namespace MyService\Model; + + use Guzzle\Service\Resource\ResourceIterator; + + /** + * Iterate over a get_users command + */ + class GetUsersIterator extends ResourceIterator + { + protected function sendRequest() + { + // If a next token is set, then add it to the command + if ($this->nextToken) { + $this->command->set('next_user', $this->nextToken); + } + + // Execute the command and parse the result + $result = $this->command->execute(); + + // Parse the next token + $this->nextToken = isset($result['next_user']) ? $result['next_user'] : false; + + return $result['users']; + } + } + +As you can see, it's pretty simple to implement an iterator. There are a few things that you should notice from this +example: + +1. You do not need to create a new command in the ``sendRequest()`` method. A new command object is cloned from the + original command passed into the constructor of the iterator before the ``sendRequest()`` method is called. + Remember that the resource iterator expects a command that has not been executed. +2. When the ``sendRequest()`` method is first called, you will not have a ``$this->nextToken`` value, so always check + before setting it on a command. Notice that the next token is being updated each time a request is sent. +3. After fetching more resources from the service, always return an array of resources. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/async-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/async-plugin.rst new file mode 100644 index 000000000..9bd8f4251 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/async-plugin.rst @@ -0,0 +1,18 @@ +============ +Async plugin +============ + +The AsyncPlugin allows you to send requests that do not wait on a response. This is handled through cURL by utilizing +the progress event. When a request has sent all of its data to the remote server, Guzzle adds a 1ms timeout on the +request and instructs cURL to not download the body of the response. The async plugin then catches the exception and +adds a mock response to the request, along with an X-Guzzle-Async header to let you know that the response was not +fully downloaded. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Async\AsyncPlugin; + + $client = new Client('http://www.example.com'); + $client->addSubscriber(new AsyncPlugin()); + $response = $client->get()->send(); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst new file mode 100644 index 000000000..5a7694141 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst @@ -0,0 +1,22 @@ +==================== +Backoff retry plugin +==================== + +The ``Guzzle\Plugin\Backoff\BackoffPlugin`` automatically retries failed HTTP requests using custom backoff strategies: + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Backoff\BackoffPlugin; + + $client = new Client('http://www.test.com/'); + // Use a static factory method to get a backoff plugin using the exponential backoff strategy + $backoffPlugin = BackoffPlugin::getExponentialBackoff(); + + // Add the backoff plugin to the client object + $client->addSubscriber($backoffPlugin); + +The BackoffPlugin's constructor accepts a ``Guzzle\Plugin\Backoff\BackoffStrategyInterface`` object that is used to +determine when a retry should be issued and how long to delay between retries. The above code example shows how to +attach a BackoffPlugin to a client that is pre-configured to retry failed 500 and 503 responses using truncated +exponential backoff (emulating the behavior of Guzzle 2's ExponentialBackoffPlugin). diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst new file mode 100644 index 000000000..d2fd5df26 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst @@ -0,0 +1,169 @@ +================= +HTTP Cache plugin +================= + +Guzzle can leverage HTTP's caching specifications using the ``Guzzle\Plugin\Cache\CachePlugin``. The CachePlugin +provides a private transparent proxy cache that caches HTTP responses. The caching logic, based on +`RFC 2616 `_, uses HTTP headers to control caching behavior, +cache lifetime, and supports Vary, ETag, and Last-Modified based revalidation: + +.. code-block:: php + + use Guzzle\Http\Client; + use Doctrine\Common\Cache\FilesystemCache; + use Guzzle\Cache\DoctrineCacheAdapter; + use Guzzle\Plugin\Cache\CachePlugin; + use Guzzle\Plugin\Cache\DefaultCacheStorage; + + $client = new Client('http://www.test.com/'); + + $cachePlugin = new CachePlugin(array( + 'storage' => new DefaultCacheStorage( + new DoctrineCacheAdapter( + new FilesystemCache('/path/to/cache/files') + ) + ) + )); + + // Add the cache plugin to the client object + $client->addSubscriber($cachePlugin); + $client->get('http://www.wikipedia.org/')->send(); + + // The next request will revalidate against the origin server to see if it + // has been modified. If a 304 response is received the response will be + // served from cache + $client->get('http://www.wikipedia.org/')->send(); + +The cache plugin intercepts GET and HEAD requests before they are actually transferred to the origin server. The cache +plugin then generates a hash key based on the request method and URL, and checks to see if a response exists in the cache. If +a response exists in the cache, the cache adapter then checks to make sure that the caching rules associated with the response +satisfy the request, and ensures that response still fresh. If the response is acceptable for the request any required +revalidation, then the cached response is served instead of contacting the origin server. + +Vary +---- + +Cache keys are derived from a request method and a request URL. Multiple responses can map to the same cache key and +stored in Guzzle's underlying cache storage object. You should use the ``Vary`` HTTP header to tell the cache storage +object that the cache response must have been cached for a request that matches the headers specified in the Vary header +of the request. This allows you to have specific cache entries for the same request URL but variations in a request's +headers determine which cache entry is served. Please see the http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44 +for more information. + +Cache options +------------- + +There are several options you can add to requests or clients to modify the behavior of the cache plugin. + +Override cache TTL +~~~~~~~~~~~~~~~~~~ + +You can override the number of seconds a cacheable response is stored in the cache by setting the +``cache.override_ttl`` parameter on the params object of a request: + +.. code-block:: php + + // If the response to the request is cacheable, then the response will be cached for 100 seconds + $request->getParams()->set('cache.override_ttl', 100); + +If a response doesn't specify any freshness policy, it will be kept in cache for 3600 seconds by default. + +Custom caching decision +~~~~~~~~~~~~~~~~~~~~~~~ + +If the service you are interacting with does not return caching headers or returns responses that are normally +something that would not be cached, you can set a custom ``can_cache`` object on the constructor of the CachePlugin +and provide a ``Guzzle\Plugin\Cache\CanCacheInterface`` object. You can use the +``Guzzle\Plugin\Cache\CallbackCanCacheStrategy`` to easily make a caching decision based on an HTTP request and +response. + +Revalidation options +~~~~~~~~~~~~~~~~~~~~ + +You can change the revalidation behavior of a request using the ``cache.revalidate`` parameter. Setting this +parameter to ``never`` will ensure that a revalidation request is never sent, and the response is always served from +the origin server. Setting this parameter to ``skip`` will never revalidate and uses the response stored in the cache. + +Normalizing requests for caching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``cache.key_filter`` parameter if you wish to strip certain query string parameters from your +request before creating a unique hash for the request. This parameter can be useful if your requests have query +string values that cause each request URL to be unique (thus preventing a cache hit). The ``cache.key_filter`` +format is simply a comma separated list of query string values to remove from the URL when creating a cache key. +For example, here we are saying that the ``a`` and ``q`` query string variables should be ignored when generating a +cache key for the request: + +.. code-block:: php + + $request->getParams()->set('cache.key_filter', 'a, q'); + +Other options +~~~~~~~~~~~~~ + +There are many other options available to the CachePlugin that can meet almost any caching requirement, including +custom revalidation implementations, custom cache key generators, custom caching decision strategies, and custom +cache storage objects. Take a look the constructor of ``Guzzle\Plugin\Cache\CachePlugin`` for more information. + +Setting Client-wide cache settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can specify cache settings for every request created by a client by adding cache settings to the configuration +options of a client. + +.. code-block:: php + + $client = new Guzzle\Http\Client('http://www.test.com', array( + 'request.params' => array( + 'cache.override_ttl' => 3600, + 'params.cache.revalidate' => 'never' + ) + )); + + echo $client->get('/')->getParams()->get('cache.override_ttl'); + // >>> 3600 + + echo $client->get('/')->getParams()->get('cache.revalidate'); + // >>> never + +Cache revalidation +------------------ + +If the cache plugin determines that a response to a GET request needs revalidation, a conditional GET is transferred +to the origin server. If the origin server returns a 304 response, then a response containing the merged headers of +the cached response with the new response and the entity body of the cached response is returned. Custom revalidation +strategies can be injected into a CachePlugin if needed. + +Cache adapters +-------------- + +Guzzle doesn't try to reinvent the wheel when it comes to caching or logging. Plenty of other frameworks have +excellent solutions in place that you are probably already using in your applications. Guzzle uses adapters for +caching and logging. The cache plugin requires a cache adapter so that is can store responses in a cache. Guzzle +currently supports cache adapters for `Doctrine 2.0 `_ and the +`Zend Framework `_. + +Doctrine cache adapter +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + use Doctrine\Common\Cache\ArrayCache; + use Guzzle\Cache\DoctrineCacheAdapter; + use Guzzle\Plugin\Cache\CachePlugin; + + $backend = new ArrayCache(); + $adapter = new DoctrineCacheAdapter($backend); + $cache = new CachePlugin($adapter); + +Zend Framework cache adapter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + use Guzzle\Cache\ZendCacheAdapter; + use Zend\Cache\Backend\TestBackend; + + $backend = new TestBackend(); + $adapter = new ZendCacheAdapter($backend); + $cache = new CachePlugin($adapter); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst new file mode 100644 index 000000000..a6cc7d924 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst @@ -0,0 +1,33 @@ +============= +Cookie plugin +============= + +Some web services require a Cookie in order to maintain a session. The ``Guzzle\Plugin\Cookie\CookiePlugin`` will add +cookies to requests and parse cookies from responses using a CookieJar object: + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Cookie\CookiePlugin; + use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar; + + $cookiePlugin = new CookiePlugin(new ArrayCookieJar()); + + // Add the cookie plugin to a client + $client = new Client('http://www.test.com/'); + $client->addSubscriber($cookiePlugin); + + // Send the request with no cookies and parse the returned cookies + $client->get('http://www.yahoo.com/')->send(); + + // Send the request again, noticing that cookies are being sent + $request = $client->get('http://www.yahoo.com/'); + $request->send(); + + echo $request; + +You can disable cookies per-request by setting the ``cookies.disable`` value to true on a request's params object. + +.. code-block:: php + + $request->getParams()->set('cookies.disable', true); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst new file mode 100644 index 000000000..0870155b5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst @@ -0,0 +1,93 @@ +================ +Creating plugins +================ + +.. highlight:: php + +Guzzle is extremely extensible because of the behavioral modifications that can be added to requests, clients, and +commands using an event system. Before and after the majority of actions are taken in the library, an event is emitted +with the name of the event and context surrounding the event. Observers can subscribe to a subject and modify the +subject based on the events received. Guzzle's event system utilizes the Symfony2 EventDispatcher and is the backbone +of its plugin architecture. + +Overview +-------- + +Plugins must implement the ``Symfony\Component\EventDispatcher\EventSubscriberInterface`` interface. The +``EventSubscriberInterface`` requires that your class implements a static method, ``getSubscribedEvents()``, that +returns an associative array mapping events to methods on the object. See the +`Symfony2 documentation `_ for more information. + +Plugins can be attached to any subject, or object in Guzzle that implements that +``Guzzle\Common\HasDispatcherInterface``. + +Subscribing to a subject +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can subscribe an instantiated observer to an event by calling ``addSubscriber`` on a subject. + +.. code-block:: php + + $testPlugin = new TestPlugin(); + $client->addSubscriber($testPlugin); + +You can also subscribe to only specific events using a closure:: + + $client->getEventDispatcher()->addListener('request.create', function(Event $event) { + echo $event->getName(); + echo $event['request']; + }); + +``Guzzle\Common\Event`` objects are passed to notified functions. The Event object has a ``getName()`` method which +return the name of the emitted event and may contain contextual information that can be accessed like an array. + +Knowing what events to listen to +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Any class that implements the ``Guzzle\Common\HasDispatcherInterface`` must implement a static method, +``getAllEvents()``, that returns an array of the events that are emitted from the object. You can browse the source +to see each event, or you can call the static method directly in your code to get a list of available events. + +Event hooks +----------- + +* :ref:`client-events` +* :ref:`service-client-events` +* :ref:`request-events` +* ``Guzzle\Http\Curl\CurlMulti``: +* :ref:`service-builder-events` + +Examples of the event system +---------------------------- + +Simple Echo plugin +~~~~~~~~~~~~~~~~~~ + +This simple plugin prints a string containing the request that is about to be sent by listening to the +``request.before_send`` event:: + + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + + class EchoPlugin implements EventSubscriberInterface + { + public static function getSubscribedEvents() + { + return array('request.before_send' => 'onBeforeSend'); + } + + public function onBeforeSend(Guzzle\Common\Event $event) + { + echo 'About to send a request: ' . $event['request'] . "\n"; + } + } + + $client = new Guzzle\Service\Client('http://www.test.com/'); + + // Create the plugin and add it as an event subscriber + $plugin = new EchoPlugin(); + $client->addSubscriber($plugin); + + // Send a request and notice that the request is printed to the screen + $client->get('/')->send(); + +Running the above code will print a string containing the HTTP request that is about to be sent. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst new file mode 100644 index 000000000..66d4a01e3 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst @@ -0,0 +1,32 @@ +========================== +cURL authentication plugin +========================== + +.. warning:: + + The CurlAuthPlugin is deprecated. You should use the `auth` parameter of a client to add authorization headers to + every request created by a client. + + .. code-block:: php + + $client->setDefaultOption('auth', array('username', 'password', 'Basic|Digest|NTLM|Any')); + +If your web service client requires basic authorization, then you can use the CurlAuthPlugin to easily add an +Authorization header to each request sent by the client. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\CurlAuth\CurlAuthPlugin; + + $client = new Client('http://www.test.com/'); + + // Add the auth plugin to the client object + $authPlugin = new CurlAuthPlugin('username', 'password'); + $client->addSubscriber($authPlugin); + + $response = $client->get('projects/1/people')->send(); + $xml = new SimpleXMLElement($response->getBody(true)); + foreach ($xml->person as $person) { + echo $person->email . "\n"; + } diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/history-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/history-plugin.rst new file mode 100644 index 000000000..b96befe79 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/history-plugin.rst @@ -0,0 +1,24 @@ +============== +History plugin +============== + +The history plugin tracks all of the requests and responses sent through a request or client. This plugin can be +useful for crawling or unit testing. By default, the history plugin stores up to 10 requests and responses. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\History\HistoryPlugin; + + $client = new Client('http://www.test.com/'); + + // Add the history plugin to the client object + $history = new HistoryPlugin(); + $history->setLimit(5); + $client->addSubscriber($history); + + $client->get('http://www.yahoo.com/')->send(); + + echo $history->getLastRequest(); + echo $history->getLastResponse(); + echo count($history); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/log-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/log-plugin.rst new file mode 100644 index 000000000..3e2b22944 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/log-plugin.rst @@ -0,0 +1,69 @@ +========== +Log plugin +========== + +Use the ``Guzzle\Plugin\Log\LogPlugin`` to view all data sent over the wire, including entity bodies and redirects. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Log\Zf1LogAdapter; + use Guzzle\Plugin\Log\LogPlugin; + use Guzzle\Log\MessageFormatter; + + $client = new Client('http://www.test.com/'); + + $adapter = new Zf1LogAdapter( + new \Zend_Log(new \Zend_Log_Writer_Stream('php://output')) + ); + $logPlugin = new LogPlugin($adapter, MessageFormatter::DEBUG_FORMAT); + + // Attach the plugin to the client, which will in turn be attached to all + // requests generated by the client + $client->addSubscriber($logPlugin); + + $response = $client->get('http://google.com')->send(); + +The code sample above wraps a ``Zend_Log`` object using a ``Guzzle\Log\Zf1LogAdapter``. After attaching the plugin to +the client, all data sent over the wire will be logged to stdout. + +The first argument of the LogPlugin's constructor accepts a ``Guzzle\Log\LogAdapterInterface`` object. This object is +an adapter that allows you to use the logging capabilities of your favorite log implementation. The second argument of +the constructor accepts a ``Guzzle\Log\MessageFormatter`` or a log messaged format string. The format string uses +variable substitution and allows you to define the log data that is important to your application. The different +variables that can be injected are as follows: + +================== ==================================================================================== +Variable Substitution +================== ==================================================================================== +{request} Full HTTP request message +{response} Full HTTP response message +{ts} Timestamp +{host} Host of the request +{method} Method of the request +{url} URL of the request +{host} Host of the request +{protocol} Request protocol +{version} Protocol version +{resource} Resource of the request (path + query + fragment) +{port} Port of the request +{hostname} Hostname of the machine that sent the request +{code} Status code of the response (if available) +{phrase} Reason phrase of the response (if available) +{curl_error} Curl error message (if available) +{curl_code} Curl error code (if available) +{curl_stderr} Curl standard error (if available) +{connect_time} Time in seconds it took to establish the connection (if available) +{total_time} Total transaction time in seconds for last transfer (if available) +{req_header_*} Replace `*` with the lowercased name of a request header to add to the message +{res_header_*} Replace `*` with the lowercased name of a response header to add to the message +{req_body} Request body +{res_body} Response body +================== ==================================================================================== + +The LogPlugin has a helper method that can be used when debugging that will output the full HTTP request and +response of a transaction: + +.. code-block:: php + + $client->addSubscriber(LogPlugin::getDebugPlugin()); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst new file mode 100644 index 000000000..1b1cfa8a8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst @@ -0,0 +1,29 @@ +==================== +MD5 validator plugin +==================== + +Entity bodies can sometimes be modified over the wire due to a faulty TCP transport or misbehaving proxy. If an HTTP +response contains a Content-MD5 header, then a MD5 hash of the entity body of a response can be compared against the +Content-MD5 header of the response to determine if the response was delivered intact. The +``Guzzle\Plugin\Md5\Md5ValidatorPlugin`` will throw an ``UnexpectedValueException`` if the calculated MD5 hash does +not match the Content-MD5 header value: + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Md5\Md5ValidatorPlugin; + + $client = new Client('http://www.test.com/'); + + $md5Plugin = new Md5ValidatorPlugin(); + + // Add the md5 plugin to the client object + $client->addSubscriber($md5Plugin); + + $request = $client->get('http://www.yahoo.com/'); + $request->send(); + +Calculating the MD5 hash of a large entity body or an entity body that was transferred using a Content-Encoding is an +expensive operation. When working in high performance applications, you might consider skipping the MD5 hash +validation for entity bodies bigger than a certain size or Content-Encoded entity bodies +(see ``Guzzle\Plugin\Md5\Md5ValidatorPlugin`` for more information). diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst new file mode 100644 index 000000000..4900cb565 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst @@ -0,0 +1,27 @@ +=========== +Mock plugin +=========== + +The mock plugin is useful for testing Guzzle clients. The mock plugin allows you to queue an array of responses that +will satisfy requests sent from a client by consuming the request queue in FIFO order. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Mock\MockPlugin; + use Guzzle\Http\Message\Response; + + $client = new Client('http://www.test.com/'); + + $mock = new MockPlugin(); + $mock->addResponse(new Response(200)) + ->addResponse(new Response(404)); + + // Add the mock plugin to the client object + $client->addSubscriber($mock); + + // The following request will receive a 200 response from the plugin + $client->get('http://www.example.com/')->send(); + + // The following request will receive a 404 response from the plugin + $client->get('http://www.test.com/')->send(); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst new file mode 100644 index 000000000..e67eabaa1 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst @@ -0,0 +1,30 @@ +============ +OAuth plugin +============ + +Guzzle ships with an OAuth 1.0 plugin that can sign requests using a consumer key, consumer secret, OAuth token, +and OAuth secret. Here's an example showing how to send an authenticated request to the Twitter REST API: + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Oauth\OauthPlugin; + + $client = new Client('http://api.twitter.com/1'); + $oauth = new OauthPlugin(array( + 'consumer_key' => 'my_key', + 'consumer_secret' => 'my_secret', + 'token' => 'my_token', + 'token_secret' => 'my_token_secret' + )); + $client->addSubscriber($oauth); + + $response = $client->get('statuses/public_timeline.json')->send(); + +If you need to use a custom signing method, you can pass a ``signature_method`` configuration option in the +constructor of the OAuth plugin. The ``signature_method`` option must be a callable variable that accepts a string to +sign and signing key and returns a signed string. + +.. note:: + + You can omit the ``token`` and ``token_secret`` options to use two-legged OAuth. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc new file mode 100644 index 000000000..8d6d09b46 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc @@ -0,0 +1,9 @@ +* :doc:`/plugins/async-plugin` +* :doc:`/plugins/backoff-plugin` +* :doc:`/plugins/cache-plugin` +* :doc:`/plugins/cookie-plugin` +* :doc:`/plugins/history-plugin` +* :doc:`/plugins/log-plugin` +* :doc:`/plugins/md5-validator-plugin` +* :doc:`/plugins/mock-plugin` +* :doc:`/plugins/oauth-plugin` diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst new file mode 100644 index 000000000..19ae57ece --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst @@ -0,0 +1,59 @@ +====================== +Plugin system overview +====================== + +The workflow of sending a request and parsing a response is driven by Guzzle's event system, which is powered by the +`Symfony2 Event Dispatcher component `_. + +Any object in Guzzle that emits events will implement the ``Guzzle\Common\HasEventDispatcher`` interface. You can add +event subscribers directly to these objects using the ``addSubscriber()`` method, or you can grab the +``Symfony\Component\EventDispatcher\EventDispatcher`` object owned by the object using ``getEventDispatcher()`` and +add a listener or event subscriber. + +Adding event subscribers to clients +----------------------------------- + +Any event subscriber or event listener attached to the EventDispatcher of a ``Guzzle\Http\Client`` or +``Guzzle\Service\Client`` object will automatically be attached to all request objects created by the client. This +allows you to attach, for example, a HistoryPlugin to a client object, and from that point on, every request sent +through that client will utilize the HistoryPlugin. + +.. code-block:: php + + use Guzzle\Plugin\History\HistoryPlugin; + use Guzzle\Service\Client; + + $client = new Client(); + + // Create a history plugin and attach it to the client + $history = new HistoryPlugin(); + $client->addSubscriber($history); + + // Create and send a request. This request will also utilize the HistoryPlugin + $client->get('http://httpbin.org')->send(); + + // Echo out the last sent request by the client + echo $history->getLastRequest(); + +.. tip:: + + :doc:`Create event subscribers `, or *plugins*, to implement reusable logic that can be + shared across clients. Event subscribers are also easier to test than anonymous functions. + +Pre-Built plugins +----------------- + +Guzzle provides easy to use request plugins that add behavior to requests based on signal slot event notifications +powered by the Symfony2 Event Dispatcher component. + +* :doc:`async-plugin` +* :doc:`backoff-plugin` +* :doc:`cache-plugin` +* :doc:`cookie-plugin` +* :doc:`curl-auth-plugin` +* :doc:`history-plugin` +* :doc:`log-plugin` +* :doc:`md5-validator-plugin` +* :doc:`mock-plugin` +* :doc:`oauth-plugin` + diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/requirements.txt b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/requirements.txt new file mode 100644 index 000000000..f62e31837 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/requirements.txt @@ -0,0 +1,2 @@ +Sphinx>=1.2b1 +guzzle_sphinx_theme>=0.5.0 diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/testing/unit-testing.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/testing/unit-testing.rst new file mode 100644 index 000000000..f4297af30 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/testing/unit-testing.rst @@ -0,0 +1,201 @@ +=========================== +Unit Testing Guzzle clients +=========================== + +Guzzle provides several tools that will enable you to easily unit test your web service clients. + +* PHPUnit integration +* Mock responses +* node.js web server for integration testing + +PHPUnit integration +------------------- + +Guzzle is unit tested using `PHPUnit `_. Your web service client's unit tests should extend +``Guzzle\Tests\GuzzleTestCase`` so that you can take advantage of some of the built in helpers. + +In order to unit test your client, a developer would need to copy phpunit.xml.dist to phpunit.xml and make any needed +modifications. As a best practice and security measure for you and your contributors, it is recommended to add an +ignore statement to your SCM so that phpunit.xml is ignored. + +Bootstrapping +~~~~~~~~~~~~~ + +Your web service client should have a tests/ folder that contains a bootstrap.php file. The bootstrap.php file +responsible for autoloading and configuring a ``Guzzle\Service\Builder\ServiceBuilder`` that is used throughout your +unit tests for loading a configured client. You can add custom parameters to your phpunit.xml file that expects users +to provide the path to their configuration data. + +.. code-block:: php + + Guzzle\Tests\GuzzleTestCase::setServiceBuilder(Aws\Common\Aws::factory($_SERVER['CONFIG'])); + + Guzzle\Tests\GuzzleTestCase::setServiceBuilder(Guzzle\Service\Builder\ServiceBuilder::factory(array( + 'test.unfuddle' => array( + 'class' => 'Guzzle.Unfuddle.UnfuddleClient', + 'params' => array( + 'username' => 'test_user', + 'password' => '****', + 'subdomain' => 'test' + ) + ) + ))); + +The above code registers a service builder that can be used throughout your unit tests. You would then be able to +retrieve an instantiated and configured Unfuddle client by calling ``$this->getServiceBuilder()->get('test.unfuddle)``. +The above code assumes that ``$_SERVER['CONFIG']`` contains the path to a file that stores service description +configuration. + +Unit testing remote APIs +------------------------ + +Mock responses +~~~~~~~~~~~~~~ + +One of the benefits of unit testing is the ability to quickly determine if there are errors in your code. If your +unit tests run slowly, then they become tedious and will likely be run less frequently. Guzzle's philosophy on unit +testing web service clients is that no network access should be required to run the unit tests. This means that +responses are served from mock responses or local servers. By adhering to this principle, tests will run much faster +and will not require an external resource to be available. The problem with this approach is that your mock responses +must first be gathered and then subsequently updated each time the remote API changes. + +Integration testing over the internet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can perform integration testing with a web service over the internet by making calls directly to the service. If +the web service you are requesting uses a complex signing algorithm or some other specific implementation, then you +may want to include at least one actual network test that can be run specifically through the command line using +`PHPUnit group annotations `_. + +@group internet annotation +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When creating tests that require an internet connection, it is recommended that you add ``@group internet`` annotations +to your unit tests to specify which tests require network connectivity. + +You can then `run PHPUnit tests `_ that exclude the @internet +group by running ``phpunit --exclude-group internet``. + +API credentials +^^^^^^^^^^^^^^^ + +If API credentials are required to run your integration tests, you must add ```` parameters to your +phpunit.xml.dist file and extract these parameters in your bootstrap.php file. + +.. code-block:: xml + + + + + + + + + + + + + ./Tests + + + + +You can then extract the ``server`` variables in your bootstrap.php file by grabbing them from the ``$_SERVER`` +superglobal: ``$apiUser = $_SERVER['API_USER'];`` + +Further reading +^^^^^^^^^^^^^^^ + +A good discussion on the topic of testing remote APIs can be found in Sebastian Bergmann's +`Real-World Solutions for Developing High-Quality PHP Frameworks and Applications `_. + +Queueing Mock responses +----------------------- + +Mock responses can be used to test if requests are being generated correctly and responses and handled correctly by +your client. Mock responses can be queued up for a client using the ``$this->setMockResponse($client, $path)`` method +of your test class. Pass the client you are adding mock responses to and a single path or array of paths to mock +response files relative to the ``/tests/mock/ folder``. This will queue one or more mock responses for your client by +creating a simple observer on the client. Mock response files must contain a full HTTP response message: + +.. code-block:: none + + HTTP/1.1 200 OK + Date: Wed, 25 Nov 2009 12:00:00 GMT + Connection: close + Server: AmazonS3 + Content-Type: application/xml + + + EU + +After queuing mock responses for a client, you can get an array of the requests that were sent by the client that +were issued a mock response by calling ``$this->getMockedRequests()``. + +You can also use the ``Guzzle\Plugin\Mock\MockPlugin`` object directly with your clients. + +.. code-block:: php + + $plugin = new Guzzle\Plugin\Mock\MockPlugin(); + $plugin->addResponse(new Guzzle\Http\Message\Response(200)); + $client = new Guzzle\Http\Client(); + $client->addSubscriber($plugin); + + // The following request will get the mock response from the plugin in FIFO order + $request = $client->get('http://www.test.com/'); + $request->send(); + + // The MockPlugin maintains a list of requests that were mocked + $this->assertContainsOnly($request, $plugin->getReceivedRequests()); + +node.js web server for integration testing +------------------------------------------ + +Using mock responses is usually enough when testing a web service client. If your client needs to add custom cURL +options to requests, then you should use the node.js test web server to ensure that your HTTP request message is +being created correctly. + +Guzzle is based around PHP's libcurl bindings. cURL sometimes modifies an HTTP request message based on +``CURLOPT_*`` options. Headers that are added to your request by cURL will not be accounted for if you inject mock +responses into your tests. Additionally, some request entity bodies cannot be loaded by the client before transmitting +it to the sever (for example, when using a client as a sort of proxy and streaming content from a remote server). You +might also need to inspect the entity body of a ``multipart/form-data`` POST request. + +.. note:: + + You can skip all of the tests that require the node.js test web server by excluding the ``server`` group: + ``phpunit --exclude-group server`` + +Using the test server +~~~~~~~~~~~~~~~~~~~~~ + +The node.js test server receives requests and returns queued responses. The test server exposes a simple API that is +used to enqueue responses and inspect the requests that it has received. + +Retrieve the server object by calling ``$this->getServer()``. If the node.js server is not running, it will be +started as a forked process and an object that interfaces with the server will be returned. (note: stopping the +server is handled internally by Guzzle.) + +You can queue an HTTP response or an array of responses by calling ``$this->getServer()->enqueue()``: + +.. code-block:: php + + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + +The above code queues a single 200 response with an empty body. Responses are queued using a FIFO order; this +response will be returned by the server when it receives the first request and then removed from the queue. If a +request is received by a server with no queued responses, an exception will be thrown in your unit test. + +You can inspect the requests that the server has retrieved by calling ``$this->getServer()->getReceivedRequests()``. +This method accepts an optional ``$hydrate`` parameter that specifies if you are retrieving an array of string HTTP +requests or an array of ``Guzzle\Http\RequestInterface`` subclassed objects. "Hydrating" the requests will allow +greater flexibility in your unit tests so that you can easily assert the state of the various parts of a request. + +You will need to modify the base_url of your web service client in order to use it against the test server. + +.. code-block:: php + + $client = $this->getServiceBuilder()->get('my_client'); + $client->setBaseUrl($this->getServer()->getUrl()); + +After running the above code, all calls made from the ``$client`` object will be sent to the test web server. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst new file mode 100644 index 000000000..ad6070b27 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst @@ -0,0 +1,619 @@ +=========================== +Guzzle service descriptions +=========================== + +Guzzle allows you to serialize HTTP requests and parse HTTP responses using a DSL called a service descriptions. +Service descriptions define web service APIs by documenting each operation, the operation's parameters, validation +options for each parameter, an operation's response, how the response is parsed, and any errors that can be raised for +an operation. Writing a service description for a web service allows you to more quickly consume a web service than +writing concrete commands for each web service operation. + +Guzzle service descriptions can be representing using a PHP array or JSON document. Guzzle's service descriptions are +heavily inspired by `Swagger `_. + +Service description schema +========================== + +A Guzzle Service description must match the following JSON schema document. This document can also serve as a guide when +implementing a Guzzle service description. + +Download the schema here: :download:`Guzzle JSON schema document ` + +.. class:: overflow-height-500px + + .. literalinclude:: ../_downloads/guzzle-schema-1.0.json + :language: json + +Top-level attributes +-------------------- + +Service descriptions are comprised of the following top-level attributes: + +.. code-block:: json + + { + "name": "string", + "apiVersion": "string|number", + "baseUrl": "string", + "description": "string", + "operations": {}, + "models": {}, + "includes": ["string.php", "string.json"] + } + ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| Property Name | Value | Description | ++=========================================+=========================+=======================================================================================================================+ +| name | string | Name of the web service | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| apiVersion | string|number | Version identifier that the service description is compatible with | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| baseUrl or basePath | string | Base URL of the web service. Any relative URI specified in an operation will be merged with the baseUrl using the | +| | | process defined in RFC 2396. Some clients require custom logic to determine the baseUrl. In those cases, it is best | +| | | to not include a baseUrl in the service description, but rather allow the factory method of the client to configure | +| | | the client’s baseUrl. | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| description | string | Short summary of the web service | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| operations | object containing | Operations of the service. The key is the name of the operation and value is the attributes of the operation. | +| | :ref:`operation-schema` | | +| | | | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| models | object containing | Schema models that can be referenced throughout the service description. Models can be used to define how an HTTP | +| | :ref:`model-schema` | response is parsed into a ``Guzzle\Service\Resource\Model`` object when an operation uses a ``model`` ``responseType``| ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| includes | array of .js, | Service description files to include and extend from (can be a .json, .js, or .php file) | +| | .json, or .php | | +| | files. | | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| (any additional properties) | mixed | Any additional properties specified as top-level attributes are allowed and will be treated as arbitrary data | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ + +.. _operation-schema: + +Operations +---------- + +Operations are the actions that can be taken on a service. Each operation is given a unique name and has a distinct +endpoint and HTTP method. If an API has a ``DELETE /users/:id`` operation, a satisfactory operation name might be +``DeleteUser`` with a parameter of ``id`` that is inserted into the URI. + +.. class:: overflow-height-250px + + .. code-block:: json + + { + "operations": { + "operationName": { + "extends": "string", + "httpMethod": "GET|POST|PUT|DELETE|PATCH|string", + "uri": "string", + "summary": "string", + "class": "string", + "responseClass": "string", + "responseNotes": "string", + "type": "string", + "description": "string", + "responseType": "primitive|class|(model by name)|documentation|(string)", + "deprecated": false, + "errorResponses": [ + { + "code": 500, + "reason": "Unexpected Error", + "class": "string" + } + ], + "data": { + "foo": "bar", + "baz": "bam" + }, + "parameters": {} + } + } + } + +.. csv-table:: + :header: "Property Name", "Value", "Description" + :widths: 20, 15, 65 + + "extends", "string", "Extend from another operation by name. The parent operation must be defined before the child." + "httpMethod", "string", "HTTP method used with the operation (e.g. GET, POST, PUT, DELETE, PATCH, etc)" + "uri", "string", "URI of the operation. The uri attribute can contain URI templates. The variables of the URI template are parameters of the operation with a location value of uri" + "summary", "string", "Short summary of what the operation does" + "class", "string", "Custom class to instantiate instead of the default Guzzle\\Service\\Command\\OperationCommand. Using this attribute allows you to define an operation using a service description, but allows more customized logic to be implemented in user-land code." + "responseClass", "string", "Defined what is returned from the method. Can be a primitive, class name, or model name. You can specify the name of a class to return a more customized result from the operation (for example, a domain model object). When using the name of a PHP class, the class must implement ``Guzzle\Service\Command\ResponseClassInterface``." + "responseNotes", "string", "A description of the response returned by the operation" + "responseType", "string", "The type of response that the operation creates: one of primitive, class, model, or documentation. If not specified, this value will be automatically inferred based on whether or not there is a model matching the name, if a matching class name is found, or set to 'primitive' by default." + "deprecated", "boolean", "Whether or not the operation is deprecated" + "errorResponses", "array", "Errors that could occur while executing the operation. Each item of the array is an object that can contain a 'code' (HTTP response status code of the error), 'reason' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this error is encountered)" + "data", "object", "Any arbitrary data to associate with the operation" + "parameters", "object containing :ref:`parameter-schema` objects", "Parameters of the operation. Parameters are used to define how input data is serialized into a HTTP request." + "additionalParameters", "A single :ref:`parameter-schema` object", "Validation and serialization rules for any parameter supplied to the operation that was not explicitly defined." + +additionalParameters +~~~~~~~~~~~~~~~~~~~~ + +When a webservice offers a large number of parameters that all are set in the same location (for example the query +string or a JSON document), defining each parameter individually can require a lot of time and repetition. Furthermore, +some web services allow for completely arbitrary parameters to be supplied for an operation. The +``additionalParameters`` attribute can be used to solve both of these issues. + +As an example, we can define a Twitter API operation quite easily using ``additionalParameters``. The +GetMentions operation accepts a large number of query string parameters. Defining each of these parameters +is ideal because it provide much more introspection for the client and opens the possibility to use the description with +other tools (e.g. a documentation generator). However, you can very quickly provide a "catch-all" serialization rule +that will place any custom parameters supplied to an operation the generated request's query string parameters. + +.. class:: overflow-height-250px + + .. code-block:: json + + { + "name": "Twitter", + "apiVersion": "1.1", + "baseUrl": "https://api.twitter.com/1.1", + "operations": { + "GetMentions": { + "httpMethod": "GET", + "uri": "statuses/mentions_timeline.json", + "responseClass": "GetMentionsOutput", + "additionalParameters": { + "location": "query" + } + } + }, + "models": { + "GetMentionsOutput": { + "type": "object", + "additionalProperties": { + "location": "json" + } + } + } + } + +responseClass +~~~~~~~~~~~~~ + +The ``responseClass`` attribute is used to define the return value of an operation (what is returned by calling the +``getResult()`` method of a command object). The value set in the responseClass attribute can be one of "primitive" +(meaning the result with be primitive type like a string), a class name meaning the result will be an instance of a +specific user-land class, or a model name meaning the result will be a ``Guzzle\Service\Resource\Model`` object that +uses a :ref:`model schema ` to define how the HTTP response is parsed. + +.. note:: + + Using a class name with a ``responseClass`` will only work if it is supported by the ``class`` that is instantiated + for the operation. Keep this in mind when specifying a custom ``class`` attribute that points to a custom + ``Guzzle\Service\Command\CommandInterface`` class. The default ``class``, + ``Guzzle\Service\Command\OperationCommand``, does support setting custom ``class`` attributes. + +You can specify the name of a class to return a more customized result from the operation (for example, a domain model +object). When using the name of a PHP class, the class must implement ``Guzzle\Service\Command\ResponseClassInterface``. +Here's a very simple example of implementing a custom responseClass object. + +.. code-block:: json + + { + "operations": { + "test": { + "responseClass": "MyApplication\\User" + } + } + } + +.. code-block:: php + + namespace MyApplication; + + use Guzzle\Service\Command\ResponseClassInterface; + use Guzzle\Service\Command\OperationCommand; + + class User implements ResponseClassInterface + { + protected $name; + + public static function fromCommand(OperationCommand $command) + { + $response = $command->getResponse(); + $xml = $response->xml(); + + return new self((string) $xml->name); + } + + public function __construct($name) + { + $this->name = $name; + } + } + +errorResponses +~~~~~~~~~~~~~~ + +``errorResponses`` is an array containing objects that define the errors that could occur while executing the +operation. Each item of the array is an object that can contain a 'code' (HTTP response status code of the error), +'reason' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this +error is encountered). + +ErrorResponsePlugin +^^^^^^^^^^^^^^^^^^^ + +Error responses are by default only used for documentation. If you don't need very complex exception logic for your web +service errors, then you can use the ``Guzzle\Plugin\ErrorResponse\ErrorResponsePlugin`` to automatically throw defined +exceptions when one of the ``errorResponse`` rules are matched. The error response plugin will listen for the +``request.complete`` event of a request created by a command object. Every response (including a successful response) is +checked against the list of error responses for an exact match using the following order of checks: + +1. Does the errorResponse have a defined ``class``? +2. Is the errorResponse ``code`` equal to the status code of the response? +3. Is the errorResponse ``reason`` equal to the reason phrase of the response? +4. Throw the exception stored in the ``class`` attribute of the errorResponse. + +The ``class`` attribute must point to a class that implements +``Guzzle\Plugin\ErrorResponse\ErrorResponseExceptionInterface``. This interface requires that an error response class +implements ``public static function fromCommand(CommandInterface $command, Response $response)``. This method must +return an object that extends from ``\Exception``. After an exception is returned, it is thrown by the plugin. + +.. _parameter-schema: + +Parameter schema +---------------- + +Parameters in both operations and models are represented using the +`JSON schema `_ syntax. + +.. csv-table:: + :header: "Property Name", "Value", "Description" + :widths: 20, 15, 65 + + "name", "string", "Unique name of the parameter" + "type", "string|array", "Type of variable (string, number, integer, boolean, object, array, numeric, null, any). Types are using for validation and determining the structure of a parameter. You can use a union type by providing an array of simple types. If one of the union types matches the provided value, then the value is valid." + "instanceOf", "string", "When the type is an object, you can specify the class that the object must implement" + "required", "boolean", "Whether or not the parameter is required" + "default", "mixed", "Default value to use if no value is supplied" + "static", "boolean", "Set to true to specify that the parameter value cannot be changed from the default setting" + "description", "string", "Documentation of the parameter" + "location", "string", "The location of a request used to apply a parameter. Custom locations can be registered with a command, but the defaults are uri, query, statusCode, reasonPhrase, header, body, json, xml, postField, postFile, responseBody" + "sentAs", "string", "Specifies how the data being modeled is sent over the wire. For example, you may wish to include certain headers in a response model that have a normalized casing of FooBar, but the actual header is x-foo-bar. In this case, sentAs would be set to x-foo-bar." + "filters", "array", "Array of functions to to run a parameter value through." + +filters +~~~~~~~ + +Each value in the array must be a string containing the full class path to a static method or an array of complex +filter information. You can specify static methods of classes using the full namespace class name followed by +"::" (e.g. ``FooBar::baz()``). Some filters require arguments in order to properly filter a value. For complex filters, +use an object containing a ``method`` attribute pointing to a function, and an ``args`` attribute containing an +array of positional arguments to pass to the function. Arguments can contain keywords that are replaced when filtering +a value: ``@value`` is replaced with the value being filtered, and ``@api`` is replaced with the actual Parameter +object. + +.. code-block:: json + + { + "filters": [ + "strtolower", + { + "method": "MyClass::convertString", + "args": [ "test", "@value", "@api" ] + } + ] + } + +The above example will filter a parameter using ``strtolower``. It will then call the ``convertString`` static method +of ``MyClass``, passing in "test", the actual value of the parameter, and a ``Guzzle\Service\Description\Parameter`` +object. + +Operation parameter location attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The location field of top-level parameters control how a parameter is serialized when generating a request. + +uri location +^^^^^^^^^^^^ + +Parameters are injected into the ``uri`` attribute of the operation using +`URI-template expansion `_. + +.. code-block:: json + + { + "operations": { + "uriTest": { + "uri": "/test/{testValue}", + "parameters": { + "testValue": { + "location": "uri" + } + } + } + } + } + +query location +^^^^^^^^^^^^^^ + +Parameters are injected into the query string of a request. Query values can be nested, which would result in a PHP +style nested query string. The name of a parameter is the default name of the query string parameter added to the +request. You can override this behavior by specifying the ``sentAs`` attribute on the parameter. + +.. code-block:: json + + { + "operations": { + "queryTest": { + "parameters": { + "testValue": { + "location": "query", + "sentAs": "test_value" + } + } + } + } + } + +header location +^^^^^^^^^^^^^^^ + +Parameters are injected as headers on an HTTP request. The name of the parameter is used as the name of the header by +default. You can change the name of the header created by the parameter using the ``sentAs`` attribute. + +Headers that are of type ``object`` will be added as multiple headers to a request using the key of the input array as +the header key. Setting a ``sentAs`` attribute along with a type ``object`` will use the value of ``sentAs`` as a +prefix for each header key. + +body location +^^^^^^^^^^^^^ + +Parameters are injected as the body of a request. The input of these parameters may be anything that can be cast to a +string or a ``Guzzle\Http\EntityBodyInterface`` object. + +postField location +^^^^^^^^^^^^^^^^^^ + +Parameters are inserted as POST fields in a request. Nested values may be supplied and will be represented using +PHP style nested query strings. The POST field name is the same as the parameter name by default. You can use the +``sentAs`` parameter to override the POST field name. + +postFile location +^^^^^^^^^^^^^^^^^ + +Parameters are added as POST files. A postFile value may be a string pointing to a local filename or a +``Guzzle\Http\Message\PostFileInterface`` object. The name of the POST file will be the name of the parameter by +default. You can use a custom POST file name by using the ``sentAs`` attribute. + +Supports "string" and "array" types. + +json location +^^^^^^^^^^^^^ + +Parameters are added to the body of a request as top level keys of a JSON document. Nested values may be specified, +with any number of nested ``Guzzle\Common\ToArrayInterface`` objects. When JSON parameters are specified, the +``Content-Type`` of the request will change to ``application/json`` if a ``Content-Type`` has not already been specified +on the request. + +xml location +^^^^^^^^^^^^ + +Parameters are added to the body of a request as top level nodes of an XML document. Nested values may be specified, +with any number of nested ``Guzzle\Common\ToArrayInterface`` objects. When XML parameters are specified, the +``Content-Type`` of the request will change to ``application/xml`` if a ``Content-Type`` has not already been specified +on the request. + +responseBody location +^^^^^^^^^^^^^^^^^^^^^ + +Specifies the EntityBody of a response. This can be used to download the response body to a file or a custom Guzzle +EntityBody object. + +No location +^^^^^^^^^^^ + +If a parameter has no location attribute, then the parameter is simply used as a data value. + +Other locations +^^^^^^^^^^^^^^^ + +Custom locations can be registered as new locations or override default locations if needed. + +.. _model-schema: + +Model Schema +------------ + +Models are used in service descriptions to provide generic JSON schema definitions that can be extended from or used in +``$ref`` attributes. Models can also be referenced in a ``responseClass`` attribute to provide valuable output to an +operation. Models are JSON schema documents and use the exact syntax and attributes used in parameters. + +Response Models +~~~~~~~~~~~~~~~ + +Response models describe how a response is parsed into a ``Guzzle\Service\Resource\Model`` object. Response models are +always modeled as JSON schema objects. When an HTTP response is parsed using a response model, the rules specified on +each property of a response model will translate 1:1 as keys in a PHP associative array. When a ``sentAs`` attribute is +found in response model parameters, the value retrieved from the HTTP response is retrieved using the ``sentAs`` +parameter but stored in the response model using the name of the parameter. + +The location field of top-level parameters in a response model tell response parsers how data is retrieved from a +response. + +statusCode location +^^^^^^^^^^^^^^^^^^^ + +Retrieves the status code of the response. + +reasonPhrase location +^^^^^^^^^^^^^^^^^^^^^ + +Retrieves the reason phrase of the response. + +header location +^^^^^^^^^^^^^^^ + +Retrieves a header from the HTTP response. + +body location +^^^^^^^^^^^^^ + +Retrieves the body of an HTTP response. + +json location +^^^^^^^^^^^^^ + +Retrieves a top-level parameter from a JSON document contained in an HTTP response. + +You can use ``additionalProperties`` if the JSON document is wrapped in an outer array. This allows you to parse the +contents of each item in the array using the parsing rules defined in the ``additionalProperties`` schema. + +xml location +^^^^^^^^^^^^ + +Retrieves a top-level node value from an XML document contained in an HTTP response. + +Other locations +^^^^^^^^^^^^^^^ + +Custom locations can be registered as new locations or override default locations if needed. + +Example service description +--------------------------- + +Let's say you're interacting with a web service called 'Foo' that allows for the following routes and methods:: + + GET/POST /users + GET/DELETE /users/:id + +The following JSON service description implements this simple web service: + +.. class:: overflow-height-500px + + .. code-block:: json + + { + "name": "Foo", + "apiVersion": "2012-10-14", + "baseUrl": "http://api.foo.com", + "description": "Foo is an API that allows you to Baz Bar", + "operations": { + "GetUsers": { + "httpMethod": "GET", + "uri": "/users", + "summary": "Gets a list of users", + "responseClass": "GetUsersOutput" + }, + "CreateUser": { + "httpMethod": "POST", + "uri": "/users", + "summary": "Creates a new user", + "responseClass": "CreateUserOutput", + "parameters": { + "name": { + "location": "json", + "type": "string" + }, + "age": { + "location": "json", + "type": "integer" + } + } + }, + "GetUser": { + "httpMethod": "GET", + "uri": "/users/{id}", + "summary": "Retrieves a single user", + "responseClass": "GetUserOutput", + "parameters": { + "id": { + "location": "uri", + "description": "User to retrieve by ID", + "required": true + } + } + }, + "DeleteUser": { + "httpMethod": "DELETE", + "uri": "/users/{id}", + "summary": "Deletes a user", + "responseClass": "DeleteUserOutput", + "parameters": { + "id": { + "location": "uri", + "description": "User to delete by ID", + "required": true + } + } + } + }, + "models": { + "GetUsersOutput": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "location": "json", + "type": "string" + }, + "age": { + "location": "json", + "type": "integer" + } + } + } + }, + "CreateUserOutput": { + "type": "object", + "properties": { + "id": { + "location": "json", + "type": "string" + }, + "location": { + "location": "header", + "sentAs": "Location", + "type": "string" + } + } + }, + "GetUserOutput": { + "type": "object", + "properties": { + "name": { + "location": "json", + "type": "string" + }, + "age": { + "location": "json", + "type": "integer" + } + } + }, + "DeleteUserOutput": { + "type": "object", + "properties": { + "status": { + "location": "statusCode", + "type": "integer" + } + } + } + } + } + +If you attach this service description to a client, you would completely configure the client to interact with the +Foo web service and provide valuable response models for each operation. + +.. code-block:: php + + use Guzzle\Service\Description\ServiceDescription; + + $description = ServiceDescription::factory('/path/to/client.json'); + $client->setDescription($description); + + $command = $client->getCommand('DeleteUser', array('id' => 123)); + $responseModel = $client->execute($command); + echo $responseModel['status']; + +.. note:: + + You can add the service description to your client's factory method or constructor. diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst new file mode 100644 index 000000000..b7113d68b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst @@ -0,0 +1,316 @@ +======================= +Using a service builder +======================= + +The best way to instantiate Guzzle web service clients is to let Guzzle handle building the clients for you using a +ServiceBuilder. A ServiceBuilder is responsible for creating concrete client objects based on configuration settings +and helps to manage credentials for different environments. + +You don't have to use a service builder, but they help to decouple your application from concrete classes and help to +share configuration data across multiple clients. Consider the following example. Here we are creating two clients that +require the same API public key and secret key. The clients are created using their ``factory()`` methods. + +.. code-block:: php + + use MyService\FooClient; + use MyService\BarClient; + + $foo = FooClient::factory(array( + 'key' => 'abc', + 'secret' => '123', + 'custom' => 'and above all' + )); + + $bar = BarClient::factory(array( + 'key' => 'abc', + 'secret' => '123', + 'custom' => 'listen to me' + )); + +The redundant specification of the API keys can be removed using a service builder. + +.. code-block:: php + + use Guzzle\Service\Builder\ServiceBuilder; + + $builder = ServiceBuilder::factory(array( + 'services' => array( + 'abstract_client' => array( + 'params' => array( + 'key' => 'abc', + 'secret' => '123' + ) + ), + 'foo' => array( + 'extends' => 'abstract_client', + 'class' => 'MyService\FooClient', + 'params' => array( + 'custom' => 'and above all' + ) + ), + 'bar' => array( + 'extends' => 'abstract_client', + 'class' => 'MyService\FooClient', + 'params' => array( + 'custom' => 'listen to me' + ) + ) + ) + )); + + $foo = $builder->get('foo'); + $bar = $builder->get('bar'); + +You can make managing your API keys even easier by saving the service builder configuration in a JSON format in a +.json file. + +Creating a service builder +-------------------------- + +A ServiceBuilder can source information from an array, an PHP include file that returns an array, or a JSON file. + +.. code-block:: php + + use Guzzle\Service\Builder\ServiceBuilder; + + // Source service definitions from a JSON file + $builder = ServiceBuilder::factory('services.json'); + +Sourcing data from an array +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Data can be source from a PHP array. The array must contain an associative ``services`` array that maps the name of a +client to the configuration information used by the service builder to create the client. Clients are given names +which are used to identify how a client is retrieved from a service builder. This can be useful for using multiple +accounts for the same service or creating development clients vs. production clients. + +.. code-block:: php + + $services = array( + 'includes' => array( + '/path/to/other/services.json', + '/path/to/other/php_services.php' + ), + 'services' => array( + 'abstract.foo' => array( + 'params' => array( + 'username' => 'foo', + 'password' => 'bar' + ) + ), + 'bar' => array( + 'extends' => 'abstract.foo', + 'class' => 'MyClientClass', + 'params' => array( + 'other' => 'abc' + ) + ) + ) + ); + +A service builder configuration array contains two top-level array keys: + ++------------+---------------------------------------------------------------------------------------------------------+ +| Key | Description | ++============+=========================================================================================================+ +| includes | Array of paths to JSON or PHP include files to include in the configuration. | ++------------+---------------------------------------------------------------------------------------------------------+ +| services | Associative array of defined services that can be created by the service builder. Each service can | +| | contain the following keys: | +| | | +| | +------------+----------------------------------------------------------------------------------------+ | +| | | Key | Description | | +| | +============+========================================================================================+ | +| | | class | The concrete class to instantiate that implements the | | +| | | | ``Guzzle\Common\FromConfigInterface``. | | +| | +------------+----------------------------------------------------------------------------------------+ | +| | | extends | The name of a previously defined service to extend from | | +| | +------------+----------------------------------------------------------------------------------------+ | +| | | params | Associative array of parameters to pass to the factory method of the service it is | | +| | | | instantiated | | +| | +------------+----------------------------------------------------------------------------------------+ | +| | | alias | An alias that can be used in addition to the array key for retrieving a client from | | +| | | | the service builder. | | +| | +------------+----------------------------------------------------------------------------------------+ | ++------------+---------------------------------------------------------------------------------------------------------+ + +The first client defined, ``abstract.foo``, is used as a placeholder of shared configuration values. Any service +extending abstract.foo will inherit its params. As an example, this can be useful when clients share the same username +and password. + +The next client, ``bar``, extends from ``abstract.foo`` using the ``extends`` attribute referencing the client from +which to extend. Additional parameters can be merged into the original service definition when extending a parent +service. + +.. important:: + + Each client that you intend to instantiate must specify a ``class`` attribute that references the full class name + of the client being created. The class referenced in the ``class`` parameter must implement a static ``factory()`` + method that accepts an array or ``Guzzle\Common\Collection`` object and returns an instantiated object. + +Sourcing from a PHP include +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can create service builder configurations using a PHP include file. This can be useful if you wish to take +advantage of an opcode cache like APC to speed up the process of loading and processing the configuration. The PHP +include file is the same format as an array, but you simply create a PHP script that returns an array and save the +file with the .php file extension. + +.. code-block:: php + + '...'); + // Saved as config.php + +This configuration file can then be used with a service builder. + +.. code-block:: php + + $builder = ServiceBuilder::factory('/path/to/config.php'); + +Sourcing from a JSON document +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use JSON documents to serialize your service descriptions. The JSON format uses the exact same structure as +the PHP array syntax, but it's just serialized using JSON. + +.. code-block:: javascript + + { + "includes": ["/path/to/other/services.json", "/path/to/other/php_services.php"], + "services": { + "abstract.foo": { + "params": { + "username": "foo", + "password": "bar" + } + }, + "bar": { + "extends": "abstract.foo", + "class": "MyClientClass", + "params": { + "other": "abc" + } + } + } + } + +Referencing other clients in parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If one of your clients depends on another client as one of its parameters, you can reference that client by name by +enclosing the client's reference key in ``{}``. + +.. code-block:: javascript + + { + "services": { + "token": { + "class": "My\Token\TokenFactory", + "params": { + "access_key": "xyz" + } + }, + "client": { + "class": "My\Client", + "params": { + "token_client": "{token}", + "version": "1.0" + } + } + } + } + +When ``client`` is constructed by the service builder, the service builder will first create the ``token`` service +and then inject the token service into ``client``'s factory method in the ``token_client`` parameter. + +Retrieving clients from a service builder +----------------------------------------- + +Clients are referenced using a customizable name you provide in your service definition. The ServiceBuilder is a sort +of multiton object-- it will only instantiate a client once and return that client for subsequent retrievals. Clients +are retrieved by name (the array key used in the configuration) or by the ``alias`` setting of a service. + +Here's an example of retrieving a client from your ServiceBuilder: + +.. code-block:: php + + $client = $builder->get('foo'); + + // You can also use the ServiceBuilder object as an array + $client = $builder['foo']; + +Creating throwaway clients +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can get a "throwaway" client (a client that is not persisted by the ServiceBuilder) by passing ``true`` in the +second argument of ``ServiceBuilder::get()``. This allows you to create a client that will not be returned by other +parts of your code that use the service builder. Instead of passing ``true``, you can pass an array of configuration +settings that will override the configuration settings specified in the service builder. + +.. code-block:: php + + // Get a throwaway client and overwrite the "custom" setting of the client + $foo = $builder->get('foo', array( + 'custom' => 'in this world there are rules' + )); + +Getting raw configuration settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can get the raw configuration settings provided to the service builder for a specific service using the +``getData($name)`` method of a service builder. This method will null if the service was not found in the service +builder or an array of configuration settings if the service was found. + +.. code-block:: php + + $data = $builder->getData('foo'); + echo $data['key'] . "\n"; + echo $data['secret'] . "\n"; + echo $data['custom'] . "\n"; + +Adding a plugin to all clients +------------------------------ + +You can add a plugin to all clients created by a service builder using the ``addGlobalPlugin($plugin)`` method of a +service builder and passing a ``Symfony\Component\EventDispatcher\EventSubscriberInterface`` object. The service builder +will then attach each global plugin to every client as it is created. This allows you to, for example, add a LogPlugin +to every request created by a service builder for easy debugging. + +.. code-block:: php + + use Guzzle\Plugin\Log\LogPlugin; + + // Add a debug log plugin to every client as it is created + $builder->addGlobalPlugin(LogPlugin::getDebugPlugin()); + + $foo = $builder->get('foo'); + $foo->get('/')->send(); + // Should output all of the data sent over the wire + +.. _service-builder-events: + +Events emitted from a service builder +------------------------------------- + +A ``Guzzle\Service\Builder\ServiceBuilder`` object emits the following events: + ++-------------------------------+--------------------------------------------+-----------------------------------------+ +| Event name | Description | Event data | ++===============================+============================================+=========================================+ +| service_builder.create_client | Called when a client is created | * client: The created client object | ++-------------------------------+--------------------------------------------+-----------------------------------------+ + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Service\Builder\ServiceBuilder; + + $builder = ServiceBuilder::factory('/path/to/config.json'); + + // Add an event listener to print out each client client as it is created + $builder->getEventDispatcher()->addListener('service_builder.create_client', function (Event $e) { + echo 'Client created: ' . get_class($e['client']) . "\n"; + }); + + $foo = $builder->get('foo'); + // Should output the class used for the "foo" client diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst new file mode 100644 index 000000000..7ec771e1c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst @@ -0,0 +1,659 @@ +====================== +The web service client +====================== + +The ``Guzzle\Service`` namespace contains various abstractions that help to make it easier to interact with a web +service API, including commands, service descriptions, and resource iterators. + +In this chapter, we'll build a simple `Twitter API client `_. + +Creating a client +================= + +A class that extends from ``Guzzle\Service\Client`` or implements ``Guzzle\Service\ClientInterface`` must implement a +``factory()`` method in order to be used with a :doc:`service builder `. + +Factory method +-------------- + +You can use the ``factory()`` method of a client directly if you do not need a service builder. + +.. code-block:: php + + use mtdowling\TwitterClient; + + // Create a client and pass an array of configuration data + $twitter = TwitterClient::factory(array( + 'consumer_key' => '****', + 'consumer_secret' => '****', + 'token' => '****', + 'token_secret' => '****' + )); + +.. note:: + + If you'd like to follow along, here's how to get your Twitter API credentials: + + 1. Visit https://dev.twitter.com/apps + 2. Click on an application that you've created + 3. Click on the "OAuth tool" tab + 4. Copy all of the settings under "OAuth Settings" + +Implementing a factory method +----------------------------- + +Creating a client and its factory method is pretty simple. You just need to implement ``Guzzle\Service\ClientInterface`` +or extend from ``Guzzle\Service\Client``. + +.. code-block:: php + + namespace mtdowling; + + use Guzzle\Common\Collection; + use Guzzle\Plugin\Oauth\OauthPlugin; + use Guzzle\Service\Client; + use Guzzle\Service\Description\ServiceDescription; + + /** + * A simple Twitter API client + */ + class TwitterClient extends Client + { + public static function factory($config = array()) + { + // Provide a hash of default client configuration options + $default = array('base_url' => 'https://api.twitter.com/1.1'); + + // The following values are required when creating the client + $required = array( + 'base_url', + 'consumer_key', + 'consumer_secret', + 'token', + 'token_secret' + ); + + // Merge in default settings and validate the config + $config = Collection::fromConfig($config, $default, $required); + + // Create a new Twitter client + $client = new self($config->get('base_url'), $config); + + // Ensure that the OauthPlugin is attached to the client + $client->addSubscriber(new OauthPlugin($config->toArray())); + + return $client; + } + } + +Service Builder +--------------- + +A service builder is used to easily create web service clients, provides a simple configuration driven approach to +creating clients, and allows you to share configuration settings across multiple clients. You can find out more about +Guzzle's service builder in :doc:`using-the-service-builder`. + +.. code-block:: php + + use Guzzle\Service\Builder\ServiceBuilder; + + // Create a service builder and provide client configuration data + $builder = ServiceBuilder::factory('/path/to/client_config.json'); + + // Get the client from the service builder by name + $twitter = $builder->get('twitter'); + +The above example assumes you have JSON data similar to the following stored in "/path/to/client_config.json": + +.. code-block:: json + + { + "services": { + "twitter": { + "class": "mtdowling\\TwitterClient", + "params": { + "consumer_key": "****", + "consumer_secret": "****", + "token": "****", + "token_secret": "****" + } + } + } + } + +.. note:: + + A service builder becomes much more valuable when using multiple web service clients in a single application or + if you need to utilize the same client with varying configuration settings (e.g. multiple accounts). + +Commands +======== + +Commands are a concept in Guzzle that helps to hide the underlying implementation of an API by providing an easy to use +parameter driven object for each action of an API. A command is responsible for accepting an array of configuration +parameters, serializing an HTTP request, and parsing an HTTP response. Following the +`command pattern `_, commands in Guzzle offer a greater level of +flexibility when implementing and utilizing a web service client. + +Executing commands +------------------ + +You must explicitly execute a command after creating a command using the ``getCommand()`` method. A command has an +``execute()`` method that may be called, or you can use the ``execute()`` method of a client object and pass in the +command object. Calling either of these execute methods will return the result value of the command. The result value is +the result of parsing the HTTP response with the ``process()`` method. + +.. code-block:: php + + // Get a command from the client and pass an array of parameters + $command = $twitter->getCommand('getMentions', array( + 'count' => 5 + )); + + // Other parameters can be set on the command after it is created + $command['trim_user'] = false; + + // Execute the command using the command object. + // The result value contains an array of JSON data from the response + $result = $command->execute(); + + // You can retrieve the result of the command later too + $result = $command->getResult(). + +Command object also contains methods that allow you to inspect the HTTP request and response that was utilized with +the command. + +.. code-block:: php + + $request = $command->getRequest(); + $response = $command->getResponse(); + +.. note:: + + The format and notation used to retrieve commands from a client can be customized by injecting a custom command + factory, ``Guzzle\Service\Command\Factory\FactoryInterface``, on the client using ``$client->setCommandFactory()``. + +Executing with magic methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using method missing magic methods with a command, the command will be executed right away and the result of the +command is returned. + +.. code-block:: php + + $jsonData = $twitter->getMentions(array( + 'count' => 5, + 'trim_user' => true + )); + +Creating commands +----------------- + +Commands are created using either the ``getCommand()`` method of a client or a magic missing method of a client. Using +the ``getCommand()`` method allows you to create a command without executing it, allowing for customization of the +command or the request serialized by the command. + +When a client attempts to create a command, it uses the client's ``Guzzle\Service\Command\Factory\FactoryInterface``. +By default, Guzzle will utilize a command factory that first looks for a concrete class for a particular command +(concrete commands) followed by a command defined by a service description (operation commands). We'll learn more about +concrete commands and operation commands later in this chapter. + +.. code-block:: php + + // Get a command from the twitter client. + $command = $twitter->getCommand('getMentions'); + $result = $command->execute(); + +Unless you've skipped ahead, running the above code will throw an exception. + + PHP Fatal error: Uncaught exception 'Guzzle\Common\Exception\InvalidArgumentException' with message + 'Command was not found matching getMentions' + +This exception was thrown because the "getMentions" command has not yet been implemented. Let's implement one now. + +Concrete commands +~~~~~~~~~~~~~~~~~ + +Commands can be created in one of two ways: create a concrete command class that extends +``Guzzle\Service\Command\AbstractCommand`` or +:doc:`create an OperationCommand based on a service description `. The recommended +approach is to use a service description to define your web service, but you can use concrete commands when custom +logic must be implemented for marshaling or unmarshaling a HTTP message. + +Commands are the method in which you abstract away the underlying format of the requests that need to be sent to take +action on a web service. Commands in Guzzle are meant to be built by executing a series of setter methods on a command +object. Commands are only validated right before they are executed. A ``Guzzle\Service\Client`` object is responsible +for executing commands. Commands created for your web service must implement +``Guzzle\Service\Command\CommandInterface``, but it's easier to extend the ``Guzzle\Service\Command\AbstractCommand`` +class, implement the ``build()`` method, and optionally implement the ``process()`` method. + +Serializing requests +^^^^^^^^^^^^^^^^^^^^ + +The ``build()`` method of a command is responsible for using the arguments of the command to build and serialize a +HTTP request and set the request on the ``$request`` property of the command object. This step is usually taken care of +for you when using a service description driven command that uses the default +``Guzzle\Service\Command\OperationCommand``. You may wish to implement the process method yourself when you aren't +using a service description or need to implement more complex request serialization. + +.. important:::: + + When implementing a custom ``build()`` method, be sure to set the class property of ``$this->request`` to an + instantiated and ready to send request. + +The following example shows how to implement the ``getMentions`` +`Twitter API `_ method using a concrete command. + +.. code-block:: php + + namespace mtdowling\Twitter\Command; + + use Guzzle\Service\Command\AbstractCommand; + + class GetMentions extends AbstractCommand + { + protected function build() + { + // Create the request property of the command + $this->request = $this->client->get('statuses/mentions_timeline.json'); + + // Grab the query object of the request because we will use it for + // serializing command parameters on the request + $query = $this->request->getQuery(); + + if ($this['count']) { + $query->set('count', $this['count']); + } + + if ($this['since_id']) { + $query->set('since_id', $this['since_id']); + } + + if ($this['max_id']) { + $query->set('max_id', $this['max_id']); + } + + if ($this['trim_user'] !== null) { + $query->set('trim_user', $this['trim_user'] ? 'true' : 'false'); + } + + if ($this['contributor_details'] !== null) { + $query->set('contributor_details', $this['contributor_details'] ? 'true' : 'false'); + } + + if ($this['include_entities'] !== null) { + $query->set('include_entities', $this['include_entities'] ? 'true' : 'false'); + } + } + } + +By default, a client will attempt to find concrete command classes under the ``Command`` namespace of a client. First +the client will attempt to find an exact match for the name of the command to the name of the command class. If an +exact match is not found, the client will calculate a class name using inflection. This is calculated based on the +folder hierarchy of a command and converting the CamelCased named commands into snake_case. Here are some examples on +how the command names are calculated: + +#. ``Foo\Command\JarJar`` **->** jar_jar +#. ``Foo\Command\Test`` **->** test +#. ``Foo\Command\People\GetCurrentPerson`` **->** people.get_current_person + +Notice how any sub-namespace beneath ``Command`` is converted from ``\`` to ``.`` (a period). CamelCasing is converted +to lowercased snake_casing (e.g. JarJar == jar_jar). + +Parsing responses +^^^^^^^^^^^^^^^^^ + +The ``process()`` method of a command is responsible for converting an HTTP response into something more useful. For +example, a service description operation that has specified a model object in the ``responseClass`` attribute of the +operation will set a ``Guzzle\Service\Resource\Model`` object as the result of the command. This behavior can be +completely modified as needed-- even if you are using operations and responseClass models. Simply implement a custom +``process()`` method that sets the ``$this->result`` class property to whatever you choose. You can reuse parts of the +default Guzzle response parsing functionality or get inspiration from existing code by using +``Guzzle\Service\Command\OperationResponseParser`` and ``Guzzle\Service\Command\DefaultResponseParser`` classes. + +If you do not implement a custom ``process()`` method and are not using a service description, then Guzzle will attempt +to guess how a response should be processed based on the Content-Type header of the response. Because the Twitter API +sets a ``Content-Type: application/json`` header on this response, we do not need to implement any custom response +parsing. + +Operation commands +~~~~~~~~~~~~~~~~~~ + +Operation commands are commands in which the serialization of an HTTP request and the parsing of an HTTP response are +driven by a Guzzle service description. Because request serialization, validation, and response parsing are +described using a DSL, creating operation commands is a much faster process than writing concrete commands. + +Creating operation commands for our Twitter client can remove a great deal of redundancy from the previous concrete +command, and allows for a deeper runtime introspection of the API. Here's an example service description we can use to +create the Twitter API client: + +.. code-block:: json + + { + "name": "Twitter", + "apiVersion": "1.1", + "baseUrl": "https://api.twitter.com/1.1", + "description": "Twitter REST API client", + "operations": { + "GetMentions": { + "httpMethod": "GET", + "uri": "statuses/mentions_timeline.json", + "summary": "Returns the 20 most recent mentions for the authenticating user.", + "responseClass": "GetMentionsOutput", + "parameters": { + "count": { + "description": "Specifies the number of tweets to try and retrieve", + "type": "integer", + "location": "query" + }, + "since_id": { + "description": "Returns results with an ID greater than the specified ID", + "type": "integer", + "location": "query" + }, + "max_id": { + "description": "Returns results with an ID less than or equal to the specified ID.", + "type": "integer", + "location": "query" + }, + "trim_user": { + "description": "Limits the amount of data returned for each user", + "type": "boolean", + "location": "query" + }, + "contributor_details": { + "description": "Adds more data to contributor elements", + "type": "boolean", + "location": "query" + }, + "include_entities": { + "description": "The entities node will be disincluded when set to false.", + "type": "boolean", + "location": "query" + } + } + } + }, + "models": { + "GetMentionsOutput": { + "type": "object", + "additionalProperties": { + "location": "json" + } + } + } + } + +If you're lazy, you can define the API in a less descriptive manner using ``additionalParameters``. +``additionalParameters`` define the serialization and validation rules of parameters that are not explicitly defined +in a service description. + +.. code-block:: json + + { + "name": "Twitter", + "apiVersion": "1.1", + "baseUrl": "https://api.twitter.com/1.1", + "description": "Twitter REST API client", + "operations": { + "GetMentions": { + "httpMethod": "GET", + "uri": "statuses/mentions_timeline.json", + "summary": "Returns the 20 most recent mentions for the authenticating user.", + "responseClass": "GetMentionsOutput", + "additionalParameters": { + "location": "query" + } + } + }, + "models": { + "GetMentionsOutput": { + "type": "object", + "additionalProperties": { + "location": "json" + } + } + } + } + +You should attach the service description to the client at the end of the client's factory method: + +.. code-block:: php + + // ... + class TwitterClient extends Client + { + public static function factory($config = array()) + { + // ... same code as before ... + + // Set the service description + $client->setDescription(ServiceDescription::factory('path/to/twitter.json')); + + return $client; + } + } + +The client can now use operations defined in the service description instead of requiring you to create concrete +command classes. Feel free to delete the concrete command class we created earlier. + +.. code-block:: php + + $jsonData = $twitter->getMentions(array( + 'count' => 5, + 'trim_user' => true + )); + +Executing commands in parallel +------------------------------ + +Much like HTTP requests, Guzzle allows you to send multiple commands in parallel. You can send commands in parallel by +passing an array of command objects to a client's ``execute()`` method. The client will serialize each request and +send them all in parallel. If an error is encountered during the transfer, then a +``Guzzle\Service\Exception\CommandTransferException`` is thrown, which allows you to retrieve a list of commands that +succeeded and a list of commands that failed. + +.. code-block:: php + + use Guzzle\Service\Exception\CommandTransferException; + + $commands = array(); + $commands[] = $twitter->getCommand('getMentions'); + $commands[] = $twitter->getCommand('otherCommandName'); + // etc... + + try { + $result = $client->execute($commands); + foreach ($result as $command) { + echo $command->getName() . ': ' . $command->getResponse()->getStatusCode() . "\n"; + } + } catch (CommandTransferException $e) { + // Get an array of the commands that succeeded + foreach ($e->getSuccessfulCommands() as $command) { + echo $command->getName() . " succeeded\n"; + } + // Get an array of the commands that failed + foreach ($e->getFailedCommands() as $command) { + echo $command->getName() . " failed\n"; + } + } + +.. note:: + + All commands executed from a client using an array must originate from the same client. + +Special command options +----------------------- + +Guzzle exposes several options that help to control how commands are validated, serialized, and parsed. +Command options can be specified when creating a command or in the ``command.params`` parameter in the +``Guzzle\Service\Client``. + +=========================== ============================================================================================ +command.request_options Option used to add :ref:`Request options ` to the request created by a + command +command.hidden_params An array of the names of parameters ignored by the ``additionalParameters`` parameter schema +command.disable_validation Set to true to disable JSON schema validation of the command's input parameters +command.response_processing Determines how the default response parser will parse the command. One of "raw" no parsing, + "model" (the default method used to parse commands using response models defined in service + descriptions) +command.headers (deprecated) Option used to specify custom headers. Use ``command.request_options`` instead +command.on_complete (deprecated) Option used to add an onComplete method to a command. Use + ``command.after_send`` event instead +command.response_body (deprecated) Option used to change the entity body used to store a response. + Use ``command.request_options`` instead +=========================== ============================================================================================ + +Advanced client configuration +============================= + +Default command parameters +-------------------------- + +When creating a client object, you can specify default command parameters to pass into all commands. Any key value pair +present in the ``command.params`` settings of a client will be added as default parameters to any command created +by the client. + +.. code-block:: php + + $client = new Guzzle\Service\Client(array( + 'command.params' => array( + 'default_1' => 'foo', + 'another' => 'bar' + ) + )); + +Magic methods +------------- + +Client objects will, by default, attempt to create and execute commands when a missing method is invoked on a client. +This powerful concept applies to both concrete commands and operation commands powered by a service description. This +makes it appear to the end user that you have defined actual methods on a client object, when in fact, the methods are +invoked using PHP's magic ``__call`` method. + +The ``__call`` method uses the ``getCommand()`` method of a client, which uses the client's internal +``Guzzle\Service\Command\Factory\FactoryInterface`` object. The default command factory allows you to instantiate +operations defined in a client's service description. The method in which a client determines which command to +execute is defined as follows: + +1. The client will first try to find a literal match for an operation in the service description. +2. If the literal match is not found, the client will try to uppercase the first character of the operation and find + the match again. +3. If a match is still not found, the command factory will inflect the method name from CamelCase to snake_case and + attempt to find a matching command. +4. If a command still does not match, an exception is thrown. + +.. code-block:: php + + // Use the magic method + $result = $twitter->getMentions(); + + // This is exactly the same as: + $result = $twitter->getCommand('getMentions')->execute(); + +You can disable magic methods on a client by passing ``false`` to the ``enableMagicMethod()`` method. + +Custom command factory +---------------------- + +A client by default uses the ``Guzzle\Service\Command\Factory\CompositeFactory`` which allows multiple command +factories to attempt to create a command by a certain name. The default CompositeFactory uses a ``ConcreteClassFactory`` +and a ``ServiceDescriptionFactory`` if a service description is specified on a client. You can specify a custom +command factory if your client requires custom command creation logic using the ``setCommandFactory()`` method of +a client. + +Custom resource Iterator factory +-------------------------------- + +Resource iterators can be retrieved from a client using the ``getIterator($name)`` method of a client. This method uses +a client's internal ``Guzzle\Service\Resource\ResourceIteratorFactoryInterface`` object. A client by default uses a +``Guzzle\Service\Resource\ResourceIteratorClassFactory`` to attempt to find concrete classes that implement resource +iterators. The default factory will first look for matching iterators in the ``Iterator`` subdirectory of the client +followed by the ``Model`` subdirectory of a client. Use the ``setResourceIteratorFactory()`` method of a client to +specify a custom resource iterator factory. + +Plugins and events +================== + +``Guzzle\Service\Client`` exposes various events that allow you to hook in custom logic. A client object owns a +``Symfony\Component\EventDispatcher\EventDispatcher`` object that can be accessed by calling +``$client->getEventDispatcher()``. You can use the event dispatcher to add listeners (a simple callback function) or +event subscribers (classes that listen to specific events of a dispatcher). + +.. _service-client-events: + +Events emitted from a Service Client +------------------------------------ + +A ``Guzzle\Service\Client`` object emits the following events: + ++------------------------------+--------------------------------------------+------------------------------------------+ +| Event name | Description | Event data | ++==============================+============================================+==========================================+ +| client.command.create | The client created a command object | * client: Client object | +| | | * command: Command object | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.before_prepare | Before a command is validated and built. | * command: Command being prepared | +| | This is also before a request is created. | | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.after_prepare | After a command instantiates and | * command: Command that was prepared | +| | configures its request object. | | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.before_send | The client is about to execute a prepared | * command: Command to execute | +| | command | | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.after_send | The client successfully completed | * command: The command that was executed | +| | executing a command | | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.parse_response | Called when ``responseType`` is ``class`` | * command: The command with a response | +| | and the response is about to be parsed. | about to be parsed. | ++------------------------------+--------------------------------------------+------------------------------------------+ + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Service\Client; + + $client = new Client(); + + // create an event listener that operates on request objects + $client->getEventDispatcher()->addListener('command.after_prepare', function (Event $event) { + $command = $event['command']; + $request = $command->getRequest(); + + // do something with request + }); + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Common\Client; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + + class EventSubscriber implements EventSubscriberInterface + { + public static function getSubscribedEvents() + { + return array( + 'client.command.create' => 'onCommandCreate', + 'command.parse_response' => 'onParseResponse' + ); + } + + public function onCommandCreate(Event $event) + { + $client = $event['client']; + $command = $event['command']; + // operate on client and command + } + + public function onParseResponse(Event $event) + { + $command = $event['command']; + // operate on the command + } + } + + $client = new Client(); + + $client->addSubscriber(new EventSubscriber()); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/phar-stub.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phar-stub.php new file mode 100644 index 000000000..cc2b53f4f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phar-stub.php @@ -0,0 +1,16 @@ +registerNamespaces(array( + 'Guzzle' => 'phar://guzzle.phar/src', + 'Symfony\\Component\\EventDispatcher' => 'phar://guzzle.phar/vendor/symfony/event-dispatcher', + 'Doctrine' => 'phar://guzzle.phar/vendor/doctrine/common/lib', + 'Monolog' => 'phar://guzzle.phar/vendor/monolog/monolog/src' +)); +$classLoader->register(); + +__HALT_COMPILER(); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/build.properties.dist b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/build.properties.dist new file mode 100644 index 000000000..c60d3d9cf --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/build.properties.dist @@ -0,0 +1,16 @@ +# you may need to update this if you're working on a fork. +guzzle.remote=git@github.com:guzzle/guzzle.git + +# github credentials -- only used by GitHub API calls to create subtree repos +github.basicauth=username:password +# for the subtree split and testing +github.org=guzzle + +# your git path +cmd.git=git + +# your composer command +cmd.composer=composer + +# test server start +cmd.testserver=node diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/imports/dependencies.xml b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/imports/dependencies.xml new file mode 100644 index 000000000..e40e037c2 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/imports/dependencies.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + using git at ${cmd.git} + + + + found git at ${cmd.git} + + + + + + + + + + diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/imports/deploy.xml b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/imports/deploy.xml new file mode 100644 index 000000000..109e5ec4f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/imports/deploy.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + On branch ${head} + + + + + + + + + + working directory clean + + + ${git.status} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ChangeLog Match: ${version.changelog} + Guzzle\Common\Version Match: ${version.version} + + + + releasing: phing -Dnew.version=3.0.x -Dhead=master release + -- + + + + + + + + + + + + + + + BEGINNING RELEASE FOR ${new.version} + + + + + + + + + + + + + + + + + + + + + + + + Tip: to create a new release, do: phing -Dnew.version=[TAG] -Dhead=[BRANCH] release + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php new file mode 100644 index 000000000..3b7040982 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php @@ -0,0 +1,152 @@ + + * @license http://claylo.mit-license.org/2012/ MIT License + */ + +require_once 'phing/Task.php'; + +class ComposerLintTask extends Task +{ + protected $dir = null; + protected $file = null; + protected $passthru = false; + protected $composer = null; + + /** + * The setter for the dir + * + * @param string $str Directory to crawl recursively for composer files + */ + public function setDir($str) + { + $this->dir = $str; + } + + /** + * The setter for the file + * + * @param string $str Individual file to validate + */ + public function setFile($str) + { + $this->file = $str; + } + + /** + * Whether to use PHP's passthru() function instead of exec() + * + * @param boolean $passthru If passthru shall be used + */ + public function setPassthru($passthru) + { + $this->passthru = (bool) $passthru; + } + + /** + * Composer to execute. If unset, will attempt composer.phar in project + * basedir, and if that fails, will attempt global composer + * installation. + * + * @param string $str Individual file to validate + */ + public function setComposer($str) + { + $this->file = $str; + } + + /** + * The init method: do init steps + */ + public function init() + { + // nothing needed here + } + + /** + * The main entry point + */ + public function main() + { + if ($this->composer === null) { + $this->findComposer(); + } + + $files = array(); + if (!empty($this->file) && file_exists($this->file)) { + $files[] = $this->file; + } + + if (!empty($this->dir)) { + $found = $this->findFiles(); + foreach ($found as $file) { + $files[] = $this->dir . DIRECTORY_SEPARATOR . $file; + } + } + + foreach ($files as $file) { + + $cmd = $this->composer . ' validate ' . $file; + $cmd = escapeshellcmd($cmd); + + if ($this->passthru) { + $retval = null; + passthru($cmd, $retval); + if ($retval == 1) { + throw new BuildException('invalid composer.json'); + } + } else { + $out = array(); + $retval = null; + exec($cmd, $out, $retval); + if ($retval == 1) { + $err = join("\n", $out); + throw new BuildException($err); + } else { + $this->log($out[0]); + } + } + + } + + } + + /** + * Find the composer.json files using Phing's directory scanner + * + * @return array + */ + protected function findFiles() + { + $ds = new DirectoryScanner(); + $ds->setBasedir($this->dir); + $ds->setIncludes(array('**/composer.json')); + $ds->scan(); + return $ds->getIncludedFiles(); + } + + /** + * Find composer installation + * + */ + protected function findComposer() + { + $basedir = $this->project->getBasedir(); + $php = $this->project->getProperty('php.interpreter'); + + if (file_exists($basedir . '/composer.phar')) { + $this->composer = "$php $basedir/composer.phar"; + } else { + $out = array(); + exec('which composer', $out); + if (empty($out)) { + throw new BuildException( + 'Could not determine composer location.' + ); + } + $this->composer = $out[0]; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php new file mode 100644 index 000000000..f72a6b5d0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php @@ -0,0 +1,338 @@ + + * @license http://claylo.mit-license.org/2012/ MIT License + */ + +require_once 'phing/Task.php'; +require_once 'PEAR/PackageFileManager2.php'; +require_once 'PEAR/PackageFileManager/File.php'; +require_once 'PEAR/Packager.php'; + +class GuzzlePearPharPackageTask extends Task +{ + private $version; + private $deploy = true; + private $makephar = true; + + private $subpackages = array(); + + public function setVersion($str) + { + $this->version = $str; + } + + public function getVersion() + { + return $this->version; + } + + public function setDeploy($deploy) + { + $this->deploy = (bool) $deploy; + } + + public function getDeploy() + { + return $this->deploy; + } + + public function setMakephar($makephar) + { + $this->makephar = (bool) $makephar; + } + + public function getMakephar() + { + return $this->makephar; + } + + private $basedir; + private $guzzleinfo; + private $changelog_release_date; + private $changelog_notes = '-'; + + public function main() + { + $this->basedir = $this->getProject()->getBasedir(); + + if (!is_dir((string) $this->basedir.'/.subsplit')) { + throw new BuildException('PEAR packaging requires .subsplit directory'); + } + + // main composer file + $composer_file = file_get_contents((string) $this->basedir.'/.subsplit/composer.json'); + $this->guzzleinfo = json_decode($composer_file, true); + + // make sure we have a target + $pearwork = (string) $this->basedir . '/build/pearwork'; + if (!is_dir($pearwork)) { + mkdir($pearwork, 0777, true); + } + $pearlogs = (string) $this->basedir . '/build/artifacts/logs'; + if (!is_dir($pearlogs)) { + mkdir($pearlogs, 0777, true); + } + + $version = $this->getVersion(); + $this->grabChangelog(); + if ($version[0] == '2') { + $this->log('building single PEAR package'); + $this->buildSinglePackage(); + } else { + // $this->log("building PEAR subpackages"); + // $this->createSubPackages(); + // $this->log("building PEAR bundle package"); + $this->buildSinglePackage(); + } + + if ($this->getMakephar()) { + $this->log("building PHAR"); + $this->getProject()->executeTarget('package-phar'); + } + + if ($this->getDeploy()) { + $this->doDeployment(); + } + } + + public function doDeployment() + { + $basedir = (string) $this->basedir; + $this->log('beginning PEAR/PHAR deployment'); + + chdir($basedir . '/build/pearwork'); + if (!is_dir('./channel')) { + mkdir('./channel'); + } + + // Pull the PEAR channel down locally + passthru('aws s3 sync s3://pear.guzzlephp.org ./channel'); + + // add PEAR packages + foreach (scandir('./') as $file) { + if (substr($file, -4) == '.tgz') { + passthru('pirum add ./channel ' . $file); + } + } + + // if we have a new phar, add it + if ($this->getMakephar() && file_exists($basedir . '/build/artifacts/guzzle.phar')) { + rename($basedir . '/build/artifacts/guzzle.phar', './channel/guzzle.phar'); + } + + // Sync up with the S3 bucket + chdir($basedir . '/build/pearwork/channel'); + passthru('aws s3 sync . s3://pear.guzzlephp.org'); + } + + public function buildSinglePackage() + { + $v = $this->getVersion(); + $apiversion = $v[0] . '.0.0'; + + $opts = array( + 'packagedirectory' => (string) $this->basedir . '/.subsplit/src/', + 'filelistgenerator' => 'file', + 'ignore' => array('*composer.json'), + 'baseinstalldir' => '/', + 'packagefile' => 'package.xml' + //'outputdirectory' => (string) $this->basedir . '/build/pearwork/' + ); + $pfm = new PEAR_PackageFileManager2(); + $pfm->setOptions($opts); + $pfm->addRole('md', 'doc'); + $pfm->addRole('pem', 'php'); + $pfm->setPackage('Guzzle'); + $pfm->setSummary("Object-oriented PHP HTTP Client for PHP 5.3+"); + $pfm->setDescription($this->guzzleinfo['description']); + $pfm->setPackageType('php'); + $pfm->setChannel('guzzlephp.org/pear'); + $pfm->setAPIVersion($apiversion); + $pfm->setReleaseVersion($this->getVersion()); + $pfm->setAPIStability('stable'); + $pfm->setReleaseStability('stable'); + $pfm->setNotes($this->changelog_notes); + $pfm->setPackageType('php'); + $pfm->setLicense('MIT', 'http://github.com/guzzle/guzzle/blob/master/LICENSE'); + $pfm->addMaintainer('lead', 'mtdowling', 'Michael Dowling', 'mtdowling@gmail.com', 'yes'); + $pfm->setDate($this->changelog_release_date); + $pfm->generateContents(); + + $phpdep = $this->guzzleinfo['require']['php']; + $phpdep = str_replace('>=', '', $phpdep); + $pfm->setPhpDep($phpdep); + $pfm->addExtensionDep('required', 'curl'); + $pfm->setPearinstallerDep('1.4.6'); + $pfm->addPackageDepWithChannel('required', 'EventDispatcher', 'pear.symfony.com', '2.1.0'); + if (!empty($this->subpackages)) { + foreach ($this->subpackages as $package) { + $pkg = dirname($package); + $pkg = str_replace('/', '_', $pkg); + $pfm->addConflictingPackageDepWithChannel($pkg, 'guzzlephp.org/pear', false, $apiversion); + } + } + + ob_start(); + $startdir = getcwd(); + chdir((string) $this->basedir . '/build/pearwork'); + + echo "DEBUGGING GENERATED PACKAGE FILE\n"; + $result = $pfm->debugPackageFile(); + if ($result) { + $out = $pfm->writePackageFile(); + echo "\n\n\nWRITE PACKAGE FILE RESULT:\n"; + var_dump($out); + // load up package file and build package + $packager = new PEAR_Packager(); + echo "\n\n\nBUILDING PACKAGE FROM PACKAGE FILE:\n"; + $dest_package = $packager->package($opts['packagedirectory'].'package.xml'); + var_dump($dest_package); + } else { + echo "\n\n\nDEBUGGING RESULT:\n"; + var_dump($result); + } + echo "removing package.xml"; + unlink($opts['packagedirectory'].'package.xml'); + $log = ob_get_clean(); + file_put_contents((string) $this->basedir . '/build/artifacts/logs/pear_package.log', $log); + chdir($startdir); + } + + public function createSubPackages() + { + $this->findComponents(); + + foreach ($this->subpackages as $package) { + $baseinstalldir = dirname($package); + $dir = (string) $this->basedir.'/.subsplit/src/' . $baseinstalldir; + $composer_file = file_get_contents((string) $this->basedir.'/.subsplit/src/'. $package); + $package_info = json_decode($composer_file, true); + $this->log('building ' . $package_info['target-dir'] . ' subpackage'); + $this->buildSubPackage($dir, $baseinstalldir, $package_info); + } + } + + public function buildSubPackage($dir, $baseinstalldir, $info) + { + $package = str_replace('/', '_', $baseinstalldir); + $opts = array( + 'packagedirectory' => $dir, + 'filelistgenerator' => 'file', + 'ignore' => array('*composer.json', '*package.xml'), + 'baseinstalldir' => '/' . $info['target-dir'], + 'packagefile' => 'package.xml' + ); + $pfm = new PEAR_PackageFileManager2(); + $pfm->setOptions($opts); + $pfm->setPackage($package); + $pfm->setSummary($info['description']); + $pfm->setDescription($info['description']); + $pfm->setPackageType('php'); + $pfm->setChannel('guzzlephp.org/pear'); + $pfm->setAPIVersion('3.0.0'); + $pfm->setReleaseVersion($this->getVersion()); + $pfm->setAPIStability('stable'); + $pfm->setReleaseStability('stable'); + $pfm->setNotes($this->changelog_notes); + $pfm->setPackageType('php'); + $pfm->setLicense('MIT', 'http://github.com/guzzle/guzzle/blob/master/LICENSE'); + $pfm->addMaintainer('lead', 'mtdowling', 'Michael Dowling', 'mtdowling@gmail.com', 'yes'); + $pfm->setDate($this->changelog_release_date); + $pfm->generateContents(); + + $phpdep = $this->guzzleinfo['require']['php']; + $phpdep = str_replace('>=', '', $phpdep); + $pfm->setPhpDep($phpdep); + $pfm->setPearinstallerDep('1.4.6'); + + foreach ($info['require'] as $type => $version) { + if ($type == 'php') { + continue; + } + if ($type == 'symfony/event-dispatcher') { + $pfm->addPackageDepWithChannel('required', 'EventDispatcher', 'pear.symfony.com', '2.1.0'); + } + if ($type == 'ext-curl') { + $pfm->addExtensionDep('required', 'curl'); + } + if (substr($type, 0, 6) == 'guzzle') { + $gdep = str_replace('/', ' ', $type); + $gdep = ucwords($gdep); + $gdep = str_replace(' ', '_', $gdep); + $pfm->addPackageDepWithChannel('required', $gdep, 'guzzlephp.org/pear', $this->getVersion()); + } + } + + // can't have main Guzzle package AND sub-packages + $pfm->addConflictingPackageDepWithChannel('Guzzle', 'guzzlephp.org/pear', false, $apiversion); + + ob_start(); + $startdir = getcwd(); + chdir((string) $this->basedir . '/build/pearwork'); + + echo "DEBUGGING GENERATED PACKAGE FILE\n"; + $result = $pfm->debugPackageFile(); + if ($result) { + $out = $pfm->writePackageFile(); + echo "\n\n\nWRITE PACKAGE FILE RESULT:\n"; + var_dump($out); + // load up package file and build package + $packager = new PEAR_Packager(); + echo "\n\n\nBUILDING PACKAGE FROM PACKAGE FILE:\n"; + $dest_package = $packager->package($opts['packagedirectory'].'/package.xml'); + var_dump($dest_package); + } else { + echo "\n\n\nDEBUGGING RESULT:\n"; + var_dump($result); + } + echo "removing package.xml"; + unlink($opts['packagedirectory'].'/package.xml'); + $log = ob_get_clean(); + file_put_contents((string) $this->basedir . '/build/artifacts/logs/pear_package_'.$package.'.log', $log); + chdir($startdir); + } + + public function findComponents() + { + $ds = new DirectoryScanner(); + $ds->setBasedir((string) $this->basedir.'/.subsplit/src'); + $ds->setIncludes(array('**/composer.json')); + $ds->scan(); + $files = $ds->getIncludedFiles(); + $this->subpackages = $files; + } + + public function grabChangelog() + { + $cl = file((string) $this->basedir.'/.subsplit/CHANGELOG.md'); + $notes = ''; + $in_version = false; + $release_date = null; + + foreach ($cl as $line) { + $line = trim($line); + if (preg_match('/^\* '.$this->getVersion().' \(([0-9\-]+)\)$/', $line, $matches)) { + $release_date = $matches[1]; + $in_version = true; + continue; + } + if ($in_version && empty($line) && empty($notes)) { + continue; + } + if ($in_version && ! empty($line)) { + $notes .= $line."\n"; + } + if ($in_version && empty($line) && !empty($notes)) { + $in_version = false; + } + } + $this->changelog_release_date = $release_date; + + if (! empty($notes)) { + $this->changelog_notes = $notes; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php new file mode 100644 index 000000000..5d56a5bd5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php @@ -0,0 +1,385 @@ + + * @license http://claylo.mit-license.org/2012/ MIT License + */ + +require_once 'phing/tasks/ext/git/GitBaseTask.php'; + +// base - base of tree to split out +// subIndicatorFile - composer.json, package.xml? +class GuzzleSubSplitTask extends GitBaseTask +{ + /** + * What git repository to pull from and publish to + */ + protected $remote = null; + + /** + * Publish for comma-separated heads instead of all heads + */ + protected $heads = null; + + /** + * Publish for comma-separated tags instead of all tags + */ + protected $tags = null; + + /** + * Base of the tree RELATIVE TO .subsplit working dir + */ + protected $base = null; + + /** + * The presence of this file will indicate that the directory it resides + * in is at the top level of a split. + */ + protected $subIndicatorFile = 'composer.json'; + + /** + * Do everything except actually send the update. + */ + protected $dryRun = null; + + /** + * Do not sync any heads. + */ + protected $noHeads = false; + + /** + * Do not sync any tags. + */ + protected $noTags = false; + + /** + * The splits we found in the heads + */ + protected $splits; + + public function setRemote($str) + { + $this->remote = $str; + } + + public function getRemote() + { + return $this->remote; + } + + public function setHeads($str) + { + $this->heads = explode(',', $str); + } + + public function getHeads() + { + return $this->heads; + } + + public function setTags($str) + { + $this->tags = explode(',', $str); + } + + public function getTags() + { + return $this->tags; + } + + public function setBase($str) + { + $this->base = $str; + } + + public function getBase() + { + return $this->base; + } + + public function setSubIndicatorFile($str) + { + $this->subIndicatorFile = $str; + } + + public function getSubIndicatorFile() + { + return $this->subIndicatorFile; + } + + public function setDryRun($bool) + { + $this->dryRun = (bool) $bool; + } + + public function getDryRun() + { + return $this->dryRun; + } + + public function setNoHeads($bool) + { + $this->noHeads = (bool) $bool; + } + + public function getNoHeads() + { + return $this->noHeads; + } + + public function setNoTags($bool) + { + $this->noTags = (bool) $bool; + } + + public function getNoTags() + { + return $this->noTags; + } + + /** + * GitClient from VersionControl_Git + */ + protected $client = null; + + /** + * The main entry point + */ + public function main() + { + $repo = $this->getRepository(); + if (empty($repo)) { + throw new BuildException('"repository" is a required parameter'); + } + + $remote = $this->getRemote(); + if (empty($remote)) { + throw new BuildException('"remote" is a required parameter'); + } + + chdir($repo); + $this->client = $this->getGitClient(false, $repo); + + // initalized yet? + if (!is_dir('.subsplit')) { + $this->subsplitInit(); + } else { + // update + $this->subsplitUpdate(); + } + + // find all splits based on heads requested + $this->findSplits(); + + // check that GitHub has the repos + $this->verifyRepos(); + + // execute the subsplits + $this->publish(); + } + + public function publish() + { + $this->log('DRY RUN ONLY FOR NOW'); + $base = $this->getBase(); + $base = rtrim($base, '/') . '/'; + $org = $this->getOwningTarget()->getProject()->getProperty('github.org'); + + $splits = array(); + + $heads = $this->getHeads(); + foreach ($heads as $head) { + foreach ($this->splits[$head] as $component => $meta) { + $splits[] = $base . $component . ':git@github.com:'. $org.'/'.$meta['repo']; + } + + $cmd = 'git subsplit publish '; + $cmd .= escapeshellarg(implode(' ', $splits)); + + if ($this->getNoHeads()) { + $cmd .= ' --no-heads'; + } else { + $cmd .= ' --heads='.$head; + } + + if ($this->getNoTags()) { + $cmd .= ' --no-tags'; + } else { + if ($this->getTags()) { + $cmd .= ' --tags=' . escapeshellarg(implode(' ', $this->getTags())); + } + } + + passthru($cmd); + } + } + + /** + * Runs `git subsplit update` + */ + public function subsplitUpdate() + { + $repo = $this->getRepository(); + $this->log('git-subsplit update...'); + $cmd = $this->client->getCommand('subsplit'); + $cmd->addArgument('update'); + try { + $cmd->execute(); + } catch (Exception $e) { + throw new BuildException('git subsplit update failed'. $e); + } + chdir($repo . '/.subsplit'); + passthru('php ../composer.phar update --dev'); + chdir($repo); + } + + /** + * Runs `git subsplit init` based on the remote repository. + */ + public function subsplitInit() + { + $remote = $this->getRemote(); + $cmd = $this->client->getCommand('subsplit'); + $this->log('running git-subsplit init ' . $remote); + + $cmd->setArguments(array( + 'init', + $remote + )); + + try { + $output = $cmd->execute(); + } catch (Exception $e) { + throw new BuildException('git subsplit init failed'. $e); + } + $this->log(trim($output), Project::MSG_INFO); + $repo = $this->getRepository(); + chdir($repo . '/.subsplit'); + passthru('php ../composer.phar install --dev'); + chdir($repo); + } + + /** + * Find the composer.json files using Phing's directory scanner + * + * @return array + */ + protected function findSplits() + { + $this->log("checking heads for subsplits"); + $repo = $this->getRepository(); + $base = $this->getBase(); + + $splits = array(); + $heads = $this->getHeads(); + + if (!empty($base)) { + $base = '/' . ltrim($base, '/'); + } else { + $base = '/'; + } + + chdir($repo . '/.subsplit'); + foreach ($heads as $head) { + $splits[$head] = array(); + + // check each head requested *BEFORE* the actual subtree split command gets it + passthru("git checkout '$head'"); + $ds = new DirectoryScanner(); + $ds->setBasedir($repo . '/.subsplit' . $base); + $ds->setIncludes(array('**/'.$this->subIndicatorFile)); + $ds->scan(); + $files = $ds->getIncludedFiles(); + + // Process the files we found + foreach ($files as $file) { + $pkg = file_get_contents($repo . '/.subsplit' . $base .'/'. $file); + $pkg_json = json_decode($pkg, true); + $name = $pkg_json['name']; + $component = str_replace('/composer.json', '', $file); + // keep this for split cmd + $tmpreponame = explode('/', $name); + $reponame = $tmpreponame[1]; + $splits[$head][$component]['repo'] = $reponame; + $nscomponent = str_replace('/', '\\', $component); + $splits[$head][$component]['desc'] = "[READ ONLY] Subtree split of $nscomponent: " . $pkg_json['description']; + } + } + + // go back to how we found it + passthru("git checkout master"); + chdir($repo); + $this->splits = $splits; + } + + /** + * Based on list of repositories we determined we *should* have, talk + * to GitHub and make sure they're all there. + * + */ + protected function verifyRepos() + { + $this->log('verifying GitHub target repos'); + $github_org = $this->getOwningTarget()->getProject()->getProperty('github.org'); + $github_creds = $this->getOwningTarget()->getProject()->getProperty('github.basicauth'); + + if ($github_creds == 'username:password') { + $this->log('Skipping GitHub repo checks. Update github.basicauth in build.properties to verify repos.', 1); + return; + } + + $ch = curl_init('https://api.github.com/orgs/'.$github_org.'/repos?type=all'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_USERPWD, $github_creds); + // change this when we know we can use our bundled CA bundle! + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + $result = curl_exec($ch); + curl_close($ch); + $repos = json_decode($result, true); + $existing_repos = array(); + + // parse out the repos we found on GitHub + foreach ($repos as $repo) { + $tmpreponame = explode('/', $repo['full_name']); + $reponame = $tmpreponame[1]; + $existing_repos[$reponame] = $repo['description']; + } + + $heads = $this->getHeads(); + foreach ($heads as $head) { + foreach ($this->splits[$head] as $component => $meta) { + + $reponame = $meta['repo']; + + if (!isset($existing_repos[$reponame])) { + $this->log("Creating missing repo $reponame"); + $payload = array( + 'name' => $reponame, + 'description' => $meta['desc'], + 'homepage' => 'http://www.guzzlephp.org/', + 'private' => true, + 'has_issues' => false, + 'has_wiki' => false, + 'has_downloads' => true, + 'auto_init' => false + ); + $ch = curl_init('https://api.github.com/orgs/'.$github_org.'/repos'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_USERPWD, $github_creds); + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + // change this when we know we can use our bundled CA bundle! + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + $result = curl_exec($ch); + echo "Response code: ".curl_getinfo($ch, CURLINFO_HTTP_CODE)."\n"; + curl_close($ch); + } else { + $this->log("Repo $reponame exists", 2); + } + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/phpunit.xml.dist b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phpunit.xml.dist new file mode 100644 index 000000000..208fdc08e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/phpunit.xml.dist @@ -0,0 +1,48 @@ + + + + + + ./tests/Guzzle/Tests + + + + + + + + + + ./src/Guzzle + + ./src/Guzzle + ./src/Guzzle/Common/Exception/GuzzleException.php + ./src/Guzzle/Http/Exception/HttpException.php + ./src/Guzzle/Http/Exception/ServerErrorResponseException.php + ./src/Guzzle/Http/Exception/ClientErrorResponseException.php + ./src/Guzzle/Http/Exception/TooManyRedirectsException.php + ./src/Guzzle/Http/Exception/CouldNotRewindStreamException.php + ./src/Guzzle/Common/Exception/BadMethodCallException.php + ./src/Guzzle/Common/Exception/InvalidArgumentException.php + ./src/Guzzle/Common/Exception/RuntimeException.php + ./src/Guzzle/Common/Exception/UnexpectedValueException.php + ./src/Guzzle/Service/Exception/ClientNotFoundException.php + ./src/Guzzle/Service/Exception/CommandException.php + ./src/Guzzle/Service/Exception/DescriptionBuilderException.php + ./src/Guzzle/Service/Exception/ServiceBuilderException.php + ./src/Guzzle/Service/Exception/ServiceNotFoundException.php + ./src/Guzzle/Service/Exception/ValidationException.php + ./src/Guzzle/Service/Exception/JsonException.php + + + + + diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php new file mode 100644 index 000000000..0625d71c3 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php @@ -0,0 +1,66 @@ +decoratedBatch = $decoratedBatch; + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + * @codeCoverageIgnore + */ + public function __call($method, array $args) + { + return call_user_func_array(array($this->decoratedBatch, $method), $args); + } + + public function add($item) + { + $this->decoratedBatch->add($item); + + return $this; + } + + public function flush() + { + return $this->decoratedBatch->flush(); + } + + public function isEmpty() + { + return $this->decoratedBatch->isEmpty(); + } + + /** + * Trace the decorators associated with the batch + * + * @return array + */ + public function getDecorators() + { + $found = array($this); + if (method_exists($this->decoratedBatch, 'getDecorators')) { + $found = array_merge($found, $this->decoratedBatch->getDecorators()); + } + + return $found; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php new file mode 100644 index 000000000..4d41c54f8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php @@ -0,0 +1,92 @@ +transferStrategy = $transferStrategy; + $this->divisionStrategy = $divisionStrategy; + $this->queue = new \SplQueue(); + $this->queue->setIteratorMode(\SplQueue::IT_MODE_DELETE); + $this->dividedBatches = array(); + } + + public function add($item) + { + $this->queue->enqueue($item); + + return $this; + } + + public function flush() + { + $this->createBatches(); + + $items = array(); + foreach ($this->dividedBatches as $batchIndex => $dividedBatch) { + while ($dividedBatch->valid()) { + $batch = $dividedBatch->current(); + $dividedBatch->next(); + try { + $this->transferStrategy->transfer($batch); + $items = array_merge($items, $batch); + } catch (\Exception $e) { + throw new BatchTransferException($batch, $items, $e, $this->transferStrategy, $this->divisionStrategy); + } + } + // Keep the divided batch down to a minimum in case of a later exception + unset($this->dividedBatches[$batchIndex]); + } + + return $items; + } + + public function isEmpty() + { + return count($this->queue) == 0 && count($this->dividedBatches) == 0; + } + + /** + * Create batches for any queued items + */ + protected function createBatches() + { + if (count($this->queue)) { + if ($batches = $this->divisionStrategy->createBatches($this->queue)) { + // Convert arrays into iterators + if (is_array($batches)) { + $batches = new \ArrayIterator($batches); + } + $this->dividedBatches[] = $batches; + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php new file mode 100644 index 000000000..ea99b4dd0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php @@ -0,0 +1,199 @@ + 'Guzzle\Batch\BatchRequestTransfer', + 'command' => 'Guzzle\Batch\BatchCommandTransfer' + ); + + /** + * Create a new instance of the BatchBuilder + * + * @return BatchBuilder + */ + public static function factory() + { + return new self(); + } + + /** + * Automatically flush the batch when the size of the queue reaches a certain threshold. Adds {@see FlushingBatch}. + * + * @param $threshold Number of items to allow in the queue before a flush + * + * @return BatchBuilder + */ + public function autoFlushAt($threshold) + { + $this->autoFlush = $threshold; + + return $this; + } + + /** + * Maintain a history of all items that have been transferred using the batch. Adds {@see HistoryBatch}. + * + * @return BatchBuilder + */ + public function keepHistory() + { + $this->history = true; + + return $this; + } + + /** + * Buffer exceptions thrown during transfer so that you can transfer as much as possible, and after a transfer + * completes, inspect each exception that was thrown. Enables the {@see ExceptionBufferingBatch} decorator. + * + * @return BatchBuilder + */ + public function bufferExceptions() + { + $this->exceptionBuffering = true; + + return $this; + } + + /** + * Notify a callable each time a batch flush completes. Enables the {@see NotifyingBatch} decorator. + * + * @param mixed $callable Callable function to notify + * + * @return BatchBuilder + * @throws InvalidArgumentException if the argument is not callable + */ + public function notify($callable) + { + $this->afterFlush = $callable; + + return $this; + } + + /** + * Configures the batch to transfer batches of requests. Associates a {@see \Guzzle\Http\BatchRequestTransfer} + * object as both the transfer and divisor strategy. + * + * @param int $batchSize Batch size for each batch of requests + * + * @return BatchBuilder + */ + public function transferRequests($batchSize = 50) + { + $className = self::$mapping['request']; + $this->transferStrategy = new $className($batchSize); + $this->divisorStrategy = $this->transferStrategy; + + return $this; + } + + /** + * Configures the batch to transfer batches commands. Associates as + * {@see \Guzzle\Service\Command\BatchCommandTransfer} as both the transfer and divisor strategy. + * + * @param int $batchSize Batch size for each batch of commands + * + * @return BatchBuilder + */ + public function transferCommands($batchSize = 50) + { + $className = self::$mapping['command']; + $this->transferStrategy = new $className($batchSize); + $this->divisorStrategy = $this->transferStrategy; + + return $this; + } + + /** + * Specify the strategy used to divide the queue into an array of batches + * + * @param BatchDivisorInterface $divisorStrategy Strategy used to divide a batch queue into batches + * + * @return BatchBuilder + */ + public function createBatchesWith(BatchDivisorInterface $divisorStrategy) + { + $this->divisorStrategy = $divisorStrategy; + + return $this; + } + + /** + * Specify the strategy used to transport the items when flush is called + * + * @param BatchTransferInterface $transferStrategy How items are transferred + * + * @return BatchBuilder + */ + public function transferWith(BatchTransferInterface $transferStrategy) + { + $this->transferStrategy = $transferStrategy; + + return $this; + } + + /** + * Create and return the instantiated batch + * + * @return BatchInterface + * @throws RuntimeException if no transfer strategy has been specified + */ + public function build() + { + if (!$this->transferStrategy) { + throw new RuntimeException('No transfer strategy has been specified'); + } + + if (!$this->divisorStrategy) { + throw new RuntimeException('No divisor strategy has been specified'); + } + + $batch = new Batch($this->transferStrategy, $this->divisorStrategy); + + if ($this->exceptionBuffering) { + $batch = new ExceptionBufferingBatch($batch); + } + + if ($this->afterFlush) { + $batch = new NotifyingBatch($batch, $this->afterFlush); + } + + if ($this->autoFlush) { + $batch = new FlushingBatch($batch, $this->autoFlush); + } + + if ($this->history) { + $batch = new HistoryBatch($batch); + } + + return $batch; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php new file mode 100644 index 000000000..e0a2d9568 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php @@ -0,0 +1,39 @@ +callable = $callable; + $this->context = $context; + } + + public function createBatches(\SplQueue $queue) + { + return call_user_func($this->callable, $queue, $this->context); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php new file mode 100644 index 000000000..9cbf1aba4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php @@ -0,0 +1,40 @@ +callable = $callable; + $this->context = $context; + } + + public function transfer(array $batch) + { + return empty($batch) ? null : call_user_func($this->callable, $batch, $this->context); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php new file mode 100644 index 000000000..d55ac7d1f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php @@ -0,0 +1,75 @@ +batchSize = $batchSize; + } + + /** + * Creates batches by grouping commands by their associated client + * {@inheritdoc} + */ + public function createBatches(\SplQueue $queue) + { + $groups = new \SplObjectStorage(); + foreach ($queue as $item) { + if (!$item instanceof CommandInterface) { + throw new InvalidArgumentException('All items must implement Guzzle\Service\Command\CommandInterface'); + } + $client = $item->getClient(); + if (!$groups->contains($client)) { + $groups->attach($client, new \ArrayObject(array($item))); + } else { + $groups[$client]->append($item); + } + } + + $batches = array(); + foreach ($groups as $batch) { + $batches = array_merge($batches, array_chunk($groups[$batch]->getArrayCopy(), $this->batchSize)); + } + + return $batches; + } + + public function transfer(array $batch) + { + if (empty($batch)) { + return; + } + + // Get the client of the first found command + $client = reset($batch)->getClient(); + + // Keep a list of all commands with invalid clients + $invalid = array_filter($batch, function ($command) use ($client) { + return $command->getClient() !== $client; + }); + + if (!empty($invalid)) { + throw new InconsistentClientTransferException($invalid); + } + + $client->execute($batch); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php new file mode 100644 index 000000000..0214f05f4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php @@ -0,0 +1,18 @@ +batchSize = $batchSize; + } + + /** + * Creates batches of requests by grouping requests by their associated curl multi object. + * {@inheritdoc} + */ + public function createBatches(\SplQueue $queue) + { + // Create batches by client objects + $groups = new \SplObjectStorage(); + foreach ($queue as $item) { + if (!$item instanceof RequestInterface) { + throw new InvalidArgumentException('All items must implement Guzzle\Http\Message\RequestInterface'); + } + $client = $item->getClient(); + if (!$groups->contains($client)) { + $groups->attach($client, array($item)); + } else { + $current = $groups[$client]; + $current[] = $item; + $groups[$client] = $current; + } + } + + $batches = array(); + foreach ($groups as $batch) { + $batches = array_merge($batches, array_chunk($groups[$batch], $this->batchSize)); + } + + return $batches; + } + + public function transfer(array $batch) + { + if ($batch) { + reset($batch)->getClient()->send($batch); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php new file mode 100644 index 000000000..67f90a581 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php @@ -0,0 +1,47 @@ +size = $size; + } + + /** + * Set the size of each batch + * + * @param int $size Size of each batch + * + * @return BatchSizeDivisor + */ + public function setSize($size) + { + $this->size = $size; + + return $this; + } + + /** + * Get the size of each batch + * + * @return int + */ + public function getSize() + { + return $this->size; + } + + public function createBatches(\SplQueue $queue) + { + return array_chunk(iterator_to_array($queue, false), $this->size); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php new file mode 100644 index 000000000..2e0b60dad --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php @@ -0,0 +1,16 @@ +batch = $batch; + $this->transferredItems = $transferredItems; + $this->transferStrategy = $transferStrategy; + $this->divisorStrategy = $divisorStrategy; + parent::__construct( + 'Exception encountered while transferring batch: ' . $exception->getMessage(), + $exception->getCode(), + $exception + ); + } + + /** + * Get the batch that we being sent when the exception occurred + * + * @return array + */ + public function getBatch() + { + return $this->batch; + } + + /** + * Get the items transferred at the point in which the exception was encountered + * + * @return array + */ + public function getTransferredItems() + { + return $this->transferredItems; + } + + /** + * Get the transfer strategy + * + * @return TransferStrategy + */ + public function getTransferStrategy() + { + return $this->transferStrategy; + } + + /** + * Get the divisor strategy + * + * @return DivisorStrategy + */ + public function getDivisorStrategy() + { + return $this->divisorStrategy; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php new file mode 100644 index 000000000..d7a892885 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php @@ -0,0 +1,50 @@ +decoratedBatch->isEmpty()) { + try { + $transferredItems = $this->decoratedBatch->flush(); + } catch (BatchTransferException $e) { + $this->exceptions[] = $e; + $transferredItems = $e->getTransferredItems(); + } + $items = array_merge($items, $transferredItems); + } + + return $items; + } + + /** + * Get the buffered exceptions + * + * @return array Array of BatchTransferException objects + */ + public function getExceptions() + { + return $this->exceptions; + } + + /** + * Clear the buffered exceptions + */ + public function clearExceptions() + { + $this->exceptions = array(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php new file mode 100644 index 000000000..367b68427 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php @@ -0,0 +1,60 @@ +threshold = $threshold; + parent::__construct($decoratedBatch); + } + + /** + * Set the auto-flush threshold + * + * @param int $threshold The auto-flush threshold + * + * @return FlushingBatch + */ + public function setThreshold($threshold) + { + $this->threshold = $threshold; + + return $this; + } + + /** + * Get the auto-flush threshold + * + * @return int + */ + public function getThreshold() + { + return $this->threshold; + } + + public function add($item) + { + $this->decoratedBatch->add($item); + if (++$this->currentTotal >= $this->threshold) { + $this->currentTotal = 0; + $this->decoratedBatch->flush(); + } + + return $this; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php new file mode 100644 index 000000000..e345fdc34 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php @@ -0,0 +1,39 @@ +history[] = $item; + $this->decoratedBatch->add($item); + + return $this; + } + + /** + * Get the batch history + * + * @return array + */ + public function getHistory() + { + return $this->history; + } + + /** + * Clear the batch history + */ + public function clearHistory() + { + $this->history = array(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php new file mode 100644 index 000000000..96d04daa8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php @@ -0,0 +1,38 @@ +callable = $callable; + parent::__construct($decoratedBatch); + } + + public function flush() + { + $items = $this->decoratedBatch->flush(); + call_user_func($this->callable, $items); + + return $items; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json new file mode 100644 index 000000000..12404d381 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json @@ -0,0 +1,31 @@ +{ + "name": "guzzle/batch", + "description": "Guzzle batch component for batching requests, commands, or custom transfers", + "homepage": "http://guzzlephp.org/", + "keywords": ["batch", "HTTP", "REST", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/common": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Batch": "" } + }, + "suggest": { + "guzzle/http": "self.version", + "guzzle/service": "self.version" + }, + "target-dir": "Guzzle/Batch", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php new file mode 100644 index 000000000..a5c527167 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php @@ -0,0 +1,21 @@ +cache; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php new file mode 100644 index 000000000..94e623463 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php @@ -0,0 +1,117 @@ +newInstanceArgs($args); + } + } catch (\Exception $e) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php new file mode 100644 index 000000000..970c9e228 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php @@ -0,0 +1,55 @@ +callables = $callables; + } + + public function contains($id, array $options = null) + { + return call_user_func($this->callables['contains'], $id, $options); + } + + public function delete($id, array $options = null) + { + return call_user_func($this->callables['delete'], $id, $options); + } + + public function fetch($id, array $options = null) + { + return call_user_func($this->callables['fetch'], $id, $options); + } + + public function save($id, $data, $lifeTime = false, array $options = null) + { + return call_user_func($this->callables['save'], $id, $data, $lifeTime, $options); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php new file mode 100644 index 000000000..e1aaf9f81 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php @@ -0,0 +1,41 @@ +cache = $cache; + } + + public function contains($id, array $options = null) + { + return $this->cache->contains($id); + } + + public function delete($id, array $options = null) + { + return $this->cache->delete($id); + } + + public function fetch($id, array $options = null) + { + return $this->cache->fetch($id); + } + + public function save($id, $data, $lifeTime = false, array $options = null) + { + return $this->cache->save($id, $data, $lifeTime !== false ? $lifeTime : 0); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php new file mode 100644 index 000000000..68bd4af97 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php @@ -0,0 +1,31 @@ +cache = $cache; + } + + public function contains($id, array $options = null) + { + return $this->cache->test($id); + } + + public function delete($id, array $options = null) + { + return $this->cache->remove($id); + } + + public function fetch($id, array $options = null) + { + return $this->cache->load($id); + } + + public function save($id, $data, $lifeTime = false, array $options = null) + { + return $this->cache->save($data, $id, array(), $lifeTime); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php new file mode 100644 index 000000000..1fc18a555 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php @@ -0,0 +1,41 @@ +cache = $cache; + } + + public function contains($id, array $options = null) + { + return $this->cache->hasItem($id); + } + + public function delete($id, array $options = null) + { + return $this->cache->removeItem($id); + } + + public function fetch($id, array $options = null) + { + return $this->cache->getItem($id); + } + + public function save($id, $data, $lifeTime = false, array $options = null) + { + return $this->cache->setItem($id, $data); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json new file mode 100644 index 000000000..a5d999bd6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/cache", + "description": "Guzzle cache adapter component", + "homepage": "http://guzzlephp.org/", + "keywords": ["cache", "adapter", "zf", "doctrine", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/common": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Cache": "" } + }, + "target-dir": "Guzzle/Cache", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php new file mode 100644 index 000000000..d1e842b1c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php @@ -0,0 +1,49 @@ +eventDispatcher = $eventDispatcher; + + return $this; + } + + public function getEventDispatcher() + { + if (!$this->eventDispatcher) { + $this->eventDispatcher = new EventDispatcher(); + } + + return $this->eventDispatcher; + } + + public function dispatch($eventName, array $context = array()) + { + return $this->getEventDispatcher()->dispatch($eventName, new Event($context)); + } + + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->getEventDispatcher()->addSubscriber($subscriber); + + return $this; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php new file mode 100644 index 000000000..5cb1535d0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php @@ -0,0 +1,403 @@ +data = $data; + } + + /** + * Create a new collection from an array, validate the keys, and add default values where missing + * + * @param array $config Configuration values to apply. + * @param array $defaults Default parameters + * @param array $required Required parameter names + * + * @return self + * @throws InvalidArgumentException if a parameter is missing + */ + public static function fromConfig(array $config = array(), array $defaults = array(), array $required = array()) + { + $data = $config + $defaults; + + if ($missing = array_diff($required, array_keys($data))) { + throw new InvalidArgumentException('Config is missing the following keys: ' . implode(', ', $missing)); + } + + return new self($data); + } + + public function count() + { + return count($this->data); + } + + public function getIterator() + { + return new \ArrayIterator($this->data); + } + + public function toArray() + { + return $this->data; + } + + /** + * Removes all key value pairs + * + * @return Collection + */ + public function clear() + { + $this->data = array(); + + return $this; + } + + /** + * Get all or a subset of matching key value pairs + * + * @param array $keys Pass an array of keys to retrieve only a subset of key value pairs + * + * @return array Returns an array of all matching key value pairs + */ + public function getAll(array $keys = null) + { + return $keys ? array_intersect_key($this->data, array_flip($keys)) : $this->data; + } + + /** + * Get a specific key value. + * + * @param string $key Key to retrieve. + * + * @return mixed|null Value of the key or NULL + */ + public function get($key) + { + return isset($this->data[$key]) ? $this->data[$key] : null; + } + + /** + * Set a key value pair + * + * @param string $key Key to set + * @param mixed $value Value to set + * + * @return Collection Returns a reference to the object + */ + public function set($key, $value) + { + $this->data[$key] = $value; + + return $this; + } + + /** + * Add a value to a key. If a key of the same name has already been added, the key value will be converted into an + * array and the new value will be pushed to the end of the array. + * + * @param string $key Key to add + * @param mixed $value Value to add to the key + * + * @return Collection Returns a reference to the object. + */ + public function add($key, $value) + { + if (!array_key_exists($key, $this->data)) { + $this->data[$key] = $value; + } elseif (is_array($this->data[$key])) { + $this->data[$key][] = $value; + } else { + $this->data[$key] = array($this->data[$key], $value); + } + + return $this; + } + + /** + * Remove a specific key value pair + * + * @param string $key A key to remove + * + * @return Collection + */ + public function remove($key) + { + unset($this->data[$key]); + + return $this; + } + + /** + * Get all keys in the collection + * + * @return array + */ + public function getKeys() + { + return array_keys($this->data); + } + + /** + * Returns whether or not the specified key is present. + * + * @param string $key The key for which to check the existence. + * + * @return bool + */ + public function hasKey($key) + { + return array_key_exists($key, $this->data); + } + + /** + * Case insensitive search the keys in the collection + * + * @param string $key Key to search for + * + * @return bool|string Returns false if not found, otherwise returns the key + */ + public function keySearch($key) + { + foreach (array_keys($this->data) as $k) { + if (!strcasecmp($k, $key)) { + return $k; + } + } + + return false; + } + + /** + * Checks if any keys contains a certain value + * + * @param string $value Value to search for + * + * @return mixed Returns the key if the value was found FALSE if the value was not found. + */ + public function hasValue($value) + { + return array_search($value, $this->data); + } + + /** + * Replace the data of the object with the value of an array + * + * @param array $data Associative array of data + * + * @return Collection Returns a reference to the object + */ + public function replace(array $data) + { + $this->data = $data; + + return $this; + } + + /** + * Add and merge in a Collection or array of key value pair data. + * + * @param Collection|array $data Associative array of key value pair data + * + * @return Collection Returns a reference to the object. + */ + public function merge($data) + { + foreach ($data as $key => $value) { + $this->add($key, $value); + } + + return $this; + } + + /** + * Over write key value pairs in this collection with all of the data from an array or collection. + * + * @param array|\Traversable $data Values to override over this config + * + * @return self + */ + public function overwriteWith($data) + { + if (is_array($data)) { + $this->data = $data + $this->data; + } elseif ($data instanceof Collection) { + $this->data = $data->toArray() + $this->data; + } else { + foreach ($data as $key => $value) { + $this->data[$key] = $value; + } + } + + return $this; + } + + /** + * Returns a Collection containing all the elements of the collection after applying the callback function to each + * one. The Closure should accept three parameters: (string) $key, (string) $value, (array) $context and return a + * modified value + * + * @param \Closure $closure Closure to apply + * @param array $context Context to pass to the closure + * @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection + * + * @return Collection + */ + public function map(\Closure $closure, array $context = array(), $static = true) + { + $collection = $static ? new static() : new self(); + foreach ($this as $key => $value) { + $collection->add($key, $closure($key, $value, $context)); + } + + return $collection; + } + + /** + * Iterates over each key value pair in the collection passing them to the Closure. If the Closure function returns + * true, the current value from input is returned into the result Collection. The Closure must accept three + * parameters: (string) $key, (string) $value and return Boolean TRUE or FALSE for each value. + * + * @param \Closure $closure Closure evaluation function + * @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection + * + * @return Collection + */ + public function filter(\Closure $closure, $static = true) + { + $collection = ($static) ? new static() : new self(); + foreach ($this->data as $key => $value) { + if ($closure($key, $value)) { + $collection->add($key, $value); + } + } + + return $collection; + } + + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + public function offsetGet($offset) + { + return isset($this->data[$offset]) ? $this->data[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + + /** + * Set a value into a nested array key. Keys will be created as needed to set the value. + * + * @param string $path Path to set + * @param mixed $value Value to set at the key + * + * @return self + * @throws RuntimeException when trying to setPath using a nested path that travels through a scalar value + */ + public function setPath($path, $value) + { + $current =& $this->data; + $queue = explode('/', $path); + while (null !== ($key = array_shift($queue))) { + if (!is_array($current)) { + throw new RuntimeException("Trying to setPath {$path}, but {$key} is set and is not an array"); + } elseif (!$queue) { + $current[$key] = $value; + } elseif (isset($current[$key])) { + $current =& $current[$key]; + } else { + $current[$key] = array(); + $current =& $current[$key]; + } + } + + return $this; + } + + /** + * Gets a value from the collection using an array path (e.g. foo/baz/bar would retrieve bar from two nested arrays) + * Allows for wildcard searches which recursively combine matches up to the level at which the wildcard occurs. This + * can be useful for accepting any key of a sub-array and combining matching keys from each diverging path. + * + * @param string $path Path to traverse and retrieve a value from + * @param string $separator Character used to add depth to the search + * @param mixed $data Optional data to descend into (used when wildcards are encountered) + * + * @return mixed|null + */ + public function getPath($path, $separator = '/', $data = null) + { + if ($data === null) { + $data =& $this->data; + } + + $path = is_array($path) ? $path : explode($separator, $path); + while (null !== ($part = array_shift($path))) { + if (!is_array($data)) { + return null; + } elseif (isset($data[$part])) { + $data =& $data[$part]; + } elseif ($part != '*') { + return null; + } else { + // Perform a wildcard search by diverging and merging paths + $result = array(); + foreach ($data as $value) { + if (!$path) { + $result = array_merge_recursive($result, (array) $value); + } elseif (null !== ($test = $this->getPath($path, $separator, $value))) { + $result = array_merge_recursive($result, (array) $test); + } + } + return $result; + } + } + + return $data; + } + + /** + * Inject configuration settings into an input string + * + * @param string $input Input to inject + * + * @return string + * @deprecated + */ + public function inject($input) + { + Version::warn(__METHOD__ . ' is deprecated'); + $replace = array(); + foreach ($this->data as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + return strtr($input, $replace); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php new file mode 100644 index 000000000..fad76a9b8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php @@ -0,0 +1,52 @@ +context = $context; + } + + public function getIterator() + { + return new \ArrayIterator($this->context); + } + + public function offsetGet($offset) + { + return isset($this->context[$offset]) ? $this->context[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->context[$offset] = $value; + } + + public function offsetExists($offset) + { + return isset($this->context[$offset]); + } + + public function offsetUnset($offset) + { + unset($this->context[$offset]); + } + + public function toArray() + { + return $this->context; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php new file mode 100644 index 000000000..08d1c7256 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php @@ -0,0 +1,5 @@ +shortMessage = $message; + } + + /** + * Set all of the exceptions + * + * @param array $exceptions Array of exceptions + * + * @return self + */ + public function setExceptions(array $exceptions) + { + $this->exceptions = array(); + foreach ($exceptions as $exception) { + $this->add($exception); + } + + return $this; + } + + /** + * Add exceptions to the collection + * + * @param ExceptionCollection|\Exception $e Exception to add + * + * @return ExceptionCollection; + */ + public function add($e) + { + $this->exceptions[] = $e; + if ($this->message) { + $this->message .= "\n"; + } + + $this->message .= $this->getExceptionMessage($e, 0); + + return $this; + } + + /** + * Get the total number of request exceptions + * + * @return int + */ + public function count() + { + return count($this->exceptions); + } + + /** + * Allows array-like iteration over the request exceptions + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->exceptions); + } + + /** + * Get the first exception in the collection + * + * @return \Exception + */ + public function getFirst() + { + return $this->exceptions ? $this->exceptions[0] : null; + } + + private function getExceptionMessage(\Exception $e, $depth = 0) + { + static $sp = ' '; + $prefix = $depth ? str_repeat($sp, $depth) : ''; + $message = "{$prefix}(" . get_class($e) . ') ' . $e->getFile() . ' line ' . $e->getLine() . "\n"; + + if ($e instanceof self) { + if ($e->shortMessage) { + $message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->shortMessage) . "\n"; + } + foreach ($e as $ee) { + $message .= "\n" . $this->getExceptionMessage($ee, $depth + 1); + } + } else { + $message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->getMessage()) . "\n"; + $message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->getTraceAsString()) . "\n"; + } + + return str_replace(getcwd(), '.', $message); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php new file mode 100644 index 000000000..458e6f2ea --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php @@ -0,0 +1,8 @@ +=5.3.2", + "symfony/event-dispatcher": ">=2.1" + }, + "autoload": { + "psr-0": { "Guzzle\\Common": "" } + }, + "target-dir": "Guzzle/Common", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php new file mode 100644 index 000000000..5005a887c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php @@ -0,0 +1,221 @@ +body = $body; + } + + public function __toString() + { + return (string) $this->body; + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + return call_user_func_array(array($this->body, $method), $args); + } + + public function close() + { + return $this->body->close(); + } + + public function setRewindFunction($callable) + { + $this->body->setRewindFunction($callable); + + return $this; + } + + public function rewind() + { + return $this->body->rewind(); + } + + public function compress($filter = 'zlib.deflate') + { + return $this->body->compress($filter); + } + + public function uncompress($filter = 'zlib.inflate') + { + return $this->body->uncompress($filter); + } + + public function getContentLength() + { + return $this->getSize(); + } + + public function getContentType() + { + return $this->body->getContentType(); + } + + public function getContentMd5($rawOutput = false, $base64Encode = false) + { + $hash = Stream::getHash($this, 'md5', $rawOutput); + + return $hash && $base64Encode ? base64_encode($hash) : $hash; + } + + public function getContentEncoding() + { + return $this->body->getContentEncoding(); + } + + public function getMetaData($key = null) + { + return $this->body->getMetaData($key); + } + + public function getStream() + { + return $this->body->getStream(); + } + + public function setStream($stream, $size = 0) + { + $this->body->setStream($stream, $size); + + return $this; + } + + public function detachStream() + { + $this->body->detachStream(); + + return $this; + } + + public function getWrapper() + { + return $this->body->getWrapper(); + } + + public function getWrapperData() + { + return $this->body->getWrapperData(); + } + + public function getStreamType() + { + return $this->body->getStreamType(); + } + + public function getUri() + { + return $this->body->getUri(); + } + + public function getSize() + { + return $this->body->getSize(); + } + + public function isReadable() + { + return $this->body->isReadable(); + } + + public function isRepeatable() + { + return $this->isSeekable() && $this->isReadable(); + } + + public function isWritable() + { + return $this->body->isWritable(); + } + + public function isConsumed() + { + return $this->body->isConsumed(); + } + + /** + * Alias of isConsumed() + * {@inheritdoc} + */ + public function feof() + { + return $this->isConsumed(); + } + + public function isLocal() + { + return $this->body->isLocal(); + } + + public function isSeekable() + { + return $this->body->isSeekable(); + } + + public function setSize($size) + { + $this->body->setSize($size); + + return $this; + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->body->seek($offset, $whence); + } + + public function read($length) + { + return $this->body->read($length); + } + + public function write($string) + { + return $this->body->write($string); + } + + public function readLine($maxLength = null) + { + return $this->body->readLine($maxLength); + } + + public function ftell() + { + return $this->body->ftell(); + } + + public function getCustomData($key) + { + return $this->body->getCustomData($key); + } + + public function setCustomData($key, $value) + { + $this->body->setCustomData($key, $value); + + return $this; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php new file mode 100644 index 000000000..c65c13650 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php @@ -0,0 +1,229 @@ +remoteStream = $body; + $this->body = new EntityBody(fopen('php://temp', 'r+')); + } + + /** + * Will give the contents of the buffer followed by the exhausted remote stream. + * + * Warning: Loads the entire stream into memory + * + * @return string + */ + public function __toString() + { + $pos = $this->ftell(); + $this->rewind(); + + $str = ''; + while (!$this->isConsumed()) { + $str .= $this->read(16384); + } + + $this->seek($pos); + + return $str; + } + + public function getSize() + { + return max($this->body->getSize(), $this->remoteStream->getSize()); + } + + /** + * {@inheritdoc} + * @throws RuntimeException When seeking with SEEK_END or when seeking past the total size of the buffer stream + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->ftell(); + } else { + throw new RuntimeException(__CLASS__ . ' supports only SEEK_SET and SEEK_CUR seek operations'); + } + + // You cannot skip ahead past where you've read from the remote stream + if ($byte > $this->body->getSize()) { + throw new RuntimeException( + "Cannot seek to byte {$byte} when the buffered stream only contains {$this->body->getSize()} bytes" + ); + } + + return $this->body->seek($byte); + } + + public function rewind() + { + return $this->seek(0); + } + + /** + * Does not support custom rewind functions + * + * @throws RuntimeException + */ + public function setRewindFunction($callable) + { + throw new RuntimeException(__CLASS__ . ' does not support custom stream rewind functions'); + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->body->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have been filled from the remote stream, + // then we must skip bytes on the remote stream to emulate overwriting bytes from that position. This + // mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read($remaining + $this->skipReadBytes); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->body->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // When appending to the end of the currently read stream, you'll want to skip bytes from being read from + // the remote stream to emulate other stream wrappers. Basically replacing bytes of data of a fixed length. + $overflow = (strlen($string) + $this->ftell()) - $this->remoteStream->ftell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->body->write($string); + } + + /** + * {@inheritdoc} + * @link http://php.net/manual/en/function.fgets.php + */ + public function readLine($maxLength = null) + { + $buffer = ''; + $size = 0; + while (!$this->isConsumed()) { + $byte = $this->read(1); + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte == PHP_EOL || ++$size == $maxLength - 1) { + break; + } + } + + return $buffer; + } + + public function isConsumed() + { + return $this->body->isConsumed() && $this->remoteStream->isConsumed(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + return $this->remoteStream->close() && $this->body->close(); + } + + public function setStream($stream, $size = 0) + { + $this->remoteStream->setStream($stream, $size); + } + + public function getContentType() + { + return $this->remoteStream->getContentType(); + } + + public function getContentEncoding() + { + return $this->remoteStream->getContentEncoding(); + } + + public function getMetaData($key = null) + { + return $this->remoteStream->getMetaData($key); + } + + public function getStream() + { + return $this->remoteStream->getStream(); + } + + public function getWrapper() + { + return $this->remoteStream->getWrapper(); + } + + public function getWrapperData() + { + return $this->remoteStream->getWrapperData(); + } + + public function getStreamType() + { + return $this->remoteStream->getStreamType(); + } + + public function getUri() + { + return $this->remoteStream->getUri(); + } + + /** + * Always retrieve custom data from the remote stream + * {@inheritdoc} + */ + public function getCustomData($key) + { + return $this->remoteStream->getCustomData($key); + } + + /** + * Always set custom data on the remote stream + * {@inheritdoc} + */ + public function setCustomData($key, $value) + { + $this->remoteStream->setCustomData($key, $value); + + return $this; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Client.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Client.php new file mode 100644 index 000000000..3d7298dcd --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Client.php @@ -0,0 +1,524 @@ +setConfig($config ?: new Collection()); + $this->initSsl(); + $this->setBaseUrl($baseUrl); + $this->defaultHeaders = new Collection(); + $this->setRequestFactory(RequestFactory::getInstance()); + $this->userAgent = $this->getDefaultUserAgent(); + if (!$this->config[self::DISABLE_REDIRECTS]) { + $this->addSubscriber(new RedirectPlugin()); + } + } + + final public function setConfig($config) + { + if ($config instanceof Collection) { + $this->config = $config; + } elseif (is_array($config)) { + $this->config = new Collection($config); + } else { + throw new InvalidArgumentException('Config must be an array or Collection'); + } + + return $this; + } + + final public function getConfig($key = false) + { + return $key ? $this->config[$key] : $this->config; + } + + /** + * Set a default request option on the client that will be used as a default for each request + * + * @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo) + * @param mixed $value Value to set + * + * @return $this + */ + public function setDefaultOption($keyOrPath, $value) + { + $keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath; + $this->config->setPath($keyOrPath, $value); + + return $this; + } + + /** + * Retrieve a default request option from the client + * + * @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo) + * + * @return mixed|null + */ + public function getDefaultOption($keyOrPath) + { + $keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath; + + return $this->config->getPath($keyOrPath); + } + + final public function setSslVerification($certificateAuthority = true, $verifyPeer = true, $verifyHost = 2) + { + $opts = $this->config[self::CURL_OPTIONS] ?: array(); + + if ($certificateAuthority === true) { + // use bundled CA bundle, set secure defaults + $opts[CURLOPT_CAINFO] = __DIR__ . '/Resources/cacert.pem'; + $opts[CURLOPT_SSL_VERIFYPEER] = true; + $opts[CURLOPT_SSL_VERIFYHOST] = 2; + } elseif ($certificateAuthority === false) { + unset($opts[CURLOPT_CAINFO]); + $opts[CURLOPT_SSL_VERIFYPEER] = false; + $opts[CURLOPT_SSL_VERIFYHOST] = 0; + } elseif ($verifyPeer !== true && $verifyPeer !== false && $verifyPeer !== 1 && $verifyPeer !== 0) { + throw new InvalidArgumentException('verifyPeer must be 1, 0 or boolean'); + } elseif ($verifyHost !== 0 && $verifyHost !== 1 && $verifyHost !== 2) { + throw new InvalidArgumentException('verifyHost must be 0, 1 or 2'); + } else { + $opts[CURLOPT_SSL_VERIFYPEER] = $verifyPeer; + $opts[CURLOPT_SSL_VERIFYHOST] = $verifyHost; + if (is_file($certificateAuthority)) { + unset($opts[CURLOPT_CAPATH]); + $opts[CURLOPT_CAINFO] = $certificateAuthority; + } elseif (is_dir($certificateAuthority)) { + unset($opts[CURLOPT_CAINFO]); + $opts[CURLOPT_CAPATH] = $certificateAuthority; + } else { + throw new RuntimeException( + 'Invalid option passed to ' . self::SSL_CERT_AUTHORITY . ': ' . $certificateAuthority + ); + } + } + + $this->config->set(self::CURL_OPTIONS, $opts); + + return $this; + } + + public function createRequest($method = 'GET', $uri = null, $headers = null, $body = null, array $options = array()) + { + if (!$uri) { + $url = $this->getBaseUrl(); + } else { + if (!is_array($uri)) { + $templateVars = null; + } else { + list($uri, $templateVars) = $uri; + } + if (strpos($uri, '://')) { + // Use absolute URLs as-is + $url = $this->expandTemplate($uri, $templateVars); + } else { + $url = Url::factory($this->getBaseUrl())->combine($this->expandTemplate($uri, $templateVars)); + } + } + + // If default headers are provided, then merge them under any explicitly provided headers for the request + if (count($this->defaultHeaders)) { + if (!$headers) { + $headers = $this->defaultHeaders->toArray(); + } elseif (is_array($headers)) { + $headers += $this->defaultHeaders->toArray(); + } elseif ($headers instanceof Collection) { + $headers = $headers->toArray() + $this->defaultHeaders->toArray(); + } + } + + return $this->prepareRequest($this->requestFactory->create($method, (string) $url, $headers, $body), $options); + } + + public function getBaseUrl($expand = true) + { + return $expand ? $this->expandTemplate($this->baseUrl) : $this->baseUrl; + } + + public function setBaseUrl($url) + { + $this->baseUrl = $url; + + return $this; + } + + public function setUserAgent($userAgent, $includeDefault = false) + { + if ($includeDefault) { + $userAgent .= ' ' . $this->getDefaultUserAgent(); + } + $this->userAgent = $userAgent; + + return $this; + } + + /** + * Get the default User-Agent string to use with Guzzle + * + * @return string + */ + public function getDefaultUserAgent() + { + return 'Guzzle/' . Version::VERSION + . ' curl/' . CurlVersion::getInstance()->get('version') + . ' PHP/' . PHP_VERSION; + } + + public function get($uri = null, $headers = null, $options = array()) + { + // BC compat: $options can be a string, resource, etc to specify where the response body is downloaded + return is_array($options) + ? $this->createRequest('GET', $uri, $headers, null, $options) + : $this->createRequest('GET', $uri, $headers, $options); + } + + public function head($uri = null, $headers = null, array $options = array()) + { + return $this->createRequest('HEAD', $uri, $headers, null, $options); + } + + public function delete($uri = null, $headers = null, $body = null, array $options = array()) + { + return $this->createRequest('DELETE', $uri, $headers, $body, $options); + } + + public function put($uri = null, $headers = null, $body = null, array $options = array()) + { + return $this->createRequest('PUT', $uri, $headers, $body, $options); + } + + public function patch($uri = null, $headers = null, $body = null, array $options = array()) + { + return $this->createRequest('PATCH', $uri, $headers, $body, $options); + } + + public function post($uri = null, $headers = null, $postBody = null, array $options = array()) + { + return $this->createRequest('POST', $uri, $headers, $postBody, $options); + } + + public function options($uri = null, array $options = array()) + { + return $this->createRequest('OPTIONS', $uri, $options); + } + + public function send($requests) + { + if (!($requests instanceof RequestInterface)) { + return $this->sendMultiple($requests); + } + + try { + /** @var $requests RequestInterface */ + $this->getCurlMulti()->add($requests)->send(); + return $requests->getResponse(); + } catch (ExceptionCollection $e) { + throw $e->getFirst(); + } + } + + /** + * Set a curl multi object to be used internally by the client for transferring requests. + * + * @param CurlMultiInterface $curlMulti Multi object + * + * @return self + */ + public function setCurlMulti(CurlMultiInterface $curlMulti) + { + $this->curlMulti = $curlMulti; + + return $this; + } + + /** + * @return CurlMultiInterface|CurlMultiProxy + */ + public function getCurlMulti() + { + if (!$this->curlMulti) { + $this->curlMulti = new CurlMultiProxy( + self::MAX_HANDLES, + $this->getConfig('select_timeout') ?: self::DEFAULT_SELECT_TIMEOUT + ); + } + + return $this->curlMulti; + } + + public function setRequestFactory(RequestFactoryInterface $factory) + { + $this->requestFactory = $factory; + + return $this; + } + + /** + * Set the URI template expander to use with the client + * + * @param UriTemplateInterface $uriTemplate URI template expander + * + * @return self + */ + public function setUriTemplate(UriTemplateInterface $uriTemplate) + { + $this->uriTemplate = $uriTemplate; + + return $this; + } + + /** + * Expand a URI template while merging client config settings into the template variables + * + * @param string $template Template to expand + * @param array $variables Variables to inject + * + * @return string + */ + protected function expandTemplate($template, array $variables = null) + { + $expansionVars = $this->getConfig()->toArray(); + if ($variables) { + $expansionVars = $variables + $expansionVars; + } + + return $this->getUriTemplate()->expand($template, $expansionVars); + } + + /** + * Get the URI template expander used by the client + * + * @return UriTemplateInterface + */ + protected function getUriTemplate() + { + if (!$this->uriTemplate) { + $this->uriTemplate = ParserRegistry::getInstance()->getParser('uri_template'); + } + + return $this->uriTemplate; + } + + /** + * Send multiple requests in parallel + * + * @param array $requests Array of RequestInterface objects + * + * @return array Returns an array of Response objects + */ + protected function sendMultiple(array $requests) + { + $curlMulti = $this->getCurlMulti(); + foreach ($requests as $request) { + $curlMulti->add($request); + } + $curlMulti->send(); + + /** @var $request RequestInterface */ + $result = array(); + foreach ($requests as $request) { + $result[] = $request->getResponse(); + } + + return $result; + } + + /** + * Prepare a request to be sent from the Client by adding client specific behaviors and properties to the request. + * + * @param RequestInterface $request Request to prepare for the client + * @param array $options Options to apply to the request + * + * @return RequestInterface + */ + protected function prepareRequest(RequestInterface $request, array $options = array()) + { + $request->setClient($this)->setEventDispatcher(clone $this->getEventDispatcher()); + + if ($curl = $this->config[self::CURL_OPTIONS]) { + $request->getCurlOptions()->overwriteWith(CurlHandle::parseCurlConfig($curl)); + } + + if ($params = $this->config[self::REQUEST_PARAMS]) { + Version::warn('request.params is deprecated. Use request.options to add default request options.'); + $request->getParams()->overwriteWith($params); + } + + if ($this->userAgent && !$request->hasHeader('User-Agent')) { + $request->setHeader('User-Agent', $this->userAgent); + } + + if ($defaults = $this->config[self::REQUEST_OPTIONS]) { + $this->requestFactory->applyOptions($request, $defaults, RequestFactoryInterface::OPTIONS_AS_DEFAULTS); + } + + if ($options) { + $this->requestFactory->applyOptions($request, $options); + } + + $this->dispatch('client.create_request', array('client' => $this, 'request' => $request)); + + return $request; + } + + /** + * Initializes SSL settings + */ + protected function initSsl() + { + $authority = $this->config[self::SSL_CERT_AUTHORITY]; + + if ($authority === 'system') { + return; + } + + if ($authority === null) { + $authority = true; + } + + if ($authority === true && substr(__FILE__, 0, 7) == 'phar://') { + $authority = self::extractPharCacert(__DIR__ . '/Resources/cacert.pem'); + } + + $this->setSslVerification($authority); + } + + /** + * @deprecated + */ + public function getDefaultHeaders() + { + Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to retrieve default request options'); + return $this->defaultHeaders; + } + + /** + * @deprecated + */ + public function setDefaultHeaders($headers) + { + Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to specify default request options'); + if ($headers instanceof Collection) { + $this->defaultHeaders = $headers; + } elseif (is_array($headers)) { + $this->defaultHeaders = new Collection($headers); + } else { + throw new InvalidArgumentException('Headers must be an array or Collection'); + } + + return $this; + } + + /** + * @deprecated + */ + public function preparePharCacert($md5Check = true) + { + return sys_get_temp_dir() . '/guzzle-cacert.pem'; + } + + /** + * Copies the phar cacert from a phar into the temp directory. + * + * @param string $pharCacertPath Path to the phar cacert. For example: + * 'phar://aws.phar/Guzzle/Http/Resources/cacert.pem' + * + * @return string Returns the path to the extracted cacert file. + * @throws \RuntimeException Throws if the phar cacert cannot be found or + * the file cannot be copied to the temp dir. + */ + public static function extractPharCacert($pharCacertPath) + { + // Copy the cacert.pem file from the phar if it is not in the temp + // folder. + $certFile = sys_get_temp_dir() . '/guzzle-cacert.pem'; + + if (!file_exists($pharCacertPath)) { + throw new \RuntimeException("Could not find $pharCacertPath"); + } + + if (!file_exists($certFile) || + filesize($certFile) != filesize($pharCacertPath) + ) { + if (!copy($pharCacertPath, $certFile)) { + throw new \RuntimeException( + "Could not copy {$pharCacertPath} to {$certFile}: " + . var_export(error_get_last(), true) + ); + } + } + + return $certFile; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php new file mode 100644 index 000000000..10e4de2ab --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php @@ -0,0 +1,223 @@ +getCurlOptions(); + $mediator = new RequestMediator($request, $requestCurlOptions->get('emit_io')); + $tempContentLength = null; + $method = $request->getMethod(); + $bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING); + + // Prepare url + $url = (string)$request->getUrl(); + if(($pos = strpos($url, '#')) !== false ){ + // strip fragment from url + $url = substr($url, 0, $pos); + } + + // Array of default cURL options. + $curlOptions = array( + CURLOPT_URL => $url, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_RETURNTRANSFER => false, + CURLOPT_HEADER => false, + CURLOPT_PORT => $request->getPort(), + CURLOPT_HTTPHEADER => array(), + CURLOPT_WRITEFUNCTION => array($mediator, 'writeResponseBody'), + CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'), + CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0' + ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1, + // Verifies the authenticity of the peer's certificate + CURLOPT_SSL_VERIFYPEER => 1, + // Certificate must indicate that the server is the server to which you meant to connect + CURLOPT_SSL_VERIFYHOST => 2 + ); + + if (defined('CURLOPT_PROTOCOLS')) { + // Allow only HTTP and HTTPS protocols + $curlOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + // Add CURLOPT_ENCODING if Accept-Encoding header is provided + if ($acceptEncodingHeader = $request->getHeader('Accept-Encoding')) { + $curlOptions[CURLOPT_ENCODING] = (string) $acceptEncodingHeader; + // Let cURL set the Accept-Encoding header, prevents duplicate values + $request->removeHeader('Accept-Encoding'); + } + + // Enable curl debug information if the 'debug' param was set + if ($requestCurlOptions->get('debug')) { + $curlOptions[CURLOPT_STDERR] = fopen('php://temp', 'r+'); + // @codeCoverageIgnoreStart + if (false === $curlOptions[CURLOPT_STDERR]) { + throw new RuntimeException('Unable to create a stream for CURLOPT_STDERR'); + } + // @codeCoverageIgnoreEnd + $curlOptions[CURLOPT_VERBOSE] = true; + } + + // Specify settings according to the HTTP method + if ($method == 'GET') { + $curlOptions[CURLOPT_HTTPGET] = true; + } elseif ($method == 'HEAD') { + $curlOptions[CURLOPT_NOBODY] = true; + // HEAD requests do not use a write function + unset($curlOptions[CURLOPT_WRITEFUNCTION]); + } elseif (!($request instanceof EntityEnclosingRequest)) { + $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; + } else { + + $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; + + // Handle sending raw bodies in a request + if ($request->getBody()) { + // You can send the body as a string using curl's CURLOPT_POSTFIELDS + if ($bodyAsString) { + $curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Allow curl to add the Content-Length for us to account for the times when + // POST redirects are followed by GET requests + if ($tempContentLength = $request->getHeader('Content-Length')) { + $tempContentLength = (int) (string) $tempContentLength; + } + // Remove the curl generated Content-Type header if none was set manually + if (!$request->hasHeader('Content-Type')) { + $curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } else { + $curlOptions[CURLOPT_UPLOAD] = true; + // Let cURL handle setting the Content-Length header + if ($tempContentLength = $request->getHeader('Content-Length')) { + $tempContentLength = (int) (string) $tempContentLength; + $curlOptions[CURLOPT_INFILESIZE] = $tempContentLength; + } + // Add a callback for curl to read data to send with the request only if a body was specified + $curlOptions[CURLOPT_READFUNCTION] = array($mediator, 'readRequestBody'); + // Attempt to seek to the start of the stream + $request->getBody()->seek(0); + } + + } else { + + // Special handling for POST specific fields and files + $postFields = false; + if (count($request->getPostFiles())) { + $postFields = $request->getPostFields()->useUrlEncoding(false)->urlEncode(); + foreach ($request->getPostFiles() as $key => $data) { + $prefixKeys = count($data) > 1; + foreach ($data as $index => $file) { + // Allow multiple files in the same key + $fieldKey = $prefixKeys ? "{$key}[{$index}]" : $key; + $postFields[$fieldKey] = $file->getCurlValue(); + } + } + } elseif (count($request->getPostFields())) { + $postFields = (string) $request->getPostFields()->useUrlEncoding(true); + } + + if ($postFields !== false) { + if ($method == 'POST') { + unset($curlOptions[CURLOPT_CUSTOMREQUEST]); + $curlOptions[CURLOPT_POST] = true; + } + $curlOptions[CURLOPT_POSTFIELDS] = $postFields; + $request->removeHeader('Content-Length'); + } + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:'; + } + } + + // If a Content-Length header was specified but we want to allow curl to set one for us + if (null !== $tempContentLength) { + $request->removeHeader('Content-Length'); + } + + // Set custom cURL options + foreach ($requestCurlOptions->toArray() as $key => $value) { + if (is_numeric($key)) { + $curlOptions[$key] = $value; + } + } + + // Do not set an Accept header by default + if (!isset($curlOptions[CURLOPT_ENCODING])) { + $curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:'; + } + + // Add any custom headers to the request. Empty headers will cause curl to not send the header at all. + foreach ($request->getHeaderLines() as $line) { + $curlOptions[CURLOPT_HTTPHEADER][] = $line; + } + + // Add the content-length header back if it was temporarily removed + if (null !== $tempContentLength) { + $request->setHeader('Content-Length', $tempContentLength); + } + + // Apply the options to a new cURL handle. + $handle = curl_init(); + + // Enable the progress function if the 'progress' param was set + if ($requestCurlOptions->get('progress')) { + // Wrap the function in a function that provides the curl handle to the mediator's progress function + // Using this rather than injecting the handle into the mediator prevents a circular reference + $curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use ($mediator, $handle) { + $args = func_get_args(); + $args[] = $handle; + + // PHP 5.5 pushed the handle onto the start of the args + if (is_resource($args[0])) { + array_shift($args); + } + + call_user_func_array(array($mediator, 'progress'), $args); + }; + $curlOptions[CURLOPT_NOPROGRESS] = false; + } + + curl_setopt_array($handle, $curlOptions); + + return new static($handle, $curlOptions); + } + + /** + * Construct a new CurlHandle object that wraps a cURL handle + * + * @param resource $handle Configured cURL handle resource + * @param Collection|array $options Curl options to use with the handle + * + * @throws InvalidArgumentException + */ + public function __construct($handle, $options) + { + if (!is_resource($handle)) { + throw new InvalidArgumentException('Invalid handle provided'); + } + if (is_array($options)) { + $this->options = new Collection($options); + } elseif ($options instanceof Collection) { + $this->options = $options; + } else { + throw new InvalidArgumentException('Expected array or Collection'); + } + $this->handle = $handle; + } + + /** + * Destructor + */ + public function __destruct() + { + $this->close(); + } + + /** + * Close the curl handle + */ + public function close() + { + if (is_resource($this->handle)) { + curl_close($this->handle); + } + $this->handle = null; + } + + /** + * Check if the handle is available and still OK + * + * @return bool + */ + public function isAvailable() + { + return is_resource($this->handle); + } + + /** + * Get the last error that occurred on the cURL handle + * + * @return string + */ + public function getError() + { + return $this->isAvailable() ? curl_error($this->handle) : ''; + } + + /** + * Get the last error number that occurred on the cURL handle + * + * @return int + */ + public function getErrorNo() + { + if ($this->errorNo) { + return $this->errorNo; + } + + return $this->isAvailable() ? curl_errno($this->handle) : CURLE_OK; + } + + /** + * Set the curl error number + * + * @param int $error Error number to set + * + * @return CurlHandle + */ + public function setErrorNo($error) + { + $this->errorNo = $error; + + return $this; + } + + /** + * Get cURL curl_getinfo data + * + * @param int $option Option to retrieve. Pass null to retrieve all data as an array. + * + * @return array|mixed + */ + public function getInfo($option = null) + { + if (!is_resource($this->handle)) { + return null; + } + + if (null !== $option) { + return curl_getinfo($this->handle, $option) ?: null; + } + + return curl_getinfo($this->handle) ?: array(); + } + + /** + * Get the stderr output + * + * @param bool $asResource Set to TRUE to get an fopen resource + * + * @return string|resource|null + */ + public function getStderr($asResource = false) + { + $stderr = $this->getOptions()->get(CURLOPT_STDERR); + if (!$stderr) { + return null; + } + + if ($asResource) { + return $stderr; + } + + fseek($stderr, 0); + $e = stream_get_contents($stderr); + fseek($stderr, 0, SEEK_END); + + return $e; + } + + /** + * Get the URL that this handle is connecting to + * + * @return Url + */ + public function getUrl() + { + return Url::factory($this->options->get(CURLOPT_URL)); + } + + /** + * Get the wrapped curl handle + * + * @return resource|null Returns the cURL handle or null if it was closed + */ + public function getHandle() + { + return $this->isAvailable() ? $this->handle : null; + } + + /** + * Get the cURL setopt options of the handle. Changing values in the return object will have no effect on the curl + * handle after it is created. + * + * @return Collection + */ + public function getOptions() + { + return $this->options; + } + + /** + * Update a request based on the log messages of the CurlHandle + * + * @param RequestInterface $request Request to update + */ + public function updateRequestFromTransfer(RequestInterface $request) + { + if (!$request->getResponse()) { + return; + } + + // Update the transfer stats of the response + $request->getResponse()->setInfo($this->getInfo()); + + if (!$log = $this->getStderr(true)) { + return; + } + + // Parse the cURL stderr output for outgoing requests + $headers = ''; + fseek($log, 0); + while (($line = fgets($log)) !== false) { + if ($line && $line[0] == '>') { + $headers = substr(trim($line), 2) . "\r\n"; + while (($line = fgets($log)) !== false) { + if ($line[0] == '*' || $line[0] == '<') { + break; + } else { + $headers .= trim($line) . "\r\n"; + } + } + } + } + + // Add request headers to the request exactly as they were sent + if ($headers) { + $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($headers); + if (!empty($parsed['headers'])) { + $request->setHeaders(array()); + foreach ($parsed['headers'] as $name => $value) { + $request->setHeader($name, $value); + } + } + if (!empty($parsed['version'])) { + $request->setProtocolVersion($parsed['version']); + } + } + } + + /** + * Parse the config and replace curl.* configurators into the constant based values so it can be used elsewhere + * + * @param array|Collection $config The configuration we want to parse + * + * @return array + */ + public static function parseCurlConfig($config) + { + $curlOptions = array(); + foreach ($config as $key => $value) { + if (is_string($key) && defined($key)) { + // Convert constants represented as string to constant int values + $key = constant($key); + } + if (is_string($value) && defined($value)) { + $value = constant($value); + } + $curlOptions[$key] = $value; + } + + return $curlOptions; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php new file mode 100644 index 000000000..9e4e63722 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php @@ -0,0 +1,423 @@ + array('CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'), + CURLM_BAD_EASY_HANDLE => array('CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."), + CURLM_OUT_OF_MEMORY => array('CURLM_OUT_OF_MEMORY', 'You are doomed.'), + CURLM_INTERNAL_ERROR => array('CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!') + ); + + /** @var float */ + protected $selectTimeout; + + public function __construct($selectTimeout = 1.0) + { + $this->selectTimeout = $selectTimeout; + $this->multiHandle = curl_multi_init(); + // @codeCoverageIgnoreStart + if ($this->multiHandle === false) { + throw new CurlException('Unable to create multi handle'); + } + // @codeCoverageIgnoreEnd + $this->reset(); + } + + public function __destruct() + { + if (is_resource($this->multiHandle)) { + curl_multi_close($this->multiHandle); + } + } + + public function add(RequestInterface $request) + { + $this->requests[] = $request; + // If requests are currently transferring and this is async, then the + // request must be prepared now as the send() method is not called. + $this->beforeSend($request); + $this->dispatch(self::ADD_REQUEST, array('request' => $request)); + + return $this; + } + + public function all() + { + return $this->requests; + } + + public function remove(RequestInterface $request) + { + $this->removeHandle($request); + if (($index = array_search($request, $this->requests, true)) !== false) { + $request = $this->requests[$index]; + unset($this->requests[$index]); + $this->requests = array_values($this->requests); + $this->dispatch(self::REMOVE_REQUEST, array('request' => $request)); + return true; + } + + return false; + } + + public function reset($hard = false) + { + // Remove each request + if ($this->requests) { + foreach ($this->requests as $request) { + $this->remove($request); + } + } + + $this->handles = new \SplObjectStorage(); + $this->requests = $this->resourceHash = $this->exceptions = $this->successful = array(); + } + + public function send() + { + $this->perform(); + $exceptions = $this->exceptions; + $successful = $this->successful; + $this->reset(); + + if ($exceptions) { + $this->throwMultiException($exceptions, $successful); + } + } + + public function count() + { + return count($this->requests); + } + + /** + * Build and throw a MultiTransferException + * + * @param array $exceptions Exceptions encountered + * @param array $successful Successful requests + * @throws MultiTransferException + */ + protected function throwMultiException(array $exceptions, array $successful) + { + $multiException = new MultiTransferException('Errors during multi transfer'); + + while ($e = array_shift($exceptions)) { + $multiException->addFailedRequestWithException($e['request'], $e['exception']); + } + + // Add successful requests + foreach ($successful as $request) { + if (!$multiException->containsRequest($request)) { + $multiException->addSuccessfulRequest($request); + } + } + + throw $multiException; + } + + /** + * Prepare for sending + * + * @param RequestInterface $request Request to prepare + * @throws \Exception on error preparing the request + */ + protected function beforeSend(RequestInterface $request) + { + try { + $state = $request->setState(RequestInterface::STATE_TRANSFER); + if ($state == RequestInterface::STATE_TRANSFER) { + $this->addHandle($request); + } else { + // Requests might decide they don't need to be sent just before + // transfer (e.g. CachePlugin) + $this->remove($request); + if ($state == RequestInterface::STATE_COMPLETE) { + $this->successful[] = $request; + } + } + } catch (\Exception $e) { + // Queue the exception to be thrown when sent + $this->removeErroredRequest($request, $e); + } + } + + private function addHandle(RequestInterface $request) + { + $handle = $this->createCurlHandle($request)->getHandle(); + $this->checkCurlResult( + curl_multi_add_handle($this->multiHandle, $handle) + ); + } + + /** + * Create a curl handle for a request + * + * @param RequestInterface $request Request + * + * @return CurlHandle + */ + protected function createCurlHandle(RequestInterface $request) + { + $wrapper = CurlHandle::factory($request); + $this->handles[$request] = $wrapper; + $this->resourceHash[(int) $wrapper->getHandle()] = $request; + + return $wrapper; + } + + /** + * Get the data from the multi handle + */ + protected function perform() + { + $event = new Event(array('curl_multi' => $this)); + + while ($this->requests) { + // Notify each request as polling + $blocking = $total = 0; + foreach ($this->requests as $request) { + ++$total; + $event['request'] = $request; + $request->getEventDispatcher()->dispatch(self::POLLING_REQUEST, $event); + // The blocking variable just has to be non-falsey to block the loop + if ($request->getParams()->hasKey(self::BLOCKING)) { + ++$blocking; + } + } + if ($blocking == $total) { + // Sleep to prevent eating CPU because no requests are actually pending a select call + usleep(500); + } else { + $this->executeHandles(); + } + } + } + + /** + * Execute and select curl handles + */ + private function executeHandles() + { + // The first curl_multi_select often times out no matter what, but is usually required for fast transfers + $selectTimeout = 0.001; + $active = false; + do { + while (($mrc = curl_multi_exec($this->multiHandle, $active)) == CURLM_CALL_MULTI_PERFORM); + $this->checkCurlResult($mrc); + $this->processMessages(); + if ($active && curl_multi_select($this->multiHandle, $selectTimeout) === -1) { + // Perform a usleep if a select returns -1: https://bugs.php.net/bug.php?id=61141 + usleep(150); + } + $selectTimeout = $this->selectTimeout; + } while ($active); + } + + /** + * Process any received curl multi messages + */ + private function processMessages() + { + while ($done = curl_multi_info_read($this->multiHandle)) { + $request = $this->resourceHash[(int) $done['handle']]; + try { + $this->processResponse($request, $this->handles[$request], $done); + $this->successful[] = $request; + } catch (\Exception $e) { + $this->removeErroredRequest($request, $e); + } + } + } + + /** + * Remove a request that encountered an exception + * + * @param RequestInterface $request Request to remove + * @param \Exception $e Exception encountered + */ + protected function removeErroredRequest(RequestInterface $request, \Exception $e = null) + { + $this->exceptions[] = array('request' => $request, 'exception' => $e); + $this->remove($request); + $this->dispatch(self::MULTI_EXCEPTION, array('exception' => $e, 'all_exceptions' => $this->exceptions)); + } + + /** + * Check for errors and fix headers of a request based on a curl response + * + * @param RequestInterface $request Request to process + * @param CurlHandle $handle Curl handle object + * @param array $curl Array returned from curl_multi_info_read + * + * @throws CurlException on Curl error + */ + protected function processResponse(RequestInterface $request, CurlHandle $handle, array $curl) + { + // Set the transfer stats on the response + $handle->updateRequestFromTransfer($request); + // Check if a cURL exception occurred, and if so, notify things + $curlException = $this->isCurlException($request, $handle, $curl); + + // Always remove completed curl handles. They can be added back again + // via events if needed (e.g. ExponentialBackoffPlugin) + $this->removeHandle($request); + + if (!$curlException) { + if ($this->validateResponseWasSet($request)) { + $state = $request->setState( + RequestInterface::STATE_COMPLETE, + array('handle' => $handle) + ); + // Only remove the request if it wasn't resent as a result of + // the state change + if ($state != RequestInterface::STATE_TRANSFER) { + $this->remove($request); + } + } + return; + } + + // Set the state of the request to an error + $state = $request->setState(RequestInterface::STATE_ERROR, array('exception' => $curlException)); + // Allow things to ignore the error if possible + if ($state != RequestInterface::STATE_TRANSFER) { + $this->remove($request); + } + + // The error was not handled, so fail + if ($state == RequestInterface::STATE_ERROR) { + /** @var CurlException $curlException */ + throw $curlException; + } + } + + /** + * Remove a curl handle from the curl multi object + * + * @param RequestInterface $request Request that owns the handle + */ + protected function removeHandle(RequestInterface $request) + { + if (isset($this->handles[$request])) { + $handle = $this->handles[$request]; + curl_multi_remove_handle($this->multiHandle, $handle->getHandle()); + unset($this->handles[$request]); + unset($this->resourceHash[(int) $handle->getHandle()]); + $handle->close(); + } + } + + /** + * Check if a cURL transfer resulted in what should be an exception + * + * @param RequestInterface $request Request to check + * @param CurlHandle $handle Curl handle object + * @param array $curl Array returned from curl_multi_info_read + * + * @return CurlException|bool + */ + private function isCurlException(RequestInterface $request, CurlHandle $handle, array $curl) + { + if (CURLM_OK == $curl['result'] || CURLM_CALL_MULTI_PERFORM == $curl['result']) { + return false; + } + + $handle->setErrorNo($curl['result']); + $e = new CurlException(sprintf('[curl] %s: %s [url] %s', + $handle->getErrorNo(), $handle->getError(), $handle->getUrl())); + $e->setCurlHandle($handle) + ->setRequest($request) + ->setCurlInfo($handle->getInfo()) + ->setError($handle->getError(), $handle->getErrorNo()); + + return $e; + } + + /** + * Throw an exception for a cURL multi response if needed + * + * @param int $code Curl response code + * @throws CurlException + */ + private function checkCurlResult($code) + { + if ($code != CURLM_OK && $code != CURLM_CALL_MULTI_PERFORM) { + throw new CurlException(isset($this->multiErrors[$code]) + ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}" + : 'Unexpected cURL error: ' . $code + ); + } + } + + /** + * @link https://github.com/guzzle/guzzle/issues/710 + */ + private function validateResponseWasSet(RequestInterface $request) + { + if ($request->getResponse()) { + return true; + } + + $body = $request instanceof EntityEnclosingRequestInterface + ? $request->getBody() + : null; + + if (!$body) { + $rex = new RequestException( + 'No response was received for a request with no body. This' + . ' could mean that you are saturating your network.' + ); + $rex->setRequest($request); + $this->removeErroredRequest($request, $rex); + } elseif (!$body->isSeekable() || !$body->seek(0)) { + // Nothing we can do with this. Sorry! + $rex = new RequestException( + 'The connection was unexpectedly closed. The request would' + . ' have been retried, but attempting to rewind the' + . ' request body failed.' + ); + $rex->setRequest($request); + $this->removeErroredRequest($request, $rex); + } else { + $this->remove($request); + // Add the request back to the batch to retry automatically. + $this->requests[] = $request; + $this->addHandle($request); + } + + return false; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php new file mode 100644 index 000000000..0ead75735 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php @@ -0,0 +1,58 @@ +maxHandles = $maxHandles; + $this->selectTimeout = $selectTimeout; + // You can get some weird "Too many open files" errors when sending a large amount of requests in parallel. + // These two statements autoload classes before a system runs out of file descriptors so that you can get back + // valuable error messages if you run out. + class_exists('Guzzle\Http\Message\Response'); + class_exists('Guzzle\Http\Exception\CurlException'); + } + + public function add(RequestInterface $request) + { + $this->queued[] = $request; + + return $this; + } + + public function all() + { + $requests = $this->queued; + foreach ($this->handles as $handle) { + $requests = array_merge($requests, $handle->all()); + } + + return $requests; + } + + public function remove(RequestInterface $request) + { + foreach ($this->queued as $i => $r) { + if ($request === $r) { + unset($this->queued[$i]); + return true; + } + } + + foreach ($this->handles as $handle) { + if ($handle->remove($request)) { + return true; + } + } + + return false; + } + + public function reset($hard = false) + { + $this->queued = array(); + $this->groups = array(); + foreach ($this->handles as $handle) { + $handle->reset(); + } + if ($hard) { + $this->handles = array(); + } + + return $this; + } + + public function send() + { + if ($this->queued) { + $group = $this->getAvailableHandle(); + // Add this handle to a list of handles than is claimed + $this->groups[] = $group; + while ($request = array_shift($this->queued)) { + $group->add($request); + } + try { + $group->send(); + array_pop($this->groups); + $this->cleanupHandles(); + } catch (\Exception $e) { + // Remove the group and cleanup if an exception was encountered and no more requests in group + if (!$group->count()) { + array_pop($this->groups); + $this->cleanupHandles(); + } + throw $e; + } + } + } + + public function count() + { + return count($this->all()); + } + + /** + * Get an existing available CurlMulti handle or create a new one + * + * @return CurlMulti + */ + protected function getAvailableHandle() + { + // Grab a handle that is not claimed + foreach ($this->handles as $h) { + if (!in_array($h, $this->groups, true)) { + return $h; + } + } + + // All are claimed, so create one + $handle = new CurlMulti($this->selectTimeout); + $handle->setEventDispatcher($this->getEventDispatcher()); + $this->handles[] = $handle; + + return $handle; + } + + /** + * Trims down unused CurlMulti handles to limit the number of open connections + */ + protected function cleanupHandles() + { + if ($diff = max(0, count($this->handles) - $this->maxHandles)) { + for ($i = count($this->handles) - 1; $i > 0 && $diff > 0; $i--) { + if (!count($this->handles[$i])) { + unset($this->handles[$i]); + $diff--; + } + } + $this->handles = array_values($this->handles); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php new file mode 100644 index 000000000..c3f99dd25 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php @@ -0,0 +1,66 @@ +version) { + $this->version = curl_version(); + } + + return $this->version; + } + + /** + * Get a specific type of curl information + * + * @param string $type Version information to retrieve. This value is one of: + * - version_number: cURL 24 bit version number + * - version: cURL version number, as a string + * - ssl_version_number: OpenSSL 24 bit version number + * - ssl_version: OpenSSL version number, as a string + * - libz_version: zlib version number, as a string + * - host: Information about the host where cURL was built + * - features: A bitmask of the CURL_VERSION_XXX constants + * - protocols: An array of protocols names supported by cURL + * + * @return string|float|bool if the $type is found, and false if not found + */ + public function get($type) + { + $version = $this->getAll(); + + return isset($version[$type]) ? $version[$type] : false; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php new file mode 100644 index 000000000..5d1a0cd87 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php @@ -0,0 +1,147 @@ +request = $request; + $this->emitIo = $emitIo; + } + + /** + * Receive a response header from curl + * + * @param resource $curl Curl handle + * @param string $header Received header + * + * @return int + */ + public function receiveResponseHeader($curl, $header) + { + static $normalize = array("\r", "\n"); + $length = strlen($header); + $header = str_replace($normalize, '', $header); + + if (strpos($header, 'HTTP/') === 0) { + + $startLine = explode(' ', $header, 3); + $code = $startLine[1]; + $status = isset($startLine[2]) ? $startLine[2] : ''; + + // Only download the body of the response to the specified response + // body when a successful response is received. + if ($code >= 200 && $code < 300) { + $body = $this->request->getResponseBody(); + } else { + $body = EntityBody::factory(); + } + + $response = new Response($code, null, $body); + $response->setStatus($code, $status); + $this->request->startResponse($response); + + $this->request->dispatch('request.receive.status_line', array( + 'request' => $this, + 'line' => $header, + 'status_code' => $code, + 'reason_phrase' => $status + )); + + } elseif ($pos = strpos($header, ':')) { + $this->request->getResponse()->addHeader( + trim(substr($header, 0, $pos)), + trim(substr($header, $pos + 1)) + ); + } + + return $length; + } + + /** + * Received a progress notification + * + * @param int $downloadSize Total download size + * @param int $downloaded Amount of bytes downloaded + * @param int $uploadSize Total upload size + * @param int $uploaded Amount of bytes uploaded + * @param resource $handle CurlHandle object + */ + public function progress($downloadSize, $downloaded, $uploadSize, $uploaded, $handle = null) + { + $this->request->dispatch('curl.callback.progress', array( + 'request' => $this->request, + 'handle' => $handle, + 'download_size' => $downloadSize, + 'downloaded' => $downloaded, + 'upload_size' => $uploadSize, + 'uploaded' => $uploaded + )); + } + + /** + * Write data to the response body of a request + * + * @param resource $curl Curl handle + * @param string $write Data that was received + * + * @return int + */ + public function writeResponseBody($curl, $write) + { + if ($this->emitIo) { + $this->request->dispatch('curl.callback.write', array( + 'request' => $this->request, + 'write' => $write + )); + } + + if ($response = $this->request->getResponse()) { + return $response->getBody()->write($write); + } else { + // Unexpected data received before response headers - abort transfer + return 0; + } + } + + /** + * Read data from the request body and send it to curl + * + * @param resource $ch Curl handle + * @param resource $fd File descriptor + * @param int $length Amount of data to read + * + * @return string + */ + public function readRequestBody($ch, $fd, $length) + { + if (!($body = $this->request->getBody())) { + return ''; + } + + $read = (string) $body->read($length); + if ($this->emitIo) { + $this->request->dispatch('curl.callback.read', array('request' => $this->request, 'read' => $read)); + } + + return $read; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php new file mode 100644 index 000000000..b60d170f0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php @@ -0,0 +1,201 @@ +rewindFunction = $callable; + + return $this; + } + + public function rewind() + { + return $this->rewindFunction ? call_user_func($this->rewindFunction, $this) : parent::rewind(); + } + + /** + * Create a new EntityBody from a string + * + * @param string $string String of data + * + * @return EntityBody + */ + public static function fromString($string) + { + $stream = fopen('php://temp', 'r+'); + if ($string !== '') { + fwrite($stream, $string); + rewind($stream); + } + + return new static($stream); + } + + public function compress($filter = 'zlib.deflate') + { + $result = $this->handleCompression($filter); + $this->contentEncoding = $result ? $filter : false; + + return $result; + } + + public function uncompress($filter = 'zlib.inflate') + { + $offsetStart = 0; + + // When inflating gzipped data, the first 10 bytes must be stripped + // if a gzip header is present + if ($filter == 'zlib.inflate') { + // @codeCoverageIgnoreStart + if (!$this->isReadable() || ($this->isConsumed() && !$this->isSeekable())) { + return false; + } + // @codeCoverageIgnoreEnd + if (stream_get_contents($this->stream, 3, 0) === "\x1f\x8b\x08") { + $offsetStart = 10; + } + } + + $this->contentEncoding = false; + + return $this->handleCompression($filter, $offsetStart); + } + + public function getContentLength() + { + return $this->getSize(); + } + + public function getContentType() + { + return $this->getUri() ? Mimetypes::getInstance()->fromFilename($this->getUri()) : null; + } + + public function getContentMd5($rawOutput = false, $base64Encode = false) + { + if ($hash = self::getHash($this, 'md5', $rawOutput)) { + return $hash && $base64Encode ? base64_encode($hash) : $hash; + } else { + return false; + } + } + + /** + * Calculate the MD5 hash of an entity body + * + * @param EntityBodyInterface $body Entity body to calculate the hash for + * @param bool $rawOutput Whether or not to use raw output + * @param bool $base64Encode Whether or not to base64 encode raw output (only if raw output is true) + * + * @return bool|string Returns an MD5 string on success or FALSE on failure + * @deprecated This will be deprecated soon + * @codeCoverageIgnore + */ + public static function calculateMd5(EntityBodyInterface $body, $rawOutput = false, $base64Encode = false) + { + Version::warn(__CLASS__ . ' is deprecated. Use getContentMd5()'); + return $body->getContentMd5($rawOutput, $base64Encode); + } + + public function setStreamFilterContentEncoding($streamFilterContentEncoding) + { + $this->contentEncoding = $streamFilterContentEncoding; + + return $this; + } + + public function getContentEncoding() + { + return strtr($this->contentEncoding, array( + 'zlib.deflate' => 'gzip', + 'bzip2.compress' => 'compress' + )) ?: false; + } + + protected function handleCompression($filter, $offsetStart = 0) + { + // @codeCoverageIgnoreStart + if (!$this->isReadable() || ($this->isConsumed() && !$this->isSeekable())) { + return false; + } + // @codeCoverageIgnoreEnd + + $handle = fopen('php://temp', 'r+'); + $filter = @stream_filter_append($handle, $filter, STREAM_FILTER_WRITE); + if (!$filter) { + return false; + } + + // Seek to the offset start if possible + $this->seek($offsetStart); + while ($data = fread($this->stream, 8096)) { + fwrite($handle, $data); + } + + fclose($this->stream); + $this->stream = $handle; + stream_filter_remove($filter); + $stat = fstat($this->stream); + $this->size = $stat['size']; + $this->rebuildCache(); + $this->seek(0); + + // Remove any existing rewind function as the underlying stream has been replaced + $this->rewindFunction = null; + + return true; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php new file mode 100644 index 000000000..e640f5785 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php @@ -0,0 +1,73 @@ +isClientError()) { + $label = 'Client error response'; + $class = __NAMESPACE__ . '\\ClientErrorResponseException'; + } elseif ($response->isServerError()) { + $label = 'Server error response'; + $class = __NAMESPACE__ . '\\ServerErrorResponseException'; + } else { + $label = 'Unsuccessful response'; + $class = __CLASS__; + } + + $message = $label . PHP_EOL . implode(PHP_EOL, array( + '[status code] ' . $response->getStatusCode(), + '[reason phrase] ' . $response->getReasonPhrase(), + '[url] ' . $request->getUrl(), + )); + + $e = new $class($message); + $e->setResponse($response); + $e->setRequest($request); + + return $e; + } + + /** + * Set the response that caused the exception + * + * @param Response $response Response to set + */ + public function setResponse(Response $response) + { + $this->response = $response; + } + + /** + * Get the response that caused the exception + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php new file mode 100644 index 000000000..04d7ddc05 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php @@ -0,0 +1,8 @@ +curlError = $error; + $this->curlErrorNo = $number; + + return $this; + } + + /** + * Set the associated curl handle + * + * @param CurlHandle $handle Curl handle + * + * @return self + */ + public function setCurlHandle(CurlHandle $handle) + { + $this->handle = $handle; + + return $this; + } + + /** + * Get the associated cURL handle + * + * @return CurlHandle|null + */ + public function getCurlHandle() + { + return $this->handle; + } + + /** + * Get the associated cURL error message + * + * @return string|null + */ + public function getError() + { + return $this->curlError; + } + + /** + * Get the associated cURL error number + * + * @return int|null + */ + public function getErrorNo() + { + return $this->curlErrorNo; + } + + /** + * Returns curl information about the transfer + * + * @return array + */ + public function getCurlInfo() + { + return $this->curlInfo; + } + + /** + * Set curl transfer information + * + * @param array $info Array of curl transfer information + * + * @return self + * @link http://php.net/manual/en/function.curl-getinfo.php + */ + public function setCurlInfo(array $info) + { + $this->curlInfo = $info; + + return $this; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php new file mode 100644 index 000000000..ee87295d3 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php @@ -0,0 +1,10 @@ +successfulRequests, $this->failedRequests); + } + + /** + * Add to the array of successful requests + * + * @param RequestInterface $request Successful request + * + * @return self + */ + public function addSuccessfulRequest(RequestInterface $request) + { + $this->successfulRequests[] = $request; + + return $this; + } + + /** + * Add to the array of failed requests + * + * @param RequestInterface $request Failed request + * + * @return self + */ + public function addFailedRequest(RequestInterface $request) + { + $this->failedRequests[] = $request; + + return $this; + } + + /** + * Add to the array of failed requests and associate with exceptions + * + * @param RequestInterface $request Failed request + * @param \Exception $exception Exception to add and associate with + * + * @return self + */ + public function addFailedRequestWithException(RequestInterface $request, \Exception $exception) + { + $this->add($exception) + ->addFailedRequest($request) + ->exceptionForRequest[spl_object_hash($request)] = $exception; + + return $this; + } + + /** + * Get the Exception that caused the given $request to fail + * + * @param RequestInterface $request Failed command + * + * @return \Exception|null + */ + public function getExceptionForFailedRequest(RequestInterface $request) + { + $oid = spl_object_hash($request); + + return isset($this->exceptionForRequest[$oid]) ? $this->exceptionForRequest[$oid] : null; + } + + /** + * Set all of the successful requests + * + * @param array Array of requests + * + * @return self + */ + public function setSuccessfulRequests(array $requests) + { + $this->successfulRequests = $requests; + + return $this; + } + + /** + * Set all of the failed requests + * + * @param array Array of requests + * + * @return self + */ + public function setFailedRequests(array $requests) + { + $this->failedRequests = $requests; + + return $this; + } + + /** + * Get an array of successful requests sent in the multi transfer + * + * @return array + */ + public function getSuccessfulRequests() + { + return $this->successfulRequests; + } + + /** + * Get an array of failed requests sent in the multi transfer + * + * @return array + */ + public function getFailedRequests() + { + return $this->failedRequests; + } + + /** + * Check if the exception object contains a request + * + * @param RequestInterface $request Request to check + * + * @return bool + */ + public function containsRequest(RequestInterface $request) + { + return in_array($request, $this->failedRequests, true) || in_array($request, $this->successfulRequests, true); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php new file mode 100644 index 000000000..274df2cb1 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php @@ -0,0 +1,39 @@ +request = $request; + + return $this; + } + + /** + * Get the request that caused the exception + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php new file mode 100644 index 000000000..f0f7cfe48 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php @@ -0,0 +1,8 @@ +eventDispatcher = $eventDispatcher; + + return $this; + } + + public function getEventDispatcher() + { + if (!$this->eventDispatcher) { + $this->eventDispatcher = new EventDispatcher(); + } + + return $this->eventDispatcher; + } + + public function dispatch($eventName, array $context = array()) + { + return $this->getEventDispatcher()->dispatch($eventName, new Event($context)); + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->getEventDispatcher()->addSubscriber($subscriber); + + return $this; + } + + public function read($length) + { + $event = array( + 'body' => $this, + 'length' => $length, + 'read' => $this->body->read($length) + ); + $this->dispatch('body.read', $event); + + return $event['read']; + } + + public function write($string) + { + $event = array( + 'body' => $this, + 'write' => $string, + 'result' => $this->body->write($string) + ); + $this->dispatch('body.write', $event); + + return $event['result']; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php new file mode 100644 index 000000000..0d066ffce --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php @@ -0,0 +1,220 @@ +params = new Collection(); + $this->headerFactory = new HeaderFactory(); + $this->headers = new HeaderCollection(); + } + + /** + * Set the header factory to use to create headers + * + * @param HeaderFactoryInterface $factory + * + * @return self + */ + public function setHeaderFactory(HeaderFactoryInterface $factory) + { + $this->headerFactory = $factory; + + return $this; + } + + public function getParams() + { + return $this->params; + } + + public function addHeader($header, $value) + { + if (isset($this->headers[$header])) { + $this->headers[$header]->add($value); + } elseif ($value instanceof HeaderInterface) { + $this->headers[$header] = $value; + } else { + $this->headers[$header] = $this->headerFactory->createHeader($header, $value); + } + + return $this; + } + + public function addHeaders(array $headers) + { + foreach ($headers as $key => $value) { + $this->addHeader($key, $value); + } + + return $this; + } + + public function getHeader($header) + { + return $this->headers[$header]; + } + + public function getHeaders() + { + return $this->headers; + } + + public function getHeaderLines() + { + $headers = array(); + foreach ($this->headers as $value) { + $headers[] = $value->getName() . ': ' . $value; + } + + return $headers; + } + + public function setHeader($header, $value) + { + unset($this->headers[$header]); + $this->addHeader($header, $value); + + return $this; + } + + public function setHeaders(array $headers) + { + $this->headers->clear(); + foreach ($headers as $key => $value) { + $this->addHeader($key, $value); + } + + return $this; + } + + public function hasHeader($header) + { + return isset($this->headers[$header]); + } + + public function removeHeader($header) + { + unset($this->headers[$header]); + + return $this; + } + + /** + * @deprecated Use $message->getHeader()->parseParams() + * @codeCoverageIgnore + */ + public function getTokenizedHeader($header, $token = ';') + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader()->parseParams()'); + if ($this->hasHeader($header)) { + $data = new Collection(); + foreach ($this->getHeader($header)->parseParams() as $values) { + foreach ($values as $key => $value) { + if ($value === '') { + $data->set($data->count(), $key); + } else { + $data->add($key, $value); + } + } + } + return $data; + } + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function setTokenizedHeader($header, $data, $token = ';') + { + Version::warn(__METHOD__ . ' is deprecated.'); + return $this; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getCacheControlDirective($directive) + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->getDirective()'); + if (!($header = $this->getHeader('Cache-Control'))) { + return null; + } + + return $header->getDirective($directive); + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function hasCacheControlDirective($directive) + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->hasDirective()'); + if ($header = $this->getHeader('Cache-Control')) { + return $header->hasDirective($directive); + } else { + return false; + } + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function addCacheControlDirective($directive, $value = true) + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->addDirective()'); + if (!($header = $this->getHeader('Cache-Control'))) { + $this->addHeader('Cache-Control', ''); + $header = $this->getHeader('Cache-Control'); + } + + $header->addDirective($directive, $value); + + return $this; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function removeCacheControlDirective($directive) + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->removeDirective()'); + if ($header = $this->getHeader('Cache-Control')) { + $header->removeDirective($directive); + } + + return $this; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php new file mode 100644 index 000000000..212850a25 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php @@ -0,0 +1,247 @@ +postFields = new QueryString(); + parent::__construct($method, $url, $headers); + } + + /** + * @return string + */ + public function __toString() + { + // Only attempt to include the POST data if it's only fields + if (count($this->postFields) && empty($this->postFiles)) { + return parent::__toString() . (string) $this->postFields; + } + + return parent::__toString() . $this->body; + } + + public function setState($state, array $context = array()) + { + parent::setState($state, $context); + if ($state == self::STATE_TRANSFER && !$this->body && !count($this->postFields) && !count($this->postFiles)) { + $this->setHeader('Content-Length', 0)->removeHeader('Transfer-Encoding'); + } + + return $this->state; + } + + public function setBody($body, $contentType = null) + { + $this->body = EntityBody::factory($body); + + // Auto detect the Content-Type from the path of the request if possible + if ($contentType === null && !$this->hasHeader('Content-Type')) { + $contentType = $this->body->getContentType(); + } + + if ($contentType) { + $this->setHeader('Content-Type', $contentType); + } + + // Always add the Expect 100-Continue header if the body cannot be rewound. This helps with redirects. + if (!$this->body->isSeekable() && $this->expectCutoff !== false) { + $this->setHeader('Expect', '100-Continue'); + } + + // Set the Content-Length header if it can be determined + $size = $this->body->getContentLength(); + if ($size !== null && $size !== false) { + $this->setHeader('Content-Length', $size); + if ($size > $this->expectCutoff) { + $this->setHeader('Expect', '100-Continue'); + } + } elseif (!$this->hasHeader('Content-Length')) { + if ('1.1' == $this->protocolVersion) { + $this->setHeader('Transfer-Encoding', 'chunked'); + } else { + throw new RequestException( + 'Cannot determine Content-Length and cannot use chunked Transfer-Encoding when using HTTP/1.0' + ); + } + } + + return $this; + } + + public function getBody() + { + return $this->body; + } + + /** + * Set the size that the entity body of the request must exceed before adding the Expect: 100-Continue header. + * + * @param int|bool $size Cutoff in bytes. Set to false to never send the expect header (even with non-seekable data) + * + * @return self + */ + public function setExpectHeaderCutoff($size) + { + $this->expectCutoff = $size; + if ($size === false || !$this->body) { + $this->removeHeader('Expect'); + } elseif ($this->body && $this->body->getSize() && $this->body->getSize() > $size) { + $this->setHeader('Expect', '100-Continue'); + } + + return $this; + } + + public function configureRedirects($strict = false, $maxRedirects = 5) + { + $this->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, $strict); + if ($maxRedirects == 0) { + $this->getParams()->set(RedirectPlugin::DISABLE, true); + } else { + $this->getParams()->set(RedirectPlugin::MAX_REDIRECTS, $maxRedirects); + } + + return $this; + } + + public function getPostField($field) + { + return $this->postFields->get($field); + } + + public function getPostFields() + { + return $this->postFields; + } + + public function setPostField($key, $value) + { + $this->postFields->set($key, $value); + $this->processPostFields(); + + return $this; + } + + public function addPostFields($fields) + { + $this->postFields->merge($fields); + $this->processPostFields(); + + return $this; + } + + public function removePostField($field) + { + $this->postFields->remove($field); + $this->processPostFields(); + + return $this; + } + + public function getPostFiles() + { + return $this->postFiles; + } + + public function getPostFile($fieldName) + { + return isset($this->postFiles[$fieldName]) ? $this->postFiles[$fieldName] : null; + } + + public function removePostFile($fieldName) + { + unset($this->postFiles[$fieldName]); + $this->processPostFields(); + + return $this; + } + + public function addPostFile($field, $filename = null, $contentType = null, $postname = null) + { + $data = null; + + if ($field instanceof PostFileInterface) { + $data = $field; + } elseif (is_array($filename)) { + // Allow multiple values to be set in a single key + foreach ($filename as $file) { + $this->addPostFile($field, $file, $contentType); + } + return $this; + } elseif (!is_string($filename)) { + throw new RequestException('The path to a file must be a string'); + } elseif (!empty($filename)) { + // Adding an empty file will cause cURL to error out + $data = new PostFile($field, $filename, $contentType, $postname); + } + + if ($data) { + if (!isset($this->postFiles[$data->getFieldName()])) { + $this->postFiles[$data->getFieldName()] = array($data); + } else { + $this->postFiles[$data->getFieldName()][] = $data; + } + $this->processPostFields(); + } + + return $this; + } + + public function addPostFiles(array $files) + { + foreach ($files as $key => $file) { + if ($file instanceof PostFileInterface) { + $this->addPostFile($file, null, null, false); + } elseif (is_string($file)) { + // Convert non-associative array keys into 'file' + if (is_numeric($key)) { + $key = 'file'; + } + $this->addPostFile($key, $file, null, false); + } else { + throw new RequestException('File must be a string or instance of PostFileInterface'); + } + } + + return $this; + } + + /** + * Determine what type of request should be sent based on post fields + */ + protected function processPostFields() + { + if (!$this->postFiles) { + $this->removeHeader('Expect')->setHeader('Content-Type', self::URL_ENCODED); + } else { + $this->setHeader('Content-Type', self::MULTIPART); + if ($this->expectCutoff !== false) { + $this->setHeader('Expect', '100-Continue'); + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php new file mode 100644 index 000000000..49ad4595d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php @@ -0,0 +1,137 @@ + filenames where filename can be a string or PostFileInterface + * + * @return self + */ + public function addPostFiles(array $files); + + /** + * Configure how redirects are handled for the request + * + * @param bool $strict Set to true to follow strict RFC compliance when redirecting POST requests. Most + * browsers with follow a 301-302 redirect for a POST request with a GET request. This is + * the default behavior of Guzzle. Enable strict redirects to redirect these responses + * with a POST rather than a GET request. + * @param int $maxRedirects Specify the maximum number of allowed redirects. Set to 0 to disable redirects. + * + * @return self + */ + public function configureRedirects($strict = false, $maxRedirects = 5); +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php new file mode 100644 index 000000000..50597b2a6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php @@ -0,0 +1,182 @@ +header = trim($header); + $this->glue = $glue; + + foreach ((array) $values as $value) { + foreach ((array) $value as $v) { + $this->values[] = $v; + } + } + } + + public function __toString() + { + return implode($this->glue . ' ', $this->toArray()); + } + + public function add($value) + { + $this->values[] = $value; + + return $this; + } + + public function getName() + { + return $this->header; + } + + public function setName($name) + { + $this->header = $name; + + return $this; + } + + public function setGlue($glue) + { + $this->glue = $glue; + + return $this; + } + + public function getGlue() + { + return $this->glue; + } + + /** + * Normalize the header to be a single header with an array of values. + * + * If any values of the header contains the glue string value (e.g. ","), then the value will be exploded into + * multiple entries in the header. + * + * @return self + */ + public function normalize() + { + $values = $this->toArray(); + + for ($i = 0, $total = count($values); $i < $total; $i++) { + if (strpos($values[$i], $this->glue) !== false) { + // Explode on glue when the glue is not inside of a comma + foreach (preg_split('/' . preg_quote($this->glue) . '(?=([^"]*"[^"]*")*[^"]*$)/', $values[$i]) as $v) { + $values[] = trim($v); + } + unset($values[$i]); + } + } + + $this->values = array_values($values); + + return $this; + } + + public function hasValue($searchValue) + { + return in_array($searchValue, $this->toArray()); + } + + public function removeValue($searchValue) + { + $this->values = array_values(array_filter($this->values, function ($value) use ($searchValue) { + return $value != $searchValue; + })); + + return $this; + } + + public function toArray() + { + return $this->values; + } + + public function count() + { + return count($this->toArray()); + } + + public function getIterator() + { + return new \ArrayIterator($this->toArray()); + } + + public function parseParams() + { + $params = $matches = array(); + $callback = array($this, 'trimHeader'); + + // Normalize the header into a single array and iterate over all values + foreach ($this->normalize()->toArray() as $val) { + $part = array(); + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + if (!preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + continue; + } + $pieces = array_map($callback, $matches[0]); + $part[$pieces[0]] = isset($pieces[1]) ? $pieces[1] : ''; + } + if ($part) { + $params[] = $part; + } + } + + return $params; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function hasExactHeader($header) + { + Version::warn(__METHOD__ . ' is deprecated'); + return $this->header == $header; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function raw() + { + Version::warn(__METHOD__ . ' is deprecated. Use toArray()'); + return $this->toArray(); + } + + /** + * Trim a header by removing excess spaces and wrapping quotes + * + * @param $str + * + * @return string + */ + protected function trimHeader($str) + { + static $trimmed = "\"' \n\t"; + + return trim($str, $trimmed); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php new file mode 100644 index 000000000..77789e51f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php @@ -0,0 +1,121 @@ +directives = null; + } + + public function removeValue($searchValue) + { + parent::removeValue($searchValue); + $this->directives = null; + } + + /** + * Check if a specific cache control directive exists + * + * @param string $param Directive to retrieve + * + * @return bool + */ + public function hasDirective($param) + { + $directives = $this->getDirectives(); + + return isset($directives[$param]); + } + + /** + * Get a specific cache control directive + * + * @param string $param Directive to retrieve + * + * @return string|bool|null + */ + public function getDirective($param) + { + $directives = $this->getDirectives(); + + return isset($directives[$param]) ? $directives[$param] : null; + } + + /** + * Add a cache control directive + * + * @param string $param Directive to add + * @param string $value Value to set + * + * @return self + */ + public function addDirective($param, $value) + { + $directives = $this->getDirectives(); + $directives[$param] = $value; + $this->updateFromDirectives($directives); + + return $this; + } + + /** + * Remove a cache control directive by name + * + * @param string $param Directive to remove + * + * @return self + */ + public function removeDirective($param) + { + $directives = $this->getDirectives(); + unset($directives[$param]); + $this->updateFromDirectives($directives); + + return $this; + } + + /** + * Get an associative array of cache control directives + * + * @return array + */ + public function getDirectives() + { + if ($this->directives === null) { + $this->directives = array(); + foreach ($this->parseParams() as $collection) { + foreach ($collection as $key => $value) { + $this->directives[$key] = $value === '' ? true : $value; + } + } + } + + return $this->directives; + } + + /** + * Updates the header value based on the parsed directives + * + * @param array $directives Array of cache control directives + */ + protected function updateFromDirectives(array $directives) + { + $this->directives = $directives; + $this->values = array(); + + foreach ($directives as $key => $value) { + $this->values[] = $value === true ? $key : "{$key}={$value}"; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php new file mode 100644 index 000000000..8c7f6aefb --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php @@ -0,0 +1,108 @@ +headers = $headers; + } + + public function __clone() + { + foreach ($this->headers as &$header) { + $header = clone $header; + } + } + + /** + * Clears the header collection + */ + public function clear() + { + $this->headers = array(); + } + + /** + * Set a header on the collection + * + * @param HeaderInterface $header Header to add + * + * @return self + */ + public function add(HeaderInterface $header) + { + $this->headers[strtolower($header->getName())] = $header; + + return $this; + } + + /** + * Get an array of header objects + * + * @return array + */ + public function getAll() + { + return $this->headers; + } + + /** + * Alias of offsetGet + */ + public function get($key) + { + return $this->offsetGet($key); + } + + public function count() + { + return count($this->headers); + } + + public function offsetExists($offset) + { + return isset($this->headers[strtolower($offset)]); + } + + public function offsetGet($offset) + { + $l = strtolower($offset); + + return isset($this->headers[$l]) ? $this->headers[$l] : null; + } + + public function offsetSet($offset, $value) + { + $this->add($value); + } + + public function offsetUnset($offset) + { + unset($this->headers[strtolower($offset)]); + } + + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + + public function toArray() + { + $result = array(); + foreach ($this->headers as $header) { + $result[$header->getName()] = $header->toArray(); + } + + return $result; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php new file mode 100644 index 000000000..0273be52f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php @@ -0,0 +1,26 @@ + 'Guzzle\Http\Message\Header\CacheControl', + 'link' => 'Guzzle\Http\Message\Header\Link', + ); + + public function createHeader($header, $value = null) + { + $lowercase = strtolower($header); + + return isset($this->mapping[$lowercase]) + ? new $this->mapping[$lowercase]($header, $value) + : new Header($header, $value); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php new file mode 100644 index 000000000..9457cf64a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php @@ -0,0 +1,19 @@ +", "rel=\"{$rel}\""); + + foreach ($params as $k => $v) { + $values[] = "{$k}=\"{$v}\""; + } + + return $this->add(implode('; ', $values)); + } + + /** + * Check if a specific link exists for a given rel attribute + * + * @param string $rel rel value + * + * @return bool + */ + public function hasLink($rel) + { + return $this->getLink($rel) !== null; + } + + /** + * Get a specific link for a given rel attribute + * + * @param string $rel Rel value + * + * @return array|null + */ + public function getLink($rel) + { + foreach ($this->getLinks() as $link) { + if (isset($link['rel']) && $link['rel'] == $rel) { + return $link; + } + } + + return null; + } + + /** + * Get an associative array of links + * + * For example: + * Link: ; rel=front; type="image/jpeg", ; rel=back; type="image/jpeg" + * + * + * var_export($response->getLinks()); + * array( + * array( + * 'url' => 'http:/.../front.jpeg', + * 'rel' => 'back', + * 'type' => 'image/jpeg', + * ) + * ) + * + * + * @return array + */ + public function getLinks() + { + $links = $this->parseParams(); + + foreach ($links as &$link) { + $key = key($link); + unset($link[$key]); + $link['url'] = trim($key, '<> '); + } + + return $links; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php new file mode 100644 index 000000000..62bcd4391 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php @@ -0,0 +1,102 @@ +fieldName = $fieldName; + $this->setFilename($filename); + $this->postname = $postname ? $postname : basename($filename); + $this->contentType = $contentType ?: $this->guessContentType(); + } + + public function setFieldName($name) + { + $this->fieldName = $name; + + return $this; + } + + public function getFieldName() + { + return $this->fieldName; + } + + public function setFilename($filename) + { + // Remove leading @ symbol + if (strpos($filename, '@') === 0) { + $filename = substr($filename, 1); + } + + if (!is_readable($filename)) { + throw new InvalidArgumentException("Unable to open {$filename} for reading"); + } + + $this->filename = $filename; + + return $this; + } + + public function setPostname($postname) + { + $this->postname = $postname; + + return $this; + } + + public function getFilename() + { + return $this->filename; + } + + public function getPostname() + { + return $this->postname; + } + + public function setContentType($type) + { + $this->contentType = $type; + + return $this; + } + + public function getContentType() + { + return $this->contentType; + } + + public function getCurlValue() + { + // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax + // See: https://wiki.php.net/rfc/curl-file-upload + if (function_exists('curl_file_create')) { + return curl_file_create($this->filename, $this->contentType, $this->postname); + } + + // Use the old style if using an older version of PHP + $value = "@{$this->filename};filename=" . $this->postname; + if ($this->contentType) { + $value .= ';type=' . $this->contentType; + } + + return $value; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getCurlString() + { + Version::warn(__METHOD__ . ' is deprecated. Use getCurlValue()'); + return $this->getCurlValue(); + } + + /** + * Determine the Content-Type of the file + */ + protected function guessContentType() + { + return Mimetypes::getInstance()->fromFilename($this->filename) ?: 'application/octet-stream'; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php new file mode 100644 index 000000000..7f0779d1e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php @@ -0,0 +1,83 @@ +method = strtoupper($method); + $this->curlOptions = new Collection(); + $this->setUrl($url); + + if ($headers) { + // Special handling for multi-value headers + foreach ($headers as $key => $value) { + // Deal with collisions with Host and Authorization + if ($key == 'host' || $key == 'Host') { + $this->setHeader($key, $value); + } elseif ($value instanceof HeaderInterface) { + $this->addHeader($key, $value); + } else { + foreach ((array) $value as $v) { + $this->addHeader($key, $v); + } + } + } + } + + $this->setState(self::STATE_NEW); + } + + public function __clone() + { + if ($this->eventDispatcher) { + $this->eventDispatcher = clone $this->eventDispatcher; + } + $this->curlOptions = clone $this->curlOptions; + $this->params = clone $this->params; + $this->url = clone $this->url; + $this->response = $this->responseBody = null; + $this->headers = clone $this->headers; + + $this->setState(RequestInterface::STATE_NEW); + $this->dispatch('request.clone', array('request' => $this)); + } + + /** + * Get the HTTP request as a string + * + * @return string + */ + public function __toString() + { + return $this->getRawHeaders() . "\r\n\r\n"; + } + + /** + * Default method that will throw exceptions if an unsuccessful response is received. + * + * @param Event $event Received + * @throws BadResponseException if the response is not successful + */ + public static function onRequestError(Event $event) + { + $e = BadResponseException::factory($event['request'], $event['response']); + $event['request']->setState(self::STATE_ERROR, array('exception' => $e) + $event->toArray()); + throw $e; + } + + public function setClient(ClientInterface $client) + { + $this->client = $client; + + return $this; + } + + public function getClient() + { + return $this->client; + } + + public function getRawHeaders() + { + $protocolVersion = $this->protocolVersion ?: '1.1'; + + return trim($this->method . ' ' . $this->getResource()) . ' ' + . strtoupper(str_replace('https', 'http', $this->url->getScheme())) + . '/' . $protocolVersion . "\r\n" . implode("\r\n", $this->getHeaderLines()); + } + + public function setUrl($url) + { + if ($url instanceof Url) { + $this->url = $url; + } else { + $this->url = Url::factory($url); + } + + // Update the port and host header + $this->setPort($this->url->getPort()); + + if ($this->url->getUsername() || $this->url->getPassword()) { + $this->setAuth($this->url->getUsername(), $this->url->getPassword()); + // Remove the auth info from the URL + $this->url->setUsername(null); + $this->url->setPassword(null); + } + + return $this; + } + + public function send() + { + if (!$this->client) { + throw new RuntimeException('A client must be set on the request'); + } + + return $this->client->send($this); + } + + public function getResponse() + { + return $this->response; + } + + public function getQuery($asString = false) + { + return $asString + ? (string) $this->url->getQuery() + : $this->url->getQuery(); + } + + public function getMethod() + { + return $this->method; + } + + public function getScheme() + { + return $this->url->getScheme(); + } + + public function setScheme($scheme) + { + $this->url->setScheme($scheme); + + return $this; + } + + public function getHost() + { + return $this->url->getHost(); + } + + public function setHost($host) + { + $this->url->setHost($host); + $this->setPort($this->url->getPort()); + + return $this; + } + + public function getProtocolVersion() + { + return $this->protocolVersion; + } + + public function setProtocolVersion($protocol) + { + $this->protocolVersion = $protocol; + + return $this; + } + + public function getPath() + { + return '/' . ltrim($this->url->getPath(), '/'); + } + + public function setPath($path) + { + $this->url->setPath($path); + + return $this; + } + + public function getPort() + { + return $this->url->getPort(); + } + + public function setPort($port) + { + $this->url->setPort($port); + + // Include the port in the Host header if it is not the default port for the scheme of the URL + $scheme = $this->url->getScheme(); + if ($port && (($scheme == 'http' && $port != 80) || ($scheme == 'https' && $port != 443))) { + $this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost() . ':' . $port); + } else { + $this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost()); + } + + return $this; + } + + public function getUsername() + { + return $this->username; + } + + public function getPassword() + { + return $this->password; + } + + public function setAuth($user, $password = '', $scheme = CURLAUTH_BASIC) + { + static $authMap = array( + 'basic' => CURLAUTH_BASIC, + 'digest' => CURLAUTH_DIGEST, + 'ntlm' => CURLAUTH_NTLM, + 'any' => CURLAUTH_ANY + ); + + // If we got false or null, disable authentication + if (!$user) { + $this->password = $this->username = null; + $this->removeHeader('Authorization'); + $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH); + return $this; + } + + if (!is_numeric($scheme)) { + $scheme = strtolower($scheme); + if (!isset($authMap[$scheme])) { + throw new InvalidArgumentException($scheme . ' is not a valid authentication type'); + } + $scheme = $authMap[$scheme]; + } + + $this->username = $user; + $this->password = $password; + + // Bypass CURL when using basic auth to promote connection reuse + if ($scheme == CURLAUTH_BASIC) { + $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH); + $this->setHeader('Authorization', 'Basic ' . base64_encode($this->username . ':' . $this->password)); + } else { + $this->getCurlOptions() + ->set(CURLOPT_HTTPAUTH, $scheme) + ->set(CURLOPT_USERPWD, $this->username . ':' . $this->password); + } + + return $this; + } + + public function getResource() + { + $resource = $this->getPath(); + if ($query = (string) $this->url->getQuery()) { + $resource .= '?' . $query; + } + + return $resource; + } + + public function getUrl($asObject = false) + { + return $asObject ? clone $this->url : (string) $this->url; + } + + public function getState() + { + return $this->state; + } + + public function setState($state, array $context = array()) + { + $oldState = $this->state; + $this->state = $state; + + switch ($state) { + case self::STATE_NEW: + $this->response = null; + break; + case self::STATE_TRANSFER: + if ($oldState !== $state) { + // Fix Content-Length and Transfer-Encoding collisions + if ($this->hasHeader('Transfer-Encoding') && $this->hasHeader('Content-Length')) { + $this->removeHeader('Transfer-Encoding'); + } + $this->dispatch('request.before_send', array('request' => $this)); + } + break; + case self::STATE_COMPLETE: + if ($oldState !== $state) { + $this->processResponse($context); + $this->responseBody = null; + } + break; + case self::STATE_ERROR: + if (isset($context['exception'])) { + $this->dispatch('request.exception', array( + 'request' => $this, + 'response' => isset($context['response']) ? $context['response'] : $this->response, + 'exception' => isset($context['exception']) ? $context['exception'] : null + )); + } + } + + return $this->state; + } + + public function getCurlOptions() + { + return $this->curlOptions; + } + + public function startResponse(Response $response) + { + $this->state = self::STATE_TRANSFER; + $response->setEffectiveUrl((string) $this->getUrl()); + $this->response = $response; + + return $this; + } + + public function setResponse(Response $response, $queued = false) + { + $response->setEffectiveUrl((string) $this->url); + + if ($queued) { + $ed = $this->getEventDispatcher(); + $ed->addListener('request.before_send', $f = function ($e) use ($response, &$f, $ed) { + $e['request']->setResponse($response); + $ed->removeListener('request.before_send', $f); + }, -9999); + } else { + $this->response = $response; + // If a specific response body is specified, then use it instead of the response's body + if ($this->responseBody && !$this->responseBody->getCustomData('default') && !$response->isRedirect()) { + $this->getResponseBody()->write((string) $this->response->getBody()); + } else { + $this->responseBody = $this->response->getBody(); + } + $this->setState(self::STATE_COMPLETE); + } + + return $this; + } + + public function setResponseBody($body) + { + // Attempt to open a file for writing if a string was passed + if (is_string($body)) { + // @codeCoverageIgnoreStart + if (!($body = fopen($body, 'w+'))) { + throw new InvalidArgumentException('Could not open ' . $body . ' for writing'); + } + // @codeCoverageIgnoreEnd + } + + $this->responseBody = EntityBody::factory($body); + + return $this; + } + + public function getResponseBody() + { + if ($this->responseBody === null) { + $this->responseBody = EntityBody::factory()->setCustomData('default', true); + } + + return $this->responseBody; + } + + /** + * Determine if the response body is repeatable (readable + seekable) + * + * @return bool + * @deprecated Use getResponseBody()->isSeekable() + * @codeCoverageIgnore + */ + public function isResponseBodyRepeatable() + { + Version::warn(__METHOD__ . ' is deprecated. Use $request->getResponseBody()->isRepeatable()'); + return !$this->responseBody ? true : $this->responseBody->isRepeatable(); + } + + public function getCookies() + { + if ($cookie = $this->getHeader('Cookie')) { + $data = ParserRegistry::getInstance()->getParser('cookie')->parseCookie($cookie); + return $data['cookies']; + } + + return array(); + } + + public function getCookie($name) + { + $cookies = $this->getCookies(); + + return isset($cookies[$name]) ? $cookies[$name] : null; + } + + public function addCookie($name, $value) + { + if (!$this->hasHeader('Cookie')) { + $this->setHeader('Cookie', "{$name}={$value}"); + } else { + $this->getHeader('Cookie')->add("{$name}={$value}"); + } + + // Always use semicolons to separate multiple cookie headers + $this->getHeader('Cookie')->setGlue(';'); + + return $this; + } + + public function removeCookie($name) + { + if ($cookie = $this->getHeader('Cookie')) { + foreach ($cookie as $cookieValue) { + if (strpos($cookieValue, $name . '=') === 0) { + $cookie->removeValue($cookieValue); + } + } + } + + return $this; + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher) + { + $this->eventDispatcher = $eventDispatcher; + $this->eventDispatcher->addListener('request.error', array(__CLASS__, 'onRequestError'), -255); + + return $this; + } + + public function getEventDispatcher() + { + if (!$this->eventDispatcher) { + $this->setEventDispatcher(new EventDispatcher()); + } + + return $this->eventDispatcher; + } + + public function dispatch($eventName, array $context = array()) + { + $context['request'] = $this; + + return $this->getEventDispatcher()->dispatch($eventName, new Event($context)); + } + + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->getEventDispatcher()->addSubscriber($subscriber); + + return $this; + } + + /** + * Get an array containing the request and response for event notifications + * + * @return array + */ + protected function getEventArray() + { + return array( + 'request' => $this, + 'response' => $this->response + ); + } + + /** + * Process a received response + * + * @param array $context Contextual information + * @throws RequestException|BadResponseException on unsuccessful responses + */ + protected function processResponse(array $context = array()) + { + if (!$this->response) { + // If no response, then processResponse shouldn't have been called + $e = new RequestException('Error completing request'); + $e->setRequest($this); + throw $e; + } + + $this->state = self::STATE_COMPLETE; + + // A request was sent, but we don't know if we'll send more or if the final response will be successful + $this->dispatch('request.sent', $this->getEventArray() + $context); + + // Some response processors will remove the response or reset the state (example: ExponentialBackoffPlugin) + if ($this->state == RequestInterface::STATE_COMPLETE) { + + // The request completed, so the HTTP transaction is complete + $this->dispatch('request.complete', $this->getEventArray()); + + // If the response is bad, allow listeners to modify it or throw exceptions. You can change the response by + // modifying the Event object in your listeners or calling setResponse() on the request + if ($this->response->isError()) { + $event = new Event($this->getEventArray()); + $this->getEventDispatcher()->dispatch('request.error', $event); + // Allow events of request.error to quietly change the response + if ($event['response'] !== $this->response) { + $this->response = $event['response']; + } + } + + // If a successful response was received, dispatch an event + if ($this->response->isSuccessful()) { + $this->dispatch('request.success', $this->getEventArray()); + } + } + } + + /** + * @deprecated Use Guzzle\Plugin\Cache\DefaultCanCacheStrategy + * @codeCoverageIgnore + */ + public function canCache() + { + Version::warn(__METHOD__ . ' is deprecated. Use Guzzle\Plugin\Cache\DefaultCanCacheStrategy.'); + if (class_exists('Guzzle\Plugin\Cache\DefaultCanCacheStrategy')) { + $canCache = new \Guzzle\Plugin\Cache\DefaultCanCacheStrategy(); + return $canCache->canCacheRequest($this); + } else { + return false; + } + } + + /** + * @deprecated Use the history plugin (not emitting a warning as this is built-into the RedirectPlugin for now) + * @codeCoverageIgnore + */ + public function setIsRedirect($isRedirect) + { + $this->isRedirect = $isRedirect; + + return $this; + } + + /** + * @deprecated Use the history plugin + * @codeCoverageIgnore + */ + public function isRedirect() + { + Version::warn(__METHOD__ . ' is deprecated. Use the HistoryPlugin to track this.'); + return $this->isRedirect; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php new file mode 100644 index 000000000..ba00a7676 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php @@ -0,0 +1,359 @@ +methods = array_flip(get_class_methods(__CLASS__)); + } + + public function fromMessage($message) + { + $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($message); + + if (!$parsed) { + return false; + } + + $request = $this->fromParts($parsed['method'], $parsed['request_url'], + $parsed['headers'], $parsed['body'], $parsed['protocol'], + $parsed['version']); + + // EntityEnclosingRequest adds an "Expect: 100-Continue" header when using a raw request body for PUT or POST + // requests. This factory method should accurately reflect the message, so here we are removing the Expect + // header if one was not supplied in the message. + if (!isset($parsed['headers']['Expect']) && !isset($parsed['headers']['expect'])) { + $request->removeHeader('Expect'); + } + + return $request; + } + + public function fromParts( + $method, + array $urlParts, + $headers = null, + $body = null, + $protocol = 'HTTP', + $protocolVersion = '1.1' + ) { + return $this->create($method, Url::buildUrl($urlParts), $headers, $body) + ->setProtocolVersion($protocolVersion); + } + + public function create($method, $url, $headers = null, $body = null, array $options = array()) + { + $method = strtoupper($method); + + if ($method == 'GET' || $method == 'HEAD' || $method == 'TRACE') { + // Handle non-entity-enclosing request methods + $request = new $this->requestClass($method, $url, $headers); + if ($body) { + // The body is where the response body will be stored + $type = gettype($body); + if ($type == 'string' || $type == 'resource' || $type == 'object') { + $request->setResponseBody($body); + } + } + } else { + // Create an entity enclosing request by default + $request = new $this->entityEnclosingRequestClass($method, $url, $headers); + if ($body || $body === '0') { + // Add POST fields and files to an entity enclosing request if an array is used + if (is_array($body) || $body instanceof Collection) { + // Normalize PHP style cURL uploads with a leading '@' symbol + foreach ($body as $key => $value) { + if (is_string($value) && substr($value, 0, 1) == '@') { + $request->addPostFile($key, $value); + unset($body[$key]); + } + } + // Add the fields if they are still present and not all files + $request->addPostFields($body); + } else { + // Add a raw entity body body to the request + $request->setBody($body, (string) $request->getHeader('Content-Type')); + if ((string) $request->getHeader('Transfer-Encoding') == 'chunked') { + $request->removeHeader('Content-Length'); + } + } + } + } + + if ($options) { + $this->applyOptions($request, $options); + } + + return $request; + } + + /** + * Clone a request while changing the method. Emulates the behavior of + * {@see Guzzle\Http\Message\Request::clone}, but can change the HTTP method. + * + * @param RequestInterface $request Request to clone + * @param string $method Method to set + * + * @return RequestInterface + */ + public function cloneRequestWithMethod(RequestInterface $request, $method) + { + // Create the request with the same client if possible + if ($request->getClient()) { + $cloned = $request->getClient()->createRequest($method, $request->getUrl(), $request->getHeaders()); + } else { + $cloned = $this->create($method, $request->getUrl(), $request->getHeaders()); + } + + $cloned->getCurlOptions()->replace($request->getCurlOptions()->toArray()); + $cloned->setEventDispatcher(clone $request->getEventDispatcher()); + // Ensure that that the Content-Length header is not copied if changing to GET or HEAD + if (!($cloned instanceof EntityEnclosingRequestInterface)) { + $cloned->removeHeader('Content-Length'); + } elseif ($request instanceof EntityEnclosingRequestInterface) { + $cloned->setBody($request->getBody()); + } + $cloned->getParams()->replace($request->getParams()->toArray()); + $cloned->dispatch('request.clone', array('request' => $cloned)); + + return $cloned; + } + + public function applyOptions(RequestInterface $request, array $options = array(), $flags = self::OPTIONS_NONE) + { + // Iterate over each key value pair and attempt to apply a config using function visitors + foreach ($options as $key => $value) { + $method = "visit_{$key}"; + if (isset($this->methods[$method])) { + $this->{$method}($request, $value, $flags); + } + } + } + + protected function visit_headers(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('headers value must be an array'); + } + + if ($flags & self::OPTIONS_AS_DEFAULTS) { + // Merge headers in but do not overwrite existing values + foreach ($value as $key => $header) { + if (!$request->hasHeader($key)) { + $request->setHeader($key, $header); + } + } + } else { + $request->addHeaders($value); + } + } + + protected function visit_body(RequestInterface $request, $value, $flags) + { + if ($request instanceof EntityEnclosingRequestInterface) { + $request->setBody($value); + } else { + throw new InvalidArgumentException('Attempting to set a body on a non-entity-enclosing request'); + } + } + + protected function visit_allow_redirects(RequestInterface $request, $value, $flags) + { + if ($value === false) { + $request->getParams()->set(RedirectPlugin::DISABLE, true); + } + } + + protected function visit_auth(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('auth value must be an array'); + } + + $request->setAuth($value[0], isset($value[1]) ? $value[1] : null, isset($value[2]) ? $value[2] : 'basic'); + } + + protected function visit_query(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('query value must be an array'); + } + + if ($flags & self::OPTIONS_AS_DEFAULTS) { + // Merge query string values in but do not overwrite existing values + $query = $request->getQuery(); + $query->overwriteWith(array_diff_key($value, $query->toArray())); + } else { + $request->getQuery()->overwriteWith($value); + } + } + + protected function visit_cookies(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('cookies value must be an array'); + } + + foreach ($value as $name => $v) { + $request->addCookie($name, $v); + } + } + + protected function visit_events(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('events value must be an array'); + } + + foreach ($value as $name => $method) { + if (is_array($method)) { + $request->getEventDispatcher()->addListener($name, $method[0], $method[1]); + } else { + $request->getEventDispatcher()->addListener($name, $method); + } + } + } + + protected function visit_plugins(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('plugins value must be an array'); + } + + foreach ($value as $plugin) { + $request->addSubscriber($plugin); + } + } + + protected function visit_exceptions(RequestInterface $request, $value, $flags) + { + if ($value === false || $value === 0) { + $dispatcher = $request->getEventDispatcher(); + foreach ($dispatcher->getListeners('request.error') as $listener) { + if (is_array($listener) && $listener[0] == 'Guzzle\Http\Message\Request' && $listener[1] = 'onRequestError') { + $dispatcher->removeListener('request.error', $listener); + break; + } + } + } + } + + protected function visit_save_to(RequestInterface $request, $value, $flags) + { + $request->setResponseBody($value); + } + + protected function visit_params(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('params value must be an array'); + } + + $request->getParams()->overwriteWith($value); + } + + protected function visit_timeout(RequestInterface $request, $value, $flags) + { + if (defined('CURLOPT_TIMEOUT_MS')) { + $request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, $value * 1000); + } else { + $request->getCurlOptions()->set(CURLOPT_TIMEOUT, $value); + } + } + + protected function visit_connect_timeout(RequestInterface $request, $value, $flags) + { + if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { + $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, $value * 1000); + } else { + $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, $value); + } + } + + protected function visit_debug(RequestInterface $request, $value, $flags) + { + if ($value) { + $request->getCurlOptions()->set(CURLOPT_VERBOSE, true); + } + } + + protected function visit_verify(RequestInterface $request, $value, $flags) + { + $curl = $request->getCurlOptions(); + if ($value === true || is_string($value)) { + $curl[CURLOPT_SSL_VERIFYHOST] = 2; + $curl[CURLOPT_SSL_VERIFYPEER] = true; + if ($value !== true) { + $curl[CURLOPT_CAINFO] = $value; + } + } elseif ($value === false) { + unset($curl[CURLOPT_CAINFO]); + $curl[CURLOPT_SSL_VERIFYHOST] = 0; + $curl[CURLOPT_SSL_VERIFYPEER] = false; + } + } + + protected function visit_proxy(RequestInterface $request, $value, $flags) + { + $request->getCurlOptions()->set(CURLOPT_PROXY, $value, $flags); + } + + protected function visit_cert(RequestInterface $request, $value, $flags) + { + if (is_array($value)) { + $request->getCurlOptions()->set(CURLOPT_SSLCERT, $value[0]); + $request->getCurlOptions()->set(CURLOPT_SSLCERTPASSWD, $value[1]); + } else { + $request->getCurlOptions()->set(CURLOPT_SSLCERT, $value); + } + } + + protected function visit_ssl_key(RequestInterface $request, $value, $flags) + { + if (is_array($value)) { + $request->getCurlOptions()->set(CURLOPT_SSLKEY, $value[0]); + $request->getCurlOptions()->set(CURLOPT_SSLKEYPASSWD, $value[1]); + } else { + $request->getCurlOptions()->set(CURLOPT_SSLKEY, $value); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php new file mode 100644 index 000000000..6088f10e9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php @@ -0,0 +1,105 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Reserved for WebDAV advanced collections expired proposal', + 426 => 'Upgrade required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ); + + /** @var EntityBodyInterface The response body */ + protected $body; + + /** @var string The reason phrase of the response (human readable code) */ + protected $reasonPhrase; + + /** @var string The status code of the response */ + protected $statusCode; + + /** @var array Information about the request */ + protected $info = array(); + + /** @var string The effective URL that returned this response */ + protected $effectiveUrl; + + /** @var array Cacheable response codes (see RFC 2616:13.4) */ + protected static $cacheResponseCodes = array(200, 203, 206, 300, 301, 410); + + /** + * Create a new Response based on a raw response message + * + * @param string $message Response message + * + * @return self|bool Returns false on error + */ + public static function fromMessage($message) + { + $data = ParserRegistry::getInstance()->getParser('message')->parseResponse($message); + if (!$data) { + return false; + } + + $response = new static($data['code'], $data['headers'], $data['body']); + $response->setProtocol($data['protocol'], $data['version']) + ->setStatus($data['code'], $data['reason_phrase']); + + // Set the appropriate Content-Length if the one set is inaccurate (e.g. setting to X) + $contentLength = (string) $response->getHeader('Content-Length'); + $actualLength = strlen($data['body']); + if (strlen($data['body']) > 0 && $contentLength != $actualLength) { + $response->setHeader('Content-Length', $actualLength); + } + + return $response; + } + + /** + * Construct the response + * + * @param string $statusCode The response status code (e.g. 200, 404, etc) + * @param ToArrayInterface|array $headers The response headers + * @param string|resource|EntityBodyInterface $body The body of the response + * + * @throws BadResponseException if an invalid response code is given + */ + public function __construct($statusCode, $headers = null, $body = null) + { + parent::__construct(); + $this->setStatus($statusCode); + $this->body = EntityBody::factory($body !== null ? $body : ''); + + if ($headers) { + if (is_array($headers)) { + $this->setHeaders($headers); + } elseif ($headers instanceof ToArrayInterface) { + $this->setHeaders($headers->toArray()); + } else { + throw new BadResponseException('Invalid headers argument received'); + } + } + } + + /** + * @return string + */ + public function __toString() + { + return $this->getMessage(); + } + + public function serialize() + { + return json_encode(array( + 'status' => $this->statusCode, + 'body' => (string) $this->body, + 'headers' => $this->headers->toArray() + )); + } + + public function unserialize($serialize) + { + $data = json_decode($serialize, true); + $this->__construct($data['status'], $data['headers'], $data['body']); + } + + /** + * Get the response entity body + * + * @param bool $asString Set to TRUE to return a string of the body rather than a full body object + * + * @return EntityBodyInterface|string + */ + public function getBody($asString = false) + { + return $asString ? (string) $this->body : $this->body; + } + + /** + * Set the response entity body + * + * @param EntityBodyInterface|string $body Body to set + * + * @return self + */ + public function setBody($body) + { + $this->body = EntityBody::factory($body); + + return $this; + } + + /** + * Set the protocol and protocol version of the response + * + * @param string $protocol Response protocol + * @param string $version Protocol version + * + * @return self + */ + public function setProtocol($protocol, $version) + { + $this->protocol = $protocol; + $this->protocolVersion = $version; + + return $this; + } + + /** + * Get the protocol used for the response (e.g. HTTP) + * + * @return string + */ + public function getProtocol() + { + return $this->protocol; + } + + /** + * Get the HTTP protocol version + * + * @return string + */ + public function getProtocolVersion() + { + return $this->protocolVersion; + } + + /** + * Get a cURL transfer information + * + * @param string $key A single statistic to check + * + * @return array|string|null Returns all stats if no key is set, a single stat if a key is set, or null if a key + * is set and not found + * @link http://www.php.net/manual/en/function.curl-getinfo.php + */ + public function getInfo($key = null) + { + if ($key === null) { + return $this->info; + } elseif (array_key_exists($key, $this->info)) { + return $this->info[$key]; + } else { + return null; + } + } + + /** + * Set the transfer information + * + * @param array $info Array of cURL transfer stats + * + * @return self + */ + public function setInfo(array $info) + { + $this->info = $info; + + return $this; + } + + /** + * Set the response status + * + * @param int $statusCode Response status code to set + * @param string $reasonPhrase Response reason phrase + * + * @return self + * @throws BadResponseException when an invalid response code is received + */ + public function setStatus($statusCode, $reasonPhrase = '') + { + $this->statusCode = (int) $statusCode; + + if (!$reasonPhrase && isset(self::$statusTexts[$this->statusCode])) { + $this->reasonPhrase = self::$statusTexts[$this->statusCode]; + } else { + $this->reasonPhrase = $reasonPhrase; + } + + return $this; + } + + /** + * Get the response status code + * + * @return integer + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Get the entire response as a string + * + * @return string + */ + public function getMessage() + { + $message = $this->getRawHeaders(); + + // Only include the body in the message if the size is < 2MB + $size = $this->body->getSize(); + if ($size < 2097152) { + $message .= (string) $this->body; + } + + return $message; + } + + /** + * Get the the raw message headers as a string + * + * @return string + */ + public function getRawHeaders() + { + $headers = 'HTTP/1.1 ' . $this->statusCode . ' ' . $this->reasonPhrase . "\r\n"; + $lines = $this->getHeaderLines(); + if (!empty($lines)) { + $headers .= implode("\r\n", $lines) . "\r\n"; + } + + return $headers . "\r\n"; + } + + /** + * Get the response reason phrase- a human readable version of the numeric + * status code + * + * @return string + */ + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + /** + * Get the Accept-Ranges HTTP header + * + * @return string Returns what partial content range types this server supports. + */ + public function getAcceptRanges() + { + return (string) $this->getHeader('Accept-Ranges'); + } + + /** + * Calculate the age of the response + * + * @return integer + */ + public function calculateAge() + { + $age = $this->getHeader('Age'); + + if ($age === null && $this->getDate()) { + $age = time() - strtotime($this->getDate()); + } + + return $age === null ? null : (int) (string) $age; + } + + /** + * Get the Age HTTP header + * + * @return integer|null Returns the age the object has been in a proxy cache in seconds. + */ + public function getAge() + { + return (string) $this->getHeader('Age'); + } + + /** + * Get the Allow HTTP header + * + * @return string|null Returns valid actions for a specified resource. To be used for a 405 Method not allowed. + */ + public function getAllow() + { + return (string) $this->getHeader('Allow'); + } + + /** + * Check if an HTTP method is allowed by checking the Allow response header + * + * @param string $method Method to check + * + * @return bool + */ + public function isMethodAllowed($method) + { + $allow = $this->getHeader('Allow'); + if ($allow) { + foreach (explode(',', $allow) as $allowable) { + if (!strcasecmp(trim($allowable), $method)) { + return true; + } + } + } + + return false; + } + + /** + * Get the Cache-Control HTTP header + * + * @return string + */ + public function getCacheControl() + { + return (string) $this->getHeader('Cache-Control'); + } + + /** + * Get the Connection HTTP header + * + * @return string + */ + public function getConnection() + { + return (string) $this->getHeader('Connection'); + } + + /** + * Get the Content-Encoding HTTP header + * + * @return string|null + */ + public function getContentEncoding() + { + return (string) $this->getHeader('Content-Encoding'); + } + + /** + * Get the Content-Language HTTP header + * + * @return string|null Returns the language the content is in. + */ + public function getContentLanguage() + { + return (string) $this->getHeader('Content-Language'); + } + + /** + * Get the Content-Length HTTP header + * + * @return integer Returns the length of the response body in bytes + */ + public function getContentLength() + { + return (int) (string) $this->getHeader('Content-Length'); + } + + /** + * Get the Content-Location HTTP header + * + * @return string|null Returns an alternate location for the returned data (e.g /index.htm) + */ + public function getContentLocation() + { + return (string) $this->getHeader('Content-Location'); + } + + /** + * Get the Content-Disposition HTTP header + * + * @return string|null Returns the Content-Disposition header + */ + public function getContentDisposition() + { + return (string) $this->getHeader('Content-Disposition'); + } + + /** + * Get the Content-MD5 HTTP header + * + * @return string|null Returns a Base64-encoded binary MD5 sum of the content of the response. + */ + public function getContentMd5() + { + return (string) $this->getHeader('Content-MD5'); + } + + /** + * Get the Content-Range HTTP header + * + * @return string Returns where in a full body message this partial message belongs (e.g. bytes 21010-47021/47022). + */ + public function getContentRange() + { + return (string) $this->getHeader('Content-Range'); + } + + /** + * Get the Content-Type HTTP header + * + * @return string Returns the mime type of this content. + */ + public function getContentType() + { + return (string) $this->getHeader('Content-Type'); + } + + /** + * Checks if the Content-Type is of a certain type. This is useful if the + * Content-Type header contains charset information and you need to know if + * the Content-Type matches a particular type. + * + * @param string $type Content type to check against + * + * @return bool + */ + public function isContentType($type) + { + return stripos($this->getHeader('Content-Type'), $type) !== false; + } + + /** + * Get the Date HTTP header + * + * @return string|null Returns the date and time that the message was sent. + */ + public function getDate() + { + return (string) $this->getHeader('Date'); + } + + /** + * Get the ETag HTTP header + * + * @return string|null Returns an identifier for a specific version of a resource, often a Message digest. + */ + public function getEtag() + { + return (string) $this->getHeader('ETag'); + } + + /** + * Get the Expires HTTP header + * + * @return string|null Returns the date/time after which the response is considered stale. + */ + public function getExpires() + { + return (string) $this->getHeader('Expires'); + } + + /** + * Get the Last-Modified HTTP header + * + * @return string|null Returns the last modified date for the requested object, in RFC 2822 format + * (e.g. Tue, 15 Nov 1994 12:45:26 GMT) + */ + public function getLastModified() + { + return (string) $this->getHeader('Last-Modified'); + } + + /** + * Get the Location HTTP header + * + * @return string|null Used in redirection, or when a new resource has been created. + */ + public function getLocation() + { + return (string) $this->getHeader('Location'); + } + + /** + * Get the Pragma HTTP header + * + * @return Header|null Returns the implementation-specific headers that may have various effects anywhere along + * the request-response chain. + */ + public function getPragma() + { + return (string) $this->getHeader('Pragma'); + } + + /** + * Get the Proxy-Authenticate HTTP header + * + * @return string|null Authentication to access the proxy (e.g. Basic) + */ + public function getProxyAuthenticate() + { + return (string) $this->getHeader('Proxy-Authenticate'); + } + + /** + * Get the Retry-After HTTP header + * + * @return int|null If an entity is temporarily unavailable, this instructs the client to try again after a + * specified period of time. + */ + public function getRetryAfter() + { + return (string) $this->getHeader('Retry-After'); + } + + /** + * Get the Server HTTP header + * + * @return string|null A name for the server + */ + public function getServer() + { + return (string) $this->getHeader('Server'); + } + + /** + * Get the Set-Cookie HTTP header + * + * @return string|null An HTTP cookie. + */ + public function getSetCookie() + { + return (string) $this->getHeader('Set-Cookie'); + } + + /** + * Get the Trailer HTTP header + * + * @return string|null The Trailer general field value indicates that the given set of header fields is present in + * the trailer of a message encoded with chunked transfer-coding. + */ + public function getTrailer() + { + return (string) $this->getHeader('Trailer'); + } + + /** + * Get the Transfer-Encoding HTTP header + * + * @return string|null The form of encoding used to safely transfer the entity to the user + */ + public function getTransferEncoding() + { + return (string) $this->getHeader('Transfer-Encoding'); + } + + /** + * Get the Vary HTTP header + * + * @return string|null Tells downstream proxies how to match future request headers to decide whether the cached + * response can be used rather than requesting a fresh one from the origin server. + */ + public function getVary() + { + return (string) $this->getHeader('Vary'); + } + + /** + * Get the Via HTTP header + * + * @return string|null Informs the client of proxies through which the response was sent. + */ + public function getVia() + { + return (string) $this->getHeader('Via'); + } + + /** + * Get the Warning HTTP header + * + * @return string|null A general warning about possible problems with the entity body + */ + public function getWarning() + { + return (string) $this->getHeader('Warning'); + } + + /** + * Get the WWW-Authenticate HTTP header + * + * @return string|null Indicates the authentication scheme that should be used to access the requested entity + */ + public function getWwwAuthenticate() + { + return (string) $this->getHeader('WWW-Authenticate'); + } + + /** + * Checks if HTTP Status code is a Client Error (4xx) + * + * @return bool + */ + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Checks if HTTP Status code is Server OR Client Error (4xx or 5xx) + * + * @return boolean + */ + public function isError() + { + return $this->isClientError() || $this->isServerError(); + } + + /** + * Checks if HTTP Status code is Information (1xx) + * + * @return bool + */ + public function isInformational() + { + return $this->statusCode < 200; + } + + /** + * Checks if HTTP Status code is a Redirect (3xx) + * + * @return bool + */ + public function isRedirect() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Checks if HTTP Status code is Server Error (5xx) + * + * @return bool + */ + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Checks if HTTP Status code is Successful (2xx | 304) + * + * @return bool + */ + public function isSuccessful() + { + return ($this->statusCode >= 200 && $this->statusCode < 300) || $this->statusCode == 304; + } + + /** + * Check if the response can be cached based on the response headers + * + * @return bool Returns TRUE if the response can be cached or false if not + */ + public function canCache() + { + // Check if the response is cacheable based on the code + if (!in_array((int) $this->getStatusCode(), self::$cacheResponseCodes)) { + return false; + } + + // Make sure a valid body was returned and can be cached + if ((!$this->getBody()->isReadable() || !$this->getBody()->isSeekable()) + && ($this->getContentLength() > 0 || $this->getTransferEncoding() == 'chunked')) { + return false; + } + + // Never cache no-store resources (this is a private cache, so private + // can be cached) + if ($this->getHeader('Cache-Control') && $this->getHeader('Cache-Control')->hasDirective('no-store')) { + return false; + } + + return $this->isFresh() || $this->getFreshness() === null || $this->canValidate(); + } + + /** + * Gets the number of seconds from the current time in which this response is still considered fresh + * + * @return int|null Returns the number of seconds + */ + public function getMaxAge() + { + if ($header = $this->getHeader('Cache-Control')) { + // s-max-age, then max-age, then Expires + if ($age = $header->getDirective('s-maxage')) { + return $age; + } + if ($age = $header->getDirective('max-age')) { + return $age; + } + } + + if ($this->getHeader('Expires')) { + return strtotime($this->getExpires()) - time(); + } + + return null; + } + + /** + * Check if the response is considered fresh. + * + * A response is considered fresh when its age is less than or equal to the freshness lifetime (maximum age) of the + * response. + * + * @return bool|null + */ + public function isFresh() + { + $fresh = $this->getFreshness(); + + return $fresh === null ? null : $fresh >= 0; + } + + /** + * Check if the response can be validated against the origin server using a conditional GET request. + * + * @return bool + */ + public function canValidate() + { + return $this->getEtag() || $this->getLastModified(); + } + + /** + * Get the freshness of the response by returning the difference of the maximum lifetime of the response and the + * age of the response (max-age - age). + * + * Freshness values less than 0 mean that the response is no longer fresh and is ABS(freshness) seconds expired. + * Freshness values of greater than zero is the number of seconds until the response is no longer fresh. A NULL + * result means that no freshness information is available. + * + * @return int + */ + public function getFreshness() + { + $maxAge = $this->getMaxAge(); + $age = $this->calculateAge(); + + return $maxAge && $age ? ($maxAge - $age) : null; + } + + /** + * Parse the JSON response body and return an array + * + * @return array|string|int|bool|float + * @throws RuntimeException if the response body is not in JSON format + */ + public function json() + { + $data = json_decode((string) $this->body, true); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new RuntimeException('Unable to parse response body into JSON: ' . json_last_error()); + } + + return $data === null ? array() : $data; + } + + /** + * Parse the XML response body and return a \SimpleXMLElement. + * + * In order to prevent XXE attacks, this method disables loading external + * entities. If you rely on external entities, then you must parse the + * XML response manually by accessing the response body directly. + * + * @return \SimpleXMLElement + * @throws RuntimeException if the response body is not in XML format + * @link http://websec.io/2012/08/27/Preventing-XXE-in-PHP.html + */ + public function xml() + { + $errorMessage = null; + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + libxml_clear_errors(); + + try { + $xml = new \SimpleXMLElement((string) $this->body ?: '', LIBXML_NONET); + if ($error = libxml_get_last_error()) { + $errorMessage = $error->message; + } + } catch (\Exception $e) { + $errorMessage = $e->getMessage(); + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + if ($errorMessage) { + throw new RuntimeException('Unable to parse response body into XML: ' . $errorMessage); + } + + return $xml; + } + + /** + * Get the redirect count of this response + * + * @return int + */ + public function getRedirectCount() + { + return (int) $this->params->get(RedirectPlugin::REDIRECT_COUNT); + } + + /** + * Set the effective URL that resulted in this response (e.g. the last redirect URL) + * + * @param string $url The effective URL + * + * @return self + */ + public function setEffectiveUrl($url) + { + $this->effectiveUrl = $url; + + return $this; + } + + /** + * Get the effective URL that resulted in this response (e.g. the last redirect URL) + * + * @return string + */ + public function getEffectiveUrl() + { + return $this->effectiveUrl; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getPreviousResponse() + { + Version::warn(__METHOD__ . ' is deprecated. Use the HistoryPlugin.'); + return null; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function setRequest($request) + { + Version::warn(__METHOD__ . ' is deprecated'); + return $this; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getRequest() + { + Version::warn(__METHOD__ . ' is deprecated'); + return null; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php new file mode 100644 index 000000000..d71586a05 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php @@ -0,0 +1,962 @@ + 'text/vnd.in3d.3dml', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/pkix-attr-cert', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'apk' => 'application/vnd.android.package-archive', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'asa' => 'text/plain', + 'asax' => 'application/octet-stream', + 'asc' => 'application/pgp-signature', + 'ascx' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'ashx' => 'text/plain', + 'asm' => 'text/x-asm', + 'asmx' => 'text/plain', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asp' => 'text/plain', + 'aspx' => 'text/plain', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'aw' => 'application/applixware', + 'axd' => 'text/plain', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azw' => 'application/vnd.amazon.ebook', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'bmi' => 'application/vnd.bmi', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'btif' => 'image/prs.btif', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'cab' => 'application/vnd.ms-cab-compressed', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cc' => 'text/x-c', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfc' => 'application/x-coldfusion', + 'cfm' => 'application/x-coldfusion', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/java-vm', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'cs' => 'text/plain', + 'csh' => 'application/x-csh', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/x-msdownload', + 'dmg' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.document.macroenabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'exe' => 'application/x-msdownload', + 'exi' => 'application/exi', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/x-f4v', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gdl' => 'model/vnd.gdl', + 'geo' => 'application/vnd.dynageo', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gph' => 'application/vnd.flographit', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxt' => 'application/vnd.geonext', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/x-c', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hta' => 'application/octet-stream', + 'htc' => 'text/html', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/octet-stream', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'java' => 'text/x-java-source', + 'jisp' => 'application/vnd.jisp', + 'jlt' => 'application/vnd.hp-jlyt', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', + 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/mp4', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msty' => 'application/vnd.muvee.style', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nsf' => 'application/vnd.lotus-notes', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'oprc' => 'application/vnd.palm', + 'org' => 'application/vnd.lotus-organizer', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'application/x-font-otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/vnd.palm', + 'pdf' => 'application/pdf', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp-encrypted', + 'php' => 'text/x-php', + 'phps' => 'application/x-httpd-phps', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-mobipocket-ebook', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'image/vnd.adobe.photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rb' => 'text/plain', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'resx' => 'text/xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rnc' => 'application/relax-ng-compact-syntax', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'rtx' => 'text/richtext', + 's' => 'text/x-asm', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shf' => 'application/shf+xml', + 'sig' => 'application/pgp-signature', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'src' => 'application/x-wais-source', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'application/vnd.ms-pki.stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sub' => 'image/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tmo' => 'application/vnd.tmobile-livetv', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'application/x-font-ttf', + 'ttf' => 'application/x-font-ttf', + 'ttl' => 'text/turtle', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvx' => 'application/vnd.dece.unspecified', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'application/x-msmetafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-ms-wmz', + 'woff' => 'application/x-font-woff', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', + 'x3d' => 'application/vnd.hzn-3d-crossword', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xml' => 'application/xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml' + ); + + /** + * Get a singleton instance of the class + * + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Get a mimetype value from a file extension + * + * @param string $extension File extension + * + * @return string|null + * + */ + public function fromExtension($extension) + { + $extension = strtolower($extension); + + return isset($this->mimetypes[$extension]) ? $this->mimetypes[$extension] : null; + } + + /** + * Get a mimetype from a filename + * + * @param string $filename Filename to generate a mimetype from + * + * @return string|null + */ + public function fromFilename($filename) + { + return $this->fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php new file mode 100644 index 000000000..4b4e49d05 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php @@ -0,0 +1,20 @@ +isUrlEncoding()) { + return array($query->encodeValue($key) => implode(',', array_map(array($query, 'encodeValue'), $value))); + } else { + return array($key => implode(',', $value)); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php new file mode 100644 index 000000000..1bf1730e4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php @@ -0,0 +1,22 @@ +isUrlEncoding()) { + return array($query->encodeValue($key) => array_map(array($query, 'encodeValue'), $value)); + } else { + return array($key => $value); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php new file mode 100644 index 000000000..133ea2bd9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php @@ -0,0 +1,27 @@ + $v) { + $k = "{$key}[{$k}]"; + if (is_array($v)) { + $ret = array_merge($ret, self::aggregate($k, $v, $query)); + } else { + $ret[$query->encodeValue($k)] = $query->encodeValue($v); + } + } + + return $ret; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php new file mode 100644 index 000000000..72bee620c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php @@ -0,0 +1,22 @@ +add($key, $value); + $foundDuplicates = true; + } elseif ($paramIsPhpStyleArray) { + $q[$key] = array($value); + } else { + $q[$key] = $value; + } + } else { + // Uses false by default to represent keys with no trailing "=" sign. + $q->add($key, false); + } + } + + // Use the duplicate aggregator if duplicates were found and not using PHP style arrays + if ($foundDuplicates && !$foundPhpStyle) { + $q->setAggregator(new DuplicateAggregator()); + } + + return $q; + } + + /** + * Convert the query string parameters to a query string string + * + * @return string + * @throws RuntimeException + */ + public function __toString() + { + if (!$this->data) { + return ''; + } + + $queryList = array(); + foreach ($this->prepareData($this->data) as $name => $value) { + $queryList[] = $this->convertKvp($name, $value); + } + + return implode($this->fieldSeparator, $queryList); + } + + /** + * Get the query string field separator + * + * @return string + */ + public function getFieldSeparator() + { + return $this->fieldSeparator; + } + + /** + * Get the query string value separator + * + * @return string + */ + public function getValueSeparator() + { + return $this->valueSeparator; + } + + /** + * Returns the type of URL encoding used by the query string + * + * One of: false, "RFC 3986", or "application/x-www-form-urlencoded" + * + * @return bool|string + */ + public function getUrlEncoding() + { + return $this->urlEncode; + } + + /** + * Returns true or false if using URL encoding + * + * @return bool + */ + public function isUrlEncoding() + { + return $this->urlEncode !== false; + } + + /** + * Provide a function for combining multi-valued query string parameters into a single or multiple fields + * + * @param null|QueryAggregatorInterface $aggregator Pass in a QueryAggregatorInterface object to handle converting + * deeply nested query string variables into a flattened array. + * Pass null to use the default PHP style aggregator. For legacy + * reasons, this function accepts a callable that must accepts a + * $key, $value, and query object. + * @return self + * @see \Guzzle\Http\QueryString::aggregateUsingComma() + */ + public function setAggregator(QueryAggregatorInterface $aggregator = null) + { + // Use the default aggregator if none was set + if (!$aggregator) { + if (!self::$defaultAggregator) { + self::$defaultAggregator = new PhpAggregator(); + } + $aggregator = self::$defaultAggregator; + } + + $this->aggregator = $aggregator; + + return $this; + } + + /** + * Set whether or not field names and values should be rawurlencoded + * + * @param bool|string $encode Set to TRUE to use RFC 3986 encoding (rawurlencode), false to disable encoding, or + * form_urlencoding to use application/x-www-form-urlencoded encoding (urlencode) + * @return self + */ + public function useUrlEncoding($encode) + { + $this->urlEncode = ($encode === true) ? self::RFC_3986 : $encode; + + return $this; + } + + /** + * Set the query string separator + * + * @param string $separator The query string separator that will separate fields + * + * @return self + */ + public function setFieldSeparator($separator) + { + $this->fieldSeparator = $separator; + + return $this; + } + + /** + * Set the query string value separator + * + * @param string $separator The query string separator that will separate values from fields + * + * @return self + */ + public function setValueSeparator($separator) + { + $this->valueSeparator = $separator; + + return $this; + } + + /** + * Returns an array of url encoded field names and values + * + * @return array + */ + public function urlEncode() + { + return $this->prepareData($this->data); + } + + /** + * URL encodes a value based on the url encoding type of the query string object + * + * @param string $value Value to encode + * + * @return string + */ + public function encodeValue($value) + { + if ($this->urlEncode == self::RFC_3986) { + return rawurlencode($value); + } elseif ($this->urlEncode == self::FORM_URLENCODED) { + return urlencode($value); + } else { + return (string) $value; + } + } + + /** + * Url encode parameter data and convert nested query strings into a flattened hash. + * + * @param array $data The data to encode + * + * @return array Returns an array of encoded values and keys + */ + protected function prepareData(array $data) + { + // If no aggregator is present then set the default + if (!$this->aggregator) { + $this->setAggregator(null); + } + + $temp = array(); + foreach ($data as $key => $value) { + if ($value === false || $value === null) { + // False and null will not include the "=". Use an empty string to include the "=". + $temp[$this->encodeValue($key)] = $value; + } elseif (is_array($value)) { + $temp = array_merge($temp, $this->aggregator->aggregate($key, $value, $this)); + } else { + $temp[$this->encodeValue($key)] = $this->encodeValue($value); + } + } + + return $temp; + } + + /** + * Converts a key value pair that can contain strings, nulls, false, or arrays + * into a single string. + * + * @param string $name Name of the field + * @param mixed $value Value of the field + * @return string + */ + private function convertKvp($name, $value) + { + if ($value === self::BLANK || $value === null || $value === false) { + return $name; + } elseif (!is_array($value)) { + return $name . $this->valueSeparator . $value; + } + + $result = ''; + foreach ($value as $v) { + $result .= $this->convertKvp($name, $v) . $this->fieldSeparator; + } + + return rtrim($result, $this->fieldSeparator); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php new file mode 100644 index 000000000..ef282733b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php @@ -0,0 +1,122 @@ +setLimit($limit)->setOffset($offset); + } + + /** + * Returns only a subset of the decorated entity body when cast as a string + * {@inheritdoc} + */ + public function __toString() + { + if (!$this->body->isReadable() || + (!$this->body->isSeekable() && $this->body->isConsumed()) + ) { + return ''; + } + + $originalPos = $this->body->ftell(); + $this->body->seek($this->offset); + $data = ''; + while (!$this->feof()) { + $data .= $this->read(1048576); + } + $this->body->seek($originalPos); + + return (string) $data ?: ''; + } + + public function isConsumed() + { + return $this->body->isConsumed() || + ($this->body->ftell() >= $this->offset + $this->limit); + } + + /** + * Returns the Content-Length of the limited subset of data + * {@inheritdoc} + */ + public function getContentLength() + { + $length = $this->body->getContentLength(); + + return $length === false + ? $this->limit + : min($this->limit, min($length, $this->offset + $this->limit) - $this->offset); + } + + /** + * Allow for a bounded seek on the read limited entity body + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + return $whence === SEEK_SET + ? $this->body->seek(max($this->offset, min($this->offset + $this->limit, $offset))) + : false; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @return self + */ + public function setOffset($offset) + { + $this->body->seek($offset); + $this->offset = $offset; + + return $this; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the stream + * + * @param int $limit Total number of bytes to allow to be read from the stream + * + * @return self + */ + public function setLimit($limit) + { + $this->limit = $limit; + + return $this; + } + + public function read($length) + { + // Check if the current position is less than the total allowed bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->body->ftell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte limit is not exceeded + return $this->body->read(min($remaining, $length)); + } else { + return false; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php new file mode 100644 index 000000000..1a824b8b7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php @@ -0,0 +1,250 @@ + array('onRequestSent', 100), + 'request.clone' => 'cleanupRequest', + 'request.before_send' => 'cleanupRequest' + ); + } + + /** + * Clean up the parameters of a request when it is cloned + * + * @param Event $event Event emitted + */ + public function cleanupRequest(Event $event) + { + $params = $event['request']->getParams(); + unset($params[self::REDIRECT_COUNT]); + unset($params[self::PARENT_REQUEST]); + } + + /** + * Called when a request receives a redirect response + * + * @param Event $event Event emitted + */ + public function onRequestSent(Event $event) + { + $response = $event['response']; + $request = $event['request']; + + // Only act on redirect requests with Location headers + if (!$response || $request->getParams()->get(self::DISABLE)) { + return; + } + + // Trace the original request based on parameter history + $original = $this->getOriginalRequest($request); + + // Terminating condition to set the effective response on the original request + if (!$response->isRedirect() || !$response->hasHeader('Location')) { + if ($request !== $original) { + // This is a terminating redirect response, so set it on the original request + $response->getParams()->set(self::REDIRECT_COUNT, $original->getParams()->get(self::REDIRECT_COUNT)); + $original->setResponse($response); + $response->setEffectiveUrl($request->getUrl()); + } + return; + } + + $this->sendRedirectRequest($original, $request, $response); + } + + /** + * Get the original request that initiated a series of redirects + * + * @param RequestInterface $request Request to get the original request from + * + * @return RequestInterface + */ + protected function getOriginalRequest(RequestInterface $request) + { + $original = $request; + // The number of redirects is held on the original request, so determine which request that is + while ($parent = $original->getParams()->get(self::PARENT_REQUEST)) { + $original = $parent; + } + + return $original; + } + + /** + * Create a redirect request for a specific request object + * + * Takes into account strict RFC compliant redirection (e.g. redirect POST with POST) vs doing what most clients do + * (e.g. redirect POST with GET). + * + * @param RequestInterface $request Request being redirected + * @param RequestInterface $original Original request + * @param int $statusCode Status code of the redirect + * @param string $location Location header of the redirect + * + * @return RequestInterface Returns a new redirect request + * @throws CouldNotRewindStreamException If the body needs to be rewound but cannot + */ + protected function createRedirectRequest( + RequestInterface $request, + $statusCode, + $location, + RequestInterface $original + ) { + $redirectRequest = null; + $strict = $original->getParams()->get(self::STRICT_REDIRECTS); + + // Switch method to GET for 303 redirects. 301 and 302 redirects also switch to GET unless we are forcing RFC + // compliance to emulate what most browsers do. NOTE: IE only switches methods on 301/302 when coming from a POST. + if ($request instanceof EntityEnclosingRequestInterface && ($statusCode == 303 || (!$strict && $statusCode <= 302))) { + $redirectRequest = RequestFactory::getInstance()->cloneRequestWithMethod($request, 'GET'); + } else { + $redirectRequest = clone $request; + } + + $redirectRequest->setIsRedirect(true); + // Always use the same response body when redirecting + $redirectRequest->setResponseBody($request->getResponseBody()); + + $location = Url::factory($location); + // If the location is not absolute, then combine it with the original URL + if (!$location->isAbsolute()) { + $originalUrl = $redirectRequest->getUrl(true); + // Remove query string parameters and just take what is present on the redirect Location header + $originalUrl->getQuery()->clear(); + $location = $originalUrl->combine((string) $location, true); + } + + $redirectRequest->setUrl($location); + + // Add the parent request to the request before it sends (make sure it's before the onRequestClone event too) + $redirectRequest->getEventDispatcher()->addListener( + 'request.before_send', + $func = function ($e) use (&$func, $request, $redirectRequest) { + $redirectRequest->getEventDispatcher()->removeListener('request.before_send', $func); + $e['request']->getParams()->set(RedirectPlugin::PARENT_REQUEST, $request); + } + ); + + // Rewind the entity body of the request if needed + if ($redirectRequest instanceof EntityEnclosingRequestInterface && $redirectRequest->getBody()) { + $body = $redirectRequest->getBody(); + // Only rewind the body if some of it has been read already, and throw an exception if the rewind fails + if ($body->ftell() && !$body->rewind()) { + throw new CouldNotRewindStreamException( + 'Unable to rewind the non-seekable entity body of the request after redirecting. cURL probably ' + . 'sent part of body before the redirect occurred. Try adding acustom rewind function using on the ' + . 'entity body of the request using setRewindFunction().' + ); + } + } + + return $redirectRequest; + } + + /** + * Prepare the request for redirection and enforce the maximum number of allowed redirects per client + * + * @param RequestInterface $original Original request + * @param RequestInterface $request Request to prepare and validate + * @param Response $response The current response + * + * @return RequestInterface + */ + protected function prepareRedirection(RequestInterface $original, RequestInterface $request, Response $response) + { + $params = $original->getParams(); + // This is a new redirect, so increment the redirect counter + $current = $params[self::REDIRECT_COUNT] + 1; + $params[self::REDIRECT_COUNT] = $current; + // Use a provided maximum value or default to a max redirect count of 5 + $max = isset($params[self::MAX_REDIRECTS]) ? $params[self::MAX_REDIRECTS] : $this->defaultMaxRedirects; + + // Throw an exception if the redirect count is exceeded + if ($current > $max) { + $this->throwTooManyRedirectsException($original, $max); + return false; + } else { + // Create a redirect request based on the redirect rules set on the request + return $this->createRedirectRequest( + $request, + $response->getStatusCode(), + trim($response->getLocation()), + $original + ); + } + } + + /** + * Send a redirect request and handle any errors + * + * @param RequestInterface $original The originating request + * @param RequestInterface $request The current request being redirected + * @param Response $response The response of the current request + * + * @throws BadResponseException|\Exception + */ + protected function sendRedirectRequest(RequestInterface $original, RequestInterface $request, Response $response) + { + // Validate and create a redirect request based on the original request and current response + if ($redirectRequest = $this->prepareRedirection($original, $request, $response)) { + try { + $redirectRequest->send(); + } catch (BadResponseException $e) { + $e->getResponse(); + if (!$e->getResponse()) { + throw $e; + } + } + } + } + + /** + * Throw a too many redirects exception for a request + * + * @param RequestInterface $original Request + * @param int $max Max allowed redirects + * + * @throws TooManyRedirectsException when too many redirects have been issued + */ + protected function throwTooManyRedirectsException(RequestInterface $original, $max) + { + $original->getEventDispatcher()->addListener( + 'request.complete', + $func = function ($e) use (&$func, $original, $max) { + $original->getEventDispatcher()->removeListener('request.complete', $func); + $str = "{$max} redirects were issued for this request:\n" . $e['request']->getRawHeaders(); + throw new TooManyRedirectsException($str); + } + ); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem new file mode 100644 index 000000000..18ce70381 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem @@ -0,0 +1,3870 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla downloaded on: Wed Aug 13 21:49:32 2014 +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## http://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl verison 1.22. +## SHA1: bf2c15b3019e696660321d2227d942936dc50aa7 +## + + +GTE CyberTrust Global Root +========================== +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg +Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG +A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz +MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL +Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 +IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u +sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql +HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID +AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW +M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF +NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + +Thawte Server CA +================ +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE +AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j +b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV +BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u +c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG +A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0 +ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl +/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7 +1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J +GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ +GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +Thawte Premium Server CA +======================== +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE +AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl +ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT +AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU +VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 +aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ +cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2 +aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh +Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/ +qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm +SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf +8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t +UCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +Equifax Secure CA +================= +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE +ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT +B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR +fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW +8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG +A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE +CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG +A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS +spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB +Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 +zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB +BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 +70+sB3c4 +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 +f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol +hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA +TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah +WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf +Tqj/ZA1k +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO +FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71 +lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB +MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT +1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD +Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9 +-----END CERTIFICATE----- + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +GlobalSign Root CA - R2 +======================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6 +ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp +s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN +S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL +TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C +ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i +YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN +BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp +9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu +01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7 +9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +ValiCert Class 1 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy +MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi +GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm +DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG +lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX +icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP +Orf1LXLI +-----END CERTIFICATE----- + +ValiCert Class 2 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC +CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf +ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ +SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV +UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8 +W9ViH0Pd +-----END CERTIFICATE----- + +RSA Root Certificate 1 +====================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td +3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H +BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs +3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF +V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r +on+jjBXu +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 +EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc +cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw +EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj +055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f +j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 +xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa +t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS +tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM +8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW +Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX +Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt +mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd +RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG +UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +Entrust.net Secure Server CA +============================ +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV +BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg +cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl +ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG +A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi +eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p +dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ +aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5 +gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw +ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw +CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l +dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw +NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow +HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA +BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN +Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9 +n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Equifax Secure Global eBusiness CA +================================== +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp +bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx +HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds +b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV +PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN +qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn +hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs +MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN +I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY +NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 1 +============================= +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB +LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE +ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz +IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ +1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a +IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk +MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW +Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF +AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5 +lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+ +KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +AddTrust Low-Value Services Root +================================ +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU +cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw +CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO +ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6 +54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr +oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1 +Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui +GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w +HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT +RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw +HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt +ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph +iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr +mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj +ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +AddTrust External Root +====================== +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD +VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw +NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU +cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg +Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 ++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw +Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo +aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy +2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 +7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL +VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk +VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl +j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 +e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u +G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +AddTrust Public Services Root +============================= +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU +cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ +BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l +dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu +nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i +d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG +Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw +HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G +A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G +A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4 +JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL ++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9 +Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H +EufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +AddTrust Qualified Certificates Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU +cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx +CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ +IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx +64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3 +KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o +L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR +wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU +MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE +BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y +azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG +GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze +RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB +iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE= +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +RSA Security 2048 v3 +==================== +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK +ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy +MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb +BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7 +Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb +WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH +KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP ++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E +FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY +v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj +0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj +VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395 +nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA +pKnXwiJPZ9d37CAFYd4= +-----END CERTIFICATE----- + +GeoTrust Global CA +================== +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw +MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo +BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet +8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc +T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU +vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk +DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q +zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4 +d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2 +mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p +XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm +Mw== +-----END CERTIFICATE----- + +GeoTrust Global CA 2 +==================== +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw +MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/ +NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k +LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA +Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b +HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH +K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7 +srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh +ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL +OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC +x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF +H4z1Ir+rzoPz4iIprn2DQKi6bA== +-----END CERTIFICATE----- + +GeoTrust Universal CA +===================== +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1 +MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu +Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t +JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e +RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs +7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d +8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V +qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga +Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB +Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu +KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08 +ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0 +XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2 +qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL +oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK +xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF +KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2 +DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK +xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU +p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI +P/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +GeoTrust Universal CA 2 +======================= +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0 +MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg +SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0 +DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17 +j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q +JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a +QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2 +WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP +20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn +ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC +SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG +8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2 ++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ +4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+ +mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq +A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg +Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP +pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d +FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp +gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm +X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +America Online Root Certification Authority 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG +v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z +DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh +sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP +8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z +o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf +GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF +VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft +3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g +Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds +sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 +-----END CERTIFICATE----- + +America Online Root Certification Authority 2 +============================================= +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en +fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8 +f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO +qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN +RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0 +gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn +6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid +FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6 +Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj +B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op +aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY +T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p ++DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg +JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy +zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO +ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh +1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf +GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff +Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP +cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk= +-----END CERTIFICATE----- + +Visa eCommerce Root +=================== +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG +EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug +QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2 +WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm +VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL +F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b +RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0 +TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI +/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs +GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc +CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW +YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz +zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu +YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +Certum Root CA +============== +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK +ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla +Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u +by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x +wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL +kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ +89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K +Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P +NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+ +GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg +GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/ +0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS +qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +Comodo Secure Services root +=========================== +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw +MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu +Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi +BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP +9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc +rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC +oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V +p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E +FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj +YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm +aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm +4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL +DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw +pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H +RR3B7Hzs/Sk= +-----END CERTIFICATE----- + +Comodo Trusted Services root +============================ +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw +MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h +bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw +IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7 +3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y +/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6 +juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS +ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud +DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp +ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl +cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw +uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA +BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l +R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O +9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +QuoVadis Root CA +================ +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE +ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz +MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp +cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD +EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk +J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL +F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL +YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen +AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w +PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y +ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7 +MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj +YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs +ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW +Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu +BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw +FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6 +tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo +fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul +LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x +gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi +5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi +5nrQNiOKSnQ2+Q== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +Sonera Class 2 Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG +U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw +NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh +IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3 +/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT +dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG +f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P +tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH +nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT +XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt +0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI +cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph +Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx +EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH +llpwrN9M +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA +============================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE +ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w +HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh +bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt +vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P +jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca +C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth +vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6 +22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV +HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v +dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN +BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR +EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw +MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y +nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR +iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== +-----END CERTIFICATE----- + +TDC Internet Root CA +==================== +-----BEGIN CERTIFICATE----- +MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE +ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx +NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu +ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j +xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL +znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc +5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6 +otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI +AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM +VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM +MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC +AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe +UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G +CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m +gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ +2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb +O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU +Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l +-----END CERTIFICATE----- + +UTN DATACorp SGC Root CA +======================== +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ +BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa +MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w +HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy +dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys +raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo +wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA +9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv +33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud +DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9 +BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD +LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3 +DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft +Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0 +I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx +EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP +DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI +-----END CERTIFICATE----- + +UTN USERFirst Hardware Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd +BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx +OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0 +eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz +ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI +wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd +tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8 +i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf +Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw +gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF +lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF +UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF +BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW +XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2 +lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn +iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 +nfhmqA== +-----END CERTIFICATE----- + +Camerfirma Chambers of Commerce Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx +NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp +cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn +MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC +AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU +xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH +NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW +DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV +d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud +EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v +cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P +AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh +bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD +VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz +aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi +fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD +L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN +UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n +ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1 +erfutGWaIZDgqtCYvDi1czyL+Nw= +-----END CERTIFICATE----- + +Camerfirma Global Chambersign Root +================================== +-----BEGIN CERTIFICATE----- +MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx +NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt +YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg +MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw +ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J +1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O +by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl +6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c +8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/ +BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j +aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B +Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj +aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y +ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh +bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA +PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y +gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ +PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4 +IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes +t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +-----END CERTIFICATE----- + +NetLock Notary (Class A) Root +============================= +-----BEGIN CERTIFICATE----- +MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI +EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j +ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX +DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH +EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD +VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz +cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM +D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ +z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC +/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7 +tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6 +4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG +A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC +Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv +bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu +IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn +LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0 +ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz +IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh +IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu +b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh +bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg +Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp +bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5 +ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP +ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB +CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr +KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM +8CgHrTwXZoi1/baI +-----END CERTIFICATE----- + +NetLock Business (Class B) Root +=============================== +-----BEGIN CERTIFICATE----- +MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg +VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD +VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv +bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg +VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S +o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr +1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV +HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ +RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh +dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0 +ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv +c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg +YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh +c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz +Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA +bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl +IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2 +YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj +cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM +43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR +stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI +-----END CERTIFICATE----- + +NetLock Express (Class C) Root +============================== +-----BEGIN CERTIFICATE----- +MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD +KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ +BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j +ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z +W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63 +euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw +DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN +RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn +YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB +IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i +aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0 +ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs +ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo +dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y +emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k +IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ +UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg +YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2 +xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW +gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj +YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH +AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw +Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg +U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5 +LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh +cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT +dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC +AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh +3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm +vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk +fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3 +fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ +EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl +1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/ +lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro +g14= +-----END CERTIFICATE----- + +Taiwan GRCA +=========== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG +EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X +DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv +dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN +w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 +BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O +1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO +htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov +J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 +Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t +B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB +O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 +lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV +HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 +09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj +Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 +Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU +D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz +DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk +Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk +7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ +CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy ++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS +-----END CERTIFICATE----- + +Swisscom Root CA 1 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4 +MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM +MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF +NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe +AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC +b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn +7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN +cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp +WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5 +haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY +MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j +BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9 +MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn +jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ +MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H +VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl +vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl +OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3 +1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq +nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy +x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW +NY6E0F/6MBr1mmz0DlP5OlvRHA== +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +Certplus Class 2 Primary CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE +BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN +OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy +dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR +5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ +Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO +YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e +e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME +CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ +YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t +L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD +P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R +TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+ +7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW +//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +DST Root CA X3 +============== +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK +ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X +DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 +cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT +rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 +UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy +xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d +utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ +MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug +dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE +GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw +RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS +fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +DST ACES CA X6 +============== +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT +MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha +MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE +CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI +DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa +pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow +GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy +MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu +Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy +dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU +CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2 +5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t +Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq +nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs +vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3 +oKfN5XozNmr6mis= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 1 +============================================== +-----BEGIN CERTIFICATE----- +MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP +MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0 +acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx +MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg +U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB +TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC +aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX +yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i +Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ +8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4 +W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME +BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46 +sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE +q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy +B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY +nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2 +============================================== +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN +MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr +dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G +A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls +acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe +LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI +x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g +QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr +5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB +AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt +Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 +Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+ +hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P +9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5 +UrbnBEI= +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ +cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN +b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9 +nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge +RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt +tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI +hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K +Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN +NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa +Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG +1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +thawte Primary Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3 +MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg +SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv +KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT +FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs +oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ +1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc +q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K +aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p +afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF +AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE +uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89 +jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH +z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G5 +============================================================ +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln +biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh +dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz +j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD +Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ +Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r +fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv +Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG +SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+ +X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE +KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC +Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE +ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +Network Solutions Certificate Authority +======================================= +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG +EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr +IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx +MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx +jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT +aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT +crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc +/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB +AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv +bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA +A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q +4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ +GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD +ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +WellsSecure Public Root Certificate Authority +============================================= +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM +F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw +NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN +MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl +bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD +VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1 +iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13 +i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8 +bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB +K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB +AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu +cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm +lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB +i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww +GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI +K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0 +bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj +qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es +E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ +tylv2G0xffX8oRAHh84vWdw+WNs= +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +IGC/A +===== +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD +VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE +Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy +MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI +EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT +STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2 +TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW +So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy +HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd +frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ +tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB +egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC +iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK +q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q +MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg +Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI +lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF +0mBWWg== +-----END CERTIFICATE----- + +Security Communication EV RootCA1 +================================= +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE +BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl +Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO +/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX +WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z +ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4 +bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK +9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm +iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG +Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW +mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW +T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GA CA +=============================== +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE +BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG +A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH +bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD +VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw +IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 +IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 +Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg +Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD +d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ +/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R +LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm +MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 ++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY +okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE +BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL +EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0 +MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz +dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT +GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG +d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N +oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc +QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ +PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb +MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG +IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD +VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3 +LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A +dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn +AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA +4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg +AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA +egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6 +Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO +PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv +c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h +cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw +IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT +WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV +MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER +MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp +Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal +HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT +nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE +aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a +86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK +yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB +S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +AC Ra\xC3\xADz Certic\xC3\xA1mara S.A. +====================================== +-----BEGIN CERTIFICATE----- +MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT +AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg +LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w +HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+ +U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh +IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN +yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU +2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3 +4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP +2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm +8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf +HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa +Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK +5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b +czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g +ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF +BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug +cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf +AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX +EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v +/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3 +MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4 +3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk +eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f +/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h +RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU +Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 2 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw +MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw +IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2 +xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ +Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u +SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G +dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ +KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj +TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP +JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk +vQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 3 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw +MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W +yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo +6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ +uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk +2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE +O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8 +yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9 +IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal +092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc +5A== +-----END CERTIFICATE----- + +TC TrustCenter Universal CA I +============================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy +IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN +MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg +VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw +JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC +qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv +xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw +ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O +gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j +BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG +1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy +vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3 +ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT +ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a +7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY +-----END CERTIFICATE----- + +Deutsche Telekom Root CA 2 +========================== +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT +RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG +A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5 +MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G +A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS +b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5 +bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI +KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY +AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK +Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV +jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV +HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr +E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy +zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8 +rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G +dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +ComSign Secured CA +================== +-----BEGIN CERTIFICATE----- +MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE +AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w +NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD +QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs +49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH +7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB +kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1 +9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw +AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t +U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA +j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC +AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a +BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp +FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP +51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz +OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== +-----END CERTIFICATE----- + +Cybertrust Global Root +====================== +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li +ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4 +MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD +ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA ++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW +0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL +AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin +89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT +8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2 +MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G +A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO +lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi +5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2 +hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T +X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3 +============================================================================================================================= +-----BEGIN CERTIFICATE----- +MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH +DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q +aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry +b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV +BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg +S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4 +MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl +IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF +n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl +IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft +dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl +cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO +Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1 +xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR +6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL +hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd +BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4 +N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT +y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh +LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M +dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI= +-----END CERTIFICATE----- + +Buypass Class 2 CA 1 +==================== +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2 +MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh +c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M +cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83 +0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4 +0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R +uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P +AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV +1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt +7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2 +fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w +wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho +-----END CERTIFICATE----- + +Buypass Class 3 CA 1 +==================== +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1 +MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh +c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx +ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0 +n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia +AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c +1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P +AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7 +pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA +EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5 +htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj +el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 +-----END CERTIFICATE----- + +EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 +========================================================================== +-----BEGIN CERTIFICATE----- +MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg +QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe +Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p +ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt +IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by +X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b +gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr +eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ +TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy +Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn +uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI +qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm +ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0 +Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW +Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t +FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm +zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k +XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT +bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU +RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK +1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt +2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ +Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9 +AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +CNNIC ROOT +========== +-----BEGIN CERTIFICATE----- +MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE +ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw +OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD +o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz +VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT +VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or +czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK +y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC +wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S +lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5 +Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM +O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8 +BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2 +G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m +mxE= +-----END CERTIFICATE----- + +ApplicationCA - Japanese Government +=================================== +-----BEGIN CERTIFICATE----- +MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT +SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw +MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl +cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4 +fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN +wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE +jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu +nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU +WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV +BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD +vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs +o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g +/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD +io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW +dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL +rosot4LKGAfmt1t06SAZf7IbiVQ= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G3 +============================================= +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0 +IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz +NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo +YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT +LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j +K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE +c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C +IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu +dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr +2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9 +cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE +Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s +t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +thawte Primary Root CA - G2 +=========================== +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC +VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu +IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg +Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV +MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG +b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt +IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS +LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5 +8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU +mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN +G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K +rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +thawte Primary Root CA - G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w +ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD +VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG +A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At +P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC ++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY +7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW +vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ +KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK +A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC +8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm +er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G2 +============================================= +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu +Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1 +OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl +b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG +BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc +KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+ +EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m +ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2 +npaqBA+K +-----END CERTIFICATE----- + +VeriSign Universal Root Certification Authority +=============================================== +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj +1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP +MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72 +9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I +AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR +tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G +CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O +a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3 +Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx +Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx +P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P +wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4 +mJO37M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G4 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC +VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3 +b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz +ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU +cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo +b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8 +Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz +rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw +HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u +Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD +A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx +AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +============================================ +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA - G2 +================================== +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC +TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l +ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ +5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn +vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj +CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil +e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR +OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI +CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65 +48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi +trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737 +qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB +AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC +ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA +A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz ++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj +f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN +kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk +CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF +URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb +CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h +oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV +IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm +66+KAQ== +-----END CERTIFICATE----- + +CA Disig +======== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK +QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw +MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz +bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm +GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD +Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo +hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt +ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w +gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P +AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz +aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff +ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa +BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t +WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3 +mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ +CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K +ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA +4Z7CRneC9VkGjCFMhwnN5ag= +-----END CERTIFICATE----- + +Juur-SK +======= +-----BEGIN CERTIFICATE----- +MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA +c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw +DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG +SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy +aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf +TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC ++Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw +UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa +Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF +MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD +HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh +AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA +cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr +AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw +cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE +FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G +A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo +ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL +abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678 +IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh +Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2 +yyqcjg== +-----END CERTIFICATE----- + +Hongkong Post Root CA 1 +======================= +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT +DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx +NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n +IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 +ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr +auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh +qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY +V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV +HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i +h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio +l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei +IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps +T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT +c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +ACEDICOM Root +============= +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD +T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4 +MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG +A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk +WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD +YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew +MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb +m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk +HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT +xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2 +3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9 +2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq +TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz +4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU +9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv +bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg +aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP +eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk +zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1 +ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI +KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq +nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE +I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp +MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o +tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 +f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol +hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky +CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX +bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/ +D/xwzoiQ +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi +=================================================== +-----BEGIN CERTIFICATE----- +MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz +ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3 +MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0 +cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u +aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY +8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y +jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI +JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk +9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG +SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d +F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq +D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4 +Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq +fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH +DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA +bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx +ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx +51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk +R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP +T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f +Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl +osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR +crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR +saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD +KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi +6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Chambers of Commerce Root - 2008 +================================ +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy +Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl +ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF +EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl +cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA +XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj +h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/ +ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk +NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g +D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331 +lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ +0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2 +EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI +G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ +BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh +bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh +bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC +CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH +AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1 +wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH +3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU +RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6 +M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1 +YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF +9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK +zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG +nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ +-----END CERTIFICATE----- + +Global Chambersign Root - 2008 +============================== +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx +NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg +Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ +QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf +VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf +XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0 +ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB +/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA +TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M +H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe +Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF +HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB +AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT +BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE +BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm +aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm +aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp +1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0 +dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG +/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6 +ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s +dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg +9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH +foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du +qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr +P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq +c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +Certinomis - Autorité Racine +============================= +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK +Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg +LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG +A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw +JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa +wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly +Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw +2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N +jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q +c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC +lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb +xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g +530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna +4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ +KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x +WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva +R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40 +nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B +CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv +JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE +qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b +WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE +wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/ +vgt2Fl43N+bYdJeimUV5 +-----END CERTIFICATE----- + +Root CA Generalitat Valenciana +============================== +-----BEGIN CERTIFICATE----- +MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE +ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290 +IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3 +WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE +CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2 +F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B +ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ +D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte +JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB +AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n +dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB +ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl +AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA +YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy +AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA +aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt +AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA +YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu +AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA +OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0 +dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV +BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G +A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S +b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh +TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz +Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63 +NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH +iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt ++GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= +-----END CERTIFICATE----- + +A-Trust-nQual-03 +================ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE +Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy +a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R +dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw +RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0 +ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1 +c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA +zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n +yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE +SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4 +iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V +cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV +eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40 +ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr +sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd +JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS +mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6 +ahq97BvIxYSazQ== +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +EC-ACC +====== +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE +BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w +ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD +VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE +CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT +BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7 +MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt +SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl +Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh +cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK +w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT +ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4 +HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a +E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw +0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD +VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0 +Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l +dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ +lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa +Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe +l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2 +E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D +5EI= +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2011 +======================================================= +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT +O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y +aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT +AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo +IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI +1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa +71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u +8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH +3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ +MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 +MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu +b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt +XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD +/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N +7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Trustis FPS Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290 +IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV +BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ +RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk +H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa +cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt +o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA +AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd +BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c +GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC +yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P +8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV +l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl +iB6XzCGcKQENZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ +Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0 +dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu +c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv +bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0 +aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t +L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG +cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5 +fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm +N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN +Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T +tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX +e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA +2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs +HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib +D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8= +-----END CERTIFICATE----- + +StartCom Certification Authority G2 +=================================== +-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE +ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O +o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG +4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi +Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul +Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs +O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H +vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L +nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS +FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa +z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ +KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K +2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk +J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+ +JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG +/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc +nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld +blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc +l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm +7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm +obp573PYtlNXLfbQ4ddI +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +EE Certification Centre Root CA +=============================== +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy +dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw +MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB +UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy +ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM +TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 +rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw +93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN +P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ +MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF +BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj +xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM +lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU +3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM +dcGWxZ0= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2007 +================================================= +-----BEGIN CERTIFICATE----- +MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X +DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl +a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN +BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp +bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N +YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv +KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya +KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT +rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC +AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s +Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I +aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO +Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb +BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK +poRq0Tl9 +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +PSCProcert +========== +-----BEGIN CERTIFICATE----- +MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk +ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ +MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz +dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl +cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw +IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw +MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w +DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD +ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp +Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC +wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA +3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh +RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO +EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2 +0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH +0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU +td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw +Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp +r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/ +AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz +Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId +xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp +ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH +EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h +Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k +ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG +9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG +MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG +LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52 +ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy +YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v +Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o +dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq +T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN +g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q +uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1 +n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn +FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo +5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq +3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5 +poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y +eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km +-----END CERTIFICATE----- + +China Internet Network Information Center EV Certificates Root +============================================================== +-----BEGIN CERTIFICATE----- +MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D +aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg +Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG +A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM +PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl +cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y +jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV +98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H +klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23 +KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC +7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD +glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5 +0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM +7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws +ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0 +5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8= +-----END CERTIFICATE----- + +Swisscom Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2 +MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM +LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo +ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ +wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH +Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a +SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS +NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab +mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY +Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3 +qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O +BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu +MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO +v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ +82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz +o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs +a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx +OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW +mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o ++sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC +rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX +5OfNeOI5wSsSnqaeG8XmDtkx2Q== +-----END CERTIFICATE----- + +Swisscom Root EV CA 2 +===================== +-----BEGIN CERTIFICATE----- +MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE +BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl +cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN +MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT +HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg +Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz +o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy +Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti +GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li +qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH +Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG +alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa +m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox +bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi +xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED +MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB +bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL +j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU +wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7 +XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH +59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/ +23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq +J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA +HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi +uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW +l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc= +-----END CERTIFICATE----- + +CA Disig Root R1 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy +3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8 +u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2 +m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk +CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa +YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6 +vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL +LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX +ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is +XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ +04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR +xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B +LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM +CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb +VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85 +YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS +ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix +lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N +UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ +a7+h89n07eLw4+1knj0vllJPgFOL +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php new file mode 100644 index 000000000..dbd4c1841 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php @@ -0,0 +1,157 @@ +createRequest($method, $url, null, null, $options); + + if (isset($options['stream'])) { + if ($options['stream'] instanceof StreamRequestFactoryInterface) { + return $options['stream']->fromRequest($request); + } elseif ($options['stream'] == true) { + $streamFactory = new PhpStreamRequestFactory(); + return $streamFactory->fromRequest($request); + } + } + + return $request->send(); + } + + /** + * Send a GET request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function get($url, $options = array()) + { + return self::request('GET', $url, $options); + } + + /** + * Send a HEAD request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function head($url, $options = array()) + { + return self::request('HEAD', $url, $options); + } + + /** + * Send a DELETE request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function delete($url, $options = array()) + { + return self::request('DELETE', $url, $options); + } + + /** + * Send a POST request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function post($url, $options = array()) + { + return self::request('POST', $url, $options); + } + + /** + * Send a PUT request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function put($url, $options = array()) + { + return self::request('PUT', $url, $options); + } + + /** + * Send a PATCH request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function patch($url, $options = array()) + { + return self::request('PATCH', $url, $options); + } + + /** + * Send an OPTIONS request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function options($url, $options = array()) + { + return self::request('OPTIONS', $url, $options); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Url.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Url.php new file mode 100644 index 000000000..6a4e77245 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/Url.php @@ -0,0 +1,554 @@ + null, 'host' => null, 'path' => null, 'port' => null, 'query' => null, + 'user' => null, 'pass' => null, 'fragment' => null); + + if (false === ($parts = parse_url($url))) { + throw new InvalidArgumentException('Was unable to parse malformed url: ' . $url); + } + + $parts += $defaults; + + // Convert the query string into a QueryString object + if ($parts['query'] || 0 !== strlen($parts['query'])) { + $parts['query'] = QueryString::fromString($parts['query']); + } + + return new static($parts['scheme'], $parts['host'], $parts['user'], + $parts['pass'], $parts['port'], $parts['path'], $parts['query'], + $parts['fragment']); + } + + /** + * Build a URL from parse_url parts. The generated URL will be a relative URL if a scheme or host are not provided. + * + * @param array $parts Array of parse_url parts + * + * @return string + */ + public static function buildUrl(array $parts) + { + $url = $scheme = ''; + + if (isset($parts['scheme'])) { + $scheme = $parts['scheme']; + $url .= $scheme . ':'; + } + + if (isset($parts['host'])) { + $url .= '//'; + if (isset($parts['user'])) { + $url .= $parts['user']; + if (isset($parts['pass'])) { + $url .= ':' . $parts['pass']; + } + $url .= '@'; + } + + $url .= $parts['host']; + + // Only include the port if it is not the default port of the scheme + if (isset($parts['port']) + && !(($scheme == 'http' && $parts['port'] == 80) || ($scheme == 'https' && $parts['port'] == 443)) + ) { + $url .= ':' . $parts['port']; + } + } + + // Add the path component if present + if (isset($parts['path']) && 0 !== strlen($parts['path'])) { + // Always ensure that the path begins with '/' if set and something is before the path + if ($url && $parts['path'][0] != '/' && substr($url, -1) != '/') { + $url .= '/'; + } + $url .= $parts['path']; + } + + // Add the query string if present + if (isset($parts['query'])) { + $url .= '?' . $parts['query']; + } + + // Ensure that # is only added to the url if fragment contains anything. + if (isset($parts['fragment'])) { + $url .= '#' . $parts['fragment']; + } + + return $url; + } + + /** + * Create a new URL from URL parts + * + * @param string $scheme Scheme of the URL + * @param string $host Host of the URL + * @param string $username Username of the URL + * @param string $password Password of the URL + * @param int $port Port of the URL + * @param string $path Path of the URL + * @param QueryString|array|string $query Query string of the URL + * @param string $fragment Fragment of the URL + */ + public function __construct($scheme, $host, $username = null, $password = null, $port = null, $path = null, QueryString $query = null, $fragment = null) + { + $this->scheme = $scheme; + $this->host = $host; + $this->port = $port; + $this->username = $username; + $this->password = $password; + $this->fragment = $fragment; + if (!$query) { + $this->query = new QueryString(); + } else { + $this->setQuery($query); + } + $this->setPath($path); + } + + /** + * Clone the URL + */ + public function __clone() + { + $this->query = clone $this->query; + } + + /** + * Returns the URL as a URL string + * + * @return string + */ + public function __toString() + { + return self::buildUrl($this->getParts()); + } + + /** + * Get the parts of the URL as an array + * + * @return array + */ + public function getParts() + { + $query = (string) $this->query; + + return array( + 'scheme' => $this->scheme, + 'user' => $this->username, + 'pass' => $this->password, + 'host' => $this->host, + 'port' => $this->port, + 'path' => $this->getPath(), + 'query' => $query !== '' ? $query : null, + 'fragment' => $this->fragment, + ); + } + + /** + * Set the host of the request. + * + * @param string $host Host to set (e.g. www.yahoo.com, yahoo.com) + * + * @return Url + */ + public function setHost($host) + { + if (strpos($host, ':') === false) { + $this->host = $host; + } else { + list($host, $port) = explode(':', $host); + $this->host = $host; + $this->setPort($port); + } + + return $this; + } + + /** + * Get the host part of the URL + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the scheme part of the URL (http, https, ftp, etc) + * + * @param string $scheme Scheme to set + * + * @return Url + */ + public function setScheme($scheme) + { + if ($this->scheme == 'http' && $this->port == 80) { + $this->port = null; + } elseif ($this->scheme == 'https' && $this->port == 443) { + $this->port = null; + } + + $this->scheme = $scheme; + + return $this; + } + + /** + * Get the scheme part of the URL + * + * @return string + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Set the port part of the URL + * + * @param int $port Port to set + * + * @return Url + */ + public function setPort($port) + { + $this->port = $port; + + return $this; + } + + /** + * Get the port part of the URl. Will return the default port for a given scheme if no port has been set. + * + * @return int|null + */ + public function getPort() + { + if ($this->port) { + return $this->port; + } elseif ($this->scheme == 'http') { + return 80; + } elseif ($this->scheme == 'https') { + return 443; + } + + return null; + } + + /** + * Set the path part of the URL + * + * @param array|string $path Path string or array of path segments + * + * @return Url + */ + public function setPath($path) + { + static $pathReplace = array(' ' => '%20', '?' => '%3F'); + if (is_array($path)) { + $path = '/' . implode('/', $path); + } + + $this->path = strtr($path, $pathReplace); + + return $this; + } + + /** + * Normalize the URL so that double slashes and relative paths are removed + * + * @return Url + */ + public function normalizePath() + { + if (!$this->path || $this->path == '/' || $this->path == '*') { + return $this; + } + + $results = array(); + $segments = $this->getPathSegments(); + foreach ($segments as $segment) { + if ($segment == '..') { + array_pop($results); + } elseif ($segment != '.' && $segment != '') { + $results[] = $segment; + } + } + + // Combine the normalized parts and add the leading slash if needed + $this->path = ($this->path[0] == '/' ? '/' : '') . implode('/', $results); + + // Add the trailing slash if necessary + if ($this->path != '/' && end($segments) == '') { + $this->path .= '/'; + } + + return $this; + } + + /** + * Add a relative path to the currently set path. + * + * @param string $relativePath Relative path to add + * + * @return Url + */ + public function addPath($relativePath) + { + if ($relativePath != '/' && is_string($relativePath) && strlen($relativePath) > 0) { + // Add a leading slash if needed + if ($relativePath[0] != '/') { + $relativePath = '/' . $relativePath; + } + $this->setPath(str_replace('//', '/', $this->path . $relativePath)); + } + + return $this; + } + + /** + * Get the path part of the URL + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Get the path segments of the URL as an array + * + * @return array + */ + public function getPathSegments() + { + return array_slice(explode('/', $this->getPath()), 1); + } + + /** + * Set the password part of the URL + * + * @param string $password Password to set + * + * @return Url + */ + public function setPassword($password) + { + $this->password = $password; + + return $this; + } + + /** + * Get the password part of the URL + * + * @return null|string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set the username part of the URL + * + * @param string $username Username to set + * + * @return Url + */ + public function setUsername($username) + { + $this->username = $username; + + return $this; + } + + /** + * Get the username part of the URl + * + * @return null|string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Get the query part of the URL as a QueryString object + * + * @return QueryString + */ + public function getQuery() + { + return $this->query; + } + + /** + * Set the query part of the URL + * + * @param QueryString|string|array $query Query to set + * + * @return Url + */ + public function setQuery($query) + { + if (is_string($query)) { + $output = null; + parse_str($query, $output); + $this->query = new QueryString($output); + } elseif (is_array($query)) { + $this->query = new QueryString($query); + } elseif ($query instanceof QueryString) { + $this->query = $query; + } + + return $this; + } + + /** + * Get the fragment part of the URL + * + * @return null|string + */ + public function getFragment() + { + return $this->fragment; + } + + /** + * Set the fragment part of the URL + * + * @param string $fragment Fragment to set + * + * @return Url + */ + public function setFragment($fragment) + { + $this->fragment = $fragment; + + return $this; + } + + /** + * Check if this is an absolute URL + * + * @return bool + */ + public function isAbsolute() + { + return $this->scheme && $this->host; + } + + /** + * Combine the URL with another URL. Follows the rules specific in RFC 3986 section 5.4. + * + * @param string $url Relative URL to combine with + * @param bool $strictRfc3986 Set to true to use strict RFC 3986 compliance when merging paths. When first + * released, Guzzle used an incorrect algorithm for combining relative URL paths. In + * order to not break users, we introduced this flag to allow the merging of URLs based + * on strict RFC 3986 section 5.4.1. This means that "http://a.com/foo/baz" merged with + * "bar" would become "http://a.com/foo/bar". When this value is set to false, it would + * become "http://a.com/foo/baz/bar". + * @return Url + * @throws InvalidArgumentException + * @link http://tools.ietf.org/html/rfc3986#section-5.4 + */ + public function combine($url, $strictRfc3986 = false) + { + $url = self::factory($url); + + // Use the more absolute URL as the base URL + if (!$this->isAbsolute() && $url->isAbsolute()) { + $url = $url->combine($this); + } + + // Passing a URL with a scheme overrides everything + if ($buffer = $url->getScheme()) { + $this->scheme = $buffer; + $this->host = $url->getHost(); + $this->port = $url->getPort(); + $this->username = $url->getUsername(); + $this->password = $url->getPassword(); + $this->path = $url->getPath(); + $this->query = $url->getQuery(); + $this->fragment = $url->getFragment(); + return $this; + } + + // Setting a host overrides the entire rest of the URL + if ($buffer = $url->getHost()) { + $this->host = $buffer; + $this->port = $url->getPort(); + $this->username = $url->getUsername(); + $this->password = $url->getPassword(); + $this->path = $url->getPath(); + $this->query = $url->getQuery(); + $this->fragment = $url->getFragment(); + return $this; + } + + $path = $url->getPath(); + $query = $url->getQuery(); + + if (!$path) { + if (count($query)) { + $this->addQuery($query, $strictRfc3986); + } + } else { + if ($path[0] == '/') { + $this->path = $path; + } elseif ($strictRfc3986) { + $this->path .= '/../' . $path; + } else { + $this->path .= '/' . $path; + } + $this->normalizePath(); + $this->addQuery($query, $strictRfc3986); + } + + $this->fragment = $url->getFragment(); + + return $this; + } + + private function addQuery(QueryString $new, $strictRfc386) + { + if (!$strictRfc386) { + $new->merge($this->query); + } + + $this->query = $new; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/composer.json new file mode 100644 index 000000000..9384a5bf9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Http/composer.json @@ -0,0 +1,32 @@ +{ + "name": "guzzle/http", + "description": "HTTP libraries used by Guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": ["http client", "http", "client", "Guzzle", "curl"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/common": "self.version", + "guzzle/parser": "self.version", + "guzzle/stream": "self.version" + }, + "suggest": { + "ext-curl": "*" + }, + "autoload": { + "psr-0": { "Guzzle\\Http": "" } + }, + "target-dir": "Guzzle/Http", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php new file mode 100644 index 000000000..c6997734c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php @@ -0,0 +1,38 @@ + array(), + 'camel' => array() + ); + + /** @var int Max entries per cache */ + protected $maxCacheSize; + + /** @var InflectorInterface Decorated inflector */ + protected $decoratedInflector; + + /** + * @param InflectorInterface $inflector Inflector being decorated + * @param int $maxCacheSize Maximum number of cached items to hold per cache + */ + public function __construct(InflectorInterface $inflector, $maxCacheSize = 500) + { + $this->decoratedInflector = $inflector; + $this->maxCacheSize = $maxCacheSize; + } + + public function snake($word) + { + if (!isset($this->cache['snake'][$word])) { + $this->pruneCache('snake'); + $this->cache['snake'][$word] = $this->decoratedInflector->snake($word); + } + + return $this->cache['snake'][$word]; + } + + /** + * Converts strings from snake_case to upper CamelCase + * + * @param string $word Value to convert into upper CamelCase + * + * @return string + */ + public function camel($word) + { + if (!isset($this->cache['camel'][$word])) { + $this->pruneCache('camel'); + $this->cache['camel'][$word] = $this->decoratedInflector->camel($word); + } + + return $this->cache['camel'][$word]; + } + + /** + * Prune one of the named caches by removing 20% of the cache if it is full + * + * @param string $cache Type of cache to prune + */ + protected function pruneCache($cache) + { + if (count($this->cache[$cache]) == $this->maxCacheSize) { + $this->cache[$cache] = array_slice($this->cache[$cache], $this->maxCacheSize * 0.2); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php new file mode 100644 index 000000000..db37e4fe4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php @@ -0,0 +1,59 @@ + array(), + 'camel' => array() + ); + + /** @var InflectorInterface Decorated inflector */ + protected $decoratedInflector; + + /** + * @param InflectorInterface $inflector Inflector being decorated + * @param array $snake Hash of pre-computed camel to snake + * @param array $camel Hash of pre-computed snake to camel + * @param bool $mirror Mirror snake and camel reflections + */ + public function __construct(InflectorInterface $inflector, array $snake = array(), array $camel = array(), $mirror = false) + { + if ($mirror) { + $camel = array_merge(array_flip($snake), $camel); + $snake = array_merge(array_flip($camel), $snake); + } + + $this->decoratedInflector = $inflector; + $this->mapping = array( + 'snake' => $snake, + 'camel' => $camel + ); + } + + public function snake($word) + { + return isset($this->mapping['snake'][$word]) + ? $this->mapping['snake'][$word] + : $this->decoratedInflector->snake($word); + } + + /** + * Converts strings from snake_case to upper CamelCase + * + * @param string $word Value to convert into upper CamelCase + * + * @return string + */ + public function camel($word) + { + return isset($this->mapping['camel'][$word]) + ? $this->mapping['camel'][$word] + : $this->decoratedInflector->camel($word); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json new file mode 100644 index 000000000..93f9e7b72 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json @@ -0,0 +1,26 @@ +{ + "name": "guzzle/inflection", + "description": "Guzzle inflection component", + "homepage": "http://guzzlephp.org/", + "keywords": ["inflection", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2" + }, + "autoload": { + "psr-0": { "Guzzle\\Inflection": "" } + }, + "target-dir": "Guzzle/Inflection", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php new file mode 100644 index 000000000..1b6bd7e53 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php @@ -0,0 +1,19 @@ +getArrayIterator()->append($iterator); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php new file mode 100644 index 000000000..d76cdd439 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php @@ -0,0 +1,56 @@ +chunkSize = $chunkSize; + } + + public function rewind() + { + parent::rewind(); + $this->next(); + } + + public function next() + { + $this->chunk = array(); + for ($i = 0; $i < $this->chunkSize && parent::valid(); $i++) { + $this->chunk[] = parent::current(); + parent::next(); + } + } + + public function current() + { + return $this->chunk; + } + + public function valid() + { + return (bool) $this->chunk; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php new file mode 100644 index 000000000..b103367b6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php @@ -0,0 +1,36 @@ +callback = $callback; + } + + public function accept() + { + return call_user_func($this->callback, $this->current()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php new file mode 100644 index 000000000..7e586bda6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php @@ -0,0 +1,34 @@ +callback = $callback; + } + + public function current() + { + return call_user_func($this->callback, parent::current()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php new file mode 100644 index 000000000..de4ab0360 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php @@ -0,0 +1,27 @@ +getInnerIterator(); + while ($i instanceof \OuterIterator) { + $i = $i->getInnerIterator(); + } + + return call_user_func_array(array($i, $name), $args); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md new file mode 100644 index 000000000..8bb7e08e2 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md @@ -0,0 +1,25 @@ +Guzzle Iterator +=============== + +Provides useful Iterators and Iterator decorators + +- ChunkedIterator: Pulls out chunks from an inner iterator and yields the chunks as arrays +- FilterIterator: Used when PHP 5.4's CallbackFilterIterator is not available +- MapIterator: Maps values before yielding +- MethodProxyIterator: Proxies missing method calls to the innermost iterator + +### Installing via Composer + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php + +# Add Guzzle as a dependency +php composer.phar require guzzle/iterator:~3.0 +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json new file mode 100644 index 000000000..ee1737987 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/iterator", + "description": "Provides helpful iterators and iterator decorators", + "keywords": ["iterator", "guzzle"], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/common": ">=2.8.0" + }, + "autoload": { + "psr-0": { "Guzzle\\Iterator": "/" } + }, + "target-dir": "Guzzle/Iterator", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php new file mode 100644 index 000000000..7f6271bcb --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php @@ -0,0 +1,16 @@ +log; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php new file mode 100644 index 000000000..a70fc8d42 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php @@ -0,0 +1,34 @@ +logs[] = array('message' => $message, 'priority' => $priority, 'extras' => $extras); + } + + /** + * Get logged entries + * + * @return array + */ + public function getLogs() + { + return $this->logs; + } + + /** + * Clears logged entries + */ + public function clearLogs() + { + $this->logs = array(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php new file mode 100644 index 000000000..d4bb73f21 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php @@ -0,0 +1,23 @@ +log = $logObject; + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + call_user_func($this->log, $message, $priority, $extras); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php new file mode 100644 index 000000000..d7ac4ea7c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php @@ -0,0 +1,18 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{curl_stderr}"; + const SHORT_FORMAT = '[{ts}] "{method} {resource} {protocol}/{version}" {code}'; + + /** + * @var string Template used to format log messages + */ + protected $template; + + /** + * @param string $template Log message template + */ + public function __construct($template = self::DEFAULT_FORMAT) + { + $this->template = $template ?: self::DEFAULT_FORMAT; + } + + /** + * Set the template to use for logging + * + * @param string $template Log message template + * + * @return self + */ + public function setTemplate($template) + { + $this->template = $template; + + return $this; + } + + /** + * Returns a formatted message + * + * @param RequestInterface $request Request that was sent + * @param Response $response Response that was received + * @param CurlHandle $handle Curl handle associated with the message + * @param array $customData Associative array of custom template data + * + * @return string + */ + public function format( + RequestInterface $request, + Response $response = null, + CurlHandle $handle = null, + array $customData = array() + ) { + $cache = $customData; + + return preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $handle, &$cache) { + + if (array_key_exists($matches[1], $cache)) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = (string) $request; + break; + case 'response': + $result = (string) $response; + break; + case 'req_body': + $result = $request instanceof EntityEnclosingRequestInterface + ? (string) $request->getBody() : ''; + break; + case 'res_body': + $result = $response ? $response->getBody(true) : ''; + break; + case 'ts': + $result = gmdate('c'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'url': + $result = (string) $request->getUrl(); + break; + case 'resource': + $result = $request->getResource(); + break; + case 'protocol': + $result = 'HTTP'; + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'host': + $result = $request->getHost(); + break; + case 'hostname': + $result = gethostname(); + break; + case 'port': + $result = $request->getPort(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : ''; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : ''; + break; + case 'connect_time': + $result = $handle && $handle->getInfo(CURLINFO_CONNECT_TIME) + ? $handle->getInfo(CURLINFO_CONNECT_TIME) + : ($response ? $response->getInfo('connect_time') : ''); + break; + case 'total_time': + $result = $handle && $handle->getInfo(CURLINFO_TOTAL_TIME) + ? $handle->getInfo(CURLINFO_TOTAL_TIME) + : ($response ? $response->getInfo('total_time') : ''); + break; + case 'curl_error': + $result = $handle ? $handle->getError() : ''; + break; + case 'curl_code': + $result = $handle ? $handle->getErrorNo() : ''; + break; + case 'curl_stderr': + $result = $handle ? $handle->getStderr() : ''; + break; + default: + if (strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeader(substr($matches[1], 11)); + } elseif ($response && strpos($matches[1], 'res_header_') === 0) { + $result = $response->getHeader(substr($matches[1], 11)); + } + } + + $cache[$matches[1]] = $result; + return $result; + }, + $this->template + ); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php new file mode 100644 index 000000000..6afe7b62a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php @@ -0,0 +1,34 @@ + Logger::DEBUG, + LOG_INFO => Logger::INFO, + LOG_WARNING => Logger::WARNING, + LOG_ERR => Logger::ERROR, + LOG_CRIT => Logger::CRITICAL, + LOG_ALERT => Logger::ALERT + ); + + public function __construct(Logger $logObject) + { + $this->log = $logObject; + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + $this->log->addRecord(self::$mapping[$priority], $message, $extras); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php new file mode 100644 index 000000000..38a2b600d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php @@ -0,0 +1,36 @@ + LogLevel::DEBUG, + LOG_INFO => LogLevel::INFO, + LOG_WARNING => LogLevel::WARNING, + LOG_ERR => LogLevel::ERROR, + LOG_CRIT => LogLevel::CRITICAL, + LOG_ALERT => LogLevel::ALERT + ); + + public function __construct(LoggerInterface $logObject) + { + $this->log = $logObject; + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + $this->log->log(self::$mapping[$priority], $message, $extras); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php new file mode 100644 index 000000000..0ea8e3b1d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php @@ -0,0 +1,24 @@ +log = $logObject; + Version::warn(__CLASS__ . ' is deprecated'); + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + $this->log->log($message, $priority, $extras); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php new file mode 100644 index 000000000..863f6a1c4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php @@ -0,0 +1,21 @@ +log = $logObject; + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + $this->log->log($priority, $message, $extras); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/composer.json new file mode 100644 index 000000000..a8213e8b4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Log/composer.json @@ -0,0 +1,29 @@ +{ + "name": "guzzle/log", + "description": "Guzzle log adapter component", + "homepage": "http://guzzlephp.org/", + "keywords": ["log", "adapter", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2" + }, + "autoload": { + "psr-0": { "Guzzle\\Log": "" } + }, + "suggest": { + "guzzle/http": "self.version" + }, + "target-dir": "Guzzle/Log", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php new file mode 100644 index 000000000..4349eeb38 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php @@ -0,0 +1,131 @@ + 'Domain', + 'path' => 'Path', + 'max_age' => 'Max-Age', + 'expires' => 'Expires', + 'version' => 'Version', + 'secure' => 'Secure', + 'port' => 'Port', + 'discard' => 'Discard', + 'comment' => 'Comment', + 'comment_url' => 'Comment-Url', + 'http_only' => 'HttpOnly' + ); + + public function parseCookie($cookie, $host = null, $path = null, $decode = false) + { + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + + // The name of the cookie (first kvp) must include an equal sign. + if (empty($pieces) || !strpos($pieces[0], '=')) { + return false; + } + + // Create the default return array + $data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array( + 'cookies' => array(), + 'data' => array(), + 'path' => null, + 'http_only' => false, + 'discard' => false, + 'domain' => $host + )); + $foundNonCookies = 0; + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + + if (count($cookieParts) == 1) { + // Can be a single value (e.g. secure, httpOnly) + $value = true; + } else { + // Be sure to strip wrapping quotes + $value = trim($cookieParts[1], " \n\r\t\0\x0B\""); + if ($decode) { + $value = urldecode($value); + } + } + + // Only check for non-cookies when cookies have been found + if (!empty($data['cookies'])) { + foreach (self::$cookieParts as $mapValue => $search) { + if (!strcasecmp($search, $key)) { + $data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value; + $foundNonCookies++; + continue 2; + } + } + } + + // If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a + // cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data. + $data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value; + } + + // Calculate the expires date + if (!$data['expires'] && $data['max_age']) { + $data['expires'] = time() + (int) $data['max_age']; + } + + // Check path attribute according RFC6265 http://tools.ietf.org/search/rfc6265#section-5.2.4 + // "If the attribute-value is empty or if the first character of the + // attribute-value is not %x2F ("/"): + // Let cookie-path be the default-path. + // Otherwise: + // Let cookie-path be the attribute-value." + if (!$data['path'] || substr($data['path'], 0, 1) !== '/') { + $data['path'] = $this->getDefaultPath($path); + } + + return $data; + } + + /** + * Get default cookie path according to RFC 6265 + * http://tools.ietf.org/search/rfc6265#section-5.1.4 Paths and Path-Match + * + * @param string $path Request uri-path + * + * @return string + */ + protected function getDefaultPath($path) { + // "The user agent MUST use an algorithm equivalent to the following algorithm + // to compute the default-path of a cookie:" + + // "2. If the uri-path is empty or if the first character of the uri-path is not + // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps. + if (empty($path) || substr($path, 0, 1) !== '/') { + return '/'; + } + + // "3. If the uri-path contains no more than one %x2F ("/") character, output + // %x2F ("/") and skip the remaining step." + if ($path === "/") { + return $path; + } + + $rightSlashPos = strrpos($path, '/'); + if ($rightSlashPos === 0) { + return "/"; + } + + // "4. Output the characters of the uri-path from the first character up to, + // but not including, the right-most %x2F ("/")." + return substr($path, 0, $rightSlashPos); + + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php new file mode 100644 index 000000000..d21ffe21c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php @@ -0,0 +1,33 @@ + $requestUrl, + 'scheme' => 'http' + ); + + // Check for the Host header + if (isset($parts['headers']['Host'])) { + $urlParts['host'] = $parts['headers']['Host']; + } elseif (isset($parts['headers']['host'])) { + $urlParts['host'] = $parts['headers']['host']; + } else { + $urlParts['host'] = null; + } + + if (false === strpos($urlParts['host'], ':')) { + $urlParts['port'] = ''; + } else { + $hostParts = explode(':', $urlParts['host']); + $urlParts['host'] = trim($hostParts[0]); + $urlParts['port'] = (int) trim($hostParts[1]); + if ($urlParts['port'] == 443) { + $urlParts['scheme'] = 'https'; + } + } + + // Check if a query is present + $path = $urlParts['path']; + $qpos = strpos($path, '?'); + if ($qpos) { + $urlParts['query'] = substr($path, $qpos + 1); + $urlParts['path'] = substr($path, 0, $qpos); + } else { + $urlParts['query'] = ''; + } + + return $urlParts; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php new file mode 100644 index 000000000..efc1aa322 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php @@ -0,0 +1,110 @@ +parseMessage($message); + + // Parse the protocol and protocol version + if (isset($parts['start_line'][2])) { + $startParts = explode('/', $parts['start_line'][2]); + $protocol = strtoupper($startParts[0]); + $version = isset($startParts[1]) ? $startParts[1] : '1.1'; + } else { + $protocol = 'HTTP'; + $version = '1.1'; + } + + $parsed = array( + 'method' => strtoupper($parts['start_line'][0]), + 'protocol' => $protocol, + 'version' => $version, + 'headers' => $parts['headers'], + 'body' => $parts['body'] + ); + + $parsed['request_url'] = $this->getUrlPartsFromMessage(isset($parts['start_line'][1]) ? $parts['start_line'][1] : '' , $parsed); + + return $parsed; + } + + public function parseResponse($message) + { + if (!$message) { + return false; + } + + $parts = $this->parseMessage($message); + list($protocol, $version) = explode('/', trim($parts['start_line'][0])); + + return array( + 'protocol' => $protocol, + 'version' => $version, + 'code' => $parts['start_line'][1], + 'reason_phrase' => isset($parts['start_line'][2]) ? $parts['start_line'][2] : '', + 'headers' => $parts['headers'], + 'body' => $parts['body'] + ); + } + + /** + * Parse a message into parts + * + * @param string $message Message to parse + * + * @return array + */ + protected function parseMessage($message) + { + $startLine = null; + $headers = array(); + $body = ''; + + // Iterate over each line in the message, accounting for line endings + $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { + + $line = $lines[$i]; + + // If two line breaks were encountered, then this is the end of body + if (empty($line)) { + if ($i < $totalLines - 1) { + $body = implode('', array_slice($lines, $i + 2)); + } + break; + } + + // Parse message headers + if (!$startLine) { + $startLine = explode(' ', $line, 3); + } elseif (strpos($line, ':')) { + $parts = explode(':', $line, 2); + $key = trim($parts[0]); + $value = isset($parts[1]) ? trim($parts[1]) : ''; + if (!isset($headers[$key])) { + $headers[$key] = $value; + } elseif (!is_array($headers[$key])) { + $headers[$key] = array($headers[$key], $value); + } else { + $headers[$key][] = $value; + } + } + } + + return array( + 'start_line' => $startLine, + 'headers' => $headers, + 'body' => $body + ); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php new file mode 100644 index 000000000..cc448088d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php @@ -0,0 +1,27 @@ + $parts->requestMethod, + 'protocol' => 'HTTP', + 'version' => number_format($parts->httpVersion, 1), + 'headers' => $parts->headers, + 'body' => $parts->body + ); + + $parsed['request_url'] = $this->getUrlPartsFromMessage($parts->requestUrl, $parsed); + + return $parsed; + } + + public function parseResponse($message) + { + if (!$message) { + return false; + } + + $parts = http_parse_message($message); + + return array( + 'protocol' => 'HTTP', + 'version' => number_format($parts->httpVersion, 1), + 'code' => $parts->responseCode, + 'reason_phrase' => $parts->responseStatus, + 'headers' => $parts->headers, + 'body' => $parts->body + ); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php new file mode 100644 index 000000000..f8386831c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php @@ -0,0 +1,75 @@ + 'Guzzle\\Parser\\Message\\MessageParser', + 'cookie' => 'Guzzle\\Parser\\Cookie\\CookieParser', + 'url' => 'Guzzle\\Parser\\Url\\UrlParser', + 'uri_template' => 'Guzzle\\Parser\\UriTemplate\\UriTemplate', + ); + + /** + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new static; + } + + return self::$instance; + } + + public function __construct() + { + // Use the PECL URI template parser if available + if (extension_loaded('uri_template')) { + $this->mapping['uri_template'] = 'Guzzle\\Parser\\UriTemplate\\PeclUriTemplate'; + } + } + + /** + * Get a parser by name from an instance + * + * @param string $name Name of the parser to retrieve + * + * @return mixed|null + */ + public function getParser($name) + { + if (!isset($this->instances[$name])) { + if (!isset($this->mapping[$name])) { + return null; + } + $class = $this->mapping[$name]; + $this->instances[$name] = new $class(); + } + + return $this->instances[$name]; + } + + /** + * Register a custom parser by name with the register + * + * @param string $name Name or handle of the parser to register + * @param mixed $parser Instantiated parser to register + */ + public function registerParser($name, $parser) + { + $this->instances[$name] = $parser; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php new file mode 100644 index 000000000..b0764e837 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php @@ -0,0 +1,26 @@ + true, '#' => true, '.' => true, '/' => true, ';' => true, '?' => true, '&' => true + ); + + /** @var array Delimiters */ + private static $delims = array( + ':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' + ); + + /** @var array Percent encoded delimiters */ + private static $delimsPct = array( + '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', + '%3B', '%3D' + ); + + public function expand($template, array $variables) + { + if ($this->regex == self::DEFAULT_PATTERN && false === strpos($template, '{')) { + return $template; + } + + $this->template = $template; + $this->variables = $variables; + + return preg_replace_callback($this->regex, array($this, 'expandMatch'), $this->template); + } + + /** + * Set the regex patten used to expand URI templates + * + * @param string $regexPattern + */ + public function setRegex($regexPattern) + { + $this->regex = $regexPattern; + } + + /** + * Parse an expression into parts + * + * @param string $expression Expression to parse + * + * @return array Returns an associative array of parts + */ + private function parseExpression($expression) + { + // Check for URI operators + $operator = ''; + + if (isset(self::$operatorHash[$expression[0]])) { + $operator = $expression[0]; + $expression = substr($expression, 1); + } + + $values = explode(',', $expression); + foreach ($values as &$value) { + $value = trim($value); + $varspec = array(); + $substrPos = strpos($value, ':'); + if ($substrPos) { + $varspec['value'] = substr($value, 0, $substrPos); + $varspec['modifier'] = ':'; + $varspec['position'] = (int) substr($value, $substrPos + 1); + } elseif (substr($value, -1) == '*') { + $varspec['modifier'] = '*'; + $varspec['value'] = substr($value, 0, -1); + } else { + $varspec['value'] = (string) $value; + $varspec['modifier'] = ''; + } + $value = $varspec; + } + + return array( + 'operator' => $operator, + 'values' => $values + ); + } + + /** + * Process an expansion + * + * @param array $matches Matches met in the preg_replace_callback + * + * @return string Returns the replacement string + */ + private function expandMatch(array $matches) + { + static $rfc1738to3986 = array( + '+' => '%20', + '%7e' => '~' + ); + + $parsed = self::parseExpression($matches[1]); + $replacements = array(); + + $prefix = $parsed['operator']; + $joiner = $parsed['operator']; + $useQueryString = false; + if ($parsed['operator'] == '?') { + $joiner = '&'; + $useQueryString = true; + } elseif ($parsed['operator'] == '&') { + $useQueryString = true; + } elseif ($parsed['operator'] == '#') { + $joiner = ','; + } elseif ($parsed['operator'] == ';') { + $useQueryString = true; + } elseif ($parsed['operator'] == '' || $parsed['operator'] == '+') { + $joiner = ','; + $prefix = ''; + } + + foreach ($parsed['values'] as $value) { + + if (!array_key_exists($value['value'], $this->variables) || $this->variables[$value['value']] === null) { + continue; + } + + $variable = $this->variables[$value['value']]; + $actuallyUseQueryString = $useQueryString; + $expanded = ''; + + if (is_array($variable)) { + + $isAssoc = $this->isAssoc($variable); + $kvp = array(); + foreach ($variable as $key => $var) { + + if ($isAssoc) { + $key = rawurlencode($key); + $isNestedArray = is_array($var); + } else { + $isNestedArray = false; + } + + if (!$isNestedArray) { + $var = rawurlencode($var); + if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { + $var = $this->decodeReserved($var); + } + } + + if ($value['modifier'] == '*') { + if ($isAssoc) { + if ($isNestedArray) { + // Nested arrays must allow for deeply nested structures + $var = strtr(http_build_query(array($key => $var)), $rfc1738to3986); + } else { + $var = $key . '=' . $var; + } + } elseif ($key > 0 && $actuallyUseQueryString) { + $var = $value['value'] . '=' . $var; + } + } + + $kvp[$key] = $var; + } + + if (empty($variable)) { + $actuallyUseQueryString = false; + } elseif ($value['modifier'] == '*') { + $expanded = implode($joiner, $kvp); + if ($isAssoc) { + // Don't prepend the value name when using the explode modifier with an associative array + $actuallyUseQueryString = false; + } + } else { + if ($isAssoc) { + // When an associative array is encountered and the explode modifier is not set, then the + // result must be a comma separated list of keys followed by their respective values. + foreach ($kvp as $k => &$v) { + $v = $k . ',' . $v; + } + } + $expanded = implode(',', $kvp); + } + + } else { + if ($value['modifier'] == ':') { + $variable = substr($variable, 0, $value['position']); + } + $expanded = rawurlencode($variable); + if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { + $expanded = $this->decodeReserved($expanded); + } + } + + if ($actuallyUseQueryString) { + if (!$expanded && $joiner != '&') { + $expanded = $value['value']; + } else { + $expanded = $value['value'] . '=' . $expanded; + } + } + + $replacements[] = $expanded; + } + + $ret = implode($joiner, $replacements); + if ($ret && $prefix) { + return $prefix . $ret; + } + + return $ret; + } + + /** + * Determines if an array is associative + * + * @param array $array Array to check + * + * @return bool + */ + private function isAssoc(array $array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } + + /** + * Removes percent encoding on reserved characters (used with + and # modifiers) + * + * @param string $string String to fix + * + * @return string + */ + private function decodeReserved($string) + { + return str_replace(self::$delimsPct, self::$delims, $string); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php new file mode 100644 index 000000000..c81d51548 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php @@ -0,0 +1,21 @@ +utf8 = $utf8; + } + + public function parseUrl($url) + { + Version::warn(__CLASS__ . ' is deprecated. Just use parse_url()'); + + static $defaults = array('scheme' => null, 'host' => null, 'path' => null, 'port' => null, 'query' => null, + 'user' => null, 'pass' => null, 'fragment' => null); + + $parts = parse_url($url); + + // Need to handle query parsing specially for UTF-8 requirements + if ($this->utf8 && isset($parts['query'])) { + $queryPos = strpos($url, '?'); + if (isset($parts['fragment'])) { + $parts['query'] = substr($url, $queryPos + 1, strpos($url, '#') - $queryPos - 1); + } else { + $parts['query'] = substr($url, $queryPos + 1); + } + } + + return $parts + $defaults; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php new file mode 100644 index 000000000..89ac4b307 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php @@ -0,0 +1,19 @@ +=5.3.2" + }, + "autoload": { + "psr-0": { "Guzzle\\Parser": "" } + }, + "target-dir": "Guzzle/Parser", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php new file mode 100644 index 000000000..ae5941873 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php @@ -0,0 +1,84 @@ + 'onBeforeSend', + 'request.exception' => 'onRequestTimeout', + 'request.sent' => 'onRequestSent', + 'curl.callback.progress' => 'onCurlProgress' + ); + } + + /** + * Event used to ensure that progress callback are emitted from the curl handle's request mediator. + * + * @param Event $event + */ + public function onBeforeSend(Event $event) + { + // Ensure that progress callbacks are dispatched + $event['request']->getCurlOptions()->set('progress', true); + } + + /** + * Event emitted when a curl progress function is called. When the amount of data uploaded == the amount of data to + * upload OR any bytes have been downloaded, then time the request out after 1ms because we're done with + * transmitting the request, and tell curl not download a body. + * + * @param Event $event + */ + public function onCurlProgress(Event $event) + { + if ($event['handle'] && + ($event['downloaded'] || (isset($event['uploaded']) && $event['upload_size'] === $event['uploaded'])) + ) { + // Timeout after 1ms + curl_setopt($event['handle'], CURLOPT_TIMEOUT_MS, 1); + // Even if the response is quick, tell curl not to download the body. + // - Note that we can only perform this shortcut if the request transmitted a body so as to ensure that the + // request method is not converted to a HEAD request before the request was sent via curl. + if ($event['uploaded']) { + curl_setopt($event['handle'], CURLOPT_NOBODY, true); + } + } + } + + /** + * Event emitted when a curl exception occurs. Ignore the exception and set a mock response. + * + * @param Event $event + */ + public function onRequestTimeout(Event $event) + { + if ($event['exception'] instanceof CurlException) { + $event['request']->setResponse(new Response(200, array( + 'X-Guzzle-Async' => 'Did not wait for the response' + ))); + } + } + + /** + * Event emitted when a request completes because it took less than 1ms. Add an X-Guzzle-Async header to notify the + * caller that there is no body in the message. + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + // Let the caller know this was meant to be async + $event['request']->getResponse()->setHeader('X-Guzzle-Async', 'Did not wait for the response'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json new file mode 100644 index 000000000..dc3fc5bf8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-async", + "description": "Guzzle async request plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Async": "" } + }, + "target-dir": "Guzzle/Plugin/Async", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php new file mode 100644 index 000000000..0a8598345 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php @@ -0,0 +1,91 @@ +next = $next; + } + + /** + * Get the next backoff strategy in the chain + * + * @return AbstractBackoffStrategy|null + */ + public function getNext() + { + return $this->next; + } + + public function getBackoffPeriod( + $retries, + RequestInterface $request, + Response $response = null, + HttpException $e = null + ) { + $delay = $this->getDelay($retries, $request, $response, $e); + if ($delay === false) { + // The strategy knows that this must not be retried + return false; + } elseif ($delay === null) { + // If the strategy is deferring a decision and the next strategy will not make a decision then return false + return !$this->next || !$this->next->makesDecision() + ? false + : $this->next->getBackoffPeriod($retries, $request, $response, $e); + } elseif ($delay === true) { + // if the strategy knows that it must retry but is deferring to the next to determine the delay + if (!$this->next) { + return 0; + } else { + $next = $this->next; + while ($next->makesDecision() && $next->getNext()) { + $next = $next->getNext(); + } + return !$next->makesDecision() ? $next->getBackoffPeriod($retries, $request, $response, $e) : 0; + } + } else { + return $delay; + } + } + + /** + * Check if the strategy does filtering and makes decisions on whether or not to retry. + * + * Strategies that return false will never retry if all of the previous strategies in a chain defer on a backoff + * decision. + * + * @return bool + */ + abstract public function makesDecision(); + + /** + * Implement the concrete strategy + * + * @param int $retries Number of retries of the request + * @param RequestInterface $request Request that was sent + * @param Response $response Response that was received. Note that there may not be a response + * @param HttpException $e Exception that was encountered if any + * + * @return bool|int|null Returns false to not retry or the number of seconds to delay between retries. Return true + * or null to defer to the next strategy if available, and if not, return 0. + */ + abstract protected function getDelay( + $retries, + RequestInterface $request, + Response $response = null, + HttpException $e = null + ); +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php new file mode 100644 index 000000000..6ebee6c1a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php @@ -0,0 +1,40 @@ +errorCodes = array_fill_keys($codes ?: static::$defaultErrorCodes, 1); + $this->next = $next; + } + + /** + * Get the default failure codes to retry + * + * @return array + */ + public static function getDefaultFailureCodes() + { + return static::$defaultErrorCodes; + } + + public function makesDecision() + { + return true; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php new file mode 100644 index 000000000..ec54c289e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php @@ -0,0 +1,76 @@ +logger = $logger; + $this->formatter = $formatter ?: new MessageFormatter(self::DEFAULT_FORMAT); + } + + public static function getSubscribedEvents() + { + return array(BackoffPlugin::RETRY_EVENT => 'onRequestRetry'); + } + + /** + * Set the template to use for logging + * + * @param string $template Log message template + * + * @return self + */ + public function setTemplate($template) + { + $this->formatter->setTemplate($template); + + return $this; + } + + /** + * Called when a request is being retried + * + * @param Event $event Event emitted + */ + public function onRequestRetry(Event $event) + { + $this->logger->log($this->formatter->format( + $event['request'], + $event['response'], + $event['handle'], + array( + 'retries' => $event['retries'], + 'delay' => $event['delay'] + ) + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php new file mode 100644 index 000000000..99ace0538 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php @@ -0,0 +1,126 @@ +strategy = $strategy; + } + + /** + * Retrieve a basic truncated exponential backoff plugin that will retry HTTP errors and cURL errors + * + * @param int $maxRetries Maximum number of retries + * @param array $httpCodes HTTP response codes to retry + * @param array $curlCodes cURL error codes to retry + * + * @return self + */ + public static function getExponentialBackoff( + $maxRetries = 3, + array $httpCodes = null, + array $curlCodes = null + ) { + return new self(new TruncatedBackoffStrategy($maxRetries, + new HttpBackoffStrategy($httpCodes, + new CurlBackoffStrategy($curlCodes, + new ExponentialBackoffStrategy() + ) + ) + )); + } + + public static function getAllEvents() + { + return array(self::RETRY_EVENT); + } + + public static function getSubscribedEvents() + { + return array( + 'request.sent' => 'onRequestSent', + 'request.exception' => 'onRequestSent', + CurlMultiInterface::POLLING_REQUEST => 'onRequestPoll' + ); + } + + /** + * Called when a request has been sent and isn't finished processing + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + $request = $event['request']; + $response = $event['response']; + $exception = $event['exception']; + + $params = $request->getParams(); + $retries = (int) $params->get(self::RETRY_PARAM); + $delay = $this->strategy->getBackoffPeriod($retries, $request, $response, $exception); + + if ($delay !== false) { + // Calculate how long to wait until the request should be retried + $params->set(self::RETRY_PARAM, ++$retries) + ->set(self::DELAY_PARAM, microtime(true) + $delay); + // Send the request again + $request->setState(RequestInterface::STATE_TRANSFER); + $this->dispatch(self::RETRY_EVENT, array( + 'request' => $request, + 'response' => $response, + 'handle' => ($exception && $exception instanceof CurlException) ? $exception->getCurlHandle() : null, + 'retries' => $retries, + 'delay' => $delay + )); + } + } + + /** + * Called when a request is polling in the curl multi object + * + * @param Event $event + */ + public function onRequestPoll(Event $event) + { + $request = $event['request']; + $delay = $request->getParams()->get(self::DELAY_PARAM); + + // If the duration of the delay has passed, retry the request using the pool + if (null !== $delay && microtime(true) >= $delay) { + // Remove the request from the pool and then add it back again. This is required for cURL to know that we + // want to retry sending the easy handle. + $request->getParams()->remove(self::DELAY_PARAM); + // Rewind the request body if possible + if ($request instanceof EntityEnclosingRequestInterface && $request->getBody()) { + $request->getBody()->seek(0); + } + $multi = $event['curl_multi']; + $multi->remove($request); + $multi->add($request); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php new file mode 100644 index 000000000..4e590dbe0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php @@ -0,0 +1,30 @@ +callback = $callback; + $this->decision = (bool) $decision; + $this->next = $next; + } + + public function makesDecision() + { + return $this->decision; + } + + protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null) + { + return call_user_func($this->callback, $retries, $request, $response, $e); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php new file mode 100644 index 000000000..061d2a407 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php @@ -0,0 +1,34 @@ +delay = $delay; + } + + public function makesDecision() + { + return false; + } + + protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null) + { + return $this->delay; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php new file mode 100644 index 000000000..a584ed4a2 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php @@ -0,0 +1,28 @@ +errorCodes[$e->getErrorNo()]) ? true : null; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php new file mode 100644 index 000000000..fb2912d50 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php @@ -0,0 +1,25 @@ +isSuccessful()) { + return false; + } else { + return isset($this->errorCodes[$response->getStatusCode()]) ? true : null; + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php new file mode 100644 index 000000000..b35e8a490 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php @@ -0,0 +1,36 @@ +step = $step; + } + + public function makesDecision() + { + return false; + } + + protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null) + { + return $retries * $this->step; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php new file mode 100644 index 000000000..4fd73fedf --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php @@ -0,0 +1,25 @@ +errorCodes[$response->getReasonPhrase()]) ? true : null; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php new file mode 100644 index 000000000..3608f3584 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php @@ -0,0 +1,36 @@ +max = $maxRetries; + $this->next = $next; + } + + public function makesDecision() + { + return true; + } + + protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null) + { + return $retries < $this->max ? null : false; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json new file mode 100644 index 000000000..91c122cb4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json @@ -0,0 +1,28 @@ +{ + "name": "guzzle/plugin-backoff", + "description": "Guzzle backoff retry plugins", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version", + "guzzle/log": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Backoff": "" } + }, + "target-dir": "Guzzle/Plugin/Backoff", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php new file mode 100644 index 000000000..7790f8844 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php @@ -0,0 +1,11 @@ + new DefaultCacheStorage($options)); + } elseif ($options instanceof CacheStorageInterface) { + $options = array('storage' => $options); + } elseif ($options) { + $options = array('storage' => new DefaultCacheStorage(CacheAdapterFactory::fromCache($options))); + } elseif (!class_exists('Doctrine\Common\Cache\ArrayCache')) { + // @codeCoverageIgnoreStart + throw new InvalidArgumentException('No cache was provided and Doctrine is not installed'); + // @codeCoverageIgnoreEnd + } + } + + $this->autoPurge = isset($options['auto_purge']) ? $options['auto_purge'] : false; + + // Add a cache storage if a cache adapter was provided + $this->storage = isset($options['storage']) + ? $options['storage'] + : new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache())); + + if (!isset($options['can_cache'])) { + $this->canCache = new DefaultCanCacheStrategy(); + } else { + $this->canCache = is_callable($options['can_cache']) + ? new CallbackCanCacheStrategy($options['can_cache']) + : $options['can_cache']; + } + + // Use the provided revalidation strategy or the default + $this->revalidation = isset($options['revalidation']) + ? $options['revalidation'] + : new DefaultRevalidation($this->storage, $this->canCache); + } + + public static function getSubscribedEvents() + { + return array( + 'request.before_send' => array('onRequestBeforeSend', -255), + 'request.sent' => array('onRequestSent', 255), + 'request.error' => array('onRequestError', 0), + 'request.exception' => array('onRequestException', 0), + ); + } + + /** + * Check if a response in cache will satisfy the request before sending + * + * @param Event $event + */ + public function onRequestBeforeSend(Event $event) + { + $request = $event['request']; + $request->addHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION)); + + if (!$this->canCache->canCacheRequest($request)) { + switch ($request->getMethod()) { + case 'PURGE': + $this->purge($request); + $request->setResponse(new Response(200, array(), 'purged')); + break; + case 'PUT': + case 'POST': + case 'DELETE': + case 'PATCH': + if ($this->autoPurge) { + $this->purge($request); + } + } + return; + } + + if ($response = $this->storage->fetch($request)) { + $params = $request->getParams(); + $params['cache.lookup'] = true; + $response->setHeader( + 'Age', + time() - strtotime($response->getDate() ? : $response->getLastModified() ?: 'now') + ); + // Validate that the response satisfies the request + if ($this->canResponseSatisfyRequest($request, $response)) { + if (!isset($params['cache.hit'])) { + $params['cache.hit'] = true; + } + $request->setResponse($response); + } + } + } + + /** + * If possible, store a response in cache after sending + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + $request = $event['request']; + $response = $event['response']; + + if ($request->getParams()->get('cache.hit') === null && + $this->canCache->canCacheRequest($request) && + $this->canCache->canCacheResponse($response) + ) { + $this->storage->cache($request, $response); + } + + $this->addResponseHeaders($request, $response); + } + + /** + * If possible, return a cache response on an error + * + * @param Event $event + */ + public function onRequestError(Event $event) + { + $request = $event['request']; + + if (!$this->canCache->canCacheRequest($request)) { + return; + } + + if ($response = $this->storage->fetch($request)) { + $response->setHeader( + 'Age', + time() - strtotime($response->getLastModified() ? : $response->getDate() ?: 'now') + ); + + if ($this->canResponseSatisfyFailedRequest($request, $response)) { + $request->getParams()->set('cache.hit', 'error'); + $this->addResponseHeaders($request, $response); + $event['response'] = $response; + $event->stopPropagation(); + } + } + } + + /** + * If possible, set a cache response on a cURL exception + * + * @param Event $event + * + * @return null + */ + public function onRequestException(Event $event) + { + if (!$event['exception'] instanceof CurlException) { + return; + } + + $request = $event['request']; + if (!$this->canCache->canCacheRequest($request)) { + return; + } + + if ($response = $this->storage->fetch($request)) { + $response->setHeader('Age', time() - strtotime($response->getDate() ? : 'now')); + if (!$this->canResponseSatisfyFailedRequest($request, $response)) { + return; + } + $request->getParams()->set('cache.hit', 'error'); + $request->setResponse($response); + $this->addResponseHeaders($request, $response); + $event->stopPropagation(); + } + } + + /** + * Check if a cache response satisfies a request's caching constraints + * + * @param RequestInterface $request Request to validate + * @param Response $response Response to validate + * + * @return bool + */ + public function canResponseSatisfyRequest(RequestInterface $request, Response $response) + { + $responseAge = $response->calculateAge(); + $reqc = $request->getHeader('Cache-Control'); + $resc = $response->getHeader('Cache-Control'); + + // Check the request's max-age header against the age of the response + if ($reqc && $reqc->hasDirective('max-age') && + $responseAge > $reqc->getDirective('max-age')) { + return false; + } + + // Check the response's max-age header + if ($response->isFresh() === false) { + $maxStale = $reqc ? $reqc->getDirective('max-stale') : null; + if (null !== $maxStale) { + if ($maxStale !== true && $response->getFreshness() < (-1 * $maxStale)) { + return false; + } + } elseif ($resc && $resc->hasDirective('max-age') + && $responseAge > $resc->getDirective('max-age') + ) { + return false; + } + } + + if ($this->revalidation->shouldRevalidate($request, $response)) { + try { + return $this->revalidation->revalidate($request, $response); + } catch (CurlException $e) { + $request->getParams()->set('cache.hit', 'error'); + return $this->canResponseSatisfyFailedRequest($request, $response); + } + } + + return true; + } + + /** + * Check if a cache response satisfies a failed request's caching constraints + * + * @param RequestInterface $request Request to validate + * @param Response $response Response to validate + * + * @return bool + */ + public function canResponseSatisfyFailedRequest(RequestInterface $request, Response $response) + { + $reqc = $request->getHeader('Cache-Control'); + $resc = $response->getHeader('Cache-Control'); + $requestStaleIfError = $reqc ? $reqc->getDirective('stale-if-error') : null; + $responseStaleIfError = $resc ? $resc->getDirective('stale-if-error') : null; + + if (!$requestStaleIfError && !$responseStaleIfError) { + return false; + } + + if (is_numeric($requestStaleIfError) && $response->getAge() - $response->getMaxAge() > $requestStaleIfError) { + return false; + } + + if (is_numeric($responseStaleIfError) && $response->getAge() - $response->getMaxAge() > $responseStaleIfError) { + return false; + } + + return true; + } + + /** + * Purge all cache entries for a given URL + * + * @param string $url URL to purge + */ + public function purge($url) + { + // BC compatibility with previous version that accepted a Request object + $url = $url instanceof RequestInterface ? $url->getUrl() : $url; + $this->storage->purge($url); + } + + /** + * Add the plugin's headers to a response + * + * @param RequestInterface $request Request + * @param Response $response Response to add headers to + */ + protected function addResponseHeaders(RequestInterface $request, Response $response) + { + $params = $request->getParams(); + $response->setHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION)); + + $lookup = ($params['cache.lookup'] === true ? 'HIT' : 'MISS') . ' from GuzzleCache'; + if ($header = $response->getHeader('X-Cache-Lookup')) { + // Don't add duplicates + $values = $header->toArray(); + $values[] = $lookup; + $response->setHeader('X-Cache-Lookup', array_unique($values)); + } else { + $response->setHeader('X-Cache-Lookup', $lookup); + } + + if ($params['cache.hit'] === true) { + $xcache = 'HIT from GuzzleCache'; + } elseif ($params['cache.hit'] == 'error') { + $xcache = 'HIT_ERROR from GuzzleCache'; + } else { + $xcache = 'MISS from GuzzleCache'; + } + + if ($header = $response->getHeader('X-Cache')) { + // Don't add duplicates + $values = $header->toArray(); + $values[] = $xcache; + $response->setHeader('X-Cache', array_unique($values)); + } else { + $response->setHeader('X-Cache', $xcache); + } + + if ($response->isFresh() === false) { + $response->addHeader('Warning', sprintf('110 GuzzleCache/%s "Response is stale"', Version::VERSION)); + if ($params['cache.hit'] === 'error') { + $response->addHeader('Warning', sprintf('111 GuzzleCache/%s "Revalidation failed"', Version::VERSION)); + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php new file mode 100644 index 000000000..f3d915458 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php @@ -0,0 +1,43 @@ +requestCallback = $requestCallback; + $this->responseCallback = $responseCallback; + } + + public function canCacheRequest(RequestInterface $request) + { + return $this->requestCallback + ? call_user_func($this->requestCallback, $request) + : parent::canCacheRequest($request); + } + + public function canCacheResponse(Response $response) + { + return $this->responseCallback + ? call_user_func($this->responseCallback, $response) + : parent::canCacheResponse($response); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php new file mode 100644 index 000000000..6e01a8e74 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php @@ -0,0 +1,30 @@ +getParams()->get(self::CACHE_KEY); + + if (!$key) { + + $cloned = clone $request; + $cloned->removeHeader('Cache-Control'); + + // Check to see how and if the key should be filtered + foreach (explode(';', $request->getParams()->get(self::CACHE_KEY_FILTER)) as $part) { + $pieces = array_map('trim', explode('=', $part)); + if (isset($pieces[1])) { + foreach (array_map('trim', explode(',', $pieces[1])) as $remove) { + if ($pieces[0] == 'header') { + $cloned->removeHeader($remove); + } elseif ($pieces[0] == 'query') { + $cloned->getQuery()->remove($remove); + } + } + } + } + + $raw = (string) $cloned; + $key = 'GZ' . md5($raw); + $request->getParams()->set(self::CACHE_KEY, $key)->set(self::CACHE_KEY_RAW, $raw); + } + + return $key; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php new file mode 100644 index 000000000..26d7a8b27 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php @@ -0,0 +1,266 @@ +cache = CacheAdapterFactory::fromCache($cache); + $this->defaultTtl = $defaultTtl; + $this->keyPrefix = $keyPrefix; + } + + public function cache(RequestInterface $request, Response $response) + { + $currentTime = time(); + + $overrideTtl = $request->getParams()->get('cache.override_ttl'); + if ($overrideTtl) { + $ttl = $overrideTtl; + } else { + $maxAge = $response->getMaxAge(); + if ($maxAge !== null) { + $ttl = $maxAge; + } else { + $ttl = $this->defaultTtl; + } + } + + if ($cacheControl = $response->getHeader('Cache-Control')) { + $stale = $cacheControl->getDirective('stale-if-error'); + if ($stale === true) { + $ttl += $ttl; + } else if (is_numeric($stale)) { + $ttl += $stale; + } + } + + // Determine which manifest key should be used + $key = $this->getCacheKey($request); + $persistedRequest = $this->persistHeaders($request); + $entries = array(); + + if ($manifest = $this->cache->fetch($key)) { + // Determine which cache entries should still be in the cache + $vary = $response->getVary(); + foreach (unserialize($manifest) as $entry) { + // Check if the entry is expired + if ($entry[4] < $currentTime) { + continue; + } + $entry[1]['vary'] = isset($entry[1]['vary']) ? $entry[1]['vary'] : ''; + if ($vary != $entry[1]['vary'] || !$this->requestsMatch($vary, $entry[0], $persistedRequest)) { + $entries[] = $entry; + } + } + } + + // Persist the response body if needed + $bodyDigest = null; + if ($response->getBody() && $response->getBody()->getContentLength() > 0) { + $bodyDigest = $this->getBodyKey($request->getUrl(), $response->getBody()); + $this->cache->save($bodyDigest, (string) $response->getBody(), $ttl); + } + + array_unshift($entries, array( + $persistedRequest, + $this->persistHeaders($response), + $response->getStatusCode(), + $bodyDigest, + $currentTime + $ttl + )); + + $this->cache->save($key, serialize($entries)); + } + + public function delete(RequestInterface $request) + { + $key = $this->getCacheKey($request); + if ($entries = $this->cache->fetch($key)) { + // Delete each cached body + foreach (unserialize($entries) as $entry) { + if ($entry[3]) { + $this->cache->delete($entry[3]); + } + } + $this->cache->delete($key); + } + } + + public function purge($url) + { + foreach (array('GET', 'HEAD', 'POST', 'PUT', 'DELETE') as $method) { + $this->delete(new Request($method, $url)); + } + } + + public function fetch(RequestInterface $request) + { + $key = $this->getCacheKey($request); + if (!($entries = $this->cache->fetch($key))) { + return null; + } + + $match = null; + $headers = $this->persistHeaders($request); + $entries = unserialize($entries); + foreach ($entries as $index => $entry) { + if ($this->requestsMatch(isset($entry[1]['vary']) ? $entry[1]['vary'] : '', $headers, $entry[0])) { + $match = $entry; + break; + } + } + + if (!$match) { + return null; + } + + // Ensure that the response is not expired + $response = null; + if ($match[4] < time()) { + $response = -1; + } else { + $response = new Response($match[2], $match[1]); + if ($match[3]) { + if ($body = $this->cache->fetch($match[3])) { + $response->setBody($body); + } else { + // The response is not valid because the body was somehow deleted + $response = -1; + } + } + } + + if ($response === -1) { + // Remove the entry from the metadata and update the cache + unset($entries[$index]); + if ($entries) { + $this->cache->save($key, serialize($entries)); + } else { + $this->cache->delete($key); + } + return null; + } + + return $response; + } + + /** + * Hash a request URL into a string that returns cache metadata + * + * @param RequestInterface $request + * + * @return string + */ + protected function getCacheKey(RequestInterface $request) + { + // Allow cache.key_filter to trim down the URL cache key by removing generate query string values (e.g. auth) + if ($filter = $request->getParams()->get('cache.key_filter')) { + $url = $request->getUrl(true); + foreach (explode(',', $filter) as $remove) { + $url->getQuery()->remove(trim($remove)); + } + } else { + $url = $request->getUrl(); + } + + return $this->keyPrefix . md5($request->getMethod() . ' ' . $url); + } + + /** + * Create a cache key for a response's body + * + * @param string $url URL of the entry + * @param EntityBodyInterface $body Response body + * + * @return string + */ + protected function getBodyKey($url, EntityBodyInterface $body) + { + return $this->keyPrefix . md5($url) . $body->getContentMd5(); + } + + /** + * Determines whether two Request HTTP header sets are non-varying + * + * @param string $vary Response vary header + * @param array $r1 HTTP header array + * @param array $r2 HTTP header array + * + * @return bool + */ + private function requestsMatch($vary, $r1, $r2) + { + if ($vary) { + foreach (explode(',', $vary) as $header) { + $key = trim(strtolower($header)); + $v1 = isset($r1[$key]) ? $r1[$key] : null; + $v2 = isset($r2[$key]) ? $r2[$key] : null; + if ($v1 !== $v2) { + return false; + } + } + } + + return true; + } + + /** + * Creates an array of cacheable and normalized message headers + * + * @param MessageInterface $message + * + * @return array + */ + private function persistHeaders(MessageInterface $message) + { + // Headers are excluded from the caching (see RFC 2616:13.5.1) + static $noCache = array( + 'age' => true, + 'connection' => true, + 'keep-alive' => true, + 'proxy-authenticate' => true, + 'proxy-authorization' => true, + 'te' => true, + 'trailers' => true, + 'transfer-encoding' => true, + 'upgrade' => true, + 'set-cookie' => true, + 'set-cookie2' => true + ); + + // Clone the response to not destroy any necessary headers when caching + $headers = $message->getHeaders()->getAll(); + $headers = array_diff_key($headers, $noCache); + // Cast the headers to a string + $headers = array_map(function ($h) { return (string) $h; }, $headers); + + return $headers; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php new file mode 100644 index 000000000..3ca1fbf19 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php @@ -0,0 +1,32 @@ +getMethod() != RequestInterface::GET && $request->getMethod() != RequestInterface::HEAD) { + return false; + } + + // Never cache requests when using no-store + if ($request->hasHeader('Cache-Control') && $request->getHeader('Cache-Control')->hasDirective('no-store')) { + return false; + } + + return true; + } + + public function canCacheResponse(Response $response) + { + return $response->isSuccessful() && $response->canCache(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php new file mode 100644 index 000000000..af33234ee --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php @@ -0,0 +1,174 @@ +storage = $cache; + $this->canCache = $canCache ?: new DefaultCanCacheStrategy(); + } + + public function revalidate(RequestInterface $request, Response $response) + { + try { + $revalidate = $this->createRevalidationRequest($request, $response); + $validateResponse = $revalidate->send(); + if ($validateResponse->getStatusCode() == 200) { + return $this->handle200Response($request, $validateResponse); + } elseif ($validateResponse->getStatusCode() == 304) { + return $this->handle304Response($request, $validateResponse, $response); + } + } catch (BadResponseException $e) { + $this->handleBadResponse($e); + } + + // Other exceptions encountered in the revalidation request are ignored + // in hopes that sending a request to the origin server will fix it + return false; + } + + public function shouldRevalidate(RequestInterface $request, Response $response) + { + if ($request->getMethod() != RequestInterface::GET) { + return false; + } + + $reqCache = $request->getHeader('Cache-Control'); + $resCache = $response->getHeader('Cache-Control'); + + $revalidate = $request->getHeader('Pragma') == 'no-cache' || + ($reqCache && ($reqCache->hasDirective('no-cache') || $reqCache->hasDirective('must-revalidate'))) || + ($resCache && ($resCache->hasDirective('no-cache') || $resCache->hasDirective('must-revalidate'))); + + // Use the strong ETag validator if available and the response contains no Cache-Control directive + if (!$revalidate && !$resCache && $response->hasHeader('ETag')) { + $revalidate = true; + } + + return $revalidate; + } + + /** + * Handles a bad response when attempting to revalidate + * + * @param BadResponseException $e Exception encountered + * + * @throws BadResponseException + */ + protected function handleBadResponse(BadResponseException $e) + { + // 404 errors mean the resource no longer exists, so remove from + // cache, and prevent an additional request by throwing the exception + if ($e->getResponse()->getStatusCode() == 404) { + $this->storage->delete($e->getRequest()); + throw $e; + } + } + + /** + * Creates a request to use for revalidation + * + * @param RequestInterface $request Request + * @param Response $response Response to revalidate + * + * @return RequestInterface returns a revalidation request + */ + protected function createRevalidationRequest(RequestInterface $request, Response $response) + { + $revalidate = clone $request; + $revalidate->removeHeader('Pragma')->removeHeader('Cache-Control'); + + if ($response->getLastModified()) { + $revalidate->setHeader('If-Modified-Since', $response->getLastModified()); + } + + if ($response->getEtag()) { + $revalidate->setHeader('If-None-Match', $response->getEtag()); + } + + // Remove any cache plugins that might be on the request to prevent infinite recursive revalidations + $dispatcher = $revalidate->getEventDispatcher(); + foreach ($dispatcher->getListeners() as $eventName => $listeners) { + foreach ($listeners as $listener) { + if (is_array($listener) && $listener[0] instanceof CachePlugin) { + $dispatcher->removeListener($eventName, $listener); + } + } + } + + return $revalidate; + } + + /** + * Handles a 200 response response from revalidating. The server does not support validation, so use this response. + * + * @param RequestInterface $request Request that was sent + * @param Response $validateResponse Response received + * + * @return bool Returns true if valid, false if invalid + */ + protected function handle200Response(RequestInterface $request, Response $validateResponse) + { + $request->setResponse($validateResponse); + if ($this->canCache->canCacheResponse($validateResponse)) { + $this->storage->cache($request, $validateResponse); + } + + return false; + } + + /** + * Handle a 304 response and ensure that it is still valid + * + * @param RequestInterface $request Request that was sent + * @param Response $validateResponse Response received + * @param Response $response Original cached response + * + * @return bool Returns true if valid, false if invalid + */ + protected function handle304Response(RequestInterface $request, Response $validateResponse, Response $response) + { + static $replaceHeaders = array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified'); + + // Make sure that this response has the same ETag + if ($validateResponse->getEtag() != $response->getEtag()) { + return false; + } + + // Replace cached headers with any of these headers from the + // origin server that might be more up to date + $modified = false; + foreach ($replaceHeaders as $name) { + if ($validateResponse->hasHeader($name)) { + $modified = true; + $response->setHeader($name, $validateResponse->getHeader($name)); + } + } + + // Store the updated response in cache + if ($modified && $this->canCache->canCacheResponse($response)) { + $this->storage->cache($request, $response); + } + + return true; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php new file mode 100644 index 000000000..88b86f3ca --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php @@ -0,0 +1,19 @@ +=5.3.2", + "guzzle/http": "self.version", + "guzzle/cache": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Cache": "" } + }, + "target-dir": "Guzzle/Plugin/Cache", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php new file mode 100644 index 000000000..5218e5f0e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php @@ -0,0 +1,538 @@ + '', + 'value' => '', + 'domain' => '', + 'path' => '/', + 'expires' => null, + 'max_age' => 0, + 'comment' => null, + 'comment_url' => null, + 'port' => array(), + 'version' => null, + 'secure' => false, + 'discard' => false, + 'http_only' => false + ); + + $this->data = array_merge($defaults, $data); + // Extract the expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the expires date + $this->setExpires(time() + (int) $this->getMaxAge()); + } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { + $this->setExpires(strtotime($this->getExpires())); + } + } + + /** + * Get the cookie as an array + * + * @return array + */ + public function toArray() + { + return $this->data; + } + + /** + * Get the cookie name + * + * @return string + */ + public function getName() + { + return $this->data['name']; + } + + /** + * Set the cookie name + * + * @param string $name Cookie name + * + * @return Cookie + */ + public function setName($name) + { + return $this->setData('name', $name); + } + + /** + * Get the cookie value + * + * @return string + */ + public function getValue() + { + return $this->data['value']; + } + + /** + * Set the cookie value + * + * @param string $value Cookie value + * + * @return Cookie + */ + public function setValue($value) + { + return $this->setData('value', $value); + } + + /** + * Get the domain + * + * @return string|null + */ + public function getDomain() + { + return $this->data['domain']; + } + + /** + * Set the domain of the cookie + * + * @param string $domain + * + * @return Cookie + */ + public function setDomain($domain) + { + return $this->setData('domain', $domain); + } + + /** + * Get the path + * + * @return string + */ + public function getPath() + { + return $this->data['path']; + } + + /** + * Set the path of the cookie + * + * @param string $path Path of the cookie + * + * @return Cookie + */ + public function setPath($path) + { + return $this->setData('path', $path); + } + + /** + * Maximum lifetime of the cookie in seconds + * + * @return int|null + */ + public function getMaxAge() + { + return $this->data['max_age']; + } + + /** + * Set the max-age of the cookie + * + * @param int $maxAge Max age of the cookie in seconds + * + * @return Cookie + */ + public function setMaxAge($maxAge) + { + return $this->setData('max_age', $maxAge); + } + + /** + * The UNIX timestamp when the cookie expires + * + * @return mixed + */ + public function getExpires() + { + return $this->data['expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire + * + * @param int $timestamp Unix timestamp + * + * @return Cookie + */ + public function setExpires($timestamp) + { + return $this->setData('expires', $timestamp); + } + + /** + * Version of the cookie specification. RFC 2965 is 1 + * + * @return mixed + */ + public function getVersion() + { + return $this->data['version']; + } + + /** + * Set the cookie version + * + * @param string|int $version Version to set + * + * @return Cookie + */ + public function setVersion($version) + { + return $this->setData('version', $version); + } + + /** + * Get whether or not this is a secure cookie + * + * @return null|bool + */ + public function getSecure() + { + return $this->data['secure']; + } + + /** + * Set whether or not the cookie is secure + * + * @param bool $secure Set to true or false if secure + * + * @return Cookie + */ + public function setSecure($secure) + { + return $this->setData('secure', (bool) $secure); + } + + /** + * Get whether or not this is a session cookie + * + * @return null|bool + */ + public function getDiscard() + { + return $this->data['discard']; + } + + /** + * Set whether or not this is a session cookie + * + * @param bool $discard Set to true or false if this is a session cookie + * + * @return Cookie + */ + public function setDiscard($discard) + { + return $this->setData('discard', $discard); + } + + /** + * Get the comment + * + * @return string|null + */ + public function getComment() + { + return $this->data['comment']; + } + + /** + * Set the comment of the cookie + * + * @param string $comment Cookie comment + * + * @return Cookie + */ + public function setComment($comment) + { + return $this->setData('comment', $comment); + } + + /** + * Get the comment URL of the cookie + * + * @return string|null + */ + public function getCommentUrl() + { + return $this->data['comment_url']; + } + + /** + * Set the comment URL of the cookie + * + * @param string $commentUrl Cookie comment URL for more information + * + * @return Cookie + */ + public function setCommentUrl($commentUrl) + { + return $this->setData('comment_url', $commentUrl); + } + + /** + * Get an array of acceptable ports this cookie can be used with + * + * @return array + */ + public function getPorts() + { + return $this->data['port']; + } + + /** + * Set a list of acceptable ports this cookie can be used with + * + * @param array $ports Array of acceptable ports + * + * @return Cookie + */ + public function setPorts(array $ports) + { + return $this->setData('port', $ports); + } + + /** + * Get whether or not this is an HTTP only cookie + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['http_only']; + } + + /** + * Set whether or not this is an HTTP only cookie + * + * @param bool $httpOnly Set to true or false if this is HTTP only + * + * @return Cookie + */ + public function setHttpOnly($httpOnly) + { + return $this->setData('http_only', $httpOnly); + } + + /** + * Get an array of extra cookie data + * + * @return array + */ + public function getAttributes() + { + return $this->data['data']; + } + + /** + * Get a specific data point from the extra cookie data + * + * @param string $name Name of the data point to retrieve + * + * @return null|string + */ + public function getAttribute($name) + { + return array_key_exists($name, $this->data['data']) ? $this->data['data'][$name] : null; + } + + /** + * Set a cookie data attribute + * + * @param string $name Name of the attribute to set + * @param string $value Value to set + * + * @return Cookie + */ + public function setAttribute($name, $value) + { + $this->data['data'][$name] = $value; + + return $this; + } + + /** + * Check if the cookie matches a path value + * + * @param string $path Path to check against + * + * @return bool + */ + public function matchesPath($path) + { + // RFC6265 http://tools.ietf.org/search/rfc6265#section-5.1.4 + // A request-path path-matches a given cookie-path if at least one of + // the following conditions holds: + + // o The cookie-path and the request-path are identical. + if ($path == $this->getPath()) { + return true; + } + + $pos = stripos($path, $this->getPath()); + if ($pos === 0) { + // o The cookie-path is a prefix of the request-path, and the last + // character of the cookie-path is %x2F ("/"). + if (substr($this->getPath(), -1, 1) === "/") { + return true; + } + + // o The cookie-path is a prefix of the request-path, and the first + // character of the request-path that is not included in the cookie- + // path is a %x2F ("/") character. + if (substr($path, strlen($this->getPath()), 1) === "/") { + return true; + } + } + + return false; + } + + /** + * Check if the cookie matches a domain value + * + * @param string $domain Domain to check against + * + * @return bool + */ + public function matchesDomain($domain) + { + // Remove the leading '.' as per spec in RFC 6265: http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim($this->getDomain(), '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { + return true; + } + + // Matching the subdomain according to RFC 6265: http://tools.ietf.org/html/rfc6265#section-5.1.3 + if (filter_var($domain, FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/i', $domain); + } + + /** + * Check if the cookie is compatible with a specific port + * + * @param int $port Port to check + * + * @return bool + */ + public function matchesPort($port) + { + return count($this->getPorts()) == 0 || in_array($port, $this->getPorts()); + } + + /** + * Check if the cookie is expired + * + * @return bool + */ + public function isExpired() + { + return $this->getExpires() && time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265 + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + // Names must not be empty, but can be 0 + $name = $this->getName(); + if (empty($name) && !is_numeric($name)) { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (strpbrk($name, self::getInvalidCharacters()) !== false) { + return 'The cookie name must not contain invalid characters: ' . $name; + } + + // Value must not be empty, but can be 0 + $value = $this->getValue(); + if (empty($value) && !is_numeric($value)) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0 + // A "0" is not a valid internet domain, but may be used as server name in a private network + $domain = $this->getDomain(); + if (empty($domain) && !is_numeric($domain)) { + return 'The cookie domain must not be empty'; + } + + return true; + } + + /** + * Set a value and return the cookie object + * + * @param string $key Key to set + * @param string $value Value to set + * + * @return Cookie + */ + private function setData($key, $value) + { + $this->data[$key] = $value; + + return $this; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php new file mode 100644 index 000000000..6b675039f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php @@ -0,0 +1,237 @@ +strictMode = $strictMode; + } + + /** + * Enable or disable strict mode on the cookie jar + * + * @param bool $strictMode Set to true to throw exceptions when invalid cookies are added. False to ignore them. + * + * @return self + */ + public function setStrictMode($strictMode) + { + $this->strictMode = $strictMode; + } + + public function remove($domain = null, $path = null, $name = null) + { + $cookies = $this->all($domain, $path, $name, false, false); + $this->cookies = array_filter($this->cookies, function (Cookie $cookie) use ($cookies) { + return !in_array($cookie, $cookies, true); + }); + + return $this; + } + + public function removeTemporary() + { + $this->cookies = array_filter($this->cookies, function (Cookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + }); + + return $this; + } + + public function removeExpired() + { + $currentTime = time(); + $this->cookies = array_filter($this->cookies, function (Cookie $cookie) use ($currentTime) { + return !$cookie->getExpires() || $currentTime < $cookie->getExpires(); + }); + + return $this; + } + + public function all($domain = null, $path = null, $name = null, $skipDiscardable = false, $skipExpired = true) + { + return array_values(array_filter($this->cookies, function (Cookie $cookie) use ( + $domain, + $path, + $name, + $skipDiscardable, + $skipExpired + ) { + return false === (($name && $cookie->getName() != $name) || + ($skipExpired && $cookie->isExpired()) || + ($skipDiscardable && ($cookie->getDiscard() || !$cookie->getExpires())) || + ($path && !$cookie->matchesPath($path)) || + ($domain && !$cookie->matchesDomain($domain))); + })); + } + + public function add(Cookie $cookie) + { + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new InvalidCookieException($result); + } else { + $this->removeCookieIfEmpty($cookie); + return false; + } + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, domain, port and name are identical + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getPorts() != $cookie->getPorts() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + /** + * Serializes the cookie cookieJar + * + * @return string + */ + public function serialize() + { + // Only serialize long term cookies and unexpired cookies + return json_encode(array_map(function (Cookie $cookie) { + return $cookie->toArray(); + }, $this->all(null, null, null, true, true))); + } + + /** + * Unserializes the cookie cookieJar + */ + public function unserialize($data) + { + $data = json_decode($data, true); + if (empty($data)) { + $this->cookies = array(); + } else { + $this->cookies = array_map(function (array $cookie) { + return new Cookie($cookie); + }, $data); + } + } + + /** + * Returns the total number of stored cookies + * + * @return int + */ + public function count() + { + return count($this->cookies); + } + + /** + * Returns an iterator + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->cookies); + } + + public function addCookiesFromResponse(Response $response, RequestInterface $request = null) + { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + $parser = ParserRegistry::getInstance()->getParser('cookie'); + foreach ($cookieHeader as $cookie) { + if ($parsed = $request + ? $parser->parseCookie($cookie, $request->getHost(), $request->getPath()) + : $parser->parseCookie($cookie) + ) { + // Break up cookie v2 into multiple cookies + foreach ($parsed['cookies'] as $key => $value) { + $row = $parsed; + $row['name'] = $key; + $row['value'] = $value; + unset($row['cookies']); + $this->add(new Cookie($row)); + } + } + } + } + } + + public function getMatchingCookies(RequestInterface $request) + { + // Find cookies that match this request + $cookies = $this->all($request->getHost(), $request->getPath()); + // Remove ineligible cookies + foreach ($cookies as $index => $cookie) { + if (!$cookie->matchesPort($request->getPort()) || ($cookie->getSecure() && $request->getScheme() != 'https')) { + unset($cookies[$index]); + } + }; + + return $cookies; + } + + /** + * If a cookie already exists and the server asks to set it again with a null value, the + * cookie must be deleted. + * + * @param \Guzzle\Plugin\Cookie\Cookie $cookie + */ + private function removeCookieIfEmpty(Cookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->remove($cookie->getDomain(), $cookie->getPath(), $cookie->getName()); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php new file mode 100644 index 000000000..7faa7d21f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php @@ -0,0 +1,85 @@ +filename = $cookieFile; + $this->load(); + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->persist(); + } + + /** + * Save the contents of the data array to the file + * + * @throws RuntimeException if the file cannot be found or created + */ + protected function persist() + { + if (false === file_put_contents($this->filename, $this->serialize())) { + // @codeCoverageIgnoreStart + throw new RuntimeException('Unable to open file ' . $this->filename); + // @codeCoverageIgnoreEnd + } + } + + /** + * Load the contents of the json formatted file into the data array and discard any unsaved state + */ + protected function load() + { + $json = file_get_contents($this->filename); + if (false === $json) { + // @codeCoverageIgnoreStart + throw new RuntimeException('Unable to open file ' . $this->filename); + // @codeCoverageIgnoreEnd + } + + $this->unserialize($json); + $this->cookies = $this->cookies ?: array(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php new file mode 100644 index 000000000..df3210ee1 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php @@ -0,0 +1,70 @@ +cookieJar = $cookieJar ?: new ArrayCookieJar(); + } + + public static function getSubscribedEvents() + { + return array( + 'request.before_send' => array('onRequestBeforeSend', 125), + 'request.sent' => array('onRequestSent', 125) + ); + } + + /** + * Get the cookie cookieJar + * + * @return CookieJarInterface + */ + public function getCookieJar() + { + return $this->cookieJar; + } + + /** + * Add cookies before a request is sent + * + * @param Event $event + */ + public function onRequestBeforeSend(Event $event) + { + $request = $event['request']; + if (!$request->getParams()->get('cookies.disable')) { + $request->removeHeader('Cookie'); + // Find cookies that match this request + foreach ($this->cookieJar->getMatchingCookies($request) as $cookie) { + $request->addCookie($cookie->getName(), $cookie->getValue()); + } + } + } + + /** + * Extract cookies from a sent request + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + $this->cookieJar->addCookiesFromResponse($event['response'], $event['request']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php new file mode 100644 index 000000000..b1fa6fd89 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php @@ -0,0 +1,7 @@ +=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Cookie": "" } + }, + "target-dir": "Guzzle/Plugin/Cookie", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php new file mode 100644 index 000000000..610e60cad --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php @@ -0,0 +1,46 @@ +getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest'); + */ +class CurlAuthPlugin implements EventSubscriberInterface +{ + private $username; + private $password; + private $scheme; + + /** + * @param string $username HTTP basic auth username + * @param string $password Password + * @param int $scheme Curl auth scheme + */ + public function __construct($username, $password, $scheme=CURLAUTH_BASIC) + { + Version::warn(__CLASS__ . " is deprecated. Use \$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');"); + $this->username = $username; + $this->password = $password; + $this->scheme = $scheme; + } + + public static function getSubscribedEvents() + { + return array('client.create_request' => array('onRequestCreate', 255)); + } + + /** + * Add basic auth + * + * @param Event $event + */ + public function onRequestCreate(Event $event) + { + $event['request']->setAuth($this->username, $this->password, $this->scheme); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json new file mode 100644 index 000000000..edc8b24e5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-curlauth", + "description": "Guzzle cURL authorization plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "curl", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\CurlAuth": "" } + }, + "target-dir": "Guzzle/Plugin/CurlAuth", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php new file mode 100644 index 000000000..5dce8bd6c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php @@ -0,0 +1,22 @@ + array('onCommandBeforeSend', -1)); + } + + /** + * Adds a listener to requests before they sent from a command + * + * @param Event $event Event emitted + */ + public function onCommandBeforeSend(Event $event) + { + $command = $event['command']; + if ($operation = $command->getOperation()) { + if ($operation->getErrorResponses()) { + $request = $command->getRequest(); + $request->getEventDispatcher() + ->addListener('request.complete', $this->getErrorClosure($request, $command, $operation)); + } + } + } + + /** + * @param RequestInterface $request Request that received an error + * @param CommandInterface $command Command that created the request + * @param Operation $operation Operation that defines the request and errors + * + * @return \Closure Returns a closure + * @throws ErrorResponseException + */ + protected function getErrorClosure(RequestInterface $request, CommandInterface $command, Operation $operation) + { + return function (Event $event) use ($request, $command, $operation) { + $response = $event['response']; + foreach ($operation->getErrorResponses() as $error) { + if (!isset($error['class'])) { + continue; + } + if (isset($error['code']) && $response->getStatusCode() != $error['code']) { + continue; + } + if (isset($error['reason']) && $response->getReasonPhrase() != $error['reason']) { + continue; + } + $className = $error['class']; + $errorClassInterface = __NAMESPACE__ . '\\ErrorResponseExceptionInterface'; + if (!class_exists($className)) { + throw new ErrorResponseException("{$className} does not exist"); + } elseif (!(in_array($errorClassInterface, class_implements($className)))) { + throw new ErrorResponseException("{$className} must implement {$errorClassInterface}"); + } + throw $className::fromCommand($command, $response); + } + }; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php new file mode 100644 index 000000000..1d89e40e7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php @@ -0,0 +1,7 @@ +=5.3.2", + "guzzle/service": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\ErrorResponse": "" } + }, + "target-dir": "Guzzle/Plugin/ErrorResponse", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php new file mode 100644 index 000000000..7375e892b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php @@ -0,0 +1,163 @@ + array('onRequestSent', 9999)); + } + + /** + * Convert to a string that contains all request and response headers + * + * @return string + */ + public function __toString() + { + $lines = array(); + foreach ($this->transactions as $entry) { + $response = isset($entry['response']) ? $entry['response'] : ''; + $lines[] = '> ' . trim($entry['request']) . "\n\n< " . trim($response) . "\n"; + } + + return implode("\n", $lines); + } + + /** + * Add a request to the history + * + * @param RequestInterface $request Request to add + * @param Response $response Response of the request + * + * @return HistoryPlugin + */ + public function add(RequestInterface $request, Response $response = null) + { + if (!$response && $request->getResponse()) { + $response = $request->getResponse(); + } + + $this->transactions[] = array('request' => $request, 'response' => $response); + if (count($this->transactions) > $this->getlimit()) { + array_shift($this->transactions); + } + + return $this; + } + + /** + * Set the max number of requests to store + * + * @param int $limit Limit + * + * @return HistoryPlugin + */ + public function setLimit($limit) + { + $this->limit = (int) $limit; + + return $this; + } + + /** + * Get the request limit + * + * @return int + */ + public function getLimit() + { + return $this->limit; + } + + /** + * Get all of the raw transactions in the form of an array of associative arrays containing + * 'request' and 'response' keys. + * + * @return array + */ + public function getAll() + { + return $this->transactions; + } + + /** + * Get the requests in the history + * + * @return \ArrayIterator + */ + public function getIterator() + { + // Return an iterator just like the old iteration of the HistoryPlugin for BC compatibility (use getAll()) + return new \ArrayIterator(array_map(function ($entry) { + $entry['request']->getParams()->set('actual_response', $entry['response']); + return $entry['request']; + }, $this->transactions)); + } + + /** + * Get the number of requests in the history + * + * @return int + */ + public function count() + { + return count($this->transactions); + } + + /** + * Get the last request sent + * + * @return RequestInterface + */ + public function getLastRequest() + { + $last = end($this->transactions); + + return $last['request']; + } + + /** + * Get the last response in the history + * + * @return Response|null + */ + public function getLastResponse() + { + $last = end($this->transactions); + + return isset($last['response']) ? $last['response'] : null; + } + + /** + * Clears the history + * + * @return HistoryPlugin + */ + public function clear() + { + $this->transactions = array(); + + return $this; + } + + public function onRequestSent(Event $event) + { + $this->add($event['request'], $event['response']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json new file mode 100644 index 000000000..ba0bf2c4d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-history", + "description": "Guzzle history plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\History": "" } + }, + "target-dir": "Guzzle/Plugin/History", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php new file mode 100644 index 000000000..cabdea854 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php @@ -0,0 +1,161 @@ +logAdapter = $logAdapter; + $this->formatter = $formatter instanceof MessageFormatter ? $formatter : new MessageFormatter($formatter); + $this->wireBodies = $wireBodies; + } + + /** + * Get a log plugin that outputs full request, response, and curl error information to stderr + * + * @param bool $wireBodies Set to false to disable request/response body output when they use are not repeatable + * @param resource $stream Stream to write to when logging. Defaults to STDERR when it is available + * + * @return self + */ + public static function getDebugPlugin($wireBodies = true, $stream = null) + { + if ($stream === null) { + if (defined('STDERR')) { + $stream = STDERR; + } else { + $stream = fopen('php://output', 'w'); + } + } + + return new self(new ClosureLogAdapter(function ($m) use ($stream) { + fwrite($stream, $m . PHP_EOL); + }), "# Request:\n{request}\n\n# Response:\n{response}\n\n# Errors: {curl_code} {curl_error}", $wireBodies); + } + + public static function getSubscribedEvents() + { + return array( + 'curl.callback.write' => array('onCurlWrite', 255), + 'curl.callback.read' => array('onCurlRead', 255), + 'request.before_send' => array('onRequestBeforeSend', 255), + 'request.sent' => array('onRequestSent', 255) + ); + } + + /** + * Event triggered when curl data is read from a request + * + * @param Event $event + */ + public function onCurlRead(Event $event) + { + // Stream the request body to the log if the body is not repeatable + if ($wire = $event['request']->getParams()->get('request_wire')) { + $wire->write($event['read']); + } + } + + /** + * Event triggered when curl data is written to a response + * + * @param Event $event + */ + public function onCurlWrite(Event $event) + { + // Stream the response body to the log if the body is not repeatable + if ($wire = $event['request']->getParams()->get('response_wire')) { + $wire->write($event['write']); + } + } + + /** + * Called before a request is sent + * + * @param Event $event + */ + public function onRequestBeforeSend(Event $event) + { + if ($this->wireBodies) { + $request = $event['request']; + // Ensure that curl IO events are emitted + $request->getCurlOptions()->set('emit_io', true); + // We need to make special handling for content wiring and non-repeatable streams. + if ($request instanceof EntityEnclosingRequestInterface && $request->getBody() + && (!$request->getBody()->isSeekable() || !$request->getBody()->isReadable()) + ) { + // The body of the request cannot be recalled so logging the body will require us to buffer it + $request->getParams()->set('request_wire', EntityBody::factory()); + } + if (!$request->getResponseBody()->isRepeatable()) { + // The body of the response cannot be recalled so logging the body will require us to buffer it + $request->getParams()->set('response_wire', EntityBody::factory()); + } + } + } + + /** + * Triggers the actual log write when a request completes + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + $request = $event['request']; + $response = $event['response']; + $handle = $event['handle']; + + if ($wire = $request->getParams()->get('request_wire')) { + $request = clone $request; + $request->setBody($wire); + } + + if ($wire = $request->getParams()->get('response_wire')) { + $response = clone $response; + $response->setBody($wire); + } + + // Send the log message to the adapter, adding a category and host + $priority = $response && $response->isError() ? LOG_ERR : LOG_DEBUG; + $message = $this->formatter->format($request, $response, $handle); + $this->logAdapter->log($message, $priority, array( + 'request' => $request, + 'response' => $response, + 'handle' => $handle + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json new file mode 100644 index 000000000..130e6da0a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json @@ -0,0 +1,28 @@ +{ + "name": "guzzle/plugin-log", + "description": "Guzzle log plugin for over the wire logging", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "log", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version", + "guzzle/log": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Log": "" } + }, + "target-dir": "Guzzle/Plugin/Log", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php new file mode 100644 index 000000000..851242433 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php @@ -0,0 +1,57 @@ +contentMd5Param = $contentMd5Param; + $this->validateMd5Param = $validateMd5Param; + } + + public static function getSubscribedEvents() + { + return array('command.before_send' => array('onCommandBeforeSend', -255)); + } + + public function onCommandBeforeSend(Event $event) + { + $command = $event['command']; + $request = $command->getRequest(); + + // Only add an MD5 is there is a MD5 option on the operation and it has a payload + if ($request instanceof EntityEnclosingRequestInterface && $request->getBody() + && $command->getOperation()->hasParam($this->contentMd5Param)) { + // Check if an MD5 checksum value should be passed along to the request + if ($command[$this->contentMd5Param] === true) { + if (false !== ($md5 = $request->getBody()->getContentMd5(true, true))) { + $request->setHeader('Content-MD5', $md5); + } + } + } + + // Check if MD5 validation should be used with the response + if ($command[$this->validateMd5Param] === true) { + $request->addSubscriber(new Md5ValidatorPlugin(true, false)); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php new file mode 100644 index 000000000..5d7a3785e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php @@ -0,0 +1,88 @@ +contentLengthCutoff = $contentLengthCutoff; + $this->contentEncoded = $contentEncoded; + } + + public static function getSubscribedEvents() + { + return array('request.complete' => array('onRequestComplete', 255)); + } + + /** + * {@inheritdoc} + * @throws UnexpectedValueException + */ + public function onRequestComplete(Event $event) + { + $response = $event['response']; + + if (!$contentMd5 = $response->getContentMd5()) { + return; + } + + $contentEncoding = $response->getContentEncoding(); + if ($contentEncoding && !$this->contentEncoded) { + return false; + } + + // Make sure that the size of the request is under the cutoff size + if ($this->contentLengthCutoff) { + $size = $response->getContentLength() ?: $response->getBody()->getSize(); + if (!$size || $size > $this->contentLengthCutoff) { + return; + } + } + + if (!$contentEncoding) { + $hash = $response->getBody()->getContentMd5(); + } elseif ($contentEncoding == 'gzip') { + $response->getBody()->compress('zlib.deflate'); + $hash = $response->getBody()->getContentMd5(); + $response->getBody()->uncompress(); + } elseif ($contentEncoding == 'compress') { + $response->getBody()->compress('bzip2.compress'); + $hash = $response->getBody()->getContentMd5(); + $response->getBody()->uncompress(); + } else { + return; + } + + if ($contentMd5 !== $hash) { + throw new UnexpectedValueException( + "The response entity body may have been modified over the wire. The Content-MD5 " + . "received ({$contentMd5}) did not match the calculated MD5 hash ({$hash})." + ); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json new file mode 100644 index 000000000..0602d0609 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-md5", + "description": "Guzzle MD5 plugins", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Md5": "" } + }, + "target-dir": "Guzzle/Plugin/Md5", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php new file mode 100644 index 000000000..2440578cf --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php @@ -0,0 +1,245 @@ +readBodies = $readBodies; + $this->temporary = $temporary; + if ($items) { + foreach ($items as $item) { + if ($item instanceof \Exception) { + $this->addException($item); + } else { + $this->addResponse($item); + } + } + } + } + + public static function getSubscribedEvents() + { + // Use a number lower than the CachePlugin + return array('request.before_send' => array('onRequestBeforeSend', -999)); + } + + public static function getAllEvents() + { + return array('mock.request'); + } + + /** + * Get a mock response from a file + * + * @param string $path File to retrieve a mock response from + * + * @return Response + * @throws InvalidArgumentException if the file is not found + */ + public static function getMockFile($path) + { + if (!file_exists($path)) { + throw new InvalidArgumentException('Unable to open mock file: ' . $path); + } + + return Response::fromMessage(file_get_contents($path)); + } + + /** + * Set whether or not to consume the entity body of a request when a mock + * response is used + * + * @param bool $readBodies Set to true to read and consume entity bodies + * + * @return self + */ + public function readBodies($readBodies) + { + $this->readBodies = $readBodies; + + return $this; + } + + /** + * Returns the number of remaining mock responses + * + * @return int + */ + public function count() + { + return count($this->queue); + } + + /** + * Add a response to the end of the queue + * + * @param string|Response $response Response object or path to response file + * + * @return MockPlugin + * @throws InvalidArgumentException if a string or Response is not passed + */ + public function addResponse($response) + { + if (!($response instanceof Response)) { + if (!is_string($response)) { + throw new InvalidArgumentException('Invalid response'); + } + $response = self::getMockFile($response); + } + + $this->queue[] = $response; + + return $this; + } + + /** + * Add an exception to the end of the queue + * + * @param CurlException $e Exception to throw when the request is executed + * + * @return MockPlugin + */ + public function addException(CurlException $e) + { + $this->queue[] = $e; + + return $this; + } + + /** + * Clear the queue + * + * @return MockPlugin + */ + public function clearQueue() + { + $this->queue = array(); + + return $this; + } + + /** + * Returns an array of mock responses remaining in the queue + * + * @return array + */ + public function getQueue() + { + return $this->queue; + } + + /** + * Check if this is a temporary plugin + * + * @return bool + */ + public function isTemporary() + { + return $this->temporary; + } + + /** + * Get a response from the front of the list and add it to a request + * + * @param RequestInterface $request Request to mock + * + * @return self + * @throws CurlException When request.send is called and an exception is queued + */ + public function dequeue(RequestInterface $request) + { + $this->dispatch('mock.request', array('plugin' => $this, 'request' => $request)); + + $item = array_shift($this->queue); + if ($item instanceof Response) { + if ($this->readBodies && $request instanceof EntityEnclosingRequestInterface) { + $request->getEventDispatcher()->addListener('request.sent', $f = function (Event $event) use (&$f) { + while ($data = $event['request']->getBody()->read(8096)); + // Remove the listener after one-time use + $event['request']->getEventDispatcher()->removeListener('request.sent', $f); + }); + } + $request->setResponse($item); + } elseif ($item instanceof CurlException) { + // Emulates exceptions encountered while transferring requests + $item->setRequest($request); + $state = $request->setState(RequestInterface::STATE_ERROR, array('exception' => $item)); + // Only throw if the exception wasn't handled + if ($state == RequestInterface::STATE_ERROR) { + throw $item; + } + } + + return $this; + } + + /** + * Clear the array of received requests + */ + public function flush() + { + $this->received = array(); + } + + /** + * Get an array of requests that were mocked by this plugin + * + * @return array + */ + public function getReceivedRequests() + { + return $this->received; + } + + /** + * Called when a request is about to be sent + * + * @param Event $event + * @throws \OutOfBoundsException When queue is empty + */ + public function onRequestBeforeSend(Event $event) + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + $request = $event['request']; + $this->received[] = $request; + // Detach the filter from the client so it's a one-time use + if ($this->temporary && count($this->queue) == 1 && $request->getClient()) { + $request->getClient()->getEventDispatcher()->removeSubscriber($this); + } + $this->dequeue($request); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json new file mode 100644 index 000000000..f8201e31f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-mock", + "description": "Guzzle Mock plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["mock", "plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Mock": "" } + }, + "target-dir": "Guzzle/Plugin/Mock", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php new file mode 100644 index 000000000..95e0c3e4a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php @@ -0,0 +1,306 @@ +config = Collection::fromConfig($config, array( + 'version' => '1.0', + 'request_method' => self::REQUEST_METHOD_HEADER, + 'consumer_key' => 'anonymous', + 'consumer_secret' => 'anonymous', + 'signature_method' => 'HMAC-SHA1', + 'signature_callback' => function($stringToSign, $key) { + return hash_hmac('sha1', $stringToSign, $key, true); + } + ), array( + 'signature_method', 'signature_callback', 'version', + 'consumer_key', 'consumer_secret' + )); + } + + public static function getSubscribedEvents() + { + return array( + 'request.before_send' => array('onRequestBeforeSend', -1000) + ); + } + + /** + * Request before-send event handler + * + * @param Event $event Event received + * @return array + * @throws \InvalidArgumentException + */ + public function onRequestBeforeSend(Event $event) + { + $timestamp = $this->getTimestamp($event); + $request = $event['request']; + $nonce = $this->generateNonce($request); + $authorizationParams = $this->getOauthParams($timestamp, $nonce); + $authorizationParams['oauth_signature'] = $this->getSignature($request, $timestamp, $nonce); + + switch ($this->config['request_method']) { + case self::REQUEST_METHOD_HEADER: + $request->setHeader( + 'Authorization', + $this->buildAuthorizationHeader($authorizationParams) + ); + break; + case self::REQUEST_METHOD_QUERY: + foreach ($authorizationParams as $key => $value) { + $request->getQuery()->set($key, $value); + } + break; + default: + throw new \InvalidArgumentException(sprintf( + 'Invalid consumer method "%s"', + $this->config['request_method'] + )); + } + + return $authorizationParams; + } + + /** + * Builds the Authorization header for a request + * + * @param array $authorizationParams Associative array of authorization parameters + * + * @return string + */ + private function buildAuthorizationHeader($authorizationParams) + { + $authorizationString = 'OAuth '; + foreach ($authorizationParams as $key => $val) { + if ($val) { + $authorizationString .= $key . '="' . urlencode($val) . '", '; + } + } + + return substr($authorizationString, 0, -2); + } + + /** + * Calculate signature for request + * + * @param RequestInterface $request Request to generate a signature for + * @param integer $timestamp Timestamp to use for nonce + * @param string $nonce + * + * @return string + */ + public function getSignature(RequestInterface $request, $timestamp, $nonce) + { + $string = $this->getStringToSign($request, $timestamp, $nonce); + $key = urlencode($this->config['consumer_secret']) . '&' . urlencode($this->config['token_secret']); + + return base64_encode(call_user_func($this->config['signature_callback'], $string, $key)); + } + + /** + * Calculate string to sign + * + * @param RequestInterface $request Request to generate a signature for + * @param int $timestamp Timestamp to use for nonce + * @param string $nonce + * + * @return string + */ + public function getStringToSign(RequestInterface $request, $timestamp, $nonce) + { + $params = $this->getParamsToSign($request, $timestamp, $nonce); + + // Convert booleans to strings. + $params = $this->prepareParameters($params); + + // Build signing string from combined params + $parameterString = clone $request->getQuery(); + $parameterString->replace($params); + + $url = Url::factory($request->getUrl())->setQuery('')->setFragment(null); + + return strtoupper($request->getMethod()) . '&' + . rawurlencode($url) . '&' + . rawurlencode((string) $parameterString); + } + + /** + * Get the oauth parameters as named by the oauth spec + * + * @param $timestamp + * @param $nonce + * @return Collection + */ + protected function getOauthParams($timestamp, $nonce) + { + $params = new Collection(array( + 'oauth_consumer_key' => $this->config['consumer_key'], + 'oauth_nonce' => $nonce, + 'oauth_signature_method' => $this->config['signature_method'], + 'oauth_timestamp' => $timestamp, + )); + + // Optional parameters should not be set if they have not been set in the config as + // the parameter may be considered invalid by the Oauth service. + $optionalParams = array( + 'callback' => 'oauth_callback', + 'token' => 'oauth_token', + 'verifier' => 'oauth_verifier', + 'version' => 'oauth_version' + ); + + foreach ($optionalParams as $optionName => $oauthName) { + if (isset($this->config[$optionName]) == true) { + $params[$oauthName] = $this->config[$optionName]; + } + } + + return $params; + } + + /** + * Get all of the parameters required to sign a request including: + * * The oauth params + * * The request GET params + * * The params passed in the POST body (with a content-type of application/x-www-form-urlencoded) + * + * @param RequestInterface $request Request to generate a signature for + * @param integer $timestamp Timestamp to use for nonce + * @param string $nonce + * + * @return array + */ + public function getParamsToSign(RequestInterface $request, $timestamp, $nonce) + { + $params = $this->getOauthParams($timestamp, $nonce); + + // Add query string parameters + $params->merge($request->getQuery()); + + // Add POST fields to signing string if required + if ($this->shouldPostFieldsBeSigned($request)) + { + $params->merge($request->getPostFields()); + } + + // Sort params + $params = $params->toArray(); + uksort($params, 'strcmp'); + + return $params; + } + + /** + * Decide whether the post fields should be added to the base string that Oauth signs. + * This implementation is correct. Non-conformant APIs may require that this method be + * overwritten e.g. the Flickr API incorrectly adds the post fields when the Content-Type + * is 'application/x-www-form-urlencoded' + * + * @param $request + * @return bool Whether the post fields should be signed or not + */ + public function shouldPostFieldsBeSigned($request) + { + if (!$this->config->get('disable_post_params') && + $request instanceof EntityEnclosingRequestInterface && + false !== strpos($request->getHeader('Content-Type'), 'application/x-www-form-urlencoded')) + { + return true; + } + + return false; + } + + /** + * Returns a Nonce Based on the unique id and URL. This will allow for multiple requests in parallel with the same + * exact timestamp to use separate nonce's. + * + * @param RequestInterface $request Request to generate a nonce for + * + * @return string + */ + public function generateNonce(RequestInterface $request) + { + return sha1(uniqid('', true) . $request->getUrl()); + } + + /** + * Gets timestamp from event or create new timestamp + * + * @param Event $event Event containing contextual information + * + * @return int + */ + public function getTimestamp(Event $event) + { + return $event['timestamp'] ?: time(); + } + + /** + * Convert booleans to strings, removed unset parameters, and sorts the array + * + * @param array $data Data array + * + * @return array + */ + protected function prepareParameters($data) + { + ksort($data); + foreach ($data as $key => &$value) { + switch (gettype($value)) { + case 'NULL': + unset($data[$key]); + break; + case 'array': + $data[$key] = self::prepareParameters($value); + break; + case 'boolean': + $data[$key] = $value ? 'true' : 'false'; + break; + } + } + + return $data; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json new file mode 100644 index 000000000..c9766ba16 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-oauth", + "description": "Guzzle OAuth plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["oauth", "plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Oauth": "" } + }, + "target-dir": "Guzzle/Plugin/Oauth", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json new file mode 100644 index 000000000..2bbe64cc5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json @@ -0,0 +1,44 @@ +{ + "name": "guzzle/plugin", + "description": "Guzzle plugin component containing all Guzzle HTTP plugins", + "homepage": "http://guzzlephp.org/", + "keywords": ["http", "client", "plugin", "extension", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "suggest": { + "guzzle/cache": "self.version", + "guzzle/log": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin": "" } + }, + "target-dir": "Guzzle/Plugin", + "replace": { + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version" + }, + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php new file mode 100644 index 000000000..cd06f5722 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php @@ -0,0 +1,177 @@ + 'JSON_ERROR_NONE - No errors', + JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ); + + public function load($config, array $options = array()) + { + // Reset the array of loaded files because this is a new config + $this->loadedFiles = array(); + + if (is_string($config)) { + $config = $this->loadFile($config); + } elseif (!is_array($config)) { + throw new InvalidArgumentException('Unknown type passed to configuration loader: ' . gettype($config)); + } else { + $this->mergeIncludes($config); + } + + return $this->build($config, $options); + } + + /** + * Add an include alias to the loader + * + * @param string $filename Filename to alias (e.g. _foo) + * @param string $alias Actual file to use (e.g. /path/to/foo.json) + * + * @return self + */ + public function addAlias($filename, $alias) + { + $this->aliases[$filename] = $alias; + + return $this; + } + + /** + * Remove an alias from the loader + * + * @param string $alias Alias to remove + * + * @return self + */ + public function removeAlias($alias) + { + unset($this->aliases[$alias]); + + return $this; + } + + /** + * Perform the parsing of a config file and create the end result + * + * @param array $config Configuration data + * @param array $options Options to use when building + * + * @return mixed + */ + protected abstract function build($config, array $options); + + /** + * Load a configuration file (can load JSON or PHP files that return an array when included) + * + * @param string $filename File to load + * + * @return array + * @throws InvalidArgumentException + * @throws RuntimeException when the JSON cannot be parsed + */ + protected function loadFile($filename) + { + if (isset($this->aliases[$filename])) { + $filename = $this->aliases[$filename]; + } + + switch (pathinfo($filename, PATHINFO_EXTENSION)) { + case 'js': + case 'json': + $level = error_reporting(0); + $json = file_get_contents($filename); + error_reporting($level); + + if ($json === false) { + $err = error_get_last(); + throw new InvalidArgumentException("Unable to open {$filename}: " . $err['message']); + } + + $config = json_decode($json, true); + // Throw an exception if there was an error loading the file + if ($error = json_last_error()) { + $message = isset(self::$jsonErrors[$error]) ? self::$jsonErrors[$error] : 'Unknown error'; + throw new RuntimeException("Error loading JSON data from {$filename}: ({$error}) - {$message}"); + } + break; + case 'php': + if (!is_readable($filename)) { + throw new InvalidArgumentException("Unable to open {$filename} for reading"); + } + $config = require $filename; + if (!is_array($config)) { + throw new InvalidArgumentException('PHP files must return an array of configuration data'); + } + break; + default: + throw new InvalidArgumentException('Unknown file extension: ' . $filename); + } + + // Keep track of this file being loaded to prevent infinite recursion + $this->loadedFiles[$filename] = true; + + // Merge include files into the configuration array + $this->mergeIncludes($config, dirname($filename)); + + return $config; + } + + /** + * Merges in all include files + * + * @param array $config Config data that contains includes + * @param string $basePath Base path to use when a relative path is encountered + * + * @return array Returns the merged and included data + */ + protected function mergeIncludes(&$config, $basePath = null) + { + if (!empty($config['includes'])) { + foreach ($config['includes'] as &$path) { + // Account for relative paths + if ($path[0] != DIRECTORY_SEPARATOR && !isset($this->aliases[$path]) && $basePath) { + $path = "{$basePath}/{$path}"; + } + // Don't load the same files more than once + if (!isset($this->loadedFiles[$path])) { + $this->loadedFiles[$path] = true; + $config = $this->mergeData($this->loadFile($path), $config); + } + } + } + } + + /** + * Default implementation for merging two arrays of data (uses array_merge_recursive) + * + * @param array $a Original data + * @param array $b Data to merge into the original and overwrite existing values + * + * @return array + */ + protected function mergeData(array $a, array $b) + { + return array_merge_recursive($a, $b); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php new file mode 100644 index 000000000..38150db4b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php @@ -0,0 +1,189 @@ +load($config, $globalParameters); + } + + /** + * @param array $serviceBuilderConfig Service configuration settings: + * - name: Name of the service + * - class: Client class to instantiate using a factory method + * - params: array of key value pair configuration settings for the builder + */ + public function __construct(array $serviceBuilderConfig = array()) + { + $this->builderConfig = $serviceBuilderConfig; + } + + public static function getAllEvents() + { + return array('service_builder.create_client'); + } + + public function unserialize($serialized) + { + $this->builderConfig = json_decode($serialized, true); + } + + public function serialize() + { + return json_encode($this->builderConfig); + } + + /** + * Attach a plugin to every client created by the builder + * + * @param EventSubscriberInterface $plugin Plugin to attach to each client + * + * @return self + */ + public function addGlobalPlugin(EventSubscriberInterface $plugin) + { + $this->plugins[] = $plugin; + + return $this; + } + + /** + * Get data from the service builder without triggering the building of a service + * + * @param string $name Name of the service to retrieve + * + * @return array|null + */ + public function getData($name) + { + return isset($this->builderConfig[$name]) ? $this->builderConfig[$name] : null; + } + + public function get($name, $throwAway = false) + { + if (!isset($this->builderConfig[$name])) { + + // Check to see if arbitrary data is being referenced + if (isset($this->clients[$name])) { + return $this->clients[$name]; + } + + // Check aliases and return a match if found + foreach ($this->builderConfig as $actualName => $config) { + if (isset($config['alias']) && $config['alias'] == $name) { + return $this->get($actualName, $throwAway); + } + } + throw new ServiceNotFoundException('No service is registered as ' . $name); + } + + if (!$throwAway && isset($this->clients[$name])) { + return $this->clients[$name]; + } + + $builder =& $this->builderConfig[$name]; + + // Convert references to the actual client + foreach ($builder['params'] as &$v) { + if (is_string($v) && substr($v, 0, 1) == '{' && substr($v, -1) == '}') { + $v = $this->get(trim($v, '{} ')); + } + } + + // Get the configured parameters and merge in any parameters provided for throw-away clients + $config = $builder['params']; + if (is_array($throwAway)) { + $config = $throwAway + $config; + } + + $client = $builder['class']::factory($config); + + if (!$throwAway) { + $this->clients[$name] = $client; + } + + if ($client instanceof ClientInterface) { + foreach ($this->plugins as $plugin) { + $client->addSubscriber($plugin); + } + // Dispatch an event letting listeners know a client was created + $this->dispatch('service_builder.create_client', array('client' => $client)); + } + + return $client; + } + + public function set($key, $service) + { + if (is_array($service) && isset($service['class']) && isset($service['params'])) { + $this->builderConfig[$key] = $service; + } else { + $this->clients[$key] = $service; + } + + return $this; + } + + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + public function offsetUnset($offset) + { + unset($this->builderConfig[$offset]); + unset($this->clients[$offset]); + } + + public function offsetExists($offset) + { + return isset($this->builderConfig[$offset]) || isset($this->clients[$offset]); + } + + public function offsetGet($offset) + { + return $this->get($offset); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php new file mode 100644 index 000000000..4fc310a47 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php @@ -0,0 +1,40 @@ + &$service) { + + $service['params'] = isset($service['params']) ? $service['params'] : array(); + + // Check if this client builder extends another client + if (!empty($service['extends'])) { + + // Make sure that the service it's extending has been defined + if (!isset($services[$service['extends']])) { + throw new ServiceNotFoundException( + "{$name} is trying to extend a non-existent service: {$service['extends']}" + ); + } + + $extended = &$services[$service['extends']]; + + // Use the correct class attribute + if (empty($service['class'])) { + $service['class'] = isset($extended['class']) ? $extended['class'] : ''; + } + if ($extendsParams = isset($extended['params']) ? $extended['params'] : false) { + $service['params'] = $service['params'] + $extendsParams; + } + } + + // Overwrite default values with global parameter values + if (!empty($options)) { + $service['params'] = $options + $service['params']; + } + + $service['class'] = isset($service['class']) ? $service['class'] : ''; + } + + return new $class($services); + } + + protected function mergeData(array $a, array $b) + { + $result = $b + $a; + + // Merge services using a recursive union of arrays + if (isset($a['services']) && $b['services']) { + + // Get a union of the services of the two arrays + $result['services'] = $b['services'] + $a['services']; + + // Merge each service in using a union of the two arrays + foreach ($result['services'] as $name => &$service) { + + // By default, services completely override a previously defined service unless it extends itself + if (isset($a['services'][$name]['extends']) + && isset($b['services'][$name]['extends']) + && $b['services'][$name]['extends'] == $name + ) { + $service += $a['services'][$name]; + // Use the `extends` attribute of the parent + $service['extends'] = $a['services'][$name]['extends']; + // Merge parameters using a union if both have parameters + if (isset($a['services'][$name]['params'])) { + $service['params'] += $a['services'][$name]['params']; + } + } + } + } + + return $result; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php new file mode 100644 index 000000000..26f8360cc --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php @@ -0,0 +1,46 @@ +loader = $loader; + $this->cache = $cache; + } + + public function load($config, array $options = array()) + { + if (!is_string($config)) { + $key = false; + } else { + $key = 'loader_' . crc32($config); + if ($result = $this->cache->fetch($key)) { + return $result; + } + } + + $result = $this->loader->load($config, $options); + if ($key) { + $this->cache->save($key, $result); + } + + return $result; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Client.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Client.php new file mode 100644 index 000000000..3e5f8e53d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Client.php @@ -0,0 +1,297 @@ +getCommand($method, isset($args[0]) ? $args[0] : array())->getResult(); + } + + public function getCommand($name, array $args = array()) + { + // Add global client options to the command + if ($options = $this->getConfig(self::COMMAND_PARAMS)) { + $args += $options; + } + + if (!($command = $this->getCommandFactory()->factory($name, $args))) { + throw new InvalidArgumentException("Command was not found matching {$name}"); + } + + $command->setClient($this); + $this->dispatch('client.command.create', array('client' => $this, 'command' => $command)); + + return $command; + } + + /** + * Set the command factory used to create commands by name + * + * @param CommandFactoryInterface $factory Command factory + * + * @return self + */ + public function setCommandFactory(CommandFactoryInterface $factory) + { + $this->commandFactory = $factory; + + return $this; + } + + /** + * Set the resource iterator factory associated with the client + * + * @param ResourceIteratorFactoryInterface $factory Resource iterator factory + * + * @return self + */ + public function setResourceIteratorFactory(ResourceIteratorFactoryInterface $factory) + { + $this->resourceIteratorFactory = $factory; + + return $this; + } + + public function getIterator($command, array $commandOptions = null, array $iteratorOptions = array()) + { + if (!($command instanceof CommandInterface)) { + $command = $this->getCommand($command, $commandOptions ?: array()); + } + + return $this->getResourceIteratorFactory()->build($command, $iteratorOptions); + } + + public function execute($command) + { + if ($command instanceof CommandInterface) { + $this->send($this->prepareCommand($command)); + $this->dispatch('command.after_send', array('command' => $command)); + return $command->getResult(); + } elseif (is_array($command) || $command instanceof \Traversable) { + return $this->executeMultiple($command); + } else { + throw new InvalidArgumentException('Command must be a command or array of commands'); + } + } + + public function setDescription(ServiceDescriptionInterface $service) + { + $this->serviceDescription = $service; + + if ($this->getCommandFactory() && $this->getCommandFactory() instanceof CompositeFactory) { + $this->commandFactory->add(new Command\Factory\ServiceDescriptionFactory($service)); + } + + // If a baseUrl was set on the description, then update the client + if ($baseUrl = $service->getBaseUrl()) { + $this->setBaseUrl($baseUrl); + } + + return $this; + } + + public function getDescription() + { + return $this->serviceDescription; + } + + /** + * Set the inflector used with the client + * + * @param InflectorInterface $inflector Inflection object + * + * @return self + */ + public function setInflector(InflectorInterface $inflector) + { + $this->inflector = $inflector; + + return $this; + } + + /** + * Get the inflector used with the client + * + * @return self + */ + public function getInflector() + { + if (!$this->inflector) { + $this->inflector = Inflector::getDefault(); + } + + return $this->inflector; + } + + /** + * Prepare a command for sending and get the RequestInterface object created by the command + * + * @param CommandInterface $command Command to prepare + * + * @return RequestInterface + */ + protected function prepareCommand(CommandInterface $command) + { + // Set the client and prepare the command + $request = $command->setClient($this)->prepare(); + // Set the state to new if the command was previously executed + $request->setState(RequestInterface::STATE_NEW); + $this->dispatch('command.before_send', array('command' => $command)); + + return $request; + } + + /** + * Execute multiple commands in parallel + * + * @param array|Traversable $commands Array of CommandInterface objects to execute + * + * @return array Returns an array of the executed commands + * @throws Exception\CommandTransferException + */ + protected function executeMultiple($commands) + { + $requests = array(); + $commandRequests = new \SplObjectStorage(); + + foreach ($commands as $command) { + $request = $this->prepareCommand($command); + $commandRequests[$request] = $command; + $requests[] = $request; + } + + try { + $this->send($requests); + foreach ($commands as $command) { + $this->dispatch('command.after_send', array('command' => $command)); + } + return $commands; + } catch (MultiTransferException $failureException) { + // Throw a CommandTransferException using the successful and failed commands + $e = CommandTransferException::fromMultiTransferException($failureException); + + // Remove failed requests from the successful requests array and add to the failures array + foreach ($failureException->getFailedRequests() as $request) { + if (isset($commandRequests[$request])) { + $e->addFailedCommand($commandRequests[$request]); + unset($commandRequests[$request]); + } + } + + // Always emit the command after_send events for successful commands + foreach ($commandRequests as $success) { + $e->addSuccessfulCommand($commandRequests[$success]); + $this->dispatch('command.after_send', array('command' => $commandRequests[$success])); + } + + throw $e; + } + } + + protected function getResourceIteratorFactory() + { + if (!$this->resourceIteratorFactory) { + // Build the default resource iterator factory if one is not set + $clientClass = get_class($this); + $prefix = substr($clientClass, 0, strrpos($clientClass, '\\')); + $this->resourceIteratorFactory = new ResourceIteratorClassFactory(array( + "{$prefix}\\Iterator", + "{$prefix}\\Model" + )); + } + + return $this->resourceIteratorFactory; + } + + /** + * Get the command factory associated with the client + * + * @return CommandFactoryInterface + */ + protected function getCommandFactory() + { + if (!$this->commandFactory) { + $this->commandFactory = CompositeFactory::getDefaultChain($this); + } + + return $this->commandFactory; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function enableMagicMethods($isEnabled) + { + Version::warn(__METHOD__ . ' is deprecated'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php new file mode 100644 index 000000000..814154f00 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php @@ -0,0 +1,68 @@ +operation = $operation ?: $this->createOperation(); + foreach ($this->operation->getParams() as $name => $arg) { + $currentValue = $this[$name]; + $configValue = $arg->getValue($currentValue); + // If default or static values are set, then this should always be updated on the config object + if ($currentValue !== $configValue) { + $this[$name] = $configValue; + } + } + + $headers = $this[self::HEADERS_OPTION]; + if (!$headers instanceof Collection) { + $this[self::HEADERS_OPTION] = new Collection((array) $headers); + } + + // You can set a command.on_complete option in your parameters to set an onComplete callback + if ($onComplete = $this['command.on_complete']) { + unset($this['command.on_complete']); + $this->setOnComplete($onComplete); + } + + // Set the hidden additional parameters + if (!$this[self::HIDDEN_PARAMS]) { + $this[self::HIDDEN_PARAMS] = array( + self::HEADERS_OPTION, + self::RESPONSE_PROCESSING, + self::HIDDEN_PARAMS, + self::REQUEST_OPTIONS + ); + } + + $this->init(); + } + + /** + * Custom clone behavior + */ + public function __clone() + { + $this->request = null; + $this->result = null; + } + + /** + * Execute the command in the same manner as calling a function + * + * @return mixed Returns the result of {@see AbstractCommand::execute} + */ + public function __invoke() + { + return $this->execute(); + } + + public function getName() + { + return $this->operation->getName(); + } + + /** + * Get the API command information about the command + * + * @return OperationInterface + */ + public function getOperation() + { + return $this->operation; + } + + public function setOnComplete($callable) + { + if (!is_callable($callable)) { + throw new InvalidArgumentException('The onComplete function must be callable'); + } + + $this->onComplete = $callable; + + return $this; + } + + public function execute() + { + if (!$this->client) { + throw new CommandException('A client must be associated with the command before it can be executed.'); + } + + return $this->client->execute($this); + } + + public function getClient() + { + return $this->client; + } + + public function setClient(ClientInterface $client) + { + $this->client = $client; + + return $this; + } + + public function getRequest() + { + if (!$this->request) { + throw new CommandException('The command must be prepared before retrieving the request'); + } + + return $this->request; + } + + public function getResponse() + { + if (!$this->isExecuted()) { + $this->execute(); + } + + return $this->request->getResponse(); + } + + public function getResult() + { + if (!$this->isExecuted()) { + $this->execute(); + } + + if (null === $this->result) { + $this->process(); + // Call the onComplete method if one is set + if ($this->onComplete) { + call_user_func($this->onComplete, $this); + } + } + + return $this->result; + } + + public function setResult($result) + { + $this->result = $result; + + return $this; + } + + public function isPrepared() + { + return $this->request !== null; + } + + public function isExecuted() + { + return $this->request !== null && $this->request->getState() == 'complete'; + } + + public function prepare() + { + if (!$this->isPrepared()) { + if (!$this->client) { + throw new CommandException('A client must be associated with the command before it can be prepared.'); + } + + // If no response processing value was specified, then attempt to use the highest level of processing + if (!isset($this[self::RESPONSE_PROCESSING])) { + $this[self::RESPONSE_PROCESSING] = self::TYPE_MODEL; + } + + // Notify subscribers of the client that the command is being prepared + $this->client->dispatch('command.before_prepare', array('command' => $this)); + + // Fail on missing required arguments, and change parameters via filters + $this->validate(); + // Delegate to the subclass that implements the build method + $this->build(); + + // Add custom request headers set on the command + if ($headers = $this[self::HEADERS_OPTION]) { + foreach ($headers as $key => $value) { + $this->request->setHeader($key, $value); + } + } + + // Add any curl options to the request + if ($options = $this[Client::CURL_OPTIONS]) { + $this->request->getCurlOptions()->overwriteWith(CurlHandle::parseCurlConfig($options)); + } + + // Set a custom response body + if ($responseBody = $this[self::RESPONSE_BODY]) { + $this->request->setResponseBody($responseBody); + } + + $this->client->dispatch('command.after_prepare', array('command' => $this)); + } + + return $this->request; + } + + /** + * Set the validator used to validate and prepare command parameters and nested JSON schemas. If no validator is + * set, then the command will validate using the default {@see SchemaValidator}. + * + * @param ValidatorInterface $validator Validator used to prepare and validate properties against a JSON schema + * + * @return self + */ + public function setValidator(ValidatorInterface $validator) + { + $this->validator = $validator; + + return $this; + } + + public function getRequestHeaders() + { + return $this[self::HEADERS_OPTION]; + } + + /** + * Initialize the command (hook that can be implemented in subclasses) + */ + protected function init() {} + + /** + * Create the request object that will carry out the command + */ + abstract protected function build(); + + /** + * Hook used to create an operation for concrete commands that are not associated with a service description + * + * @return OperationInterface + */ + protected function createOperation() + { + return new Operation(array('name' => get_class($this))); + } + + /** + * Create the result of the command after the request has been completed. + * Override this method in subclasses to customize this behavior + */ + protected function process() + { + $this->result = $this[self::RESPONSE_PROCESSING] != self::TYPE_RAW + ? DefaultResponseParser::getInstance()->parse($this) + : $this->request->getResponse(); + } + + /** + * Validate and prepare the command based on the schema and rules defined by the command's Operation object + * + * @throws ValidationException when validation errors occur + */ + protected function validate() + { + // Do not perform request validation/transformation if it is disable + if ($this[self::DISABLE_VALIDATION]) { + return; + } + + $errors = array(); + $validator = $this->getValidator(); + foreach ($this->operation->getParams() as $name => $schema) { + $value = $this[$name]; + if (!$validator->validate($schema, $value)) { + $errors = array_merge($errors, $validator->getErrors()); + } elseif ($value !== $this[$name]) { + // Update the config value if it changed and no validation errors were encountered + $this->data[$name] = $value; + } + } + + // Validate additional parameters + $hidden = $this[self::HIDDEN_PARAMS]; + + if ($properties = $this->operation->getAdditionalParameters()) { + foreach ($this->toArray() as $name => $value) { + // It's only additional if it isn't defined in the schema + if (!$this->operation->hasParam($name) && !in_array($name, $hidden)) { + // Always set the name so that error messages are useful + $properties->setName($name); + if (!$validator->validate($properties, $value)) { + $errors = array_merge($errors, $validator->getErrors()); + } elseif ($value !== $this[$name]) { + $this->data[$name] = $value; + } + } + } + } + + if (!empty($errors)) { + $e = new ValidationException('Validation errors: ' . implode("\n", $errors)); + $e->setErrors($errors); + throw $e; + } + } + + /** + * Get the validator used to prepare and validate properties. If no validator has been set on the command, then + * the default {@see SchemaValidator} will be used. + * + * @return ValidatorInterface + */ + protected function getValidator() + { + if (!$this->validator) { + $this->validator = SchemaValidator::getInstance(); + } + + return $this->validator; + } + + /** + * Get array of any validation errors + * If no validator has been set then return false + */ + public function getValidationErrors() + { + if (!$this->validator) { + return false; + } + + return $this->validator->getErrors(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php new file mode 100644 index 000000000..cb6ac40ce --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php @@ -0,0 +1,41 @@ +request = $closure($this, $this->operation); + + if (!$this->request || !$this->request instanceof RequestInterface) { + throw new UnexpectedValueException('Closure command did not return a RequestInterface object'); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php new file mode 100644 index 000000000..fbb61d2ff --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php @@ -0,0 +1,128 @@ +stopPropagation(); + } + + /** + * Get the created object + * + * @return mixed + */ + public function getResult() + { + return $this['result']; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php new file mode 100644 index 000000000..2dc4acd37 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php @@ -0,0 +1,169 @@ +factory = $factory; + } + + /** + * Add a location visitor to the serializer + * + * @param string $location Location to associate with the visitor + * @param RequestVisitorInterface $visitor Visitor to attach + * + * @return self + */ + public function addVisitor($location, RequestVisitorInterface $visitor) + { + $this->factory->addRequestVisitor($location, $visitor); + + return $this; + } + + public function prepare(CommandInterface $command) + { + $request = $this->createRequest($command); + // Keep an array of visitors found in the operation + $foundVisitors = array(); + $operation = $command->getOperation(); + + // Add arguments to the request using the location attribute + foreach ($operation->getParams() as $name => $arg) { + /** @var $arg \Guzzle\Service\Description\Parameter */ + $location = $arg->getLocation(); + // Skip 'uri' locations because they've already been processed + if ($location && $location != 'uri') { + // Instantiate visitors as they are detected in the properties + if (!isset($foundVisitors[$location])) { + $foundVisitors[$location] = $this->factory->getRequestVisitor($location); + } + // Ensure that a value has been set for this parameter + $value = $command[$name]; + if ($value !== null) { + // Apply the parameter value with the location visitor + $foundVisitors[$location]->visit($command, $request, $arg, $value); + } + } + } + + // Serialize additional parameters + if ($additional = $operation->getAdditionalParameters()) { + if ($visitor = $this->prepareAdditionalParameters($operation, $command, $request, $additional)) { + $foundVisitors[$additional->getLocation()] = $visitor; + } + } + + // Call the after method on each visitor found in the operation + foreach ($foundVisitors as $visitor) { + $visitor->after($command, $request); + } + + return $request; + } + + /** + * Serialize additional parameters + * + * @param OperationInterface $operation Operation that owns the command + * @param CommandInterface $command Command to prepare + * @param RequestInterface $request Request to serialize + * @param Parameter $additional Additional parameters + * + * @return null|RequestVisitorInterface + */ + protected function prepareAdditionalParameters( + OperationInterface $operation, + CommandInterface $command, + RequestInterface $request, + Parameter $additional + ) { + if (!($location = $additional->getLocation())) { + return; + } + + $visitor = $this->factory->getRequestVisitor($location); + $hidden = $command[$command::HIDDEN_PARAMS]; + + foreach ($command->toArray() as $key => $value) { + // Ignore values that are null or built-in command options + if ($value !== null + && !in_array($key, $hidden) + && !$operation->hasParam($key) + ) { + $additional->setName($key); + $visitor->visit($command, $request, $additional, $value); + } + } + + return $visitor; + } + + /** + * Create a request for the command and operation + * + * @param CommandInterface $command Command to create a request for + * + * @return RequestInterface + */ + protected function createRequest(CommandInterface $command) + { + $operation = $command->getOperation(); + $client = $command->getClient(); + $options = $command[AbstractCommand::REQUEST_OPTIONS] ?: array(); + + // If the command does not specify a template, then assume the base URL of the client + if (!($uri = $operation->getUri())) { + return $client->createRequest($operation->getHttpMethod(), $client->getBaseUrl(), null, null, $options); + } + + // Get the path values and use the client config settings + $variables = array(); + foreach ($operation->getParams() as $name => $arg) { + if ($arg->getLocation() == 'uri') { + if (isset($command[$name])) { + $variables[$name] = $arg->filter($command[$name]); + if (!is_array($variables[$name])) { + $variables[$name] = (string) $variables[$name]; + } + } + } + } + + return $client->createRequest($operation->getHttpMethod(), array($uri, $variables), null, null, $options); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php new file mode 100644 index 000000000..4fe380376 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php @@ -0,0 +1,55 @@ +getRequest()->getResponse(); + + // Account for hard coded content-type values specified in service descriptions + if ($contentType = $command['command.expects']) { + $response->setHeader('Content-Type', $contentType); + } else { + $contentType = (string) $response->getHeader('Content-Type'); + } + + return $this->handleParsing($command, $response, $contentType); + } + + protected function handleParsing(CommandInterface $command, Response $response, $contentType) + { + $result = $response; + if ($result->getBody()) { + if (stripos($contentType, 'json') !== false) { + $result = $result->json(); + } elseif (stripos($contentType, 'xml') !== false) { + $result = $result->xml(); + } + } + + return $result; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php new file mode 100644 index 000000000..1c5ce0741 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php @@ -0,0 +1,39 @@ +client = $client; + $this->aliases = $aliases; + } + + public function factory($name, array $args = array()) + { + if (isset($this->aliases[$name])) { + try { + return $this->client->getCommand($this->aliases[$name], $args); + } catch (InvalidArgumentException $e) { + return null; + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php new file mode 100644 index 000000000..8c46983d6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php @@ -0,0 +1,154 @@ +getDescription()) { + $factories[] = new ServiceDescriptionFactory($description); + } + $factories[] = new ConcreteClassFactory($client); + + return new self($factories); + } + + /** + * @param array $factories Array of command factories + */ + public function __construct(array $factories = array()) + { + $this->factories = $factories; + } + + /** + * Add a command factory to the chain + * + * @param FactoryInterface $factory Factory to add + * @param string|FactoryInterface $before Insert the new command factory before a command factory class or object + * matching a class name. + * @return CompositeFactory + */ + public function add(FactoryInterface $factory, $before = null) + { + $pos = null; + + if ($before) { + foreach ($this->factories as $i => $f) { + if ($before instanceof FactoryInterface) { + if ($f === $before) { + $pos = $i; + break; + } + } elseif (is_string($before)) { + if ($f instanceof $before) { + $pos = $i; + break; + } + } + } + } + + if ($pos === null) { + $this->factories[] = $factory; + } else { + array_splice($this->factories, $i, 0, array($factory)); + } + + return $this; + } + + /** + * Check if the chain contains a specific command factory + * + * @param FactoryInterface|string $factory Factory to check + * + * @return bool + */ + public function has($factory) + { + return (bool) $this->find($factory); + } + + /** + * Remove a specific command factory from the chain + * + * @param string|FactoryInterface $factory Factory to remove by name or instance + * + * @return CompositeFactory + */ + public function remove($factory = null) + { + if (!($factory instanceof FactoryInterface)) { + $factory = $this->find($factory); + } + + $this->factories = array_values(array_filter($this->factories, function($f) use ($factory) { + return $f !== $factory; + })); + + return $this; + } + + /** + * Get a command factory by class name + * + * @param string|FactoryInterface $factory Command factory class or instance + * + * @return null|FactoryInterface + */ + public function find($factory) + { + foreach ($this->factories as $f) { + if ($factory === $f || (is_string($factory) && $f instanceof $factory)) { + return $f; + } + } + } + + /** + * Create a command using the associated command factories + * + * @param string $name Name of the command + * @param array $args Command arguments + * + * @return CommandInterface + */ + public function factory($name, array $args = array()) + { + foreach ($this->factories as $factory) { + $command = $factory->factory($name, $args); + if ($command) { + return $command; + } + } + } + + public function count() + { + return count($this->factories); + } + + public function getIterator() + { + return new \ArrayIterator($this->factories); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php new file mode 100644 index 000000000..0e93deaa0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php @@ -0,0 +1,47 @@ +client = $client; + $this->inflector = $inflector ?: Inflector::getDefault(); + } + + public function factory($name, array $args = array()) + { + // Determine the class to instantiate based on the namespace of the current client and the default directory + $prefix = $this->client->getConfig('command.prefix'); + if (!$prefix) { + // The prefix can be specified in a factory method and is cached + $prefix = implode('\\', array_slice(explode('\\', get_class($this->client)), 0, -1)) . '\\Command\\'; + $this->client->getConfig()->set('command.prefix', $prefix); + } + + $class = $prefix . str_replace(' ', '\\', ucwords(str_replace('.', ' ', $this->inflector->camel($name)))); + + // Create the concrete command if it exists + if (class_exists($class)) { + return new $class($args); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php new file mode 100644 index 000000000..35c299d9d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php @@ -0,0 +1,21 @@ +map = $map; + } + + public function factory($name, array $args = array()) + { + if (isset($this->map[$name])) { + $class = $this->map[$name]; + + return new $class($args); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php new file mode 100644 index 000000000..b943a5b50 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php @@ -0,0 +1,71 @@ +setServiceDescription($description); + $this->inflector = $inflector; + } + + /** + * Change the service description used with the factory + * + * @param ServiceDescriptionInterface $description Service description to use + * + * @return FactoryInterface + */ + public function setServiceDescription(ServiceDescriptionInterface $description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the service description + * + * @return ServiceDescriptionInterface + */ + public function getServiceDescription() + { + return $this->description; + } + + public function factory($name, array $args = array()) + { + $command = $this->description->getOperation($name); + + // If a command wasn't found, then try to uppercase the first letter and try again + if (!$command) { + $command = $this->description->getOperation(ucfirst($name)); + // If an inflector was passed, then attempt to get the command using snake_case inflection + if (!$command && $this->inflector) { + $command = $this->description->getOperation($this->inflector->snake($name)); + } + } + + if ($command) { + $class = $command->getClass(); + return new $class($args, $command, $this->description); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php new file mode 100644 index 000000000..adcfca1ba --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php @@ -0,0 +1,69 @@ +resolveRecursively($value, $param) + : $param->filter($value); + } + + /** + * Map nested parameters into the location_key based parameters + * + * @param array $value Value to map + * @param Parameter $param Parameter that holds information about the current key + * + * @return array Returns the mapped array + */ + protected function resolveRecursively(array $value, Parameter $param) + { + foreach ($value as $name => &$v) { + switch ($param->getType()) { + case 'object': + if ($subParam = $param->getProperty($name)) { + $key = $subParam->getWireName(); + $value[$key] = $this->prepareValue($v, $subParam); + if ($name != $key) { + unset($value[$name]); + } + } elseif ($param->getAdditionalProperties() instanceof Parameter) { + $v = $this->prepareValue($v, $param->getAdditionalProperties()); + } + break; + case 'array': + if ($items = $param->getItems()) { + $v = $this->prepareValue($v, $items); + } + break; + } + } + + return $param->filter($value); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php new file mode 100644 index 000000000..168d7806f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php @@ -0,0 +1,58 @@ +filter($value); + $entityBody = EntityBody::factory($value); + $request->setBody($entityBody); + $this->addExpectHeader($request, $entityBody, $param->getData('expect_header')); + // Add the Content-Encoding header if one is set on the EntityBody + if ($encoding = $entityBody->getContentEncoding()) { + $request->setHeader('Content-Encoding', $encoding); + } + } + + /** + * Add the appropriate expect header to a request + * + * @param EntityEnclosingRequestInterface $request Request to update + * @param EntityBodyInterface $body Entity body of the request + * @param string|int $expect Expect header setting + */ + protected function addExpectHeader(EntityEnclosingRequestInterface $request, EntityBodyInterface $body, $expect) + { + // Allow the `expect` data parameter to be set to remove the Expect header from the request + if ($expect === false) { + $request->removeHeader('Expect'); + } elseif ($expect !== true) { + // Default to using a MB as the point in which to start using the expect header + $expect = $expect ?: 1048576; + // If the expect_header value is numeric then only add if the size is greater than the cutoff + if (is_numeric($expect) && $body->getSize()) { + if ($body->getSize() < $expect) { + $request->removeHeader('Expect'); + } else { + $request->setHeader('Expect', '100-Continue'); + } + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php new file mode 100644 index 000000000..2a537542c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php @@ -0,0 +1,44 @@ +filter($value); + if ($param->getType() == 'object' && $param->getAdditionalProperties() instanceof Parameter) { + $this->addPrefixedHeaders($request, $param, $value); + } else { + $request->setHeader($param->getWireName(), $value); + } + } + + /** + * Add a prefixed array of headers to the request + * + * @param RequestInterface $request Request to update + * @param Parameter $param Parameter object + * @param array $value Header array to add + * + * @throws InvalidArgumentException + */ + protected function addPrefixedHeaders(RequestInterface $request, Parameter $param, $value) + { + if (!is_array($value)) { + throw new InvalidArgumentException('An array of mapped headers expected, but received a single value'); + } + $prefix = $param->getSentAs(); + foreach ($value as $headerName => $headerValue) { + $request->setHeader($prefix . $headerName, $headerValue); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php new file mode 100644 index 000000000..757e1c520 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php @@ -0,0 +1,63 @@ +data = new \SplObjectStorage(); + } + + /** + * Set the Content-Type header to add to the request if JSON is added to the body. This visitor does not add a + * Content-Type header unless you specify one here. + * + * @param string $header Header to set when JSON is added (e.g. application/json) + * + * @return self + */ + public function setContentTypeHeader($header = 'application/json') + { + $this->jsonContentType = $header; + + return $this; + } + + public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value) + { + if (isset($this->data[$command])) { + $json = $this->data[$command]; + } else { + $json = array(); + } + $json[$param->getWireName()] = $this->prepareValue($value, $param); + $this->data[$command] = $json; + } + + public function after(CommandInterface $command, RequestInterface $request) + { + if (isset($this->data[$command])) { + // Don't overwrite the Content-Type if one is set + if ($this->jsonContentType && !$request->hasHeader('Content-Type')) { + $request->setHeader('Content-Type', $this->jsonContentType); + } + + $request->setBody(json_encode($this->data[$command])); + unset($this->data[$command]); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php new file mode 100644 index 000000000..975850b74 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php @@ -0,0 +1,18 @@ +setPostField($param->getWireName(), $this->prepareValue($value, $param)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php new file mode 100644 index 000000000..0853ebe62 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php @@ -0,0 +1,24 @@ +filter($value); + if ($value instanceof PostFileInterface) { + $request->addPostFile($value); + } else { + $request->addPostFile($param->getWireName(), $value); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php new file mode 100644 index 000000000..315877aa0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php @@ -0,0 +1,18 @@ +getQuery()->set($param->getWireName(), $this->prepareValue($value, $param)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php new file mode 100644 index 000000000..14e0b2d2b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php @@ -0,0 +1,31 @@ +setResponseBody($value); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php new file mode 100644 index 000000000..5b7148787 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php @@ -0,0 +1,252 @@ +data = new \SplObjectStorage(); + } + + /** + * Change the content-type header that is added when XML is found + * + * @param string $header Header to set when XML is found + * + * @return self + */ + public function setContentTypeHeader($header) + { + $this->contentType = $header; + + return $this; + } + + public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value) + { + $xml = isset($this->data[$command]) + ? $this->data[$command] + : $this->createRootElement($param->getParent()); + $this->addXml($xml, $param, $value); + + $this->data[$command] = $xml; + } + + public function after(CommandInterface $command, RequestInterface $request) + { + $xml = null; + + // If data was found that needs to be serialized, then do so + if (isset($this->data[$command])) { + $xml = $this->finishDocument($this->data[$command]); + unset($this->data[$command]); + } else { + // Check if XML should always be sent for the command + $operation = $command->getOperation(); + if ($operation->getData('xmlAllowEmpty')) { + $xmlWriter = $this->createRootElement($operation); + $xml = $this->finishDocument($xmlWriter); + } + } + + if ($xml) { + // Don't overwrite the Content-Type if one is set + if ($this->contentType && !$request->hasHeader('Content-Type')) { + $request->setHeader('Content-Type', $this->contentType); + } + $request->setBody($xml); + } + } + + /** + * Create the root XML element to use with a request + * + * @param Operation $operation Operation object + * + * @return \XMLWriter + */ + protected function createRootElement(Operation $operation) + { + static $defaultRoot = array('name' => 'Request'); + // If no root element was specified, then just wrap the XML in 'Request' + $root = $operation->getData('xmlRoot') ?: $defaultRoot; + // Allow the XML declaration to be customized with xmlEncoding + $encoding = $operation->getData('xmlEncoding'); + + $xmlWriter = $this->startDocument($encoding); + + $xmlWriter->startElement($root['name']); + // Create the wrapping element with no namespaces if no namespaces were present + if (!empty($root['namespaces'])) { + // Create the wrapping element with an array of one or more namespaces + foreach ((array) $root['namespaces'] as $prefix => $uri) { + $nsLabel = 'xmlns'; + if (!is_numeric($prefix)) { + $nsLabel .= ':'.$prefix; + } + $xmlWriter->writeAttribute($nsLabel, $uri); + } + } + return $xmlWriter; + } + + /** + * Recursively build the XML body + * + * @param \XMLWriter $xmlWriter XML to modify + * @param Parameter $param API Parameter + * @param mixed $value Value to add + */ + protected function addXml(\XMLWriter $xmlWriter, Parameter $param, $value) + { + if ($value === null) { + return; + } + + $value = $param->filter($value); + $type = $param->getType(); + $name = $param->getWireName(); + $prefix = null; + $namespace = $param->getData('xmlNamespace'); + if (false !== strpos($name, ':')) { + list($prefix, $name) = explode(':', $name, 2); + } + + if ($type == 'object' || $type == 'array') { + if (!$param->getData('xmlFlattened')) { + $xmlWriter->startElementNS(null, $name, $namespace); + } + if ($param->getType() == 'array') { + $this->addXmlArray($xmlWriter, $param, $value); + } elseif ($param->getType() == 'object') { + $this->addXmlObject($xmlWriter, $param, $value); + } + if (!$param->getData('xmlFlattened')) { + $xmlWriter->endElement(); + } + return; + } + if ($param->getData('xmlAttribute')) { + $this->writeAttribute($xmlWriter, $prefix, $name, $namespace, $value); + } else { + $this->writeElement($xmlWriter, $prefix, $name, $namespace, $value); + } + } + + /** + * Write an attribute with namespace if used + * + * @param \XMLWriter $xmlWriter XMLWriter instance + * @param string $prefix Namespace prefix if any + * @param string $name Attribute name + * @param string $namespace The uri of the namespace + * @param string $value The attribute content + */ + protected function writeAttribute($xmlWriter, $prefix, $name, $namespace, $value) + { + if (empty($namespace)) { + $xmlWriter->writeAttribute($name, $value); + } else { + $xmlWriter->writeAttributeNS($prefix, $name, $namespace, $value); + } + } + + /** + * Write an element with namespace if used + * + * @param \XMLWriter $xmlWriter XML writer resource + * @param string $prefix Namespace prefix if any + * @param string $name Element name + * @param string $namespace The uri of the namespace + * @param string $value The element content + */ + protected function writeElement(\XMLWriter $xmlWriter, $prefix, $name, $namespace, $value) + { + $xmlWriter->startElementNS($prefix, $name, $namespace); + if (strpbrk($value, '<>&')) { + $xmlWriter->writeCData($value); + } else { + $xmlWriter->writeRaw($value); + } + $xmlWriter->endElement(); + } + + /** + * Create a new xml writer and start a document + * + * @param string $encoding document encoding + * + * @return \XMLWriter the writer resource + */ + protected function startDocument($encoding) + { + $xmlWriter = new \XMLWriter(); + $xmlWriter->openMemory(); + $xmlWriter->startDocument('1.0', $encoding); + + return $xmlWriter; + } + + /** + * End the document and return the output + * + * @param \XMLWriter $xmlWriter + * + * @return \string the writer resource + */ + protected function finishDocument($xmlWriter) + { + $xmlWriter->endDocument(); + + return $xmlWriter->outputMemory(); + } + + /** + * Add an array to the XML + */ + protected function addXmlArray(\XMLWriter $xmlWriter, Parameter $param, &$value) + { + if ($items = $param->getItems()) { + foreach ($value as $v) { + $this->addXml($xmlWriter, $items, $v); + } + } + } + + /** + * Add an object to the XML + */ + protected function addXmlObject(\XMLWriter $xmlWriter, Parameter $param, &$value) + { + $noAttributes = array(); + // add values which have attributes + foreach ($value as $name => $v) { + if ($property = $param->getProperty($name)) { + if ($property->getData('xmlAttribute')) { + $this->addXml($xmlWriter, $property, $v); + } else { + $noAttributes[] = array('value' => $v, 'property' => $property); + } + } + } + // now add values with no attributes + foreach ($noAttributes as $element) { + $this->addXml($xmlWriter, $element['property'], $element['value']); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php new file mode 100644 index 000000000..d87eeb945 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php @@ -0,0 +1,26 @@ +getName()] = $param->filter($response->getBody()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php new file mode 100644 index 000000000..0f8737cbd --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php @@ -0,0 +1,50 @@ +getType() == 'object' && $param->getAdditionalProperties() instanceof Parameter) { + $this->processPrefixedHeaders($response, $param, $value); + } else { + $value[$param->getName()] = $param->filter((string) $response->getHeader($param->getWireName())); + } + } + + /** + * Process a prefixed header array + * + * @param Response $response Response that contains the headers + * @param Parameter $param Parameter object + * @param array $value Value response array to modify + */ + protected function processPrefixedHeaders(Response $response, Parameter $param, &$value) + { + // Grab prefixed headers that should be placed into an array with the prefix stripped + if ($prefix = $param->getSentAs()) { + $container = $param->getName(); + $len = strlen($prefix); + // Find all matching headers and place them into the containing element + foreach ($response->getHeaders()->toArray() as $key => $header) { + if (stripos($key, $prefix) === 0) { + // Account for multi-value headers + $value[$container][substr($key, $len)] = count($header) == 1 ? end($header) : $header; + } + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php new file mode 100644 index 000000000..a609ebd8c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php @@ -0,0 +1,93 @@ +getResponse()->json(); + } + + public function visit( + CommandInterface $command, + Response $response, + Parameter $param, + &$value, + $context = null + ) { + $name = $param->getName(); + $key = $param->getWireName(); + if (isset($value[$key])) { + $this->recursiveProcess($param, $value[$key]); + if ($key != $name) { + $value[$name] = $value[$key]; + unset($value[$key]); + } + } + } + + /** + * Recursively process a parameter while applying filters + * + * @param Parameter $param API parameter being validated + * @param mixed $value Value to validate and process. The value may change during this process. + */ + protected function recursiveProcess(Parameter $param, &$value) + { + if ($value === null) { + return; + } + + if (is_array($value)) { + $type = $param->getType(); + if ($type == 'array') { + foreach ($value as &$item) { + $this->recursiveProcess($param->getItems(), $item); + } + } elseif ($type == 'object' && !isset($value[0])) { + // On the above line, we ensure that the array is associative and not numerically indexed + $knownProperties = array(); + if ($properties = $param->getProperties()) { + foreach ($properties as $property) { + $name = $property->getName(); + $key = $property->getWireName(); + $knownProperties[$name] = 1; + if (isset($value[$key])) { + $this->recursiveProcess($property, $value[$key]); + if ($key != $name) { + $value[$name] = $value[$key]; + unset($value[$key]); + } + } + } + } + + // Remove any unknown and potentially unsafe properties + if ($param->getAdditionalProperties() === false) { + $value = array_intersect_key($value, $knownProperties); + } elseif (($additional = $param->getAdditionalProperties()) !== true) { + // Validate and filter additional properties + foreach ($value as &$v) { + $this->recursiveProcess($additional, $v); + } + } + } + } + + $value = $param->filter($value); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php new file mode 100644 index 000000000..1b10ebce7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php @@ -0,0 +1,23 @@ +getName()] = $response->getReasonPhrase(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php new file mode 100644 index 000000000..033f40c3f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php @@ -0,0 +1,46 @@ +getName()] = $response->getStatusCode(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php new file mode 100644 index 000000000..bb7124be7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php @@ -0,0 +1,151 @@ +getResponse()->xml()), true); + } + + public function visit( + CommandInterface $command, + Response $response, + Parameter $param, + &$value, + $context = null + ) { + $sentAs = $param->getWireName(); + $name = $param->getName(); + if (isset($value[$sentAs])) { + $this->recursiveProcess($param, $value[$sentAs]); + if ($name != $sentAs) { + $value[$name] = $value[$sentAs]; + unset($value[$sentAs]); + } + } + } + + /** + * Recursively process a parameter while applying filters + * + * @param Parameter $param API parameter being processed + * @param mixed $value Value to validate and process. The value may change during this process. + */ + protected function recursiveProcess(Parameter $param, &$value) + { + $type = $param->getType(); + + if (!is_array($value)) { + if ($type == 'array') { + // Cast to an array if the value was a string, but should be an array + $this->recursiveProcess($param->getItems(), $value); + $value = array($value); + } + } elseif ($type == 'object') { + $this->processObject($param, $value); + } elseif ($type == 'array') { + $this->processArray($param, $value); + } elseif ($type == 'string' && gettype($value) == 'array') { + $value = ''; + } + + if ($value !== null) { + $value = $param->filter($value); + } + } + + /** + * Process an array + * + * @param Parameter $param API parameter being parsed + * @param mixed $value Value to process + */ + protected function processArray(Parameter $param, &$value) + { + // Convert the node if it was meant to be an array + if (!isset($value[0])) { + // Collections fo nodes are sometimes wrapped in an additional array. For example: + // 12 should become: + // array('Items' => array(array('a' => 1), array('a' => 2)) + // Some nodes are not wrapped. For example: 12 + // should become array('Foo' => array(array('a' => 1), array('a' => 2)) + if ($param->getItems() && isset($value[$param->getItems()->getWireName()])) { + // Account for the case of a collection wrapping wrapped nodes: Items => Item[] + $value = $value[$param->getItems()->getWireName()]; + // If the wrapped node only had one value, then make it an array of nodes + if (!isset($value[0]) || !is_array($value)) { + $value = array($value); + } + } elseif (!empty($value)) { + // Account for repeated nodes that must be an array: Foo => Baz, Foo => Baz, but only if the + // value is set and not empty + $value = array($value); + } + } + + foreach ($value as &$item) { + $this->recursiveProcess($param->getItems(), $item); + } + } + + /** + * Process an object + * + * @param Parameter $param API parameter being parsed + * @param mixed $value Value to process + */ + protected function processObject(Parameter $param, &$value) + { + // Ensure that the array is associative and not numerically indexed + if (!isset($value[0]) && ($properties = $param->getProperties())) { + $knownProperties = array(); + foreach ($properties as $property) { + $name = $property->getName(); + $sentAs = $property->getWireName(); + $knownProperties[$name] = 1; + if ($property->getData('xmlAttribute')) { + $this->processXmlAttribute($property, $value); + } elseif (isset($value[$sentAs])) { + $this->recursiveProcess($property, $value[$sentAs]); + if ($name != $sentAs) { + $value[$name] = $value[$sentAs]; + unset($value[$sentAs]); + } + } + } + + // Remove any unknown and potentially unsafe properties + if ($param->getAdditionalProperties() === false) { + $value = array_intersect_key($value, $knownProperties); + } + } + } + + /** + * Process an XML attribute property + * + * @param Parameter $property Property to process + * @param array $value Value to process and update + */ + protected function processXmlAttribute(Parameter $property, array &$value) + { + $sentAs = $property->getWireName(); + if (isset($value['@attributes'][$sentAs])) { + $value[$property->getName()] = $value['@attributes'][$sentAs]; + unset($value['@attributes'][$sentAs]); + if (empty($value['@attributes'])) { + unset($value['@attributes']); + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php new file mode 100644 index 000000000..74cb62813 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php @@ -0,0 +1,138 @@ + 'Guzzle\Service\Command\LocationVisitor\Request\BodyVisitor', + 'request.header' => 'Guzzle\Service\Command\LocationVisitor\Request\HeaderVisitor', + 'request.json' => 'Guzzle\Service\Command\LocationVisitor\Request\JsonVisitor', + 'request.postField' => 'Guzzle\Service\Command\LocationVisitor\Request\PostFieldVisitor', + 'request.postFile' => 'Guzzle\Service\Command\LocationVisitor\Request\PostFileVisitor', + 'request.query' => 'Guzzle\Service\Command\LocationVisitor\Request\QueryVisitor', + 'request.response_body' => 'Guzzle\Service\Command\LocationVisitor\Request\ResponseBodyVisitor', + 'request.responseBody' => 'Guzzle\Service\Command\LocationVisitor\Request\ResponseBodyVisitor', + 'request.xml' => 'Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor', + 'response.body' => 'Guzzle\Service\Command\LocationVisitor\Response\BodyVisitor', + 'response.header' => 'Guzzle\Service\Command\LocationVisitor\Response\HeaderVisitor', + 'response.json' => 'Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor', + 'response.reasonPhrase' => 'Guzzle\Service\Command\LocationVisitor\Response\ReasonPhraseVisitor', + 'response.statusCode' => 'Guzzle\Service\Command\LocationVisitor\Response\StatusCodeVisitor', + 'response.xml' => 'Guzzle\Service\Command\LocationVisitor\Response\XmlVisitor' + ); + + /** @var array Array of mappings of location names to classes */ + protected $mappings; + + /** @var array Cache of instantiated visitors */ + protected $cache = array(); + + /** + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * @param array $mappings Array mapping request.name and response.name to location visitor classes. Leave null to + * use the default values. + */ + public function __construct(array $mappings = null) + { + $this->mappings = $mappings === null ? self::$defaultMappings : $mappings; + } + + /** + * Get an instance of a request visitor by location name + * + * @param string $visitor Visitor name + * + * @return RequestVisitorInterface + */ + public function getRequestVisitor($visitor) + { + return $this->getKey('request.' . $visitor); + } + + /** + * Get an instance of a response visitor by location name + * + * @param string $visitor Visitor name + * + * @return ResponseVisitorInterface + */ + public function getResponseVisitor($visitor) + { + return $this->getKey('response.' . $visitor); + } + + /** + * Add a response visitor to the factory by name + * + * @param string $name Name of the visitor + * @param RequestVisitorInterface $visitor Visitor to add + * + * @return self + */ + public function addRequestVisitor($name, RequestVisitorInterface $visitor) + { + $this->cache['request.' . $name] = $visitor; + + return $this; + } + + /** + * Add a response visitor to the factory by name + * + * @param string $name Name of the visitor + * @param ResponseVisitorInterface $visitor Visitor to add + * + * @return self + */ + public function addResponseVisitor($name, ResponseVisitorInterface $visitor) + { + $this->cache['response.' . $name] = $visitor; + + return $this; + } + + /** + * Get a visitor by key value name + * + * @param string $key Key name to retrieve + * + * @return mixed + * @throws InvalidArgumentException + */ + private function getKey($key) + { + if (!isset($this->cache[$key])) { + if (!isset($this->mappings[$key])) { + list($type, $name) = explode('.', $key); + throw new InvalidArgumentException("No {$type} visitor has been mapped for {$name}"); + } + $this->cache[$key] = new $this->mappings[$key]; + } + + return $this->cache[$key]; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php new file mode 100644 index 000000000..0748b5af0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php @@ -0,0 +1,89 @@ +responseParser = $parser; + + return $this; + } + + /** + * Set the request serializer used with the command + * + * @param RequestSerializerInterface $serializer Request serializer + * + * @return self + */ + public function setRequestSerializer(RequestSerializerInterface $serializer) + { + $this->requestSerializer = $serializer; + + return $this; + } + + /** + * Get the request serializer used with the command + * + * @return RequestSerializerInterface + */ + public function getRequestSerializer() + { + if (!$this->requestSerializer) { + // Use the default request serializer if none was found + $this->requestSerializer = DefaultRequestSerializer::getInstance(); + } + + return $this->requestSerializer; + } + + /** + * Get the response parser used for the operation + * + * @return ResponseParserInterface + */ + public function getResponseParser() + { + if (!$this->responseParser) { + // Use the default response parser if none was found + $this->responseParser = OperationResponseParser::getInstance(); + } + + return $this->responseParser; + } + + protected function build() + { + // Prepare and serialize the request + $this->request = $this->getRequestSerializer()->prepare($this); + } + + protected function process() + { + // Do not process the response if 'command.response_processing' is set to 'raw' + $this->result = $this[self::RESPONSE_PROCESSING] == self::TYPE_RAW + ? $this->request->getResponse() + : $this->getResponseParser()->parse($this); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php new file mode 100644 index 000000000..ca00bc062 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php @@ -0,0 +1,195 @@ +factory = $factory; + $this->schemaInModels = $schemaInModels; + } + + /** + * Add a location visitor to the command + * + * @param string $location Location to associate with the visitor + * @param ResponseVisitorInterface $visitor Visitor to attach + * + * @return self + */ + public function addVisitor($location, ResponseVisitorInterface $visitor) + { + $this->factory->addResponseVisitor($location, $visitor); + + return $this; + } + + protected function handleParsing(CommandInterface $command, Response $response, $contentType) + { + $operation = $command->getOperation(); + $type = $operation->getResponseType(); + $model = null; + + if ($type == OperationInterface::TYPE_MODEL) { + $model = $operation->getServiceDescription()->getModel($operation->getResponseClass()); + } elseif ($type == OperationInterface::TYPE_CLASS) { + return $this->parseClass($command); + } + + if (!$model) { + // Return basic processing if the responseType is not model or the model cannot be found + return parent::handleParsing($command, $response, $contentType); + } elseif ($command[AbstractCommand::RESPONSE_PROCESSING] != AbstractCommand::TYPE_MODEL) { + // Returns a model with no visiting if the command response processing is not model + return new Model(parent::handleParsing($command, $response, $contentType)); + } else { + // Only inject the schema into the model if "schemaInModel" is true + return new Model($this->visitResult($model, $command, $response), $this->schemaInModels ? $model : null); + } + } + + /** + * Parse a class object + * + * @param CommandInterface $command Command to parse into an object + * + * @return mixed + * @throws ResponseClassException + */ + protected function parseClass(CommandInterface $command) + { + // Emit the operation.parse_class event. If a listener injects a 'result' property, then that will be the result + $event = new CreateResponseClassEvent(array('command' => $command)); + $command->getClient()->getEventDispatcher()->dispatch('command.parse_response', $event); + if ($result = $event->getResult()) { + return $result; + } + + $className = $command->getOperation()->getResponseClass(); + if (!method_exists($className, 'fromCommand')) { + throw new ResponseClassException("{$className} must exist and implement a static fromCommand() method"); + } + + return $className::fromCommand($command); + } + + /** + * Perform transformations on the result array + * + * @param Parameter $model Model that defines the structure + * @param CommandInterface $command Command that performed the operation + * @param Response $response Response received + * + * @return array Returns the array of result data + */ + protected function visitResult(Parameter $model, CommandInterface $command, Response $response) + { + $foundVisitors = $result = $knownProps = array(); + $props = $model->getProperties(); + + foreach ($props as $schema) { + if ($location = $schema->getLocation()) { + // Trigger the before method on the first found visitor of this type + if (!isset($foundVisitors[$location])) { + $foundVisitors[$location] = $this->factory->getResponseVisitor($location); + $foundVisitors[$location]->before($command, $result); + } + } + } + + // Visit additional properties when it is an actual schema + if (($additional = $model->getAdditionalProperties()) instanceof Parameter) { + $this->visitAdditionalProperties($model, $command, $response, $additional, $result, $foundVisitors); + } + + // Apply the parameter value with the location visitor + foreach ($props as $schema) { + $knownProps[$schema->getName()] = 1; + if ($location = $schema->getLocation()) { + $foundVisitors[$location]->visit($command, $response, $schema, $result); + } + } + + // Remove any unknown and potentially unsafe top-level properties + if ($additional === false) { + $result = array_intersect_key($result, $knownProps); + } + + // Call the after() method of each found visitor + foreach ($foundVisitors as $visitor) { + $visitor->after($command); + } + + return $result; + } + + protected function visitAdditionalProperties( + Parameter $model, + CommandInterface $command, + Response $response, + Parameter $additional, + &$result, + array &$foundVisitors + ) { + // Only visit when a location is specified + if ($location = $additional->getLocation()) { + if (!isset($foundVisitors[$location])) { + $foundVisitors[$location] = $this->factory->getResponseVisitor($location); + $foundVisitors[$location]->before($command, $result); + } + // Only traverse if an array was parsed from the before() visitors + if (is_array($result)) { + // Find each additional property + foreach (array_keys($result) as $key) { + // Check if the model actually knows this property. If so, then it is not additional + if (!$model->getProperty($key)) { + // Set the name to the key so that we can parse it with each visitor + $additional->setName($key); + $foundVisitors[$location]->visit($command, $response, $additional, $result); + } + } + // Reset the additionalProperties name to null + $additional->setName(null); + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php new file mode 100644 index 000000000..60b9334d4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php @@ -0,0 +1,21 @@ + true, 'httpMethod' => true, 'uri' => true, 'class' => true, 'responseClass' => true, + 'responseType' => true, 'responseNotes' => true, 'notes' => true, 'summary' => true, 'documentationUrl' => true, + 'deprecated' => true, 'data' => true, 'parameters' => true, 'additionalParameters' => true, + 'errorResponses' => true + ); + + /** @var array Parameters */ + protected $parameters = array(); + + /** @var Parameter Additional parameters schema */ + protected $additionalParameters; + + /** @var string Name of the command */ + protected $name; + + /** @var string HTTP method */ + protected $httpMethod; + + /** @var string This is a short summary of what the operation does */ + protected $summary; + + /** @var string A longer text field to explain the behavior of the operation. */ + protected $notes; + + /** @var string Reference URL providing more information about the operation */ + protected $documentationUrl; + + /** @var string HTTP URI of the command */ + protected $uri; + + /** @var string Class of the command object */ + protected $class; + + /** @var string This is what is returned from the method */ + protected $responseClass; + + /** @var string Type information about the response */ + protected $responseType; + + /** @var string Information about the response returned by the operation */ + protected $responseNotes; + + /** @var bool Whether or not the command is deprecated */ + protected $deprecated; + + /** @var array Array of errors that could occur when running the command */ + protected $errorResponses; + + /** @var ServiceDescriptionInterface */ + protected $description; + + /** @var array Extra operation information */ + protected $data; + + /** + * Builds an Operation object using an array of configuration data: + * - name: (string) Name of the command + * - httpMethod: (string) HTTP method of the operation + * - uri: (string) URI template that can create a relative or absolute URL + * - class: (string) Concrete class that implements this command + * - parameters: (array) Associative array of parameters for the command. {@see Parameter} for information. + * - summary: (string) This is a short summary of what the operation does + * - notes: (string) A longer text field to explain the behavior of the operation. + * - documentationUrl: (string) Reference URL providing more information about the operation + * - responseClass: (string) This is what is returned from the method. Can be a primitive, PSR-0 compliant + * class name, or model. + * - responseNotes: (string) Information about the response returned by the operation + * - responseType: (string) One of 'primitive', 'class', 'model', or 'documentation'. If not specified, this + * value will be automatically inferred based on whether or not there is a model matching the + * name, if a matching PSR-0 compliant class name is found, or set to 'primitive' by default. + * - deprecated: (bool) Set to true if this is a deprecated command + * - errorResponses: (array) Errors that could occur when executing the command. Array of hashes, each with a + * 'code' (the HTTP response code), 'reason' (response reason phrase or description of the + * error), and 'class' (a custom exception class that would be thrown if the error is + * encountered). + * - data: (array) Any extra data that might be used to help build or serialize the operation + * - additionalParameters: (null|array) Parameter schema to use when an option is passed to the operation that is + * not in the schema + * + * @param array $config Array of configuration data + * @param ServiceDescriptionInterface $description Service description used to resolve models if $ref tags are found + */ + public function __construct(array $config = array(), ServiceDescriptionInterface $description = null) + { + $this->description = $description; + + // Get the intersection of the available properties and properties set on the operation + foreach (array_intersect_key($config, self::$properties) as $key => $value) { + $this->{$key} = $value; + } + + $this->class = $this->class ?: self::DEFAULT_COMMAND_CLASS; + $this->deprecated = (bool) $this->deprecated; + $this->errorResponses = $this->errorResponses ?: array(); + $this->data = $this->data ?: array(); + + if (!$this->responseClass) { + $this->responseClass = 'array'; + $this->responseType = 'primitive'; + } elseif ($this->responseType) { + // Set the response type to perform validation + $this->setResponseType($this->responseType); + } else { + // A response class was set and no response type was set, so guess what the type is + $this->inferResponseType(); + } + + // Parameters need special handling when adding + if ($this->parameters) { + foreach ($this->parameters as $name => $param) { + if ($param instanceof Parameter) { + $param->setName($name)->setParent($this); + } elseif (is_array($param)) { + $param['name'] = $name; + $this->addParam(new Parameter($param, $this->description)); + } + } + } + + if ($this->additionalParameters) { + if ($this->additionalParameters instanceof Parameter) { + $this->additionalParameters->setParent($this); + } elseif (is_array($this->additionalParameters)) { + $this->setadditionalParameters(new Parameter($this->additionalParameters, $this->description)); + } + } + } + + public function toArray() + { + $result = array(); + // Grab valid properties and filter out values that weren't set + foreach (array_keys(self::$properties) as $check) { + if ($value = $this->{$check}) { + $result[$check] = $value; + } + } + // Remove the name property + unset($result['name']); + // Parameters need to be converted to arrays + $result['parameters'] = array(); + foreach ($this->parameters as $key => $param) { + $result['parameters'][$key] = $param->toArray(); + } + // Additional parameters need to be cast to an array + if ($this->additionalParameters instanceof Parameter) { + $result['additionalParameters'] = $this->additionalParameters->toArray(); + } + + return $result; + } + + public function getServiceDescription() + { + return $this->description; + } + + public function setServiceDescription(ServiceDescriptionInterface $description) + { + $this->description = $description; + + return $this; + } + + public function getParams() + { + return $this->parameters; + } + + public function getParamNames() + { + return array_keys($this->parameters); + } + + public function hasParam($name) + { + return isset($this->parameters[$name]); + } + + public function getParam($param) + { + return isset($this->parameters[$param]) ? $this->parameters[$param] : null; + } + + /** + * Add a parameter to the command + * + * @param Parameter $param Parameter to add + * + * @return self + */ + public function addParam(Parameter $param) + { + $this->parameters[$param->getName()] = $param; + $param->setParent($this); + + return $this; + } + + /** + * Remove a parameter from the command + * + * @param string $name Name of the parameter to remove + * + * @return self + */ + public function removeParam($name) + { + unset($this->parameters[$name]); + + return $this; + } + + public function getHttpMethod() + { + return $this->httpMethod; + } + + /** + * Set the HTTP method of the command + * + * @param string $httpMethod Method to set + * + * @return self + */ + public function setHttpMethod($httpMethod) + { + $this->httpMethod = $httpMethod; + + return $this; + } + + public function getClass() + { + return $this->class; + } + + /** + * Set the concrete class of the command + * + * @param string $className Concrete class name + * + * @return self + */ + public function setClass($className) + { + $this->class = $className; + + return $this; + } + + public function getName() + { + return $this->name; + } + + /** + * Set the name of the command + * + * @param string $name Name of the command + * + * @return self + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + public function getSummary() + { + return $this->summary; + } + + /** + * Set a short summary of what the operation does + * + * @param string $summary Short summary of the operation + * + * @return self + */ + public function setSummary($summary) + { + $this->summary = $summary; + + return $this; + } + + public function getNotes() + { + return $this->notes; + } + + /** + * Set a longer text field to explain the behavior of the operation. + * + * @param string $notes Notes on the operation + * + * @return self + */ + public function setNotes($notes) + { + $this->notes = $notes; + + return $this; + } + + public function getDocumentationUrl() + { + return $this->documentationUrl; + } + + /** + * Set the URL pointing to additional documentation on the command + * + * @param string $docUrl Documentation URL + * + * @return self + */ + public function setDocumentationUrl($docUrl) + { + $this->documentationUrl = $docUrl; + + return $this; + } + + public function getResponseClass() + { + return $this->responseClass; + } + + /** + * Set what is returned from the method. Can be a primitive, class name, or model. For example: 'array', + * 'Guzzle\\Foo\\Baz', or 'MyModelName' (to reference a model by ID). + * + * @param string $responseClass Type of response + * + * @return self + */ + public function setResponseClass($responseClass) + { + $this->responseClass = $responseClass; + $this->inferResponseType(); + + return $this; + } + + public function getResponseType() + { + return $this->responseType; + } + + /** + * Set qualifying information about the responseClass. One of 'primitive', 'class', 'model', or 'documentation' + * + * @param string $responseType Response type information + * + * @return self + * @throws InvalidArgumentException + */ + public function setResponseType($responseType) + { + static $types = array( + self::TYPE_PRIMITIVE => true, + self::TYPE_CLASS => true, + self::TYPE_MODEL => true, + self::TYPE_DOCUMENTATION => true + ); + if (!isset($types[$responseType])) { + throw new InvalidArgumentException('responseType must be one of ' . implode(', ', array_keys($types))); + } + + $this->responseType = $responseType; + + return $this; + } + + public function getResponseNotes() + { + return $this->responseNotes; + } + + /** + * Set notes about the response of the operation + * + * @param string $notes Response notes + * + * @return self + */ + public function setResponseNotes($notes) + { + $this->responseNotes = $notes; + + return $this; + } + + public function getDeprecated() + { + return $this->deprecated; + } + + /** + * Set whether or not the command is deprecated + * + * @param bool $isDeprecated Set to true to mark as deprecated + * + * @return self + */ + public function setDeprecated($isDeprecated) + { + $this->deprecated = $isDeprecated; + + return $this; + } + + public function getUri() + { + return $this->uri; + } + + /** + * Set the URI template of the command + * + * @param string $uri URI template to set + * + * @return self + */ + public function setUri($uri) + { + $this->uri = $uri; + + return $this; + } + + public function getErrorResponses() + { + return $this->errorResponses; + } + + /** + * Add an error to the command + * + * @param string $code HTTP response code + * @param string $reason HTTP response reason phrase or information about the error + * @param string $class Exception class associated with the error + * + * @return self + */ + public function addErrorResponse($code, $reason, $class) + { + $this->errorResponses[] = array('code' => $code, 'reason' => $reason, 'class' => $class); + + return $this; + } + + /** + * Set all of the error responses of the operation + * + * @param array $errorResponses Hash of error name to a hash containing a code, reason, class + * + * @return self + */ + public function setErrorResponses(array $errorResponses) + { + $this->errorResponses = $errorResponses; + + return $this; + } + + public function getData($name) + { + return isset($this->data[$name]) ? $this->data[$name] : null; + } + + /** + * Set a particular data point on the operation + * + * @param string $name Name of the data value + * @param mixed $value Value to set + * + * @return self + */ + public function setData($name, $value) + { + $this->data[$name] = $value; + + return $this; + } + + /** + * Get the additionalParameters of the operation + * + * @return Parameter|null + */ + public function getAdditionalParameters() + { + return $this->additionalParameters; + } + + /** + * Set the additionalParameters of the operation + * + * @param Parameter|null $parameter Parameter to set + * + * @return self + */ + public function setAdditionalParameters($parameter) + { + if ($this->additionalParameters = $parameter) { + $this->additionalParameters->setParent($this); + } + + return $this; + } + + /** + * Infer the response type from the responseClass value + */ + protected function inferResponseType() + { + static $primitives = array('array' => 1, 'boolean' => 1, 'string' => 1, 'integer' => 1, '' => 1); + if (isset($primitives[$this->responseClass])) { + $this->responseType = self::TYPE_PRIMITIVE; + } elseif ($this->description && $this->description->hasModel($this->responseClass)) { + $this->responseType = self::TYPE_MODEL; + } else { + $this->responseType = self::TYPE_CLASS; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php new file mode 100644 index 000000000..4de41bd67 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php @@ -0,0 +1,159 @@ +getModel($data['$ref'])) { + $data = $model->toArray() + $data; + } + } elseif (isset($data['extends'])) { + // If this parameter extends from another parameter then start with the actual data + // union in the parent's data (e.g. actual supersedes parent) + if ($extends = $description->getModel($data['extends'])) { + $data += $extends->toArray(); + } + } + } + + // Pull configuration data into the parameter + foreach ($data as $key => $value) { + $this->{$key} = $value; + } + + $this->serviceDescription = $description; + $this->required = (bool) $this->required; + $this->data = (array) $this->data; + + if ($this->filters) { + $this->setFilters((array) $this->filters); + } + + if ($this->type == 'object' && $this->additionalProperties === null) { + $this->additionalProperties = true; + } + } + + /** + * Convert the object to an array + * + * @return array + */ + public function toArray() + { + static $checks = array('required', 'description', 'static', 'type', 'format', 'instanceOf', 'location', 'sentAs', + 'pattern', 'minimum', 'maximum', 'minItems', 'maxItems', 'minLength', 'maxLength', 'data', 'enum', + 'filters'); + + $result = array(); + + // Anything that is in the `Items` attribute of an array *must* include it's name if available + if ($this->parent instanceof self && $this->parent->getType() == 'array' && isset($this->name)) { + $result['name'] = $this->name; + } + + foreach ($checks as $c) { + if ($value = $this->{$c}) { + $result[$c] = $value; + } + } + + if ($this->default !== null) { + $result['default'] = $this->default; + } + + if ($this->items !== null) { + $result['items'] = $this->getItems()->toArray(); + } + + if ($this->additionalProperties !== null) { + $result['additionalProperties'] = $this->getAdditionalProperties(); + if ($result['additionalProperties'] instanceof self) { + $result['additionalProperties'] = $result['additionalProperties']->toArray(); + } + } + + if ($this->type == 'object' && $this->properties) { + $result['properties'] = array(); + foreach ($this->getProperties() as $name => $property) { + $result['properties'][$name] = $property->toArray(); + } + } + + return $result; + } + + /** + * Get the default or static value of the command based on a value + * + * @param string $value Value that is currently set + * + * @return mixed Returns the value, a static value if one is present, or a default value + */ + public function getValue($value) + { + if ($this->static || ($this->default !== null && $value === null)) { + return $this->default; + } + + return $value; + } + + /** + * Run a value through the filters OR format attribute associated with the parameter + * + * @param mixed $value Value to filter + * + * @return mixed Returns the filtered value + */ + public function filter($value) + { + // Formats are applied exclusively and supersed filters + if ($this->format) { + return SchemaFormatter::format($this->format, $value); + } + + // Convert Boolean values + if ($this->type == 'boolean' && !is_bool($value)) { + $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); + } + + // Apply filters to the value + if ($this->filters) { + foreach ($this->filters as $filter) { + if (is_array($filter)) { + // Convert complex filters that hold value place holders + foreach ($filter['args'] as &$data) { + if ($data == '@value') { + $data = $value; + } elseif ($data == '@api') { + $data = $this; + } + } + $value = call_user_func_array($filter['method'], $filter['args']); + } else { + $value = call_user_func($filter, $value); + } + } + } + + return $value; + } + + /** + * Get the name of the parameter + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get the key of the parameter, where sentAs will supersede name if it is set + * + * @return string + */ + public function getWireName() + { + return $this->sentAs ?: $this->name; + } + + /** + * Set the name of the parameter + * + * @param string $name Name to set + * + * @return self + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get the type(s) of the parameter + * + * @return string|array + */ + public function getType() + { + return $this->type; + } + + /** + * Set the type(s) of the parameter + * + * @param string|array $type Type of parameter or array of simple types used in a union + * + * @return self + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * Get if the parameter is required + * + * @return bool + */ + public function getRequired() + { + return $this->required; + } + + /** + * Set if the parameter is required + * + * @param bool $isRequired Whether or not the parameter is required + * + * @return self + */ + public function setRequired($isRequired) + { + $this->required = (bool) $isRequired; + + return $this; + } + + /** + * Get the default value of the parameter + * + * @return string|null + */ + public function getDefault() + { + return $this->default; + } + + /** + * Set the default value of the parameter + * + * @param string|null $default Default value to set + * + * @return self + */ + public function setDefault($default) + { + $this->default = $default; + + return $this; + } + + /** + * Get the description of the parameter + * + * @return string|null + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set the description of the parameter + * + * @param string $description Description + * + * @return self + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Get the minimum acceptable value for an integer + * + * @return int|null + */ + public function getMinimum() + { + return $this->minimum; + } + + /** + * Set the minimum acceptable value for an integer + * + * @param int|null $min Minimum + * + * @return self + */ + public function setMinimum($min) + { + $this->minimum = $min; + + return $this; + } + + /** + * Get the maximum acceptable value for an integer + * + * @return int|null + */ + public function getMaximum() + { + return $this->maximum; + } + + /** + * Set the maximum acceptable value for an integer + * + * @param int $max Maximum + * + * @return self + */ + public function setMaximum($max) + { + $this->maximum = $max; + + return $this; + } + + /** + * Get the minimum allowed length of a string value + * + * @return int + */ + public function getMinLength() + { + return $this->minLength; + } + + /** + * Set the minimum allowed length of a string value + * + * @param int|null $min Minimum + * + * @return self + */ + public function setMinLength($min) + { + $this->minLength = $min; + + return $this; + } + + /** + * Get the maximum allowed length of a string value + * + * @return int|null + */ + public function getMaxLength() + { + return $this->maxLength; + } + + /** + * Set the maximum allowed length of a string value + * + * @param int $max Maximum length + * + * @return self + */ + public function setMaxLength($max) + { + $this->maxLength = $max; + + return $this; + } + + /** + * Get the maximum allowed number of items in an array value + * + * @return int|null + */ + public function getMaxItems() + { + return $this->maxItems; + } + + /** + * Set the maximum allowed number of items in an array value + * + * @param int $max Maximum + * + * @return self + */ + public function setMaxItems($max) + { + $this->maxItems = $max; + + return $this; + } + + /** + * Get the minimum allowed number of items in an array value + * + * @return int + */ + public function getMinItems() + { + return $this->minItems; + } + + /** + * Set the minimum allowed number of items in an array value + * + * @param int|null $min Minimum + * + * @return self + */ + public function setMinItems($min) + { + $this->minItems = $min; + + return $this; + } + + /** + * Get the location of the parameter + * + * @return string|null + */ + public function getLocation() + { + return $this->location; + } + + /** + * Set the location of the parameter + * + * @param string|null $location Location of the parameter + * + * @return self + */ + public function setLocation($location) + { + $this->location = $location; + + return $this; + } + + /** + * Get the sentAs attribute of the parameter that used with locations to sentAs an attribute when it is being + * applied to a location. + * + * @return string|null + */ + public function getSentAs() + { + return $this->sentAs; + } + + /** + * Set the sentAs attribute + * + * @param string|null $name Name of the value as it is sent over the wire + * + * @return self + */ + public function setSentAs($name) + { + $this->sentAs = $name; + + return $this; + } + + /** + * Retrieve a known property from the parameter by name or a data property by name. When not specific name value + * is specified, all data properties will be returned. + * + * @param string|null $name Specify a particular property name to retrieve + * + * @return array|mixed|null + */ + public function getData($name = null) + { + if (!$name) { + return $this->data; + } + + if (isset($this->data[$name])) { + return $this->data[$name]; + } elseif (isset($this->{$name})) { + return $this->{$name}; + } + + return null; + } + + /** + * Set the extra data properties of the parameter or set a specific extra property + * + * @param string|array|null $nameOrData The name of a specific extra to set or an array of extras to set + * @param mixed|null $data When setting a specific extra property, specify the data to set for it + * + * @return self + */ + public function setData($nameOrData, $data = null) + { + if (is_array($nameOrData)) { + $this->data = $nameOrData; + } else { + $this->data[$nameOrData] = $data; + } + + return $this; + } + + /** + * Get whether or not the default value can be changed + * + * @return mixed|null + */ + public function getStatic() + { + return $this->static; + } + + /** + * Set to true if the default value cannot be changed + * + * @param bool $static True or false + * + * @return self + */ + public function setStatic($static) + { + $this->static = (bool) $static; + + return $this; + } + + /** + * Get an array of filters used by the parameter + * + * @return array + */ + public function getFilters() + { + return $this->filters ?: array(); + } + + /** + * Set the array of filters used by the parameter + * + * @param array $filters Array of functions to use as filters + * + * @return self + */ + public function setFilters(array $filters) + { + $this->filters = array(); + foreach ($filters as $filter) { + $this->addFilter($filter); + } + + return $this; + } + + /** + * Add a filter to the parameter + * + * @param string|array $filter Method to filter the value through + * + * @return self + * @throws InvalidArgumentException + */ + public function addFilter($filter) + { + if (is_array($filter)) { + if (!isset($filter['method'])) { + throw new InvalidArgumentException('A [method] value must be specified for each complex filter'); + } + } + + if (!$this->filters) { + $this->filters = array($filter); + } else { + $this->filters[] = $filter; + } + + return $this; + } + + /** + * Get the parent object (an {@see OperationInterface} or {@see Parameter} + * + * @return OperationInterface|Parameter|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * Set the parent object of the parameter + * + * @param OperationInterface|Parameter|null $parent Parent container of the parameter + * + * @return self + */ + public function setParent($parent) + { + $this->parent = $parent; + + return $this; + } + + /** + * Get the properties of the parameter + * + * @return array + */ + public function getProperties() + { + if (!$this->propertiesCache) { + $this->propertiesCache = array(); + foreach (array_keys($this->properties) as $name) { + $this->propertiesCache[$name] = $this->getProperty($name); + } + } + + return $this->propertiesCache; + } + + /** + * Get a specific property from the parameter + * + * @param string $name Name of the property to retrieve + * + * @return null|Parameter + */ + public function getProperty($name) + { + if (!isset($this->properties[$name])) { + return null; + } + + if (!($this->properties[$name] instanceof self)) { + $this->properties[$name]['name'] = $name; + $this->properties[$name] = new static($this->properties[$name], $this->serviceDescription); + $this->properties[$name]->setParent($this); + } + + return $this->properties[$name]; + } + + /** + * Remove a property from the parameter + * + * @param string $name Name of the property to remove + * + * @return self + */ + public function removeProperty($name) + { + unset($this->properties[$name]); + $this->propertiesCache = null; + + return $this; + } + + /** + * Add a property to the parameter + * + * @param Parameter $property Properties to set + * + * @return self + */ + public function addProperty(Parameter $property) + { + $this->properties[$property->getName()] = $property; + $property->setParent($this); + $this->propertiesCache = null; + + return $this; + } + + /** + * Get the additionalProperties value of the parameter + * + * @return bool|Parameter|null + */ + public function getAdditionalProperties() + { + if (is_array($this->additionalProperties)) { + $this->additionalProperties = new static($this->additionalProperties, $this->serviceDescription); + $this->additionalProperties->setParent($this); + } + + return $this->additionalProperties; + } + + /** + * Set the additionalProperties value of the parameter + * + * @param bool|Parameter|null $additional Boolean to allow any, an Parameter to specify a schema, or false to disallow + * + * @return self + */ + public function setAdditionalProperties($additional) + { + $this->additionalProperties = $additional; + + return $this; + } + + /** + * Set the items data of the parameter + * + * @param Parameter|null $items Items to set + * + * @return self + */ + public function setItems(Parameter $items = null) + { + if ($this->items = $items) { + $this->items->setParent($this); + } + + return $this; + } + + /** + * Get the item data of the parameter + * + * @return Parameter|null + */ + public function getItems() + { + if (is_array($this->items)) { + $this->items = new static($this->items, $this->serviceDescription); + $this->items->setParent($this); + } + + return $this->items; + } + + /** + * Get the class that the parameter must implement + * + * @return null|string + */ + public function getInstanceOf() + { + return $this->instanceOf; + } + + /** + * Set the class that the parameter must be an instance of + * + * @param string|null $instanceOf Class or interface name + * + * @return self + */ + public function setInstanceOf($instanceOf) + { + $this->instanceOf = $instanceOf; + + return $this; + } + + /** + * Get the enum of strings that are valid for the parameter + * + * @return array|null + */ + public function getEnum() + { + return $this->enum; + } + + /** + * Set the enum of strings that are valid for the parameter + * + * @param array|null $enum Array of strings or null + * + * @return self + */ + public function setEnum(array $enum = null) + { + $this->enum = $enum; + + return $this; + } + + /** + * Get the regex pattern that must match a value when the value is a string + * + * @return string + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Set the regex pattern that must match a value when the value is a string + * + * @param string $pattern Regex pattern + * + * @return self + */ + public function setPattern($pattern) + { + $this->pattern = $pattern; + + return $this; + } + + /** + * Get the format attribute of the schema + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Set the format attribute of the schema + * + * @param string $format Format to set (e.g. date, date-time, timestamp, time, date-time-http) + * + * @return self + */ + public function setFormat($format) + { + $this->format = $format; + + return $this; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php new file mode 100644 index 000000000..7f47fc9d7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php @@ -0,0 +1,156 @@ +setTimezone(self::getUtcTimeZone())->format($format); + } + + throw new InvalidArgumentException('Date/Time values must be either a string, integer, or DateTime object'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php new file mode 100644 index 000000000..b045422d4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php @@ -0,0 +1,291 @@ +castIntegerToStringType = $castIntegerToStringType; + } + + public function validate(Parameter $param, &$value) + { + $this->errors = array(); + $this->recursiveProcess($param, $value); + + if (empty($this->errors)) { + return true; + } else { + sort($this->errors); + return false; + } + } + + /** + * Get the errors encountered while validating + * + * @return array + */ + public function getErrors() + { + return $this->errors ?: array(); + } + + /** + * Recursively validate a parameter + * + * @param Parameter $param API parameter being validated + * @param mixed $value Value to validate and validate. The value may change during this validate. + * @param string $path Current validation path (used for error reporting) + * @param int $depth Current depth in the validation validate + * + * @return bool Returns true if valid, or false if invalid + */ + protected function recursiveProcess(Parameter $param, &$value, $path = '', $depth = 0) + { + // Update the value by adding default or static values + $value = $param->getValue($value); + + $required = $param->getRequired(); + // if the value is null and the parameter is not required or is static, then skip any further recursion + if ((null === $value && !$required) || $param->getStatic()) { + return true; + } + + $type = $param->getType(); + // Attempt to limit the number of times is_array is called by tracking if the value is an array + $valueIsArray = is_array($value); + // If a name is set then update the path so that validation messages are more helpful + if ($name = $param->getName()) { + $path .= "[{$name}]"; + } + + if ($type == 'object') { + + // Objects are either associative arrays, ToArrayInterface, or some other object + if ($param->getInstanceOf()) { + $instance = $param->getInstanceOf(); + if (!($value instanceof $instance)) { + $this->errors[] = "{$path} must be an instance of {$instance}"; + return false; + } + } + + // Determine whether or not this "value" has properties and should be traversed + $traverse = $temporaryValue = false; + + // Convert the value to an array + if (!$valueIsArray && $value instanceof ToArrayInterface) { + $value = $value->toArray(); + } + + if ($valueIsArray) { + // Ensure that the array is associative and not numerically indexed + if (isset($value[0])) { + $this->errors[] = "{$path} must be an array of properties. Got a numerically indexed array."; + return false; + } + $traverse = true; + } elseif ($value === null) { + // Attempt to let the contents be built up by default values if possible + $value = array(); + $temporaryValue = $valueIsArray = $traverse = true; + } + + if ($traverse) { + + if ($properties = $param->getProperties()) { + // if properties were found, the validate each property of the value + foreach ($properties as $property) { + $name = $property->getName(); + if (isset($value[$name])) { + $this->recursiveProcess($property, $value[$name], $path, $depth + 1); + } else { + $current = null; + $this->recursiveProcess($property, $current, $path, $depth + 1); + // Only set the value if it was populated with something + if (null !== $current) { + $value[$name] = $current; + } + } + } + } + + $additional = $param->getAdditionalProperties(); + if ($additional !== true) { + // If additional properties were found, then validate each against the additionalProperties attr. + $keys = array_keys($value); + // Determine the keys that were specified that were not listed in the properties of the schema + $diff = array_diff($keys, array_keys($properties)); + if (!empty($diff)) { + // Determine which keys are not in the properties + if ($additional instanceOf Parameter) { + foreach ($diff as $key) { + $this->recursiveProcess($additional, $value[$key], "{$path}[{$key}]", $depth); + } + } else { + // if additionalProperties is set to false and there are additionalProperties in the values, then fail + foreach ($diff as $prop) { + $this->errors[] = sprintf('%s[%s] is not an allowed property', $path, $prop); + } + } + } + } + + // A temporary value will be used to traverse elements that have no corresponding input value. + // This allows nested required parameters with default values to bubble up into the input. + // Here we check if we used a temp value and nothing bubbled up, then we need to remote the value. + if ($temporaryValue && empty($value)) { + $value = null; + $valueIsArray = false; + } + } + + } elseif ($type == 'array' && $valueIsArray && $param->getItems()) { + foreach ($value as $i => &$item) { + // Validate each item in an array against the items attribute of the schema + $this->recursiveProcess($param->getItems(), $item, $path . "[{$i}]", $depth + 1); + } + } + + // If the value is required and the type is not null, then there is an error if the value is not set + if ($required && $value === null && $type != 'null') { + $message = "{$path} is " . ($param->getType() ? ('a required ' . implode(' or ', (array) $param->getType())) : 'required'); + if ($param->getDescription()) { + $message .= ': ' . $param->getDescription(); + } + $this->errors[] = $message; + return false; + } + + // Validate that the type is correct. If the type is string but an integer was passed, the class can be + // instructed to cast the integer to a string to pass validation. This is the default behavior. + if ($type && (!$type = $this->determineType($type, $value))) { + if ($this->castIntegerToStringType && $param->getType() == 'string' && is_integer($value)) { + $value = (string) $value; + } else { + $this->errors[] = "{$path} must be of type " . implode(' or ', (array) $param->getType()); + } + } + + // Perform type specific validation for strings, arrays, and integers + if ($type == 'string') { + + // Strings can have enums which are a list of predefined values + if (($enum = $param->getEnum()) && !in_array($value, $enum)) { + $this->errors[] = "{$path} must be one of " . implode(' or ', array_map(function ($s) { + return '"' . addslashes($s) . '"'; + }, $enum)); + } + // Strings can have a regex pattern that the value must match + if (($pattern = $param->getPattern()) && !preg_match($pattern, $value)) { + $this->errors[] = "{$path} must match the following regular expression: {$pattern}"; + } + + $strLen = null; + if ($min = $param->getMinLength()) { + $strLen = strlen($value); + if ($strLen < $min) { + $this->errors[] = "{$path} length must be greater than or equal to {$min}"; + } + } + if ($max = $param->getMaxLength()) { + if (($strLen ?: strlen($value)) > $max) { + $this->errors[] = "{$path} length must be less than or equal to {$max}"; + } + } + + } elseif ($type == 'array') { + + $size = null; + if ($min = $param->getMinItems()) { + $size = count($value); + if ($size < $min) { + $this->errors[] = "{$path} must contain {$min} or more elements"; + } + } + if ($max = $param->getMaxItems()) { + if (($size ?: count($value)) > $max) { + $this->errors[] = "{$path} must contain {$max} or fewer elements"; + } + } + + } elseif ($type == 'integer' || $type == 'number' || $type == 'numeric') { + if (($min = $param->getMinimum()) && $value < $min) { + $this->errors[] = "{$path} must be greater than or equal to {$min}"; + } + if (($max = $param->getMaximum()) && $value > $max) { + $this->errors[] = "{$path} must be less than or equal to {$max}"; + } + } + + return empty($this->errors); + } + + /** + * From the allowable types, determine the type that the variable matches + * + * @param string $type Parameter type + * @param mixed $value Value to determine the type + * + * @return string|bool Returns the matching type on + */ + protected function determineType($type, $value) + { + foreach ((array) $type as $t) { + if ($t == 'string' && (is_string($value) || (is_object($value) && method_exists($value, '__toString')))) { + return 'string'; + } elseif ($t == 'object' && (is_array($value) || is_object($value))) { + return 'object'; + } elseif ($t == 'array' && is_array($value)) { + return 'array'; + } elseif ($t == 'integer' && is_integer($value)) { + return 'integer'; + } elseif ($t == 'boolean' && is_bool($value)) { + return 'boolean'; + } elseif ($t == 'number' && is_numeric($value)) { + return 'number'; + } elseif ($t == 'numeric' && is_numeric($value)) { + return 'numeric'; + } elseif ($t == 'null' && !$value) { + return 'null'; + } elseif ($t == 'any') { + return 'any'; + } + } + + return false; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php new file mode 100644 index 000000000..286e65eec --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php @@ -0,0 +1,271 @@ +load($config, $options); + } + + /** + * @param array $config Array of configuration data + */ + public function __construct(array $config = array()) + { + $this->fromArray($config); + } + + public function serialize() + { + return json_encode($this->toArray()); + } + + public function unserialize($json) + { + $this->operations = array(); + $this->fromArray(json_decode($json, true)); + } + + public function toArray() + { + $result = array( + 'name' => $this->name, + 'apiVersion' => $this->apiVersion, + 'baseUrl' => $this->baseUrl, + 'description' => $this->description + ) + $this->extraData; + $result['operations'] = array(); + foreach ($this->getOperations() as $name => $operation) { + $result['operations'][$operation->getName() ?: $name] = $operation->toArray(); + } + if (!empty($this->models)) { + $result['models'] = array(); + foreach ($this->models as $id => $model) { + $result['models'][$id] = $model instanceof Parameter ? $model->toArray(): $model; + } + } + + return array_filter($result); + } + + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Set the baseUrl of the description + * + * @param string $baseUrl Base URL of each operation + * + * @return self + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = $baseUrl; + + return $this; + } + + public function getOperations() + { + foreach (array_keys($this->operations) as $name) { + $this->getOperation($name); + } + + return $this->operations; + } + + public function hasOperation($name) + { + return isset($this->operations[$name]); + } + + public function getOperation($name) + { + // Lazily retrieve and build operations + if (!isset($this->operations[$name])) { + return null; + } + + if (!($this->operations[$name] instanceof Operation)) { + $this->operations[$name] = new Operation($this->operations[$name], $this); + } + + return $this->operations[$name]; + } + + /** + * Add a operation to the service description + * + * @param OperationInterface $operation Operation to add + * + * @return self + */ + public function addOperation(OperationInterface $operation) + { + $this->operations[$operation->getName()] = $operation->setServiceDescription($this); + + return $this; + } + + public function getModel($id) + { + if (!isset($this->models[$id])) { + return null; + } + + if (!($this->models[$id] instanceof Parameter)) { + $this->models[$id] = new Parameter($this->models[$id] + array('name' => $id), $this); + } + + return $this->models[$id]; + } + + public function getModels() + { + // Ensure all models are converted into parameter objects + foreach (array_keys($this->models) as $id) { + $this->getModel($id); + } + + return $this->models; + } + + public function hasModel($id) + { + return isset($this->models[$id]); + } + + /** + * Add a model to the service description + * + * @param Parameter $model Model to add + * + * @return self + */ + public function addModel(Parameter $model) + { + $this->models[$model->getName()] = $model; + + return $this; + } + + public function getApiVersion() + { + return $this->apiVersion; + } + + public function getName() + { + return $this->name; + } + + public function getDescription() + { + return $this->description; + } + + public function getData($key) + { + return isset($this->extraData[$key]) ? $this->extraData[$key] : null; + } + + public function setData($key, $value) + { + $this->extraData[$key] = $value; + + return $this; + } + + /** + * Initialize the state from an array + * + * @param array $config Configuration data + * @throws InvalidArgumentException + */ + protected function fromArray(array $config) + { + // Keep a list of default keys used in service descriptions that is later used to determine extra data keys + static $defaultKeys = array('name', 'models', 'apiVersion', 'baseUrl', 'description'); + // Pull in the default configuration values + foreach ($defaultKeys as $key) { + if (isset($config[$key])) { + $this->{$key} = $config[$key]; + } + } + + // Account for the Swagger name for Guzzle's baseUrl + if (isset($config['basePath'])) { + $this->baseUrl = $config['basePath']; + } + + // Ensure that the models and operations properties are always arrays + $this->models = (array) $this->models; + $this->operations = (array) $this->operations; + + // We want to add operations differently than adding the other properties + $defaultKeys[] = 'operations'; + + // Create operations for each operation + if (isset($config['operations'])) { + foreach ($config['operations'] as $name => $operation) { + if (!($operation instanceof Operation) && !is_array($operation)) { + throw new InvalidArgumentException('Invalid operation in service description: ' + . gettype($operation)); + } + $this->operations[$name] = $operation; + } + } + + // Get all of the additional properties of the service description and store them in a data array + foreach (array_diff(array_keys($config), $defaultKeys) as $key) { + $this->extraData[$key] = $config[$key]; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php new file mode 100644 index 000000000..5983e586b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php @@ -0,0 +1,106 @@ + $op) { + $name = $op['name'] = isset($op['name']) ? $op['name'] : $name; + // Extend other operations + if (!empty($op['extends'])) { + $this->resolveExtension($name, $op, $operations); + } + $op['parameters'] = isset($op['parameters']) ? $op['parameters'] : array(); + $operations[$name] = $op; + } + } + + return new ServiceDescription(array( + 'apiVersion' => isset($config['apiVersion']) ? $config['apiVersion'] : null, + 'baseUrl' => isset($config['baseUrl']) ? $config['baseUrl'] : null, + 'description' => isset($config['description']) ? $config['description'] : null, + 'operations' => $operations, + 'models' => isset($config['models']) ? $config['models'] : null + ) + $config); + } + + /** + * @param string $name Name of the operation + * @param array $op Operation value array + * @param array $operations Currently loaded operations + * @throws DescriptionBuilderException when extending a non-existent operation + */ + protected function resolveExtension($name, array &$op, array &$operations) + { + $resolved = array(); + $original = empty($op['parameters']) ? false: $op['parameters']; + $hasClass = !empty($op['class']); + foreach ((array) $op['extends'] as $extendedCommand) { + if (empty($operations[$extendedCommand])) { + throw new DescriptionBuilderException("{$name} extends missing operation {$extendedCommand}"); + } + $toArray = $operations[$extendedCommand]; + $resolved = empty($resolved) + ? $toArray['parameters'] + : array_merge($resolved, $toArray['parameters']); + + $op = $op + $toArray; + if (!$hasClass && isset($toArray['class'])) { + $op['class'] = $toArray['class']; + } + } + $op['parameters'] = $original ? array_merge($resolved, $original) : $resolved; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php new file mode 100644 index 000000000..94ca77da4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php @@ -0,0 +1,28 @@ +getMessage(), $e->getCode(), $e->getPrevious()); + $ce->setSuccessfulRequests($e->getSuccessfulRequests()); + + $alreadyAddedExceptions = array(); + foreach ($e->getFailedRequests() as $request) { + if ($re = $e->getExceptionForFailedRequest($request)) { + $alreadyAddedExceptions[] = $re; + $ce->addFailedRequestWithException($request, $re); + } else { + $ce->addFailedRequest($request); + } + } + + // Add any exceptions that did not map to a request + if (count($alreadyAddedExceptions) < count($e)) { + foreach ($e as $ex) { + if (!in_array($ex, $alreadyAddedExceptions)) { + $ce->add($ex); + } + } + } + + return $ce; + } + + /** + * Get all of the commands in the transfer + * + * @return array + */ + public function getAllCommands() + { + return array_merge($this->successfulCommands, $this->failedCommands); + } + + /** + * Add to the array of successful commands + * + * @param CommandInterface $command Successful command + * + * @return self + */ + public function addSuccessfulCommand(CommandInterface $command) + { + $this->successfulCommands[] = $command; + + return $this; + } + + /** + * Add to the array of failed commands + * + * @param CommandInterface $command Failed command + * + * @return self + */ + public function addFailedCommand(CommandInterface $command) + { + $this->failedCommands[] = $command; + + return $this; + } + + /** + * Get an array of successful commands + * + * @return array + */ + public function getSuccessfulCommands() + { + return $this->successfulCommands; + } + + /** + * Get an array of failed commands + * + * @return array + */ + public function getFailedCommands() + { + return $this->failedCommands; + } + + /** + * Get the Exception that caused the given $command to fail + * + * @param CommandInterface $command Failed command + * + * @return \Exception|null + */ + public function getExceptionForFailedCommand(CommandInterface $command) + { + return $this->getExceptionForFailedRequest($command->getRequest()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php new file mode 100644 index 000000000..1407e5687 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php @@ -0,0 +1,7 @@ +invalidCommands = $commands; + parent::__construct( + 'Encountered commands in a batch transfer that use inconsistent clients. The batching ' . + 'strategy you use with a command transfer must divide command batches by client.' + ); + } + + /** + * Get the invalid commands + * + * @return array + */ + public function getCommands() + { + return $this->invalidCommands; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php new file mode 100644 index 000000000..d59ff2185 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php @@ -0,0 +1,9 @@ +errors = $errors; + } + + /** + * Get any validation errors + * + * @return array + */ + public function getErrors() + { + return $this->errors; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php new file mode 100644 index 000000000..21140e772 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php @@ -0,0 +1,37 @@ +canBuild($command)) { + throw new InvalidArgumentException('Iterator was not found for ' . $command->getName()); + } + + $className = $this->getClassName($command); + + return new $className($command, $options); + } + + public function canBuild(CommandInterface $command) + { + return (bool) $this->getClassName($command); + } + + /** + * Get the name of the class to instantiate for the command + * + * @param CommandInterface $command Command that is associated with the iterator + * + * @return string + */ + abstract protected function getClassName(CommandInterface $command); +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php new file mode 100644 index 000000000..2efc133c6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php @@ -0,0 +1,67 @@ +factories = $factories; + } + + public function build(CommandInterface $command, array $options = array()) + { + if (!($factory = $this->getFactory($command))) { + throw new InvalidArgumentException('Iterator was not found for ' . $command->getName()); + } + + return $factory->build($command, $options); + } + + public function canBuild(CommandInterface $command) + { + return $this->getFactory($command) !== false; + } + + /** + * Add a factory to the composite factory + * + * @param ResourceIteratorFactoryInterface $factory Factory to add + * + * @return self + */ + public function addFactory(ResourceIteratorFactoryInterface $factory) + { + $this->factories[] = $factory; + + return $this; + } + + /** + * Get the factory that matches the command object + * + * @param CommandInterface $command Command retrieving the iterator for + * + * @return ResourceIteratorFactoryInterface|bool + */ + protected function getFactory(CommandInterface $command) + { + foreach ($this->factories as $factory) { + if ($factory->canBuild($command)) { + return $factory; + } + } + + return false; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php new file mode 100644 index 000000000..c71ca9d85 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php @@ -0,0 +1,34 @@ +map = $map; + } + + public function getClassName(CommandInterface $command) + { + $className = $command->getName(); + + if (isset($this->map[$className])) { + return $this->map[$className]; + } elseif (isset($this->map['*'])) { + // If a wildcard was added, then always use that + return $this->map['*']; + } + + return null; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php new file mode 100644 index 000000000..2322434a5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php @@ -0,0 +1,64 @@ +data = $data; + $this->structure = $structure; + } + + /** + * Get the structure of the model + * + * @return Parameter + */ + public function getStructure() + { + return $this->structure ?: new Parameter(); + } + + /** + * Provides debug information about the model object + * + * @return string + */ + public function __toString() + { + $output = 'Debug output of '; + if ($this->structure) { + $output .= $this->structure->getName() . ' '; + } + $output .= 'model'; + $output = str_repeat('=', strlen($output)) . "\n" . $output . "\n" . str_repeat('=', strlen($output)) . "\n\n"; + $output .= "Model data\n-----------\n\n"; + $output .= "This data can be retrieved from the model object using the get() method of the model " + . "(e.g. \$model->get(\$key)) or accessing the model like an associative array (e.g. \$model['key']).\n\n"; + $lines = array_slice(explode("\n", trim(print_r($this->toArray(), true))), 2, -1); + $output .= implode("\n", $lines); + + if ($this->structure) { + $output .= "\n\nModel structure\n---------------\n\n"; + $output .= "The following JSON document defines how the model was parsed from an HTTP response into the " + . "associative array structure you see above.\n\n"; + $output .= ' ' . json_encode($this->structure->toArray()) . "\n\n"; + } + + return $output . "\n"; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php new file mode 100644 index 000000000..e14152432 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php @@ -0,0 +1,254 @@ +originalCommand = $command; + + // Parse options from the array of options + $this->data = $data; + $this->limit = array_key_exists('limit', $data) ? $data['limit'] : 0; + $this->pageSize = array_key_exists('page_size', $data) ? $data['page_size'] : false; + } + + /** + * Get all of the resources as an array (Warning: this could issue a large number of requests) + * + * @return array + */ + public function toArray() + { + return iterator_to_array($this, false); + } + + public function setLimit($limit) + { + $this->limit = $limit; + $this->resetState(); + + return $this; + } + + public function setPageSize($pageSize) + { + $this->pageSize = $pageSize; + $this->resetState(); + + return $this; + } + + /** + * Get an option from the iterator + * + * @param string $key Key of the option to retrieve + * + * @return mixed|null Returns NULL if not set or the value if set + */ + public function get($key) + { + return array_key_exists($key, $this->data) ? $this->data[$key] : null; + } + + /** + * Set an option on the iterator + * + * @param string $key Key of the option to set + * @param mixed $value Value to set for the option + * + * @return ResourceIterator + */ + public function set($key, $value) + { + $this->data[$key] = $value; + + return $this; + } + + public function current() + { + return $this->resources ? current($this->resources) : false; + } + + public function key() + { + return max(0, $this->iteratedCount - 1); + } + + public function count() + { + return $this->retrievedCount; + } + + /** + * Get the total number of requests sent + * + * @return int + */ + public function getRequestCount() + { + return $this->requestCount; + } + + /** + * Rewind the Iterator to the first element and send the original command + */ + public function rewind() + { + // Use the original command + $this->command = clone $this->originalCommand; + $this->resetState(); + $this->next(); + } + + public function valid() + { + return !$this->invalid && (!$this->resources || $this->current() || $this->nextToken) + && (!$this->limit || $this->iteratedCount < $this->limit + 1); + } + + public function next() + { + $this->iteratedCount++; + + // Check if a new set of resources needs to be retrieved + $sendRequest = false; + if (!$this->resources) { + $sendRequest = true; + } else { + // iterate over the internal array + $current = next($this->resources); + $sendRequest = $current === false && $this->nextToken && (!$this->limit || $this->iteratedCount < $this->limit + 1); + } + + if ($sendRequest) { + + $this->dispatch('resource_iterator.before_send', array( + 'iterator' => $this, + 'resources' => $this->resources + )); + + // Get a new command object from the original command + $this->command = clone $this->originalCommand; + // Send a request and retrieve the newly loaded resources + $this->resources = $this->sendRequest(); + $this->requestCount++; + + // If no resources were found, then the last request was not needed + // and iteration must stop + if (empty($this->resources)) { + $this->invalid = true; + } else { + // Add to the number of retrieved resources + $this->retrievedCount += count($this->resources); + // Ensure that we rewind to the beginning of the array + reset($this->resources); + } + + $this->dispatch('resource_iterator.after_send', array( + 'iterator' => $this, + 'resources' => $this->resources + )); + } + } + + /** + * Retrieve the NextToken that can be used in other iterators. + * + * @return string Returns a NextToken + */ + public function getNextToken() + { + return $this->nextToken; + } + + /** + * Returns the value that should be specified for the page size for a request that will maintain any hard limits, + * but still honor the specified pageSize if the number of items retrieved + pageSize < hard limit + * + * @return int Returns the page size of the next request. + */ + protected function calculatePageSize() + { + if ($this->limit && $this->iteratedCount + $this->pageSize > $this->limit) { + return 1 + ($this->limit - $this->iteratedCount); + } + + return (int) $this->pageSize; + } + + /** + * Reset the internal state of the iterator without triggering a rewind() + */ + protected function resetState() + { + $this->iteratedCount = 0; + $this->retrievedCount = 0; + $this->nextToken = false; + $this->resources = null; + $this->invalid = false; + } + + /** + * Send a request to retrieve the next page of results. Hook for subclasses to implement. + * + * @return array Returns the newly loaded resources + */ + abstract protected function sendRequest(); +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php new file mode 100644 index 000000000..6aa36153f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php @@ -0,0 +1,111 @@ +iterator = $iterator; + $this->callback = $callback; + Version::warn(__CLASS__ . ' is deprecated'); + } + + /** + * Apply the callback to the contents of the resource iterator + * + * @param int $perBatch The number of records to group per batch transfer + * + * @return int Returns the number of iterated resources + */ + public function apply($perBatch = 50) + { + $this->iterated = $this->batches = $batches = 0; + $that = $this; + $it = $this->iterator; + $callback = $this->callback; + + $batch = BatchBuilder::factory() + ->createBatchesWith(new BatchSizeDivisor($perBatch)) + ->transferWith(new BatchClosureTransfer(function (array $batch) use ($that, $callback, &$batches, $it) { + $batches++; + $that->dispatch('iterator_batch.before_batch', array('iterator' => $it, 'batch' => $batch)); + call_user_func_array($callback, array($it, $batch)); + $that->dispatch('iterator_batch.after_batch', array('iterator' => $it, 'batch' => $batch)); + })) + ->autoFlushAt($perBatch) + ->build(); + + $this->dispatch('iterator_batch.created_batch', array('batch' => $batch)); + + foreach ($this->iterator as $resource) { + $this->iterated++; + $batch->add($resource); + } + + $batch->flush(); + $this->batches = $batches; + + return $this->iterated; + } + + /** + * Get the total number of batches sent + * + * @return int + */ + public function getBatchCount() + { + return $this->batches; + } + + /** + * Get the total number of iterated resources + * + * @return int + */ + public function getIteratedCount() + { + return $this->iterated; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php new file mode 100644 index 000000000..2fd998071 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php @@ -0,0 +1,60 @@ + AbcFoo). + */ +class ResourceIteratorClassFactory extends AbstractResourceIteratorFactory +{ + /** @var array List of namespaces used to look for classes */ + protected $namespaces; + + /** @var InflectorInterface Inflector used to determine class names */ + protected $inflector; + + /** + * @param string|array $namespaces List of namespaces for iterator objects + * @param InflectorInterface $inflector Inflector used to resolve class names + */ + public function __construct($namespaces = array(), InflectorInterface $inflector = null) + { + $this->namespaces = (array) $namespaces; + $this->inflector = $inflector ?: Inflector::getDefault(); + } + + /** + * Registers a namespace to check for Iterators + * + * @param string $namespace Namespace which contains Iterator classes + * + * @return self + */ + public function registerNamespace($namespace) + { + array_unshift($this->namespaces, $namespace); + + return $this; + } + + protected function getClassName(CommandInterface $command) + { + $iteratorName = $this->inflector->camel($command->getName()) . 'Iterator'; + + // Determine the name of the class to load + foreach ($this->namespaces as $namespace) { + $potentialClassName = $namespace . '\\' . $iteratorName; + if (class_exists($potentialClassName)) { + return $potentialClassName; + } + } + + return false; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php new file mode 100644 index 000000000..8b4e8dbe0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php @@ -0,0 +1,30 @@ +=5.3.2", + "guzzle/cache": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Service": "" } + }, + "target-dir": "Guzzle/Service", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php new file mode 100644 index 000000000..d115fd890 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php @@ -0,0 +1,284 @@ +contextOptions = stream_context_get_options($context); + $this->context = $context; + } elseif (is_array($context) || !$context) { + $this->contextOptions = $context; + $this->createContext($params); + } elseif ($context) { + throw new InvalidArgumentException('$context must be an array or resource'); + } + + // Dispatch the before send event + $request->dispatch('request.before_send', array( + 'request' => $request, + 'context' => $this->context, + 'context_options' => $this->contextOptions + )); + + $this->setUrl($request); + $this->addDefaultContextOptions($request); + $this->addSslOptions($request); + $this->addBodyOptions($request); + $this->addProxyOptions($request); + + // Create the file handle but silence errors + return $this->createStream($params) + ->setCustomData('request', $request) + ->setCustomData('response_headers', $this->getLastResponseHeaders()); + } + + /** + * Set an option on the context and the internal options array + * + * @param string $wrapper Stream wrapper name of http + * @param string $name Context name + * @param mixed $value Context value + * @param bool $overwrite Set to true to overwrite an existing value + */ + protected function setContextValue($wrapper, $name, $value, $overwrite = false) + { + if (!isset($this->contextOptions[$wrapper])) { + $this->contextOptions[$wrapper] = array($name => $value); + } elseif (!$overwrite && isset($this->contextOptions[$wrapper][$name])) { + return; + } + $this->contextOptions[$wrapper][$name] = $value; + stream_context_set_option($this->context, $wrapper, $name, $value); + } + + /** + * Create a stream context + * + * @param array $params Parameter array + */ + protected function createContext(array $params) + { + $options = $this->contextOptions; + $this->context = $this->createResource(function () use ($params, $options) { + return stream_context_create($options, $params); + }); + } + + /** + * Get the last response headers received by the HTTP request + * + * @return array + */ + public function getLastResponseHeaders() + { + return $this->lastResponseHeaders; + } + + /** + * Adds the default context options to the stream context options + * + * @param RequestInterface $request Request + */ + protected function addDefaultContextOptions(RequestInterface $request) + { + $this->setContextValue('http', 'method', $request->getMethod()); + $headers = $request->getHeaderLines(); + + // "Connection: close" is required to get streams to work in HTTP 1.1 + if (!$request->hasHeader('Connection')) { + $headers[] = 'Connection: close'; + } + + $this->setContextValue('http', 'header', $headers); + $this->setContextValue('http', 'protocol_version', $request->getProtocolVersion()); + $this->setContextValue('http', 'ignore_errors', true); + } + + /** + * Set the URL to use with the factory + * + * @param RequestInterface $request Request that owns the URL + */ + protected function setUrl(RequestInterface $request) + { + $this->url = $request->getUrl(true); + + // Check for basic Auth username + if ($request->getUsername()) { + $this->url->setUsername($request->getUsername()); + } + + // Check for basic Auth password + if ($request->getPassword()) { + $this->url->setPassword($request->getPassword()); + } + } + + /** + * Add SSL options to the stream context + * + * @param RequestInterface $request Request + */ + protected function addSslOptions(RequestInterface $request) + { + if ($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)) { + $this->setContextValue('ssl', 'verify_peer', true, true); + if ($cafile = $request->getCurlOptions()->get(CURLOPT_CAINFO)) { + $this->setContextValue('ssl', 'cafile', $cafile, true); + } + } else { + $this->setContextValue('ssl', 'verify_peer', false, true); + } + } + + /** + * Add body (content) specific options to the context options + * + * @param RequestInterface $request + */ + protected function addBodyOptions(RequestInterface $request) + { + // Add the content for the request if needed + if (!($request instanceof EntityEnclosingRequestInterface)) { + return; + } + + if (count($request->getPostFields())) { + $this->setContextValue('http', 'content', (string) $request->getPostFields(), true); + } elseif ($request->getBody()) { + $this->setContextValue('http', 'content', (string) $request->getBody(), true); + } + + // Always ensure a content-length header is sent + if (isset($this->contextOptions['http']['content'])) { + $headers = isset($this->contextOptions['http']['header']) ? $this->contextOptions['http']['header'] : array(); + $headers[] = 'Content-Length: ' . strlen($this->contextOptions['http']['content']); + $this->setContextValue('http', 'header', $headers, true); + } + } + + /** + * Add proxy parameters to the context if needed + * + * @param RequestInterface $request Request + */ + protected function addProxyOptions(RequestInterface $request) + { + if ($proxy = $request->getCurlOptions()->get(CURLOPT_PROXY)) { + $this->setContextValue('http', 'proxy', $proxy); + } + } + + /** + * Create the stream for the request with the context options + * + * @param array $params Parameters of the stream + * + * @return StreamInterface + */ + protected function createStream(array $params) + { + $http_response_header = null; + $url = $this->url; + $context = $this->context; + $fp = $this->createResource(function () use ($context, $url, &$http_response_header) { + return fopen((string) $url, 'r', false, $context); + }); + + // Determine the class to instantiate + $className = isset($params['stream_class']) ? $params['stream_class'] : __NAMESPACE__ . '\\Stream'; + + /** @var $stream StreamInterface */ + $stream = new $className($fp); + + // Track the response headers of the request + if (isset($http_response_header)) { + $this->lastResponseHeaders = $http_response_header; + $this->processResponseHeaders($stream); + } + + return $stream; + } + + /** + * Process response headers + * + * @param StreamInterface $stream + */ + protected function processResponseHeaders(StreamInterface $stream) + { + // Set the size on the stream if it was returned in the response + foreach ($this->lastResponseHeaders as $header) { + if ((stripos($header, 'Content-Length:')) === 0) { + $stream->setSize(trim(substr($header, 15))); + } + } + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Closure to invoke that must return a valid resource + * + * @return resource + * @throws RuntimeException on error + */ + protected function createResource($callback) + { + $errors = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = array( + 'message' => $msg, + 'file' => $file, + 'line' => $line + ); + return true; + }); + $resource = call_user_func($callback); + restore_error_handler(); + + if (!$resource) { + $message = 'Error creating resource. '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . PHP_EOL; + } + } + throw new RuntimeException(trim($message)); + } + + return $resource; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php new file mode 100644 index 000000000..12bed268d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php @@ -0,0 +1,289 @@ + array( + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, + 'rt' => true, 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a+' => true + ), + 'write' => array( + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'wb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, + 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true + ) + ); + + /** + * @param resource $stream Stream resource to wrap + * @param int $size Size of the stream in bytes. Only pass if the size cannot be obtained from the stream. + * + * @throws InvalidArgumentException if the stream is not a stream resource + */ + public function __construct($stream, $size = null) + { + $this->setStream($stream, $size); + } + + /** + * Closes the stream when the helper is destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + if (!$this->isReadable() || (!$this->isSeekable() && $this->isConsumed())) { + return ''; + } + + $originalPos = $this->ftell(); + $body = stream_get_contents($this->stream, -1, 0); + $this->seek($originalPos); + + return $body; + } + + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->cache[self::IS_READABLE] = false; + $this->cache[self::IS_WRITABLE] = false; + } + + /** + * Calculate a hash of a Stream + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @return bool|string Returns false on failure or a hash string on success + */ + public static function getHash(StreamInterface $stream, $algo, $rawOutput = false) + { + $pos = $stream->ftell(); + if (!$stream->seek(0)) { + return false; + } + + $ctx = hash_init($algo); + while (!$stream->feof()) { + hash_update($ctx, $stream->read(8192)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; + } + + public function getMetaData($key = null) + { + $meta = stream_get_meta_data($this->stream); + + return !$key ? $meta : (array_key_exists($key, $meta) ? $meta[$key] : null); + } + + public function getStream() + { + return $this->stream; + } + + public function setStream($stream, $size = null) + { + if (!is_resource($stream)) { + throw new InvalidArgumentException('Stream must be a resource'); + } + + $this->size = $size; + $this->stream = $stream; + $this->rebuildCache(); + + return $this; + } + + public function detachStream() + { + $this->stream = null; + + return $this; + } + + public function getWrapper() + { + return $this->cache[self::WRAPPER_TYPE]; + } + + public function getWrapperData() + { + return $this->getMetaData('wrapper_data') ?: array(); + } + + public function getStreamType() + { + return $this->cache[self::STREAM_TYPE]; + } + + public function getUri() + { + return $this->cache['uri']; + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + // If the stream is a file based stream and local, then use fstat + clearstatcache(true, $this->cache['uri']); + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } elseif ($this->cache[self::IS_READABLE] && $this->cache[self::SEEKABLE]) { + // Only get the size based on the content if the the stream is readable and seekable + $pos = $this->ftell(); + $this->size = strlen((string) $this); + $this->seek($pos); + return $this->size; + } + + return false; + } + + public function isReadable() + { + return $this->cache[self::IS_READABLE]; + } + + public function isRepeatable() + { + return $this->cache[self::IS_READABLE] && $this->cache[self::SEEKABLE]; + } + + public function isWritable() + { + return $this->cache[self::IS_WRITABLE]; + } + + public function isConsumed() + { + return feof($this->stream); + } + + public function feof() + { + return $this->isConsumed(); + } + + public function isLocal() + { + return $this->cache[self::IS_LOCAL]; + } + + public function isSeekable() + { + return $this->cache[self::SEEKABLE]; + } + + public function setSize($size) + { + $this->size = $size; + + return $this; + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->cache[self::SEEKABLE] ? fseek($this->stream, $offset, $whence) === 0 : false; + } + + public function read($length) + { + return fread($this->stream, $length); + } + + public function write($string) + { + // We can't know the size after writing anything + $this->size = null; + + return fwrite($this->stream, $string); + } + + public function ftell() + { + return ftell($this->stream); + } + + public function rewind() + { + return $this->seek(0); + } + + public function readLine($maxLength = null) + { + if (!$this->cache[self::IS_READABLE]) { + return false; + } else { + return $maxLength ? fgets($this->getStream(), $maxLength) : fgets($this->getStream()); + } + } + + public function setCustomData($key, $value) + { + $this->customData[$key] = $value; + + return $this; + } + + public function getCustomData($key) + { + return isset($this->customData[$key]) ? $this->customData[$key] : null; + } + + /** + * Reprocess stream metadata + */ + protected function rebuildCache() + { + $this->cache = stream_get_meta_data($this->stream); + $this->cache[self::IS_LOCAL] = stream_is_local($this->stream); + $this->cache[self::IS_READABLE] = isset(self::$readWriteHash['read'][$this->cache['mode']]); + $this->cache[self::IS_WRITABLE] = isset(self::$readWriteHash['write'][$this->cache['mode']]); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php new file mode 100644 index 000000000..6d7dc3761 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php @@ -0,0 +1,218 @@ +=5.3.2", + "guzzle/common": "self.version" + }, + "suggest": { + "guzzle/http": "To convert Guzzle request objects to PHP streams" + }, + "autoload": { + "psr-0": { "Guzzle\\Stream": "" } + }, + "target-dir": "Guzzle/Stream", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php new file mode 100644 index 000000000..951738d2a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php @@ -0,0 +1,33 @@ +getMock('Guzzle\Batch\BatchTransferInterface'), + $this->getMock('Guzzle\Batch\BatchDivisorInterface') + ); + + $decoratorA = $this->getMockBuilder('Guzzle\Batch\AbstractBatchDecorator') + ->setConstructorArgs(array($batch)) + ->getMockForAbstractClass(); + + $decoratorB = $this->getMockBuilder('Guzzle\Batch\AbstractBatchDecorator') + ->setConstructorArgs(array($decoratorA)) + ->getMockForAbstractClass(); + + $decoratorA->add('foo'); + $this->assertFalse($decoratorB->isEmpty()); + $this->assertFalse($batch->isEmpty()); + $this->assertEquals(array($decoratorB, $decoratorA), $decoratorB->getDecorators()); + $this->assertEquals(array(), $decoratorB->flush()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php new file mode 100644 index 000000000..4da09d30e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php @@ -0,0 +1,86 @@ +getMock('Guzzle\Batch\BatchTransferInterface'); + } + + private function getMockDivisor() + { + return $this->getMock('Guzzle\Batch\BatchDivisorInterface'); + } + + private function getMockBatchBuilder() + { + return BatchBuilder::factory() + ->transferWith($this->getMockTransfer()) + ->createBatchesWith($this->getMockDivisor()); + } + + public function testFactoryCreatesInstance() + { + $builder = BatchBuilder::factory(); + $this->assertInstanceOf('Guzzle\Batch\BatchBuilder', $builder); + } + + public function testAddsAutoFlush() + { + $batch = $this->getMockBatchBuilder()->autoFlushAt(10)->build(); + $this->assertInstanceOf('Guzzle\Batch\FlushingBatch', $batch); + } + + public function testAddsExceptionBuffering() + { + $batch = $this->getMockBatchBuilder()->bufferExceptions()->build(); + $this->assertInstanceOf('Guzzle\Batch\ExceptionBufferingBatch', $batch); + } + + public function testAddHistory() + { + $batch = $this->getMockBatchBuilder()->keepHistory()->build(); + $this->assertInstanceOf('Guzzle\Batch\HistoryBatch', $batch); + } + + public function testAddsNotify() + { + $batch = $this->getMockBatchBuilder()->notify(function() {})->build(); + $this->assertInstanceOf('Guzzle\Batch\NotifyingBatch', $batch); + } + + /** + * @expectedException Guzzle\Common\Exception\RuntimeException + */ + public function testTransferStrategyMustBeSet() + { + $batch = BatchBuilder::factory()->createBatchesWith($this->getMockDivisor())->build(); + } + + /** + * @expectedException Guzzle\Common\Exception\RuntimeException + */ + public function testDivisorStrategyMustBeSet() + { + $batch = BatchBuilder::factory()->transferWith($this->getMockTransfer())->build(); + } + + public function testTransfersRequests() + { + $batch = BatchBuilder::factory()->transferRequests(10)->build(); + $this->assertInstanceOf('Guzzle\Batch\BatchRequestTransfer', $this->readAttribute($batch, 'transferStrategy')); + } + + public function testTransfersCommands() + { + $batch = BatchBuilder::factory()->transferCommands(10)->build(); + $this->assertInstanceOf('Guzzle\Batch\BatchCommandTransfer', $this->readAttribute($batch, 'transferStrategy')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php new file mode 100644 index 000000000..753db7dab --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php @@ -0,0 +1,36 @@ +createBatches($queue); + $this->assertEquals(array(array('foo'), array('baz')), $batches); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php new file mode 100644 index 000000000..6ba7ae052 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php @@ -0,0 +1,52 @@ +itemsTransferred = null; + $itemsTransferred =& $this->itemsTransferred; + + $this->transferStrategy = new BatchClosureTransfer(function (array $batch) use (&$itemsTransferred) { + $itemsTransferred = $batch; + return; + }); + } + + public function testTransfersBatch() + { + $batchedItems = array('foo', 'bar', 'baz'); + $this->transferStrategy->transfer($batchedItems); + + $this->assertEquals($batchedItems, $this->itemsTransferred); + } + + public function testTransferBailsOnEmptyBatch() + { + $batchedItems = array(); + $this->transferStrategy->transfer($batchedItems); + + $this->assertNull($this->itemsTransferred); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresCallableIsCallable() + { + $foo = new BatchClosureTransfer('uh oh!'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php new file mode 100644 index 000000000..a04efabbb --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php @@ -0,0 +1,83 @@ + $command) { + if ($i % 2) { + $command->setClient($client1); + } else { + $command->setClient($client2); + } + $queue[] = $command; + } + + $batch = new BatchCommandTransfer(2); + $this->assertEquals(array( + array($commands[0], $commands[2]), + array($commands[4]), + array($commands[1], $commands[3]) + ), $batch->createBatches($queue)); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresAllItemsAreCommands() + { + $queue = new \SplQueue(); + $queue[] = 'foo'; + $batch = new BatchCommandTransfer(2); + $batch->createBatches($queue); + } + + public function testTransfersBatches() + { + $client = $this->getMockBuilder('Guzzle\Service\Client') + ->setMethods(array('send')) + ->getMock(); + $client->expects($this->once()) + ->method('send'); + $command = new Mc(); + $command->setClient($client); + $batch = new BatchCommandTransfer(2); + $batch->transfer(array($command)); + } + + public function testDoesNotTransfersEmptyBatches() + { + $batch = new BatchCommandTransfer(2); + $batch->transfer(array()); + } + + /** + * @expectedException Guzzle\Service\Exception\InconsistentClientTransferException + */ + public function testEnsuresAllCommandsUseTheSameClient() + { + $batch = new BatchCommandTransfer(2); + $client1 = new Client('http://www.example.com'); + $client2 = new Client('http://www.example.com'); + $command1 = new Mc(); + $command1->setClient($client1); + $command2 = new Mc(); + $command2->setClient($client2); + $batch->transfer(array($command1, $command2)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php new file mode 100644 index 000000000..dec7bd55e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php @@ -0,0 +1,80 @@ +setCurlMulti(new CurlMulti()); + + $client2 = new Client('http://www.example.com'); + $client2->setCurlMulti(new CurlMulti()); + + $request1 = $client1->get(); + $request2 = $client2->get(); + $request3 = $client1->get(); + $request4 = $client2->get(); + $request5 = $client1->get(); + + $queue = new \SplQueue(); + $queue[] = $request1; + $queue[] = $request2; + $queue[] = $request3; + $queue[] = $request4; + $queue[] = $request5; + + $batch = new BatchRequestTransfer(2); + $this->assertEquals(array( + array($request1, $request3), + array($request3), + array($request2, $request4) + ), $batch->createBatches($queue)); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresAllItemsAreRequests() + { + $queue = new \SplQueue(); + $queue[] = 'foo'; + $batch = new BatchRequestTransfer(2); + $batch->createBatches($queue); + } + + public function testTransfersBatches() + { + $client = new Client('http://127.0.0.1:123'); + $request = $client->get(); + // For some reason... PHP unit clones the request, which emits a request.clone event. This causes the + // 'sorted' property of the event dispatcher to contain an array in the cloned request that is not present in + // the original. + $request->dispatch('request.clone'); + + $multi = $this->getMock('Guzzle\Http\Curl\CurlMultiInterface'); + $client->setCurlMulti($multi); + $multi->expects($this->once()) + ->method('add') + ->with($request); + $multi->expects($this->once()) + ->method('send'); + + $batch = new BatchRequestTransfer(2); + $batch->transfer(array($request)); + } + + public function testDoesNotTransfersEmptyBatches() + { + $batch = new BatchRequestTransfer(2); + $batch->transfer(array()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php new file mode 100644 index 000000000..5542228fd --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php @@ -0,0 +1,24 @@ +assertEquals(3, $d->getSize()); + $d->setSize(2); + $batches = $d->createBatches($queue); + $this->assertEquals(array(array('foo', 'baz'), array('bar')), $batches); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php new file mode 100644 index 000000000..296f57aef --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php @@ -0,0 +1,91 @@ +getMock('Guzzle\Batch\BatchTransferInterface'); + } + + private function getMockDivisor() + { + return $this->getMock('Guzzle\Batch\BatchDivisorInterface'); + } + + public function testAddsItemsToQueue() + { + $batch = new Batch($this->getMockTransfer(), $this->getMockDivisor()); + $this->assertSame($batch, $batch->add('foo')); + $this->assertEquals(1, count($batch)); + } + + public function testFlushReturnsItems() + { + $transfer = $this->getMockTransfer(); + $transfer->expects($this->exactly(2)) + ->method('transfer'); + + $divisor = $this->getMockDivisor(); + $divisor->expects($this->once()) + ->method('createBatches') + ->will($this->returnValue(array(array('foo', 'baz'), array('bar')))); + + $batch = new Batch($transfer, $divisor); + + $batch->add('foo')->add('baz')->add('bar'); + $items = $batch->flush(); + + $this->assertEquals(array('foo', 'baz', 'bar'), $items); + } + + public function testThrowsExceptionContainingTheFailedBatch() + { + $called = 0; + $originalException = new \Exception('Foo!'); + + $transfer = $this->getMockTransfer(); + $transfer->expects($this->exactly(2)) + ->method('transfer') + ->will($this->returnCallback(function () use (&$called, $originalException) { + if (++$called == 2) { + throw $originalException; + } + })); + + $divisor = $this->getMockDivisor(); + $batch = new Batch($transfer, $divisor); + + // PHPunit clones objects before passing them to a callback. + // Horrible hack to get around this! + $queue = $this->readAttribute($batch, 'queue'); + + $divisor->expects($this->once()) + ->method('createBatches') + ->will($this->returnCallback(function ($batch) use ($queue) { + foreach ($queue as $item) { + $items[] = $item; + } + return array_chunk($items, 2); + })); + + $batch->add('foo')->add('baz')->add('bar')->add('bee')->add('boo'); + $this->assertFalse($batch->isEmpty()); + + try { + $items = $batch->flush(); + $this->fail('Expected exception'); + } catch (BatchTransferException $e) { + $this->assertEquals($originalException, $e->getPrevious()); + $this->assertEquals(array('bar', 'bee'), array_values($e->getBatch())); + $this->assertEquals(1, count($batch)); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php new file mode 100644 index 000000000..fd810b11f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php @@ -0,0 +1,45 @@ +getMockBuilder('Guzzle\Batch\BatchTransferInterface') + ->setMethods(array('transfer')) + ->getMock(); + + $d = new BatchSizeDivisor(1); + $batch = new Batch($t, $d); + + $called = 0; + $t->expects($this->exactly(3)) + ->method('transfer') + ->will($this->returnCallback(function ($batch) use (&$called) { + if (++$called === 2) { + throw new \Exception('Foo'); + } + })); + + $decorator = new ExceptionBufferingBatch($batch); + $decorator->add('foo')->add('baz')->add('bar'); + $result = $decorator->flush(); + + $e = $decorator->getExceptions(); + $this->assertEquals(1, count($e)); + $this->assertEquals(array('baz'), $e[0]->getBatch()); + + $decorator->clearExceptions(); + $this->assertEquals(0, count($decorator->getExceptions())); + + $this->assertEquals(array('foo', 'bar'), $result); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php new file mode 100644 index 000000000..9b37a485f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php @@ -0,0 +1,40 @@ +getMock('Guzzle\Batch\BatchTransferInterface', array('transfer')); + $d = $this->getMock('Guzzle\Batch\BatchDivisorInterface', array('createBatches')); + + $batch = new Batch($t, $d); + $queue = $this->readAttribute($batch, 'queue'); + + $d->expects($this->exactly(2)) + ->method('createBatches') + ->will($this->returnCallback(function () use ($queue) { + $items = array(); + foreach ($queue as $item) { + $items[] = $item; + } + return array($items); + })); + + $t->expects($this->exactly(2)) + ->method('transfer'); + + $flush = new FlushingBatch($batch, 3); + $this->assertEquals(3, $flush->getThreshold()); + $flush->setThreshold(2); + $flush->add('foo')->add('baz')->add('bar')->add('bee')->add('boo'); + $this->assertEquals(1, count($flush)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php new file mode 100644 index 000000000..60d6f951a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php @@ -0,0 +1,26 @@ +getMock('Guzzle\Batch\BatchTransferInterface'), + $this->getMock('Guzzle\Batch\BatchDivisorInterface') + ); + + $history = new HistoryBatch($batch); + $history->add('foo')->add('baz'); + $this->assertEquals(array('foo', 'baz'), $history->getHistory()); + $history->clearHistory(); + $this->assertEquals(array(), $history->getHistory()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php new file mode 100644 index 000000000..69a89007a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php @@ -0,0 +1,45 @@ +getMock('Guzzle\Batch\Batch', array('flush'), array( + $this->getMock('Guzzle\Batch\BatchTransferInterface'), + $this->getMock('Guzzle\Batch\BatchDivisorInterface') + )); + + $batch->expects($this->once()) + ->method('flush') + ->will($this->returnValue(array('foo', 'baz'))); + + $data = array(); + $decorator = new NotifyingBatch($batch, function ($batch) use (&$data) { + $data[] = $batch; + }); + + $decorator->add('foo')->add('baz'); + $decorator->flush(); + $this->assertEquals(array(array('foo', 'baz')), $data); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresCallableIsValid() + { + $batch = new Batch( + $this->getMock('Guzzle\Batch\BatchTransferInterface'), + $this->getMock('Guzzle\Batch\BatchDivisorInterface') + ); + $decorator = new NotifyingBatch($batch, 'foo'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php new file mode 100644 index 000000000..c4140a91d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php @@ -0,0 +1,64 @@ +cache = new ArrayCache(); + $this->adapter = new DoctrineCacheAdapter($this->cache); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testEnsuresConfigIsObject() + { + CacheAdapterFactory::fromCache(array()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testEnsuresKnownType() + { + CacheAdapterFactory::fromCache(new \stdClass()); + } + + public function cacheProvider() + { + return array( + array(new DoctrineCacheAdapter(new ArrayCache()), 'Guzzle\Cache\DoctrineCacheAdapter'), + array(new ArrayCache(), 'Guzzle\Cache\DoctrineCacheAdapter'), + array(StorageFactory::factory(array('adapter' => 'memory')), 'Guzzle\Cache\Zf2CacheAdapter'), + ); + } + + /** + * @dataProvider cacheProvider + */ + public function testCreatesNullCacheAdapterByDefault($cache, $type) + { + $adapter = CacheAdapterFactory::fromCache($cache); + $this->assertInstanceOf($type, $adapter); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php new file mode 100644 index 000000000..3e30dddc5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php @@ -0,0 +1,68 @@ +cache = new ArrayCache(); + $this->adapter = new DoctrineCacheAdapter($this->cache); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->adapter = null; + $this->cache = null; + parent::tearDown(); + } + + public function testGetCacheObject() + { + $this->assertEquals($this->cache, $this->adapter->getCacheObject()); + } + + public function testSave() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + } + + public function testFetch() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertEquals('data', $this->adapter->fetch('test')); + } + + public function testContains() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertTrue($this->adapter->contains('test')); + } + + public function testDelete() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertTrue($this->adapter->delete('test')); + $this->assertFalse($this->adapter->contains('test')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php new file mode 100644 index 000000000..12de65b5a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php @@ -0,0 +1,94 @@ +callables = array( + 'contains' => function($id, $options = array()) use ($that) { + return array_key_exists($id, $that->data); + }, + 'delete' => function($id, $options = array()) use ($that) { + unset($that->data[$id]); + return true; + }, + 'fetch' => function($id, $options = array()) use ($that) { + return array_key_exists($id, $that->data) ? $that->data[$id] : null; + }, + 'save' => function($id, $data, $lifeTime, $options = array()) use ($that) { + $that->data[$id] = $data; + return true; + } + ); + + $this->adapter = new ClosureCacheAdapter($this->callables); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->cache = null; + $this->callables = null; + parent::tearDown(); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testEnsuresCallablesArePresent() + { + $callables = $this->callables; + unset($callables['delete']); + $cache = new ClosureCacheAdapter($callables); + } + + public function testAllCallablesMustBePresent() + { + $cache = new ClosureCacheAdapter($this->callables); + } + + public function testCachesDataUsingCallables() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertEquals('data', $this->adapter->fetch('test')); + } + + public function testChecksIfCacheContainsKeys() + { + $this->adapter->save('test', 'data', 1000); + $this->assertTrue($this->adapter->contains('test')); + $this->assertFalse($this->adapter->contains('foo')); + } + + public function testDeletesFromCacheByKey() + { + $this->adapter->save('test', 'data', 1000); + $this->assertTrue($this->adapter->contains('test')); + $this->adapter->delete('test'); + $this->assertFalse($this->adapter->contains('test')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php new file mode 100644 index 000000000..e05df3f78 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php @@ -0,0 +1,20 @@ +assertEquals(false, $c->contains('foo')); + $this->assertEquals(true, $c->delete('foo')); + $this->assertEquals(false, $c->fetch('foo')); + $this->assertEquals(true, $c->save('foo', 'bar')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php new file mode 100644 index 000000000..9077c12d3 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php @@ -0,0 +1,58 @@ +cache = StorageFactory::factory(array( + 'adapter' => 'memory' + )); + $this->adapter = new Zf2CacheAdapter($this->cache); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->adapter = null; + $this->cache = null; + parent::tearDown(); + } + + public function testCachesDataUsingCallables() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertEquals('data', $this->adapter->fetch('test')); + } + + public function testChecksIfCacheContainsKeys() + { + $this->adapter->save('test', 'data', 1000); + $this->assertTrue($this->adapter->contains('test')); + $this->assertFalse($this->adapter->contains('foo')); + } + + public function testDeletesFromCacheByKey() + { + $this->adapter->save('test', 'data', 1000); + $this->assertTrue($this->adapter->contains('test')); + $this->adapter->delete('test'); + $this->assertFalse($this->adapter->contains('test')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php new file mode 100644 index 000000000..19d12e6f6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php @@ -0,0 +1,63 @@ +assertEquals(array(), AbstractHasDispatcher::getAllEvents()); + } + + public function testAllowsDispatcherToBeInjected() + { + $d = new EventDispatcher(); + $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher'); + $this->assertSame($mock, $mock->setEventDispatcher($d)); + $this->assertSame($d, $mock->getEventDispatcher()); + } + + public function testCreatesDefaultEventDispatcherIfNeeded() + { + $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher'); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\EventDispatcher', $mock->getEventDispatcher()); + } + + public function testHelperDispatchesEvents() + { + $data = array(); + $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher'); + $mock->getEventDispatcher()->addListener('test', function(Event $e) use (&$data) { + $data = $e->getIterator()->getArrayCopy(); + }); + $mock->dispatch('test', array( + 'param' => 'abc' + )); + $this->assertEquals(array( + 'param' => 'abc', + ), $data); + } + + public function testHelperAttachesSubscribers() + { + $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher'); + $subscriber = $this->getMockForAbstractClass('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcher') + ->setMethods(array('addSubscriber')) + ->getMock(); + + $dispatcher->expects($this->once()) + ->method('addSubscriber'); + + $mock->setEventDispatcher($dispatcher); + $mock->addSubscriber($subscriber); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php new file mode 100644 index 000000000..0648a02b8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php @@ -0,0 +1,529 @@ +coll = new Collection(); + } + + public function testConstructorCanBeCalledWithNoParams() + { + $this->coll = new Collection(); + $p = $this->coll->getAll(); + $this->assertEmpty($p, '-> Collection must be empty when no data is passed'); + } + + public function testConstructorCanBeCalledWithParams() + { + $testData = array( + 'test' => 'value', + 'test_2' => 'value2' + ); + $this->coll = new Collection($testData); + $this->assertEquals($this->coll->getAll(), $testData, '-> getAll() must return the data passed in the constructor'); + $this->assertEquals($this->coll->getAll(), $this->coll->toArray()); + } + + public function testImplementsIteratorAggregate() + { + $this->coll->set('key', 'value'); + $this->assertInstanceOf('ArrayIterator', $this->coll->getIterator()); + $this->assertEquals(1, count($this->coll)); + $total = 0; + foreach ($this->coll as $key => $value) { + $this->assertEquals('key', $key); + $this->assertEquals('value', $value); + $total++; + } + $this->assertEquals(1, $total); + } + + public function testCanAddValuesToExistingKeysByUsingArray() + { + $this->coll->add('test', 'value1'); + $this->assertEquals($this->coll->getAll(), array('test' => 'value1')); + $this->coll->add('test', 'value2'); + $this->assertEquals($this->coll->getAll(), array('test' => array('value1', 'value2'))); + $this->coll->add('test', 'value3'); + $this->assertEquals($this->coll->getAll(), array('test' => array('value1', 'value2', 'value3'))); + } + + public function testHandlesMergingInDisparateDataSources() + { + $params = array( + 'test' => 'value1', + 'test2' => 'value2', + 'test3' => array('value3', 'value4') + ); + $this->coll->merge($params); + $this->assertEquals($this->coll->getAll(), $params); + + // Pass the same object to itself + $this->assertEquals($this->coll->merge($this->coll), $this->coll); + } + + public function testCanClearAllDataOrSpecificKeys() + { + $this->coll->merge(array( + 'test' => 'value1', + 'test2' => 'value2' + )); + + // Clear a specific parameter by name + $this->coll->remove('test'); + + $this->assertEquals($this->coll->getAll(), array( + 'test2' => 'value2' + )); + + // Clear all parameters + $this->coll->clear(); + + $this->assertEquals($this->coll->getAll(), array()); + } + + public function testGetsValuesByKey() + { + $this->assertNull($this->coll->get('test')); + $this->coll->add('test', 'value'); + $this->assertEquals('value', $this->coll->get('test')); + $this->coll->set('test2', 'v2'); + $this->coll->set('test3', 'v3'); + $this->assertEquals(array( + 'test' => 'value', + 'test2' => 'v2' + ), $this->coll->getAll(array('test', 'test2'))); + } + + public function testProvidesKeys() + { + $this->assertEquals(array(), $this->coll->getKeys()); + $this->coll->merge(array( + 'test1' => 'value1', + 'test2' => 'value2' + )); + $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys()); + // Returns the cached array previously returned + $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys()); + $this->coll->remove('test1'); + $this->assertEquals(array('test2'), $this->coll->getKeys()); + $this->coll->add('test3', 'value3'); + $this->assertEquals(array('test2', 'test3'), $this->coll->getKeys()); + } + + public function testChecksIfHasKey() + { + $this->assertFalse($this->coll->hasKey('test')); + $this->coll->add('test', 'value'); + $this->assertEquals(true, $this->coll->hasKey('test')); + $this->coll->add('test2', 'value2'); + $this->assertEquals(true, $this->coll->hasKey('test')); + $this->assertEquals(true, $this->coll->hasKey('test2')); + $this->assertFalse($this->coll->hasKey('testing')); + $this->assertEquals(false, $this->coll->hasKey('AB-C', 'junk')); + } + + public function testChecksIfHasValue() + { + $this->assertFalse($this->coll->hasValue('value')); + $this->coll->add('test', 'value'); + $this->assertEquals('test', $this->coll->hasValue('value')); + $this->coll->add('test2', 'value2'); + $this->assertEquals('test', $this->coll->hasValue('value')); + $this->assertEquals('test2', $this->coll->hasValue('value2')); + $this->assertFalse($this->coll->hasValue('val')); + } + + public function testCanGetAllValuesByArray() + { + $this->coll->add('foo', 'bar'); + $this->coll->add('tEsT', 'value'); + $this->coll->add('tesTing', 'v2'); + $this->coll->add('key', 'v3'); + $this->assertNull($this->coll->get('test')); + $this->assertEquals(array( + 'foo' => 'bar', + 'tEsT' => 'value', + 'tesTing' => 'v2' + ), $this->coll->getAll(array( + 'foo', 'tesTing', 'tEsT' + ))); + } + + public function testImplementsCount() + { + $data = new Collection(); + $this->assertEquals(0, $data->count()); + $data->add('key', 'value'); + $this->assertEquals(1, count($data)); + $data->add('key', 'value2'); + $this->assertEquals(1, count($data)); + $data->add('key_2', 'value3'); + $this->assertEquals(2, count($data)); + } + + public function testAddParamsByMerging() + { + $params = array( + 'test' => 'value1', + 'test2' => 'value2', + 'test3' => array('value3', 'value4') + ); + + // Add some parameters + $this->coll->merge($params); + + // Add more parameters by merging them in + $this->coll->merge(array( + 'test' => 'another', + 'different_key' => 'new value' + )); + + $this->assertEquals(array( + 'test' => array('value1', 'another'), + 'test2' => 'value2', + 'test3' => array('value3', 'value4'), + 'different_key' => 'new value' + ), $this->coll->getAll()); + } + + public function testAllowsFunctionalFilter() + { + $this->coll->merge(array( + 'fruit' => 'apple', + 'number' => 'ten', + 'prepositions' => array('about', 'above', 'across', 'after'), + 'same_number' => 'ten' + )); + + $filtered = $this->coll->filter(function($key, $value) { + return $value == 'ten'; + }); + + $this->assertNotEquals($filtered, $this->coll); + + $this->assertEquals(array( + 'number' => 'ten', + 'same_number' => 'ten' + ), $filtered->getAll()); + } + + public function testAllowsFunctionalMapping() + { + $this->coll->merge(array( + 'number_1' => 1, + 'number_2' => 2, + 'number_3' => 3 + )); + + $mapped = $this->coll->map(function($key, $value) { + return $value * $value; + }); + + $this->assertNotEquals($mapped, $this->coll); + + $this->assertEquals(array( + 'number_1' => 1, + 'number_2' => 4, + 'number_3' => 9 + ), $mapped->getAll()); + } + + public function testImplementsArrayAccess() + { + $this->coll->merge(array( + 'k1' => 'v1', + 'k2' => 'v2' + )); + + $this->assertTrue($this->coll->offsetExists('k1')); + $this->assertFalse($this->coll->offsetExists('Krull')); + + $this->coll->offsetSet('k3', 'v3'); + $this->assertEquals('v3', $this->coll->offsetGet('k3')); + $this->assertEquals('v3', $this->coll->get('k3')); + + $this->coll->offsetUnset('k1'); + $this->assertFalse($this->coll->offsetExists('k1')); + } + + public function testUsesStaticWhenCreatingNew() + { + $qs = new QueryString(array( + 'a' => 'b', + 'c' => 'd' + )); + + $this->assertInstanceOf('Guzzle\\Http\\QueryString', $qs->map(function($a, $b) {})); + $this->assertInstanceOf('Guzzle\\Common\\Collection', $qs->map(function($a, $b) {}, array(), false)); + + $this->assertInstanceOf('Guzzle\\Http\\QueryString', $qs->filter(function($a, $b) {})); + $this->assertInstanceOf('Guzzle\\Common\\Collection', $qs->filter(function($a, $b) {}, false)); + } + + public function testCanReplaceAllData() + { + $this->assertSame($this->coll, $this->coll->replace(array( + 'a' => '123' + ))); + + $this->assertEquals(array( + 'a' => '123' + ), $this->coll->getAll()); + } + + public function dataProvider() + { + return array( + array('this_is_a_test', '{a}_is_a_{b}', array( + 'a' => 'this', + 'b' => 'test' + )), + array('this_is_a_test', '{abc}_is_a_{0}', array( + 'abc' => 'this', + 0 => 'test' + )), + array('this_is_a_test', '{abc}_is_a_{0}', array( + 'abc' => 'this', + 0 => 'test' + )), + array('this_is_a_test', 'this_is_a_test', array( + 'abc' => 'this' + )), + array('{abc}_is_{not_found}a_{0}', '{abc}_is_{not_found}a_{0}', array()) + ); + } + + /** + * @dataProvider dataProvider + */ + public function testInjectsConfigData($output, $input, $config) + { + $collection = new Collection($config); + $this->assertEquals($output, $collection->inject($input)); + } + + public function testCanSearchByKey() + { + $collection = new Collection(array( + 'foo' => 'bar', + 'BaZ' => 'pho' + )); + + $this->assertEquals('foo', $collection->keySearch('FOO')); + $this->assertEquals('BaZ', $collection->keySearch('baz')); + $this->assertEquals(false, $collection->keySearch('Bar')); + } + + public function testPreparesFromConfig() + { + $c = Collection::fromConfig(array( + 'a' => '123', + 'base_url' => 'http://www.test.com/' + ), array( + 'a' => 'xyz', + 'b' => 'lol' + ), array('a')); + + $this->assertInstanceOf('Guzzle\Common\Collection', $c); + $this->assertEquals(array( + 'a' => '123', + 'b' => 'lol', + 'base_url' => 'http://www.test.com/' + ), $c->getAll()); + + try { + $c = Collection::fromConfig(array(), array(), array('a')); + $this->fail('Exception not throw when missing config'); + } catch (InvalidArgumentException $e) { + } + } + + function falseyDataProvider() + { + return array( + array(false, false), + array(null, null), + array('', ''), + array(array(), array()), + array(0, 0), + ); + } + + /** + * @dataProvider falseyDataProvider + */ + public function testReturnsCorrectData($a, $b) + { + $c = new Collection(array('value' => $a)); + $this->assertSame($b, $c->get('value')); + } + + public function testRetrievesNestedKeysUsingPath() + { + $data = array( + 'foo' => 'bar', + 'baz' => array( + 'mesa' => array( + 'jar' => 'jar' + ) + ) + ); + $collection = new Collection($data); + $this->assertEquals('bar', $collection->getPath('foo')); + $this->assertEquals('jar', $collection->getPath('baz/mesa/jar')); + $this->assertNull($collection->getPath('wewewf')); + $this->assertNull($collection->getPath('baz/mesa/jar/jar')); + } + + public function testFalseyKeysStillDescend() + { + $collection = new Collection(array( + '0' => array( + 'a' => 'jar' + ), + 1 => 'other' + )); + $this->assertEquals('jar', $collection->getPath('0/a')); + $this->assertEquals('other', $collection->getPath('1')); + } + + public function getPathProvider() + { + $data = array( + 'foo' => 'bar', + 'baz' => array( + 'mesa' => array( + 'jar' => 'jar', + 'array' => array('a', 'b', 'c') + ), + 'bar' => array( + 'baz' => 'bam', + 'array' => array('d', 'e', 'f') + ) + ), + 'bam' => array( + array('foo' => 1), + array('foo' => 2), + array('array' => array('h', 'i')) + ) + ); + $c = new Collection($data); + + return array( + // Simple path selectors + array($c, 'foo', 'bar'), + array($c, 'baz', $data['baz']), + array($c, 'bam', $data['bam']), + array($c, 'baz/mesa', $data['baz']['mesa']), + array($c, 'baz/mesa/jar', 'jar'), + // Merge everything two levels under baz + array($c, 'baz/*', array( + 'jar' => 'jar', + 'array' => array_merge($data['baz']['mesa']['array'], $data['baz']['bar']['array']), + 'baz' => 'bam' + )), + // Does not barf on missing keys + array($c, 'fefwfw', null), + // Does not barf when a wildcard does not resolve correctly + array($c, '*/*/*/*/*/wefwfe', array()), + // Allows custom separator + array($c, '*|mesa', $data['baz']['mesa'], '|'), + // Merge all 'array' keys two levels under baz (the trailing * does not hurt the results) + array($c, 'baz/*/array/*', array_merge($data['baz']['mesa']['array'], $data['baz']['bar']['array'])), + // Merge all 'array' keys two levels under baz + array($c, 'baz/*/array', array_merge($data['baz']['mesa']['array'], $data['baz']['bar']['array'])), + array($c, 'baz/mesa/array', $data['baz']['mesa']['array']), + // Having a trailing * does not hurt the results + array($c, 'baz/mesa/array/*', $data['baz']['mesa']['array']), + // Merge of anything one level deep + array($c, '*', array_merge(array('bar'), $data['baz'], $data['bam'])), + // Funky merge of anything two levels deep + array($c, '*/*', array( + 'jar' => 'jar', + 'array' => array('a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'), + 'baz' => 'bam', + 'foo' => array(1, 2) + )), + // Funky merge of all 'array' keys that are two levels deep + array($c, '*/*/array', array('a', 'b', 'c', 'd', 'e', 'f', 'h', 'i')) + ); + } + + /** + * @dataProvider getPathProvider + */ + public function testGetPath(Collection $c, $path, $expected, $separator = '/') + { + $this->assertEquals($expected, $c->getPath($path, $separator)); + } + + public function testOverridesSettings() + { + $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); + $c->overwriteWith(array('foo' => 10, 'bar' => 300)); + $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->getAll()); + } + + public function testOverwriteWithCollection() + { + $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); + $b = new Collection(array('foo' => 10, 'bar' => 300)); + $c->overwriteWith($b); + $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->getAll()); + } + + public function testOverwriteWithTraversable() + { + $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); + $b = new Collection(array('foo' => 10, 'bar' => 300)); + $c->overwriteWith($b->getIterator()); + $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->getAll()); + } + + public function testCanSetNestedPathValueThatDoesNotExist() + { + $c = new Collection(array()); + $c->setPath('foo/bar/baz/123', 'hi'); + $this->assertEquals('hi', $c['foo']['bar']['baz']['123']); + } + + public function testCanSetNestedPathValueThatExists() + { + $c = new Collection(array('foo' => array('bar' => 'test'))); + $c->setPath('foo/bar', 'hi'); + $this->assertEquals('hi', $c['foo']['bar']); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testVerifiesNestedPathIsValidAtExactLevel() + { + $c = new Collection(array('foo' => 'bar')); + $c->setPath('foo/bar', 'hi'); + $this->assertEquals('hi', $c['foo']['bar']); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testVerifiesThatNestedPathIsValidAtAnyLevel() + { + $c = new Collection(array('foo' => 'bar')); + $c->setPath('foo/bar/baz', 'test'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php new file mode 100644 index 000000000..5484e1446 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php @@ -0,0 +1,62 @@ + '123', + 'other' => '456', + 'event' => 'test.notify' + )); + } + + public function testAllowsParameterInjection() + { + $event = new Event(array( + 'test' => '123' + )); + $this->assertEquals('123', $event['test']); + } + + public function testImplementsArrayAccess() + { + $event = $this->getEvent(); + $this->assertEquals('123', $event['test']); + $this->assertNull($event['foobar']); + + $this->assertTrue($event->offsetExists('test')); + $this->assertFalse($event->offsetExists('foobar')); + + unset($event['test']); + $this->assertFalse($event->offsetExists('test')); + + $event['test'] = 'new'; + $this->assertEquals('new', $event['test']); + } + + public function testImplementsIteratorAggregate() + { + $event = $this->getEvent(); + $this->assertInstanceOf('ArrayIterator', $event->getIterator()); + } + + public function testConvertsToArray() + { + $this->assertEquals(array( + 'test' => '123', + 'other' => '456', + 'event' => 'test.notify' + ), $this->getEvent()->toArray()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php new file mode 100644 index 000000000..c72a2a637 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php @@ -0,0 +1,21 @@ +getMock('Guzzle\Batch\BatchTransferInterface'); + $d = $this->getMock('Guzzle\Batch\BatchDivisorInterface'); + $transferException = new BatchTransferException(array('foo'), array(1, 2), $e, $t, $d); + $this->assertEquals(array('foo'), $transferException->getBatch()); + $this->assertSame($t, $transferException->getTransferStrategy()); + $this->assertSame($d, $transferException->getDivisorStrategy()); + $this->assertSame($e, $transferException->getPrevious()); + $this->assertEquals(array(1, 2), $transferException->getTransferredItems()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php new file mode 100644 index 000000000..2aecf2a06 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php @@ -0,0 +1,66 @@ +getExceptions(); + $e->add($exceptions[0]); + $e->add($exceptions[1]); + $this->assertContains("(Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $e->getMessage()); + $this->assertContains(" Test\n\n #0 ./", $e->getMessage()); + $this->assertSame($exceptions[0], $e->getFirst()); + } + + public function testCanSetExceptions() + { + $ex = new \Exception('foo'); + $e = new ExceptionCollection(); + $e->setExceptions(array($ex)); + $this->assertSame($ex, $e->getFirst()); + } + + public function testActsAsArray() + { + $e = new ExceptionCollection(); + $exceptions = $this->getExceptions(); + $e->add($exceptions[0]); + $e->add($exceptions[1]); + $this->assertEquals(2, count($e)); + $this->assertEquals($exceptions, $e->getIterator()->getArrayCopy()); + } + + public function testCanAddSelf() + { + $e1 = new ExceptionCollection(); + $e1->add(new \Exception("Test")); + $e2 = new ExceptionCollection('Meta description!'); + $e2->add(new \Exception("Test 2")); + $e3 = new ExceptionCollection(); + $e3->add(new \Exception('Baz')); + $e2->add($e3); + $e1->add($e2); + $message = $e1->getMessage(); + $this->assertContains("(Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message); + $this->assertContains("\n Test\n\n #0 ", $message); + $this->assertContains("\n\n(Guzzle\\Common\\Exception\\ExceptionCollection) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message); + $this->assertContains("\n\n Meta description!\n\n", $message); + $this->assertContains(" (Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message); + $this->assertContains("\n Test 2\n\n #0 ", $message); + $this->assertContains(" (Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message); + $this->assertContains(" Baz\n\n #0", $message); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php new file mode 100644 index 000000000..c3a81d1e4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php @@ -0,0 +1,27 @@ +isRunning()) { + self::$server->flush(); + } else { + self::$server->start(); + } + } + + return self::$server; + } + + /** + * Set the service builder to use for tests + * + * @param ServiceBuilderInterface $builder Service builder + */ + public static function setServiceBuilder(ServiceBuilderInterface $builder) + { + self::$serviceBuilder = $builder; + } + + /** + * Get a service builder object that can be used throughout the service tests + * + * @return ServiceBuilder + */ + public static function getServiceBuilder() + { + if (!self::$serviceBuilder) { + throw new RuntimeException('No service builder has been set via setServiceBuilder()'); + } + + return self::$serviceBuilder; + } + + /** + * Check if an event dispatcher has a subscriber + * + * @param HasDispatcherInterface $dispatcher + * @param EventSubscriberInterface $subscriber + * + * @return bool + */ + protected function hasSubscriber(HasDispatcherInterface $dispatcher, EventSubscriberInterface $subscriber) + { + $class = get_class($subscriber); + $all = array_keys(call_user_func(array($class, 'getSubscribedEvents'))); + + foreach ($all as $i => $event) { + foreach ($dispatcher->getEventDispatcher()->getListeners($event) as $e) { + if ($e[0] === $subscriber) { + unset($all[$i]); + break; + } + } + } + + return count($all) == 0; + } + + /** + * Get a wildcard observer for an event dispatcher + * + * @param HasDispatcherInterface $hasDispatcher + * + * @return MockObserver + */ + public function getWildcardObserver(HasDispatcherInterface $hasDispatcher) + { + $class = get_class($hasDispatcher); + $o = new MockObserver(); + $events = call_user_func(array($class, 'getAllEvents')); + foreach ($events as $event) { + $hasDispatcher->getEventDispatcher()->addListener($event, array($o, 'update')); + } + + return $o; + } + + /** + * Set the mock response base path + * + * @param string $path Path to mock response folder + * + * @return GuzzleTestCase + */ + public static function setMockBasePath($path) + { + self::$mockBasePath = $path; + } + + /** + * Mark a request as being mocked + * + * @param RequestInterface $request + * + * @return self + */ + public function addMockedRequest(RequestInterface $request) + { + $this->requests[] = $request; + + return $this; + } + + /** + * Get all of the mocked requests + * + * @return array + */ + public function getMockedRequests() + { + return $this->requests; + } + + /** + * Get a mock response for a client by mock file name + * + * @param string $path Relative path to the mock response file + * + * @return Response + */ + public function getMockResponse($path) + { + return $path instanceof Response + ? $path + : MockPlugin::getMockFile(self::$mockBasePath . DIRECTORY_SEPARATOR . $path); + } + + /** + * Set a mock response from a mock file on the next client request. + * + * This method assumes that mock response files are located under the + * Command/Mock/ directory of the Service being tested + * (e.g. Unfuddle/Command/Mock/). A mock response is added to the next + * request sent by the client. + * + * @param Client $client Client object to modify + * @param string $paths Path to files within the Mock folder of the service + * + * @return MockPlugin returns the created mock plugin + */ + public function setMockResponse(Client $client, $paths) + { + $this->requests = array(); + $that = $this; + $mock = new MockPlugin(null, true); + $client->getEventDispatcher()->removeSubscriber($mock); + $mock->getEventDispatcher()->addListener('mock.request', function(Event $event) use ($that) { + $that->addMockedRequest($event['request']); + }); + + if ($paths instanceof Response) { + // A single response instance has been specified, create an array with that instance + // as the only element for the following loop to work as expected + $paths = array($paths); + } + + foreach ((array) $paths as $path) { + $mock->addResponse($this->getMockResponse($path)); + } + + $client->getEventDispatcher()->addSubscriber($mock); + + return $mock; + } + + /** + * Compare HTTP headers and use special markup to filter values + * A header prefixed with '!' means it must not exist + * A header prefixed with '_' means it must be ignored + * A header value of '*' means anything after the * will be ignored + * + * @param array $filteredHeaders Array of special headers + * @param array $actualHeaders Array of headers to check against + * + * @return array|bool Returns an array of the differences or FALSE if none + */ + public function compareHeaders($filteredHeaders, $actualHeaders) + { + $comparison = new HeaderComparison(); + + return $comparison->compare($filteredHeaders, $actualHeaders); + } + + /** + * Case insensitive assertContains + * + * @param string $needle Search string + * @param string $haystack Search this + * @param string $message Optional failure message + */ + public function assertContainsIns($needle, $haystack, $message = null) + { + $this->assertContains(strtolower($needle), strtolower($haystack), $message); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php new file mode 100644 index 000000000..20feaa875 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php @@ -0,0 +1,34 @@ +getMockForAbstractClass('Guzzle\Http\AbstractEntityBodyDecorator', array($e)); + + $this->assertSame($e->getStream(), $mock->getStream()); + $this->assertSame($e->getContentLength(), $mock->getContentLength()); + $this->assertSame($e->getSize(), $mock->getSize()); + $this->assertSame($e->getContentMd5(), $mock->getContentMd5()); + $this->assertSame($e->getContentType(), $mock->getContentType()); + $this->assertSame($e->__toString(), $mock->__toString()); + $this->assertSame($e->getUri(), $mock->getUri()); + $this->assertSame($e->getStreamType(), $mock->getStreamType()); + $this->assertSame($e->getWrapper(), $mock->getWrapper()); + $this->assertSame($e->getWrapperData(), $mock->getWrapperData()); + $this->assertSame($e->isReadable(), $mock->isReadable()); + $this->assertSame($e->isWritable(), $mock->isWritable()); + $this->assertSame($e->isConsumed(), $mock->isConsumed()); + $this->assertSame($e->isLocal(), $mock->isLocal()); + $this->assertSame($e->isSeekable(), $mock->isSeekable()); + $this->assertSame($e->getContentEncoding(), $mock->getContentEncoding()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php new file mode 100644 index 000000000..e6e6cdbf5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php @@ -0,0 +1,249 @@ +decorated = EntityBody::factory('testing'); + $this->body = new CachingEntityBody($this->decorated); + } + + public function testUsesRemoteSizeIfPossible() + { + $body = EntityBody::factory('test'); + $caching = new CachingEntityBody($body); + $this->assertEquals(4, $caching->getSize()); + $this->assertEquals(4, $caching->getContentLength()); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage does not support custom stream rewind + */ + public function testDoesNotAllowRewindFunction() + { + $this->body->setRewindFunction(true); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage Cannot seek to byte 10 + */ + public function testCannotSeekPastWhatHasBeenRead() + { + $this->body->seek(10); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage supports only SEEK_SET and SEEK_CUR + */ + public function testCannotUseSeekEnd() + { + $this->body->seek(2, SEEK_END); + } + + public function testChangingUnderlyingStreamUpdatesSizeAndStream() + { + $size = filesize(__FILE__); + $s = fopen(__FILE__, 'r'); + $this->body->setStream($s, $size); + $this->assertEquals($size, $this->body->getSize()); + $this->assertEquals($size, $this->decorated->getSize()); + $this->assertSame($s, $this->body->getStream()); + $this->assertSame($s, $this->decorated->getStream()); + } + + public function testRewindUsesSeek() + { + $a = EntityBody::factory('foo'); + $d = $this->getMockBuilder('Guzzle\Http\CachingEntityBody') + ->setMethods(array('seek')) + ->setConstructorArgs(array($a)) + ->getMock(); + $d->expects($this->once()) + ->method('seek') + ->with(0) + ->will($this->returnValue(true)); + $d->rewind(); + } + + public function testCanSeekToReadBytes() + { + $this->assertEquals('te', $this->body->read(2)); + $this->body->seek(0); + $this->assertEquals('test', $this->body->read(4)); + $this->assertEquals(4, $this->body->ftell()); + $this->body->seek(2); + $this->assertEquals(2, $this->body->ftell()); + $this->body->seek(2, SEEK_CUR); + $this->assertEquals(4, $this->body->ftell()); + $this->assertEquals('ing', $this->body->read(3)); + } + + public function testWritesToBufferStream() + { + $this->body->read(2); + $this->body->write('hi'); + $this->body->rewind(); + $this->assertEquals('tehiing', (string) $this->body); + } + + public function testReadLinesFromBothStreams() + { + $this->body->seek($this->body->ftell()); + $this->body->write("test\n123\nhello\n1234567890\n"); + $this->body->rewind(); + $this->assertEquals("test\n", $this->body->readLine(7)); + $this->assertEquals("123\n", $this->body->readLine(7)); + $this->assertEquals("hello\n", $this->body->readLine(7)); + $this->assertEquals("123456", $this->body->readLine(7)); + $this->assertEquals("7890\n", $this->body->readLine(7)); + // We overwrote the decorated stream, so no more data + $this->assertEquals('', $this->body->readLine(7)); + } + + public function testSkipsOverwrittenBytes() + { + $decorated = EntityBody::factory( + implode("\n", array_map(function ($n) { + return str_pad($n, 4, '0', STR_PAD_LEFT); + }, range(0, 25))) + ); + + $body = new CachingEntityBody($decorated); + + $this->assertEquals("0000\n", $body->readLine()); + $this->assertEquals("0001\n", $body->readLine()); + // Write over part of the body yet to be read, so skip some bytes + $this->assertEquals(5, $body->write("TEST\n")); + $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes')); + // Read, which skips bytes, then reads + $this->assertEquals("0003\n", $body->readLine()); + $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes')); + $this->assertEquals("0004\n", $body->readLine()); + $this->assertEquals("0005\n", $body->readLine()); + + // Overwrite part of the cached body (so don't skip any bytes) + $body->seek(5); + $this->assertEquals(5, $body->write("ABCD\n")); + $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes')); + $this->assertEquals("TEST\n", $body->readLine()); + $this->assertEquals("0003\n", $body->readLine()); + $this->assertEquals("0004\n", $body->readLine()); + $this->assertEquals("0005\n", $body->readLine()); + $this->assertEquals("0006\n", $body->readLine()); + $this->assertEquals(5, $body->write("1234\n")); + $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes')); + + // Seek to 0 and ensure the overwritten bit is replaced + $body->rewind(); + $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50)); + + // Ensure that casting it to a string does not include the bit that was overwritten + $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body); + } + + public function testWrapsContentType() + { + $a = $this->getMockBuilder('Guzzle\Http\EntityBody') + ->setMethods(array('getContentType')) + ->setConstructorArgs(array(fopen(__FILE__, 'r'))) + ->getMock(); + $a->expects($this->once()) + ->method('getContentType') + ->will($this->returnValue('foo')); + $d = new CachingEntityBody($a); + $this->assertEquals('foo', $d->getContentType()); + } + + public function testWrapsContentEncoding() + { + $a = $this->getMockBuilder('Guzzle\Http\EntityBody') + ->setMethods(array('getContentEncoding')) + ->setConstructorArgs(array(fopen(__FILE__, 'r'))) + ->getMock(); + $a->expects($this->once()) + ->method('getContentEncoding') + ->will($this->returnValue('foo')); + $d = new CachingEntityBody($a); + $this->assertEquals('foo', $d->getContentEncoding()); + } + + public function testWrapsMetadata() + { + $a = $this->getMockBuilder('Guzzle\Http\EntityBody') + ->setMethods(array('getMetadata', 'getWrapper', 'getWrapperData', 'getStreamType', 'getUri')) + ->setConstructorArgs(array(fopen(__FILE__, 'r'))) + ->getMock(); + + $a->expects($this->once()) + ->method('getMetadata') + ->will($this->returnValue(array())); + // Called twice for getWrapper and getWrapperData + $a->expects($this->exactly(1)) + ->method('getWrapper') + ->will($this->returnValue('wrapper')); + $a->expects($this->once()) + ->method('getWrapperData') + ->will($this->returnValue(array())); + $a->expects($this->once()) + ->method('getStreamType') + ->will($this->returnValue('baz')); + $a->expects($this->once()) + ->method('getUri') + ->will($this->returnValue('path/to/foo')); + + $d = new CachingEntityBody($a); + $this->assertEquals(array(), $d->getMetaData()); + $this->assertEquals('wrapper', $d->getWrapper()); + $this->assertEquals(array(), $d->getWrapperData()); + $this->assertEquals('baz', $d->getStreamType()); + $this->assertEquals('path/to/foo', $d->getUri()); + } + + public function testWrapsCustomData() + { + $a = $this->getMockBuilder('Guzzle\Http\EntityBody') + ->setMethods(array('getCustomData', 'setCustomData')) + ->setConstructorArgs(array(fopen(__FILE__, 'r'))) + ->getMock(); + + $a->expects($this->exactly(1)) + ->method('getCustomData') + ->with('foo') + ->will($this->returnValue('bar')); + + $a->expects($this->exactly(1)) + ->method('setCustomData') + ->with('foo', 'bar') + ->will($this->returnSelf()); + + $d = new CachingEntityBody($a); + $this->assertSame($d, $d->setCustomData('foo', 'bar')); + $this->assertEquals('bar', $d->getCustomData('foo')); + } + + public function testClosesBothStreams() + { + $s = fopen('php://temp', 'r'); + $a = EntityBody::factory($s); + $d = new CachingEntityBody($a); + $d->close(); + $this->assertFalse(is_resource($s)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php new file mode 100644 index 000000000..4a91a18f9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php @@ -0,0 +1,601 @@ +assertEquals('http://www.google.com/', $client->getBaseUrl()); + $this->assertSame($client, $client->setConfig(array( + 'test' => '123' + ))); + $this->assertEquals(array('test' => '123'), $client->getConfig()->getAll()); + $this->assertEquals('123', $client->getConfig('test')); + $this->assertSame($client, $client->setBaseUrl('http://www.test.com/{test}')); + $this->assertEquals('http://www.test.com/123', $client->getBaseUrl()); + $this->assertEquals('http://www.test.com/{test}', $client->getBaseUrl(false)); + + try { + $client->setConfig(false); + } catch (\InvalidArgumentException $e) { + } + } + + public function testDescribesEvents() + { + $this->assertEquals(array('client.create_request'), Client::getAllEvents()); + } + + public function testConstructorCanAcceptConfig() + { + $client = new Client('http://www.test.com/', array( + 'data' => '123' + )); + $this->assertEquals('123', $client->getConfig('data')); + } + + public function testCanUseCollectionAsConfig() + { + $client = new Client('http://www.google.com/'); + $client->setConfig(new Collection(array( + 'api' => 'v1', + 'key' => 'value', + 'base_url' => 'http://www.google.com/' + ))); + $this->assertEquals('v1', $client->getConfig('api')); + } + + public function testExpandsUriTemplatesUsingConfig() + { + $client = new Client('http://www.google.com/'); + $client->setConfig(array('api' => 'v1', 'key' => 'value', 'foo' => 'bar')); + $ref = new \ReflectionMethod($client, 'expandTemplate'); + $ref->setAccessible(true); + $this->assertEquals('Testing...api/v1/key/value', $ref->invoke($client, 'Testing...api/{api}/key/{key}')); + } + + public function testClientAttachersObserversToRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client($this->getServer()->getUrl()); + $logPlugin = $this->getLogPlugin(); + $client->getEventDispatcher()->addSubscriber($logPlugin); + + // Get a request from the client and ensure the the observer was + // attached to the new request + $request = $client->createRequest(); + $this->assertTrue($this->hasSubscriber($request, $logPlugin)); + } + + public function testClientReturnsValidBaseUrls() + { + $client = new Client('http://www.{foo}.{data}/', array( + 'data' => '123', + 'foo' => 'bar' + )); + $this->assertEquals('http://www.bar.123/', $client->getBaseUrl()); + $client->setBaseUrl('http://www.google.com/'); + $this->assertEquals('http://www.google.com/', $client->getBaseUrl()); + } + + public function testClientAddsCurlOptionsToRequests() + { + $client = new Client('http://www.test.com/', array( + 'api' => 'v1', + // Adds the option using the curl values + 'curl.options' => array( + 'CURLOPT_HTTPAUTH' => 'CURLAUTH_DIGEST', + 'abc' => 'foo', + 'blacklist' => 'abc', + 'debug' => true + ) + )); + + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertEquals(CURLAUTH_DIGEST, $options->get(CURLOPT_HTTPAUTH)); + $this->assertEquals('foo', $options->get('abc')); + $this->assertEquals('abc', $options->get('blacklist')); + } + + public function testClientAllowsFineGrainedSslControlButIsSecureByDefault() + { + $client = new Client('https://www.secure.com/'); + + // secure by default + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertTrue($options->get(CURLOPT_SSL_VERIFYPEER)); + + // set a capath if you prefer + $client = new Client('https://www.secure.com/'); + $client->setSslVerification(__DIR__); + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertSame(__DIR__, $options->get(CURLOPT_CAPATH)); + } + + public function testConfigSettingsControlSslConfiguration() + { + // Use the default ca certs on the system + $client = new Client('https://www.secure.com/', array('ssl.certificate_authority' => 'system')); + $this->assertNull($client->getConfig('curl.options')); + // Can set the cacert value as well + $client = new Client('https://www.secure.com/', array('ssl.certificate_authority' => false)); + $options = $client->getConfig('curl.options'); + $this->assertArrayNotHasKey(CURLOPT_CAINFO, $options); + $this->assertSame(false, $options[CURLOPT_SSL_VERIFYPEER]); + $this->assertSame(0, $options[CURLOPT_SSL_VERIFYHOST]); + } + + public function testClientAllowsUnsafeOperationIfRequested() + { + // be really unsafe if you insist + $client = new Client('https://www.secure.com/', array( + 'api' => 'v1' + )); + + $client->setSslVerification(false); + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertFalse($options->get(CURLOPT_SSL_VERIFYPEER)); + $this->assertNull($options->get(CURLOPT_CAINFO)); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testThrowsExceptionForInvalidCertificate() + { + $client = new Client('https://www.secure.com/'); + $client->setSslVerification('/path/to/missing/file'); + } + + public function testClientAllowsSettingSpecificSslCaInfo() + { + // set a file other than the provided cacert.pem + $client = new Client('https://www.secure.com/', array( + 'api' => 'v1' + )); + + $client->setSslVerification(__FILE__); + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertSame(__FILE__, $options->get(CURLOPT_CAINFO)); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testClientPreventsInadvertentInsecureVerifyHostSetting() + { + // set a file other than the provided cacert.pem + $client = new Client('https://www.secure.com/', array( + 'api' => 'v1' + )); + $client->setSslVerification(__FILE__, true, true); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testClientPreventsInvalidVerifyPeerSetting() + { + // set a file other than the provided cacert.pem + $client = new Client('https://www.secure.com/', array( + 'api' => 'v1' + )); + $client->setSslVerification(__FILE__, 'yes'); + } + + public function testClientAddsParamsToRequests() + { + Version::$emitWarnings = false; + $client = new Client('http://www.example.com', array( + 'api' => 'v1', + 'request.params' => array( + 'foo' => 'bar', + 'baz' => 'jar' + ) + )); + $request = $client->createRequest(); + $this->assertEquals('bar', $request->getParams()->get('foo')); + $this->assertEquals('jar', $request->getParams()->get('baz')); + Version::$emitWarnings = true; + } + + public function urlProvider() + { + $u = $this->getServer()->getUrl() . 'base/'; + $u2 = $this->getServer()->getUrl() . 'base?z=1'; + return array( + array($u, '', $u), + array($u, 'relative/path/to/resource', $u . 'relative/path/to/resource'), + array($u, 'relative/path/to/resource?a=b&c=d', $u . 'relative/path/to/resource?a=b&c=d'), + array($u, '/absolute/path/to/resource', $this->getServer()->getUrl() . 'absolute/path/to/resource'), + array($u, '/absolute/path/to/resource?a=b&c=d', $this->getServer()->getUrl() . 'absolute/path/to/resource?a=b&c=d'), + array($u2, '/absolute/path/to/resource?a=b&c=d', $this->getServer()->getUrl() . 'absolute/path/to/resource?a=b&c=d&z=1'), + array($u2, 'relative/path/to/resource', $this->getServer()->getUrl() . 'base/relative/path/to/resource?z=1'), + array($u2, 'relative/path/to/resource?another=query', $this->getServer()->getUrl() . 'base/relative/path/to/resource?another=query&z=1') + ); + } + + /** + * @dataProvider urlProvider + */ + public function testBuildsRelativeUrls($baseUrl, $url, $result) + { + $client = new Client($baseUrl); + $this->assertEquals($result, $client->get($url)->getUrl()); + } + + public function testAllowsConfigsToBeChangedAndInjectedInBaseUrl() + { + $client = new Client('http://{a}/{b}'); + $this->assertEquals('http:///', $client->getBaseUrl()); + $this->assertEquals('http://{a}/{b}', $client->getBaseUrl(false)); + $client->setConfig(array( + 'a' => 'test.com', + 'b' => 'index.html' + )); + $this->assertEquals('http://test.com/index.html', $client->getBaseUrl()); + } + + public function testCreatesRequestsWithDefaultValues() + { + $client = new Client($this->getServer()->getUrl() . 'base'); + + // Create a GET request + $request = $client->createRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals($client->getBaseUrl(), $request->getUrl()); + + // Create a DELETE request + $request = $client->createRequest('DELETE'); + $this->assertEquals('DELETE', $request->getMethod()); + $this->assertEquals($client->getBaseUrl(), $request->getUrl()); + + // Create a HEAD request with custom headers + $request = $client->createRequest('HEAD', 'http://www.test.com/'); + $this->assertEquals('HEAD', $request->getMethod()); + $this->assertEquals('http://www.test.com/', $request->getUrl()); + + // Create a PUT request + $request = $client->createRequest('PUT'); + $this->assertEquals('PUT', $request->getMethod()); + + // Create a PUT request with injected config + $client->getConfig()->set('a', 1)->set('b', 2); + $request = $client->createRequest('PUT', '/path/{a}?q={b}'); + $this->assertEquals($request->getUrl(), $this->getServer()->getUrl() . 'path/1?q=2'); + } + + public function testClientHasHelperMethodsForCreatingRequests() + { + $url = $this->getServer()->getUrl(); + $client = new Client($url . 'base'); + $this->assertEquals('GET', $client->get()->getMethod()); + $this->assertEquals('PUT', $client->put()->getMethod()); + $this->assertEquals('POST', $client->post()->getMethod()); + $this->assertEquals('HEAD', $client->head()->getMethod()); + $this->assertEquals('DELETE', $client->delete()->getMethod()); + $this->assertEquals('OPTIONS', $client->options()->getMethod()); + $this->assertEquals('PATCH', $client->patch()->getMethod()); + $this->assertEquals($url . 'base/abc', $client->get('abc')->getUrl()); + $this->assertEquals($url . 'zxy', $client->put('/zxy')->getUrl()); + $this->assertEquals($url . 'zxy?a=b', $client->post('/zxy?a=b')->getUrl()); + $this->assertEquals($url . 'base?a=b', $client->head('?a=b')->getUrl()); + $this->assertEquals($url . 'base?a=b', $client->delete('/base?a=b')->getUrl()); + } + + public function testClientInjectsConfigsIntoUrls() + { + $client = new Client('http://www.test.com/api/v1', array( + 'test' => '123' + )); + $request = $client->get('relative/{test}'); + $this->assertEquals('http://www.test.com/api/v1/relative/123', $request->getUrl()); + } + + public function testAllowsEmptyBaseUrl() + { + $client = new Client(); + $request = $client->get('http://www.google.com/'); + $this->assertEquals('http://www.google.com/', $request->getUrl()); + $request->setResponse(new Response(200), true); + $request->send(); + } + + public function testAllowsCustomCurlMultiObjects() + { + $mock = $this->getMock('Guzzle\\Http\\Curl\\CurlMulti', array('add', 'send')); + $mock->expects($this->once()) + ->method('add') + ->will($this->returnSelf()); + $mock->expects($this->once()) + ->method('send') + ->will($this->returnSelf()); + + $client = new Client(); + $client->setCurlMulti($mock); + + $request = $client->get(); + $request->setResponse(new Response(200), true); + $client->send($request); + } + + public function testClientSendsMultipleRequests() + { + $client = new Client($this->getServer()->getUrl()); + $mock = new MockPlugin(); + + $responses = array( + new Response(200), + new Response(201), + new Response(202) + ); + + $mock->addResponse($responses[0]); + $mock->addResponse($responses[1]); + $mock->addResponse($responses[2]); + + $client->getEventDispatcher()->addSubscriber($mock); + + $requests = array( + $client->get(), + $client->head(), + $client->put('/', null, 'test') + ); + + $this->assertEquals(array( + $responses[0], + $responses[1], + $responses[2] + ), $client->send($requests)); + } + + public function testClientSendsSingleRequest() + { + $client = new Client($this->getServer()->getUrl()); + $mock = new MockPlugin(); + $response = new Response(200); + $mock->addResponse($response); + $client->getEventDispatcher()->addSubscriber($mock); + $this->assertEquals($response, $client->send($client->get())); + } + + /** + * @expectedException \Guzzle\Http\Exception\BadResponseException + */ + public function testClientThrowsExceptionForSingleRequest() + { + $client = new Client($this->getServer()->getUrl()); + $mock = new MockPlugin(); + $response = new Response(404); + $mock->addResponse($response); + $client->getEventDispatcher()->addSubscriber($mock); + $client->send($client->get()); + } + + /** + * @expectedException \Guzzle\Common\Exception\ExceptionCollection + */ + public function testClientThrowsExceptionForMultipleRequests() + { + $client = new Client($this->getServer()->getUrl()); + $mock = new MockPlugin(); + $mock->addResponse(new Response(200)); + $mock->addResponse(new Response(404)); + $client->getEventDispatcher()->addSubscriber($mock); + $client->send(array($client->get(), $client->head())); + } + + public function testQueryStringsAreNotDoubleEncoded() + { + $client = new Client('http://test.com', array( + 'path' => array('foo', 'bar'), + 'query' => 'hi there', + 'data' => array( + 'test' => 'a&b' + ) + )); + + $request = $client->get('{/path*}{?query,data*}'); + $this->assertEquals('http://test.com/foo/bar?query=hi%20there&test=a%26b', $request->getUrl()); + $this->assertEquals('hi there', $request->getQuery()->get('query')); + $this->assertEquals('a&b', $request->getQuery()->get('test')); + } + + public function testQueryStringsAreNotDoubleEncodedUsingAbsolutePaths() + { + $client = new Client('http://test.com', array( + 'path' => array('foo', 'bar'), + 'query' => 'hi there', + )); + $request = $client->get('http://test.com{?query}'); + $this->assertEquals('http://test.com?query=hi%20there', $request->getUrl()); + $this->assertEquals('hi there', $request->getQuery()->get('query')); + } + + public function testAllowsUriTemplateInjection() + { + $client = new Client('http://test.com'); + $ref = new \ReflectionMethod($client, 'getUriTemplate'); + $ref->setAccessible(true); + $a = $ref->invoke($client); + $this->assertSame($a, $ref->invoke($client)); + $client->setUriTemplate(new UriTemplate()); + $this->assertNotSame($a, $ref->invoke($client)); + } + + public function testAllowsCustomVariablesWhenExpandingTemplates() + { + $client = new Client('http://test.com', array('test' => 'hi')); + $ref = new \ReflectionMethod($client, 'expandTemplate'); + $ref->setAccessible(true); + $uri = $ref->invoke($client, 'http://{test}{?query*}', array('query' => array('han' => 'solo'))); + $this->assertEquals('http://hi?han=solo', $uri); + } + + public function testUriArrayAllowsCustomTemplateVariables() + { + $client = new Client(); + $vars = array( + 'var' => 'hi' + ); + $this->assertEquals('/hi', (string) $client->createRequest('GET', array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->get(array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->put(array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->post(array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->head(array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->options(array('/{var}', $vars))->getUrl()); + } + + public function testAllowsDefaultHeaders() + { + Version::$emitWarnings = false; + $default = array('X-Test' => 'Hi!'); + $other = array('X-Other' => 'Foo'); + + $client = new Client(); + $client->setDefaultHeaders($default); + $this->assertEquals($default, $client->getDefaultHeaders()->getAll()); + $client->setDefaultHeaders(new Collection($default)); + $this->assertEquals($default, $client->getDefaultHeaders()->getAll()); + + $request = $client->createRequest('GET', null, $other); + $this->assertEquals('Hi!', $request->getHeader('X-Test')); + $this->assertEquals('Foo', $request->getHeader('X-Other')); + + $request = $client->createRequest('GET', null, new Collection($other)); + $this->assertEquals('Hi!', $request->getHeader('X-Test')); + $this->assertEquals('Foo', $request->getHeader('X-Other')); + + $request = $client->createRequest('GET'); + $this->assertEquals('Hi!', $request->getHeader('X-Test')); + Version::$emitWarnings = true; + } + + public function testDontReuseCurlMulti() + { + $client1 = new Client(); + $client2 = new Client(); + $this->assertNotSame($client1->getCurlMulti(), $client2->getCurlMulti()); + } + + public function testGetDefaultUserAgent() + { + $client = new Client(); + $agent = $this->readAttribute($client, 'userAgent'); + $version = curl_version(); + $testAgent = sprintf('Guzzle/%s curl/%s PHP/%s', Version::VERSION, $version['version'], PHP_VERSION); + $this->assertEquals($agent, $testAgent); + + $client->setUserAgent('foo'); + $this->assertEquals('foo', $this->readAttribute($client, 'userAgent')); + } + + public function testOverwritesUserAgent() + { + $client = new Client(); + $request = $client->createRequest('GET', 'http://www.foo.com', array('User-agent' => 'foo')); + $this->assertEquals('foo', (string) $request->getHeader('User-Agent')); + } + + public function testUsesDefaultUserAgent() + { + $client = new Client(); + $request = $client->createRequest('GET', 'http://www.foo.com'); + $this->assertContains('Guzzle/', (string) $request->getHeader('User-Agent')); + } + + public function testCanSetDefaultRequestOptions() + { + $client = new Client(); + $client->getConfig()->set('request.options', array( + 'query' => array('test' => '123', 'other' => 'abc'), + 'headers' => array('Foo' => 'Bar', 'Baz' => 'Bam') + )); + $request = $client->createRequest('GET', 'http://www.foo.com?test=hello', array('Foo' => 'Test')); + // Explicit options on a request should overrule default options + $this->assertEquals('Test', (string) $request->getHeader('Foo')); + $this->assertEquals('hello', $request->getQuery()->get('test')); + // Default options should still be set + $this->assertEquals('abc', $request->getQuery()->get('other')); + $this->assertEquals('Bam', (string) $request->getHeader('Baz')); + } + + public function testCanSetSetOptionsOnRequests() + { + $client = new Client(); + $request = $client->createRequest('GET', 'http://www.foo.com?test=hello', array('Foo' => 'Test'), null, array( + 'cookies' => array('michael' => 'test') + )); + $this->assertEquals('test', $request->getCookie('michael')); + } + + public function testHasDefaultOptionsHelperMethods() + { + $client = new Client(); + // With path + $client->setDefaultOption('headers/foo', 'bar'); + $this->assertEquals('bar', $client->getDefaultOption('headers/foo')); + // With simple key + $client->setDefaultOption('allow_redirects', false); + $this->assertFalse($client->getDefaultOption('allow_redirects')); + + $this->assertEquals(array( + 'headers' => array('foo' => 'bar'), + 'allow_redirects' => false + ), $client->getConfig('request.options')); + + $request = $client->get('/'); + $this->assertEquals('bar', $request->getHeader('foo')); + } + + public function testHeadCanUseOptions() + { + $client = new Client(); + $head = $client->head('http://www.foo.com', array(), array('query' => array('foo' => 'bar'))); + $this->assertEquals('bar', $head->getQuery()->get('foo')); + } + + public function testCanSetRelativeUrlStartingWithHttp() + { + $client = new Client('http://www.foo.com'); + $this->assertEquals( + 'http://www.foo.com/httpfoo', + $client->createRequest('GET', 'httpfoo')->getUrl() + ); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php new file mode 100644 index 000000000..5bf28de36 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php @@ -0,0 +1,947 @@ +getEventDispatcher()->addListener('request.sent', function (Event $e) use ($that) { + $that->requestHandle = $e['handle']; + }); + + return $request; + } + + public function setUp() + { + $this->requestHandle = null; + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructorExpectsCurlResource() + { + $h = new CurlHandle(false, array()); + } + + public function testConstructorExpectsProperOptions() + { + $h = curl_init($this->getServer()->getUrl()); + try { + $ha = new CurlHandle($h, false); + $this->fail('Expected InvalidArgumentException'); + } catch (\InvalidArgumentException $e) { + } + + $ha = new CurlHandle($h, array( + CURLOPT_URL => $this->getServer()->getUrl() + )); + $this->assertEquals($this->getServer()->getUrl(), $ha->getOptions()->get(CURLOPT_URL)); + + $ha = new CurlHandle($h, new Collection(array( + CURLOPT_URL => $this->getServer()->getUrl() + ))); + $this->assertEquals($this->getServer()->getUrl(), $ha->getOptions()->get(CURLOPT_URL)); + } + + public function testConstructorInitializesObject() + { + $handle = curl_init($this->getServer()->getUrl()); + $h = new CurlHandle($handle, array( + CURLOPT_URL => $this->getServer()->getUrl() + )); + $this->assertSame($handle, $h->getHandle()); + $this->assertInstanceOf('Guzzle\\Http\\Url', $h->getUrl()); + $this->assertEquals($this->getServer()->getUrl(), (string) $h->getUrl()); + $this->assertEquals($this->getServer()->getUrl(), $h->getOptions()->get(CURLOPT_URL)); + } + + public function testStoresStdErr() + { + $request = RequestFactory::getInstance()->create('GET', 'http://test.com'); + $request->getCurlOptions()->set('debug', true); + $h = CurlHandle::factory($request); + $this->assertEquals($h->getStderr(true), $h->getOptions()->get(CURLOPT_STDERR)); + $this->assertInternalType('resource', $h->getStderr(true)); + $this->assertInternalType('string', $h->getStderr(false)); + $r = $h->getStderr(true); + fwrite($r, 'test'); + $this->assertEquals('test', $h->getStderr(false)); + } + + public function testStoresCurlErrorNumber() + { + $h = new CurlHandle(curl_init('http://test.com'), array(CURLOPT_URL => 'http://test.com')); + $this->assertEquals(CURLE_OK, $h->getErrorNo()); + $h->setErrorNo(CURLE_OPERATION_TIMEOUTED); + $this->assertEquals(CURLE_OPERATION_TIMEOUTED, $h->getErrorNo()); + } + + public function testAccountsForMissingStdErr() + { + $handle = curl_init('http://www.test.com/'); + $h = new CurlHandle($handle, array( + CURLOPT_URL => 'http://www.test.com/' + )); + $this->assertNull($h->getStderr(false)); + } + + public function testDeterminesIfResourceIsAvailable() + { + $handle = curl_init($this->getServer()->getUrl()); + $h = new CurlHandle($handle, array()); + $this->assertTrue($h->isAvailable()); + + // Mess it up by closing the handle + curl_close($handle); + $this->assertFalse($h->isAvailable()); + + // Mess it up by unsetting the handle + $handle = null; + $this->assertFalse($h->isAvailable()); + } + + public function testWrapsErrorsAndInfo() + { + if (!defined('CURLOPT_TIMEOUT_MS')) { + $this->markTestSkipped('Update curl'); + } + + $settings = array( + CURLOPT_PORT => 123, + CURLOPT_CONNECTTIMEOUT_MS => 1, + CURLOPT_TIMEOUT_MS => 1 + ); + + $handle = curl_init($this->getServer()->getUrl()); + curl_setopt_array($handle, $settings); + $h = new CurlHandle($handle, $settings); + @curl_exec($handle); + + $errors = array( + "couldn't connect to host", + 'timeout was reached', + 'connection time-out', + 'connect() timed out!', + 'failed connect to 127.0.0.1:123; connection refused', + 'failed to connect to 127.0.0.1 port 123: connection refused' + ); + $this->assertTrue(in_array(strtolower($h->getError()), $errors), $h->getError() . ' was not the error'); + + $this->assertTrue($h->getErrorNo() > 0); + + $this->assertEquals($this->getServer()->getUrl(), $h->getInfo(CURLINFO_EFFECTIVE_URL)); + $this->assertInternalType('array', $h->getInfo()); + + curl_close($handle); + $this->assertEquals(null, $h->getInfo('url')); + } + + public function testGetInfoWithoutDebugMode() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get($this->getServer()->getUrl()); + $response = $request->send(); + + $info = $response->getInfo(); + $this->assertFalse(empty($info)); + $this->assertEquals($this->getServer()->getUrl(), $info['url']); + } + + public function testWrapsCurlOptions() + { + $handle = curl_init($this->getServer()->getUrl()); + $h = new CurlHandle($handle, array( + CURLOPT_AUTOREFERER => true, + CURLOPT_BUFFERSIZE => 1024 + )); + + $this->assertEquals(true, $h->getOptions()->get(CURLOPT_AUTOREFERER)); + $this->assertEquals(1024, $h->getOptions()->get(CURLOPT_BUFFERSIZE)); + } + + /** + * Data provider for factory tests + * + * @return array + */ + public function dataProvider() + { + $testFile = __DIR__ . '/../../../../../phpunit.xml.dist'; + + $postBody = new QueryString(array('file' => '@' . $testFile)); + $qs = new QueryString(array( + 'x' => 'y', + 'z' => 'a' + )); + + $client = new Client(); + $userAgent = $client->getDefaultUserAgent(); + $auth = base64_encode('michael:123'); + $testFileSize = filesize($testFile); + + $tests = array( + // Send a regular GET + array('GET', 'http://www.google.com/', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_HTTPHEADER => array('Accept:', 'Host: www.google.com', 'User-Agent: ' . $userAgent), + )), + // Test that custom request methods can be used + array('TRACE', 'http://www.google.com/', null, null, array( + CURLOPT_CUSTOMREQUEST => 'TRACE' + )), + // Send a GET using a port + array('GET', 'http://127.0.0.1:8080', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_PORT => 8080, + CURLOPT_HTTPHEADER => array('Accept:', 'Host: 127.0.0.1:8080', 'User-Agent: ' . $userAgent), + )), + // Send a HEAD request + array('HEAD', 'http://www.google.com/', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_HTTPHEADER => array('Accept:', 'Host: www.google.com', 'User-Agent: ' . $userAgent), + CURLOPT_NOBODY => 1 + )), + // Send a GET using basic auth + array('GET', 'https://michael:123@127.0.0.1/index.html?q=2', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_HTTPHEADER => array( + 'Accept:', + 'Host: 127.0.0.1', + 'Authorization: Basic ' . $auth, + 'User-Agent: ' . $userAgent + ), + CURLOPT_PORT => 443 + )), + // Send a GET request with custom headers + array('GET', 'http://127.0.0.1:8124/', array( + 'x-test-data' => 'Guzzle' + ), null, array( + CURLOPT_PORT => 8124, + CURLOPT_HTTPHEADER => array( + 'Accept:', + 'Host: 127.0.0.1:8124', + 'x-test-data: Guzzle', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'x-test-data' => 'Guzzle' + )), + // Send a POST using a query string + array('POST', 'http://127.0.0.1:8124/post.php', null, $qs, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_POSTFIELDS => 'x=y&z=a', + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Content-Type: application/x-www-form-urlencoded; charset=utf-8', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '7', + '!Expect' => null, + 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8', + '!Transfer-Encoding' => null + )), + // Send a PUT using raw data + array('PUT', 'http://127.0.0.1:8124/put.php', null, EntityBody::factory(fopen($testFile, 'r+')), array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_READFUNCTION => 'callback', + CURLOPT_INFILESIZE => filesize($testFile), + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + '!Expect' => null, + 'Content-Length' => $testFileSize, + '!Transfer-Encoding' => null + )), + // Send a POST request using an array of fields + array('POST', 'http://127.0.0.1:8124/post.php', null, array( + 'x' => 'y', + 'a' => 'b' + ), array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => 'x=y&a=b', + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Content-Type: application/x-www-form-urlencoded; charset=utf-8', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '7', + '!Expect' => null, + 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8', + '!Transfer-Encoding' => null + )), + // Send a POST request with raw POST data and a custom content-type + array('POST', 'http://127.0.0.1:8124/post.php', array( + 'Content-Type' => 'application/json' + ), '{"hi":"there"}', array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_UPLOAD => true, + CURLOPT_INFILESIZE => 14, + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Content-Type: application/json', + 'User-Agent: ' . $userAgent + ), + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Type' => 'application/json', + '!Expect' => null, + 'Content-Length' => '14', + '!Transfer-Encoding' => null + )), + // Send a POST request with raw POST data, a custom content-type, and use chunked encoding + array('POST', 'http://127.0.0.1:8124/post.php', array( + 'Content-Type' => 'application/json', + 'Transfer-Encoding' => 'chunked' + ), '{"hi":"there"}', array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_UPLOAD => true, + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Transfer-Encoding: chunked', + 'Content-Type: application/json', + 'User-Agent: ' . $userAgent + ), + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Type' => 'application/json', + '!Expect' => null, + 'Transfer-Encoding' => 'chunked', + '!Content-Length' => '' + )), + // Send a POST request with no body + array('POST', 'http://127.0.0.1:8124/post.php', null, '', array( + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '0', + '!Transfer-Encoding' => null + )), + // Send a POST request with empty post fields + array('POST', 'http://127.0.0.1:8124/post.php', null, array(), array( + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '0', + '!Transfer-Encoding' => null + )), + // Send a PATCH request + array('PATCH', 'http://127.0.0.1:8124/patch.php', null, 'body', array( + CURLOPT_INFILESIZE => 4, + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + )), + // Send a DELETE request with a body + array('DELETE', 'http://127.0.0.1:8124/delete.php', null, 'body', array( + CURLOPT_CUSTOMREQUEST => 'DELETE', + CURLOPT_INFILESIZE => 4, + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '4', + '!Expect' => null, + '!Transfer-Encoding' => null + )), + + /** + * Send a request with empty path and a fragment - the fragment must be + * stripped out before sending it to curl + * + * @issue 453 + * @link https://github.com/guzzle/guzzle/issues/453 + */ + array('GET', 'http://www.google.com#head', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_HTTPHEADER => array('Accept:', 'Host: www.google.com', 'User-Agent: ' . $userAgent), + )), + ); + + $postTest = array('POST', 'http://127.0.0.1:8124/post.php', null, $postBody, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => array( + 'file' => '@' . $testFile . ';filename=phpunit.xml.dist;type=application/octet-stream' + ), + CURLOPT_HTTPHEADER => array ( + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Content-Type: multipart/form-data', + 'Expect: 100-Continue', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '*', + 'Expect' => '100-Continue', + 'Content-Type' => 'multipart/form-data; boundary=*', + '!Transfer-Encoding' => null + )); + + if (version_compare(phpversion(), '5.5.0', '>=')) { + $postTest[4][CURLOPT_POSTFIELDS] = array( + 'file' => new \CurlFile($testFile, 'application/octet-stream', 'phpunit.xml.dist') + ); + } + + $tests[] = $postTest; + + return $tests; + } + + /** + * @dataProvider dataProvider + */ + public function testFactoryCreatesCurlBasedOnRequest($method, $url, $headers, $body, $options, $expectedHeaders = null) + { + $client = new Client(); + $request = $client->createRequest($method, $url, $headers, $body); + $request->getCurlOptions()->set('debug', true); + + $originalRequest = clone $request; + $curlTest = clone $request; + $handle = CurlHandle::factory($curlTest); + + $this->assertInstanceOf('Guzzle\\Http\\Curl\\CurlHandle', $handle); + $o = $handle->getOptions()->getAll(); + + // Headers are case-insensitive + if (isset($o[CURLOPT_HTTPHEADER])) { + $o[CURLOPT_HTTPHEADER] = array_map('strtolower', $o[CURLOPT_HTTPHEADER]); + } + if (isset($options[CURLOPT_HTTPHEADER])) { + $options[CURLOPT_HTTPHEADER] = array_map('strtolower', $options[CURLOPT_HTTPHEADER]); + } + + $check = 0; + foreach ($options as $key => $value) { + $check++; + $this->assertArrayHasKey($key, $o, '-> Check number ' . $check); + if ($key != CURLOPT_HTTPHEADER && $key != CURLOPT_POSTFIELDS && (is_array($o[$key])) || $o[$key] instanceof \Closure) { + $this->assertEquals('callback', $value, '-> Check number ' . $check); + } else { + $this->assertTrue($value == $o[$key], '-> Check number ' . $check . ' - ' . var_export($value, true) . ' != ' . var_export($o[$key], true)); + } + } + + // If we are testing the actual sent headers + if ($expectedHeaders) { + + // Send the request to the test server + $client = new Client($this->getServer()->getUrl()); + $request->setClient($client); + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request->send(); + + // Get the request that was sent and create a request that we expected + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals($method, $requests[0]->getMethod()); + + $test = $this->compareHeaders($expectedHeaders, $requests[0]->getHeaders()); + $this->assertFalse($test, $test . "\nSent: \n" . $request . "\n\n" . $requests[0]); + + // Ensure only one Content-Length header is sent + if ($request->getHeader('Content-Length')) { + $this->assertEquals((string) $request->getHeader('Content-Length'), (string) $requests[0]->getHeader('Content-Length')); + } + } + } + + public function testFactoryUsesSpecifiedProtocol() + { + $request = RequestFactory::getInstance()->create('GET', 'http://127.0.0.1:8124/'); + $request->setProtocolVersion('1.1'); + $handle = CurlHandle::factory($request); + $options = $handle->getOptions(); + $this->assertEquals(CURL_HTTP_VERSION_1_1, $options[CURLOPT_HTTP_VERSION]); + } + + public function testUploadsPutData() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/'); + $request->getCurlOptions()->set('debug', true); + $request->setBody(EntityBody::factory('test'), 'text/plain', false); + $request->getCurlOptions()->set('progress', true); + + $o = $this->getWildcardObserver($request); + $request->send(); + + // Make sure that the events were dispatched + $this->assertTrue($o->has('curl.callback.progress')); + + // Ensure that the request was received exactly as intended + $r = $this->getServer()->getReceivedRequests(true); + $this->assertFalse($r[0]->hasHeader('Transfer-Encoding')); + $this->assertEquals(4, (string) $r[0]->getHeader('Content-Length')); + $sent = strtolower($r[0]); + $this->assertContains('put / http/1.1', $sent); + $this->assertContains('host: 127.0.0.1', $sent); + $this->assertContains('user-agent:', $sent); + $this->assertContains('content-type: text/plain', $sent); + } + + public function testUploadsPutDataUsingChunkedEncodingWhenLengthCannotBeDetermined() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi" + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/'); + $request->setBody(EntityBody::factory(fopen($this->getServer()->getUrl(), 'r')), 'text/plain'); + $request->send(); + + $r = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('chunked', $r[1]->getHeader('Transfer-Encoding')); + $this->assertFalse($r[1]->hasHeader('Content-Length')); + } + + public function testUploadsPutDataUsingChunkedEncodingWhenForced() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', array('Transfer-Encoding' => 'chunked'), 'hi!'); + $request->send(); + + $r = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('chunked', $r[0]->getHeader('Transfer-Encoding')); + $this->assertFalse($r[0]->hasHeader('Content-Length')); + $this->assertEquals('hi!', $r[0]->getBody(true)); + } + + public function testSendsPostRequestsWithFields() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + + $request = RequestFactory::getInstance()->create('POST', $this->getServer()->getUrl()); + $request->getCurlOptions()->set('debug', true); + $request->setClient(new Client()); + $request->addPostFields(array( + 'a' => 'b', + 'c' => 'ay! ~This is a test, isn\'t it?' + )); + $request->send(); + + // Make sure that the request was sent correctly + $r = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('a=b&c=ay%21%20~This%20is%20a%20test%2C%20isn%27t%20it%3F', (string) $r[0]->getBody()); + $this->assertFalse($r[0]->hasHeader('Transfer-Encoding')); + $this->assertEquals(56, (string) $r[0]->getHeader('Content-Length')); + $sent = strtolower($r[0]); + $this->assertContains('post / http/1.1', $sent); + $this->assertContains('content-type: application/x-www-form-urlencoded; charset=utf-8', $sent); + } + + public function testSendsPostRequestsWithFiles() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + + $request = RequestFactory::getInstance()->create('POST', $this->getServer()->getUrl()); + $request->getCurlOptions()->set('debug', true); + $request->setClient(new Client()); + $request->addPostFiles(array( + 'foo' => __FILE__, + )); + $request->addPostFields(array( + 'bar' => 'baz', + 'arr' => array('a' => 1, 'b' => 2), + )); + $this->updateForHandle($request); + $request->send(); + + // Ensure the CURLOPT_POSTFIELDS option was set properly + $options = $this->requestHandle->getOptions()->getAll(); + if (version_compare(phpversion(), '5.5.0', '<')) { + $this->assertContains('@' . __FILE__ . ';filename=CurlHandleTest.php;type=text/x-', $options[CURLOPT_POSTFIELDS]['foo']); + } else{ + $this->assertInstanceOf('CURLFile', $options[CURLOPT_POSTFIELDS]['foo']); + } + $this->assertEquals('baz', $options[CURLOPT_POSTFIELDS]['bar']); + $this->assertEquals('1', $options[CURLOPT_POSTFIELDS]['arr[a]']); + $this->assertEquals('2', $options[CURLOPT_POSTFIELDS]['arr[b]']); + // Ensure that a Content-Length header was sent by cURL + $this->assertTrue($request->hasHeader('Content-Length')); + } + + public function testCurlConfigurationOptionsAreSet() + { + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()); + $request->setClient(new Client('http://www.example.com')); + $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, 99); + $request->getCurlOptions()->set('curl.fake_opt', 99); + $request->getCurlOptions()->set(CURLOPT_PORT, 8181); + $handle = CurlHandle::factory($request); + $this->assertEquals(99, $handle->getOptions()->get(CURLOPT_CONNECTTIMEOUT)); + $this->assertEquals(8181, $handle->getOptions()->get(CURLOPT_PORT)); + $this->assertNull($handle->getOptions()->get('curl.fake_opt')); + $this->assertNull($handle->getOptions()->get('fake_opt')); + } + + public function testEnsuresRequestsHaveResponsesWhenUpdatingFromTransfer() + { + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()); + $handle = CurlHandle::factory($request); + $handle->updateRequestFromTransfer($request); + } + + public function testCanSendBodyAsString() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', null, 'foo'); + $request->getCurlOptions()->set('body_as_string', true); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(false); + $this->assertContains('PUT /', $requests[0]); + $this->assertContains("\nfoo", $requests[0]); + $this->assertContains('content-length: 3', $requests[0]); + $this->assertNotContains('content-type', $requests[0]); + } + + public function testCanSendPostBodyAsString() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/', null, 'foo'); + $request->getCurlOptions()->set('body_as_string', true); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(false); + $this->assertContains('POST /', $requests[0]); + $this->assertContains("\nfoo", $requests[0]); + $this->assertContains('content-length: 3', $requests[0]); + $this->assertNotContains('content-type', $requests[0]); + } + + public function testAllowsWireTransferInfoToBeEnabled() + { + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()); + $request->getCurlOptions()->set('debug', true); + $handle = CurlHandle::factory($request); + $this->assertNotNull($handle->getOptions()->get(CURLOPT_STDERR)); + $this->assertNotNull($handle->getOptions()->get(CURLOPT_VERBOSE)); + } + + public function testAddsCustomCurlOptions() + { + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()); + $request->getCurlOptions()->set(CURLOPT_TIMEOUT, 200); + $handle = CurlHandle::factory($request); + $this->assertEquals(200, $handle->getOptions()->get(CURLOPT_TIMEOUT)); + } + + public function testSendsPostUploadsWithContentDispositionHeaders() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\n\r\nContent-Length: 0\r\n\r\n"); + + $fileToUpload = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'TestData' . DIRECTORY_SEPARATOR . 'test_service.json'; + + $client = new Client($this->getServer()->getUrl()); + $request = $client->post(); + $request->addPostFile('foo', $fileToUpload, 'application/json'); + $request->addPostFile('foo', __FILE__); + + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $body = (string) $requests[0]->getBody(); + + $this->assertContains('Content-Disposition: form-data; name="foo[0]"; filename="', $body); + $this->assertContains('Content-Type: application/json', $body); + $this->assertContains('Content-Type: text/x-', $body); + $this->assertContains('Content-Disposition: form-data; name="foo[1]"; filename="', $body); + } + + public function requestMethodProvider() + { + return array(array('POST'), array('PUT'), array('PATCH')); + } + + /** + * @dataProvider requestMethodProvider + */ + public function testSendsRequestsWithNoBodyUsingContentLengthZero($method) + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $client->createRequest($method)->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertFalse($requests[0]->hasHeader('Transfer-Encoding')); + $this->assertTrue($requests[0]->hasHeader('Content-Length')); + $this->assertEquals('0', (string) $requests[0]->getHeader('Content-Length')); + } + + /** + * @dataProvider provideCurlConfig + */ + public function testParseCurlConfigConvertsStringKeysToConstantKeys($options, $expected) + { + $actual = CurlHandle::parseCurlConfig($options); + $this->assertEquals($expected, $actual); + } + + /** + * Data provider for curl configurations + * + * @return array + */ + public function provideCurlConfig() + { + return array( + // Conversion of option name to constant value + array( + array( + 'CURLOPT_PORT' => 10, + 'CURLOPT_TIMEOUT' => 99 + ), + array( + CURLOPT_PORT => 10, + CURLOPT_TIMEOUT => 99 + ) + ), + // Keeps non constant options + array( + array('debug' => true), + array('debug' => true) + ), + // Conversion of constant names to constant values + array( + array('debug' => 'CURLPROXY_HTTP'), + array('debug' => CURLPROXY_HTTP) + ) + ); + } + + public function testSeeksToBeginningOfStreamWhenSending() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', null, 'test'); + $request->send(); + $request->send(); + + $received = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(2, count($received)); + $this->assertEquals('test', (string) $received[0]->getBody()); + $this->assertEquals('test', (string) $received[1]->getBody()); + } + + public function testAllowsCurloptEncodingToBeSet() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/', null); + $request->getCurlOptions()->set(CURLOPT_ENCODING, ''); + $this->updateForHandle($request); + $request->send(); + $options = $this->requestHandle->getOptions()->getAll(); + $this->assertSame('', $options[CURLOPT_ENCODING]); + $received = $this->getServer()->getReceivedRequests(false); + $this->assertContainsIns('accept: */*', $received[0]); + $this->assertContainsIns('accept-encoding: ', $received[0]); + } + + public function testSendsExpectHeaderWhenSizeIsGreaterThanCutoff() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', null, 'test'); + // Start sending the expect header to 2 bytes + $this->updateForHandle($request); + $request->setExpectHeaderCutoff(2)->send(); + $options = $this->requestHandle->getOptions()->getAll(); + $this->assertContains('Expect: 100-Continue', $options[CURLOPT_HTTPHEADER]); + $received = $this->getServer()->getReceivedRequests(false); + $this->assertContainsIns('expect: 100-continue', $received[0]); + } + + public function testSetsCurloptEncodingWhenAcceptEncodingHeaderIsSet() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/', array( + 'Accept' => 'application/json', + 'Accept-Encoding' => 'gzip, deflate', + )); + $this->updateForHandle($request); + $request->send(); + $options = $this->requestHandle->getOptions()->getAll(); + $this->assertSame('gzip, deflate', $options[CURLOPT_ENCODING]); + $received = $this->getServer()->getReceivedRequests(false); + $this->assertContainsIns('accept: application/json', $received[0]); + $this->assertContainsIns('accept-encoding: gzip, deflate', $received[0]); + } + + public function testSendsPostFieldsForNonPostRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\n\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client(); + $request = $client->put($this->getServer()->getUrl(), null, array( + 'foo' => 'baz', + 'baz' => 'bar' + )); + + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('PUT', $requests[0]->getMethod()); + $this->assertEquals( + 'application/x-www-form-urlencoded; charset=utf-8', + (string) $requests[0]->getHeader('Content-Type') + ); + $this->assertEquals(15, (string) $requests[0]->getHeader('Content-Length')); + $this->assertEquals('foo=baz&baz=bar', (string) $requests[0]->getBody()); + } + + public function testSendsPostFilesForNonPostRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\n\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client(); + $request = $client->put($this->getServer()->getUrl(), null, array( + 'foo' => '@' . __FILE__ + )); + + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('PUT', $requests[0]->getMethod()); + $this->assertContains('multipart/form-data', (string) $requests[0]->getHeader('Content-Type')); + $this->assertContains('testSendsPostFilesForNonPostRequests', (string) $requests[0]->getBody()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php new file mode 100644 index 000000000..e04141c5f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php @@ -0,0 +1,110 @@ +multi = new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT); + } + + public function tearDown() + { + unset($this->multi); + } + + public function testConstructorSetsMaxHandles() + { + $m = new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT); + $this->assertEquals(self::MAX_HANDLES, $this->readAttribute($m, 'maxHandles')); + } + + public function testConstructorSetsSelectTimeout() + { + $m = new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT); + $this->assertEquals(self::SELECT_TIMEOUT, $this->readAttribute($m, 'selectTimeout')); + } + + public function testAddingRequestsAddsToQueue() + { + $r = new Request('GET', 'http://www.foo.com'); + $this->assertSame($this->multi, $this->multi->add($r)); + $this->assertEquals(1, count($this->multi)); + $this->assertEquals(array($r), $this->multi->all()); + + $this->assertTrue($this->multi->remove($r)); + $this->assertFalse($this->multi->remove($r)); + $this->assertEquals(0, count($this->multi)); + } + + public function testResetClearsState() + { + $r = new Request('GET', 'http://www.foo.com'); + $this->multi->add($r); + $this->multi->reset(); + $this->assertEquals(0, count($this->multi)); + } + + public function testSendWillSendQueuedRequestsFirst() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $events = array(); + $client->getCurlMulti()->getEventDispatcher()->addListener( + CurlMultiProxy::ADD_REQUEST, + function ($e) use (&$events) { + $events[] = $e; + } + ); + $request = $client->get(); + $request->getEventDispatcher()->addListener('request.complete', function () use ($client) { + $client->get('/foo')->send(); + }); + $request->send(); + $received = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(2, count($received)); + $this->assertEquals($this->getServer()->getUrl(), $received[0]->getUrl()); + $this->assertEquals($this->getServer()->getUrl() . 'foo', $received[1]->getUrl()); + $this->assertEquals(2, count($events)); + } + + public function testTrimsDownMaxHandleCount() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $client->setCurlMulti(new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT)); + $request = $client->get(); + $request->send(); + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $handles = $this->readAttribute($client->getCurlMulti(), 'handles'); + $this->assertEquals(2, count($handles)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php new file mode 100644 index 000000000..1272281f9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php @@ -0,0 +1,455 @@ +multi = new MockMulti(); + } + + public function tearDown() + { + unset($this->multi); + } + + public function testConstructorCreateMultiHandle() + { + $this->assertInternalType('resource', $this->multi->getHandle()); + $this->assertEquals('curl_multi', get_resource_type($this->multi->getHandle())); + } + + public function testDestructorClosesMultiHandle() + { + $handle = $this->multi->getHandle(); + $this->multi->__destruct(); + $this->assertFalse(is_resource($handle)); + } + + public function testRequestsCanBeAddedAndCounted() + { + $multi = new CurlMulti(); + $request1 = new Request('GET', 'http://www.google.com/'); + $multi->add($request1); + $this->assertEquals(array($request1), $multi->all()); + $request2 = new Request('POST', 'http://www.google.com/'); + $multi->add($request2); + $this->assertEquals(array($request1, $request2), $multi->all()); + $this->assertEquals(2, count($multi)); + } + + public function testRequestsCanBeRemoved() + { + $request1 = new Request('GET', 'http://www.google.com/'); + $this->multi->add($request1); + $request2 = new Request('PUT', 'http://www.google.com/'); + $this->multi->add($request2); + $this->assertEquals(array($request1, $request2), $this->multi->all()); + $this->assertTrue($this->multi->remove($request1)); + $this->assertFalse($this->multi->remove($request1)); + $this->assertEquals(array($request2), $this->multi->all()); + } + + public function testsResetRemovesRequestsAndResetsState() + { + $this->multi->add(new Request('GET', 'http://www.google.com/')); + $this->multi->reset(); + $this->assertEquals(array(), $this->multi->all()); + } + + public function testSendsRequestsThroughCurl() + { + $this->getServer()->enqueue(array( + "HTTP/1.1 204 No content\r\n" . + "Content-Length: 0\r\n" . + "Server: Jetty(6.1.3)\r\n\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Type: text/html; charset=utf-8\r\n" . + "Content-Length: 4\r\n" . + "Server: Jetty(6.1.3)\r\n\r\n" . + "data" + )); + + $request1 = new Request('GET', $this->getServer()->getUrl()); + $request2 = new Request('GET', $this->getServer()->getUrl()); + $this->multi->add($request1); + $this->multi->add($request2); + $this->multi->send(); + + $response1 = $request1->getResponse(); + $response2 = $request2->getResponse(); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response1); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response2); + + $this->assertTrue($response1->getBody(true) == 'data' || $response2->getBody(true) == 'data'); + $this->assertTrue($response1->getBody(true) == '' || $response2->getBody(true) == ''); + $this->assertTrue($response1->getStatusCode() == '204' || $response2->getStatusCode() == '204'); + $this->assertNotEquals((string) $response1, (string) $response2); + } + + public function testSendsThroughCurlAndAggregatesRequestExceptions() + { + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n" . + "Content-Type: text/html; charset=utf-8\r\n" . + "Content-Length: 4\r\n" . + "Server: Jetty(6.1.3)\r\n" . + "\r\n" . + "data", + "HTTP/1.1 204 No content\r\n" . + "Content-Length: 0\r\n" . + "Server: Jetty(6.1.3)\r\n" . + "\r\n", + "HTTP/1.1 404 Not Found\r\n" . + "Content-Length: 0\r\n" . + "\r\n" + )); + + $request1 = new Request('GET', $this->getServer()->getUrl()); + $request2 = new Request('HEAD', $this->getServer()->getUrl()); + $request3 = new Request('GET', $this->getServer()->getUrl()); + $this->multi->add($request1); + $this->multi->add($request2); + $this->multi->add($request3); + + try { + $this->multi->send(); + $this->fail('MultiTransferException not thrown when aggregating request exceptions'); + } catch (MultiTransferException $e) { + + $this->assertTrue($e->containsRequest($request1)); + $this->assertTrue($e->containsRequest($request2)); + $this->assertTrue($e->containsRequest($request3)); + $this->assertInstanceOf('ArrayIterator', $e->getIterator()); + $this->assertEquals(1, count($e)); + $exceptions = $e->getIterator(); + + $response1 = $request1->getResponse(); + $response2 = $request2->getResponse(); + $response3 = $request3->getResponse(); + + $this->assertNotEquals((string) $response1, (string) $response2); + $this->assertNotEquals((string) $response3, (string) $response1); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response1); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response2); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response3); + + $failed = $exceptions[0]->getResponse(); + $this->assertEquals(404, $failed->getStatusCode()); + $this->assertEquals(1, count($e)); + + // Test the IteratorAggregate functionality + foreach ($e as $except) { + $this->assertEquals($failed, $except->getResponse()); + } + + $this->assertEquals(1, count($e->getFailedRequests())); + $this->assertEquals(2, count($e->getSuccessfulRequests())); + $this->assertEquals(3, count($e->getAllRequests())); + } + } + + public function testCurlErrorsAreCaught() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + try { + $request = RequestFactory::getInstance()->create('GET', 'http://127.0.0.1:9876/'); + $request->setClient(new Client()); + $request->getCurlOptions()->set(CURLOPT_FRESH_CONNECT, true); + $request->getCurlOptions()->set(CURLOPT_FORBID_REUSE, true); + $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, 5); + $request->send(); + $this->fail('CurlException not thrown'); + } catch (CurlException $e) { + $m = $e->getMessage(); + $this->assertContains('[curl] ', $m); + $this->assertContains('[url] http://127.0.0.1:9876/', $m); + $this->assertInternalType('array', $e->getCurlInfo()); + } + } + + public function testRemovesQueuedRequests() + { + $request = RequestFactory::getInstance()->create('GET', 'http://127.0.0.1:9876/'); + $r = new Response(200); + $request->setClient(new Client()); + $request->setResponse($r, true); + $this->multi->add($request); + $this->multi->send(); + $this->assertSame($r, $request->getResponse()); + } + + public function testRemovesQueuedRequestsAddedInTransit() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $client = new Client($this->getServer()->getUrl()); + $r = $client->get(); + $r->getEventDispatcher()->addListener('request.receive.status_line', function (Event $event) use ($client) { + // Create a request using a queued response + $request = $client->get()->setResponse(new Response(200), true); + $request->send(); + }); + $r->send(); + $this->assertEquals(1, count($this->getServer()->getReceivedRequests(false))); + } + + public function testCatchesExceptionsBeforeSendingSingleRequest() + { + $client = new Client($this->getServer()->getUrl()); + $multi = new CurlMulti(); + $client->setCurlMulti($multi); + $request = $client->get(); + $request->getEventDispatcher()->addListener('request.before_send', function() { + throw new \RuntimeException('Testing!'); + }); + try { + $request->send(); + $this->fail('Did not throw'); + } catch (\RuntimeException $e) { + // Ensure it was removed + $this->assertEquals(0, count($multi)); + } + } + + /** + * @expectedException \Guzzle\Common\Exception\ExceptionCollection + * @expectedExceptionMessage Thrown before sending! + */ + public function testCatchesExceptionsBeforeSendingMultipleRequests() + { + $client = new Client($this->getServer()->getUrl()); + $request = $client->get(); + $request->getEventDispatcher()->addListener('request.before_send', function() { + throw new \RuntimeException('Thrown before sending!'); + }); + $client->send(array($request)); + } + + public function testCatchesExceptionsWhenRemovingQueuedRequests() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $r = $client->get(); + $r->getEventDispatcher()->addListener('request.sent', function() use ($client) { + // Create a request using a queued response + $client->get()->setResponse(new Response(404), true)->send(); + }); + try { + $r->send(); + $this->fail('Did not throw'); + } catch (BadResponseException $e) { + $this->assertCount(0, $client->getCurlMulti()); + } + } + + public function testCatchesExceptionsWhenRemovingQueuedRequestsBeforeSending() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $r = $client->get(); + $r->getEventDispatcher()->addListener('request.before_send', function() use ($client) { + // Create a request using a queued response + $client->get()->setResponse(new Response(404), true)->send(); + }); + try { + $r->send(); + $this->fail('Did not throw'); + } catch (BadResponseException $e) { + $this->assertCount(0, $client->getCurlMulti()); + } + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage test + */ + public function testDoesNotCatchRandomExceptionsThrownDuringPerform() + { + $client = new Client($this->getServer()->getUrl()); + $multi = $this->getMock('Guzzle\\Http\\Curl\\CurlMulti', array('perform')); + $multi->expects($this->once()) + ->method('perform') + ->will($this->throwException(new \RuntimeException('test'))); + $multi->add($client->get()); + $multi->send(); + } + + public function testDoesNotSendRequestsDecliningToBeSent() + { + if (!defined('CURLOPT_TIMEOUT_MS')) { + $this->markTestSkipped('Update curl'); + } + + // Create a client that is bound to fail connecting + $client = new Client('http://127.0.0.1:123', array( + 'curl.CURLOPT_PORT' => 123, + 'curl.CURLOPT_CONNECTTIMEOUT_MS' => 1, + )); + + $request = $client->get(); + $multi = new CurlMulti(); + $multi->add($request); + + // Listen for request exceptions, and when they occur, first change the + // state of the request back to transferring, and then just allow it to + // exception out + $request->getEventDispatcher()->addListener('request.exception', function(Event $event) use ($multi) { + $retries = $event['request']->getParams()->get('retries'); + // Allow the first failure to retry + if ($retries == 0) { + $event['request']->setState('transfer'); + $event['request']->getParams()->set('retries', 1); + // Remove the request to try again + $multi->remove($event['request']); + $multi->add($event['request']); + } + }); + + try { + $multi->send(); + $this->fail('Did not throw an exception at all!?!'); + } catch (\Exception $e) { + $this->assertEquals(1, $request->getParams()->get('retries')); + } + } + + public function testDoesNotThrowExceptionsWhenRequestsRecoverWithRetry() + { + $this->getServer()->flush(); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get(); + $request->getEventDispatcher()->addListener('request.before_send', function(Event $event) { + $event['request']->setResponse(new Response(200)); + }); + + $multi = new CurlMulti(); + $multi->add($request); + $multi->send(); + $this->assertEquals(0, count($this->getServer()->getReceivedRequests(false))); + } + + public function testDoesNotThrowExceptionsWhenRequestsRecoverWithSuccess() + { + // Attempt a port that 99.9% is not listening + $client = new Client('http://127.0.0.1:123'); + $request = $client->get(); + // Ensure it times out quickly if needed + $request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, 1)->set(CURLOPT_CONNECTTIMEOUT_MS, 1); + + $request->getEventDispatcher()->addListener('request.exception', function(Event $event) use (&$count) { + $event['request']->setResponse(new Response(200)); + }); + + $multi = new CurlMulti(); + $multi->add($request); + $multi->send(); + + // Ensure that the exception was caught, and the response was set manually + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + } + + public function testHardResetReopensMultiHandle() + { + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + + $stream = fopen('php://temp', 'w+'); + $client = new Client($this->getServer()->getUrl()); + $client->getConfig()->set('curl.CURLOPT_VERBOSE', true)->set('curl.CURLOPT_STDERR', $stream); + + $request = $client->get(); + $multi = new CurlMulti(); + $multi->add($request); + $multi->send(); + $multi->reset(true); + $multi->add($request); + $multi->send(); + + rewind($stream); + $this->assertNotContains('Re-using existing connection', stream_get_contents($stream)); + } + + public function testThrowsMeaningfulExceptionsForCurlMultiErrors() + { + $multi = new CurlMulti(); + + // Set the state of the multi object to sending to trigger the exception + $reflector = new \ReflectionMethod('Guzzle\Http\Curl\CurlMulti', 'checkCurlResult'); + $reflector->setAccessible(true); + + // Successful + $reflector->invoke($multi, 0); + + // Known error + try { + $reflector->invoke($multi, CURLM_BAD_HANDLE); + $this->fail('Expected an exception here'); + } catch (CurlException $e) { + $this->assertContains('The passed-in handle is not a valid CURLM handle.', $e->getMessage()); + $this->assertContains('CURLM_BAD_HANDLE', $e->getMessage()); + $this->assertContains(strval(CURLM_BAD_HANDLE), $e->getMessage()); + } + + // Unknown error + try { + $reflector->invoke($multi, 255); + $this->fail('Expected an exception here'); + } catch (CurlException $e) { + $this->assertEquals('Unexpected cURL error: 255', $e->getMessage()); + } + } + + public function testRequestBeforeSendIncludesContentLengthHeaderIfEmptyBody() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = new Request('PUT', $this->getServer()->getUrl()); + $that = $this; + $request->getEventDispatcher()->addListener('request.before_send', function ($event) use ($that) { + $that->assertEquals(0, $event['request']->getHeader('Content-Length')); + }); + $this->multi->add($request); + $this->multi->send(); + } + + public function testRemovesConflictingTransferEncodingHeader() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', null, fopen($this->getServer()->getUrl(), 'r')); + $request->setHeader('Content-Length', 4); + $request->send(); + $received = $this->getServer()->getReceivedRequests(true); + $this->assertFalse($received[1]->hasHeader('Transfer-Encoding')); + $this->assertEquals(4, (string) $received[1]->getHeader('Content-Length')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php new file mode 100644 index 000000000..c7b5ee6e9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php @@ -0,0 +1,39 @@ +getProperty('version'); + $refProperty->setAccessible(true); + $refProperty->setValue($instance, array()); + + $this->assertEquals($info, $instance->getAll()); + $this->assertEquals($info, $instance->getAll()); + + $this->assertEquals($info['version'], $instance->get('version')); + $this->assertFalse($instance->get('foo')); + } + + public function testIsSingleton() + { + $refObject = new \ReflectionClass('Guzzle\Http\Curl\CurlVersion'); + $refProperty = $refObject->getProperty('instance'); + $refProperty->setAccessible(true); + $refProperty->setValue(null, null); + + $this->assertInstanceOf('Guzzle\Http\Curl\CurlVersion', CurlVersion::getInstance()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php new file mode 100644 index 000000000..c69e0c904 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php @@ -0,0 +1,67 @@ +events[] = $event; + } + + public function testEmitsEvents() + { + $request = new EntityEnclosingRequest('PUT', 'http://www.example.com'); + $request->setBody('foo'); + $request->setResponse(new Response(200)); + + // Ensure that IO events are emitted + $request->getCurlOptions()->set('emit_io', true); + + // Attach listeners for each event type + $request->getEventDispatcher()->addListener('curl.callback.progress', array($this, 'event')); + $request->getEventDispatcher()->addListener('curl.callback.read', array($this, 'event')); + $request->getEventDispatcher()->addListener('curl.callback.write', array($this, 'event')); + + $mediator = new RequestMediator($request, true); + + $mediator->progress('a', 'b', 'c', 'd'); + $this->assertEquals(1, count($this->events)); + $this->assertEquals('curl.callback.progress', $this->events[0]->getName()); + + $this->assertEquals(3, $mediator->writeResponseBody('foo', 'bar')); + $this->assertEquals(2, count($this->events)); + $this->assertEquals('curl.callback.write', $this->events[1]->getName()); + $this->assertEquals('bar', $this->events[1]['write']); + $this->assertSame($request, $this->events[1]['request']); + + $this->assertEquals('foo', $mediator->readRequestBody('a', 'b', 3)); + $this->assertEquals(3, count($this->events)); + $this->assertEquals('curl.callback.read', $this->events[2]->getName()); + $this->assertEquals('foo', $this->events[2]['read']); + $this->assertSame($request, $this->events[2]['request']); + } + + public function testDoesNotUseRequestResponseBodyWhenNotCustom() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 307 Foo\r\nLocation: /foo\r\nContent-Length: 2\r\n\r\nHI", + "HTTP/1.1 301 Foo\r\nLocation: /foo\r\nContent-Length: 2\r\n\r\nFI", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", + )); + $client = new Client($this->getServer()->getUrl()); + $response = $client->get()->send(); + $this->assertEquals('test', $response->getBody(true)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php new file mode 100644 index 000000000..124a44da6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php @@ -0,0 +1,182 @@ +assertEquals('data', (string) $body); + $this->assertEquals(4, $body->getContentLength()); + $this->assertEquals('PHP', $body->getWrapper()); + $this->assertEquals('TEMP', $body->getStreamType()); + + $handle = fopen(__DIR__ . '/../../../../phpunit.xml.dist', 'r'); + if (!$handle) { + $this->fail('Could not open test file'); + } + $body = EntityBody::factory($handle); + $this->assertEquals(__DIR__ . '/../../../../phpunit.xml.dist', $body->getUri()); + $this->assertTrue($body->isLocal()); + $this->assertEquals(__DIR__ . '/../../../../phpunit.xml.dist', $body->getUri()); + $this->assertEquals(filesize(__DIR__ . '/../../../../phpunit.xml.dist'), $body->getContentLength()); + + // make sure that a body will return as the same object + $this->assertTrue($body === EntityBody::factory($body)); + } + + public function testFactoryCreatesTempStreamByDefault() + { + $body = EntityBody::factory(''); + $this->assertEquals('PHP', $body->getWrapper()); + $this->assertEquals('TEMP', $body->getStreamType()); + $body = EntityBody::factory(); + $this->assertEquals('PHP', $body->getWrapper()); + $this->assertEquals('TEMP', $body->getStreamType()); + } + + public function testFactoryCanCreateFromObject() + { + $body = EntityBody::factory(new QueryString(array('foo' => 'bar'))); + $this->assertEquals('foo=bar', (string) $body); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testFactoryEnsuresObjectsHaveToStringMethod() + { + EntityBody::factory(new \stdClass('a')); + } + + public function testHandlesCompression() + { + $body = EntityBody::factory('testing 123...testing 123'); + $this->assertFalse($body->getContentEncoding(), '-> getContentEncoding() must initially return FALSE'); + $size = $body->getContentLength(); + $body->compress(); + $this->assertEquals('gzip', $body->getContentEncoding(), '-> getContentEncoding() must return the correct encoding after compressing'); + $this->assertEquals(gzdeflate('testing 123...testing 123'), (string) $body); + $this->assertTrue($body->getContentLength() < $size); + $this->assertTrue($body->uncompress()); + $this->assertEquals('testing 123...testing 123', (string) $body); + $this->assertFalse($body->getContentEncoding(), '-> getContentEncoding() must reset to FALSE'); + + if (in_array('bzip2.*', stream_get_filters())) { + $this->assertTrue($body->compress('bzip2.compress')); + $this->assertEquals('compress', $body->getContentEncoding(), '-> compress() must set \'compress\' as the Content-Encoding'); + } + + $this->assertFalse($body->compress('non-existent'), '-> compress() must return false when a non-existent stream filter is used'); + + // Release the body + unset($body); + + // Use gzip compression on the initial content. This will include a + // gzip header which will need to be stripped when deflating the stream + $body = EntityBody::factory(gzencode('test')); + $this->assertSame($body, $body->setStreamFilterContentEncoding('zlib.deflate')); + $this->assertTrue($body->uncompress('zlib.inflate')); + $this->assertEquals('test', (string) $body); + unset($body); + + // Test using a very long string + $largeString = ''; + for ($i = 0; $i < 25000; $i++) { + $largeString .= chr(rand(33, 126)); + } + $body = EntityBody::factory($largeString); + $this->assertEquals($largeString, (string) $body); + $this->assertTrue($body->compress()); + $this->assertNotEquals($largeString, (string) $body); + $compressed = (string) $body; + $this->assertTrue($body->uncompress()); + $this->assertEquals($largeString, (string) $body); + $this->assertEquals($compressed, gzdeflate($largeString)); + + $body = EntityBody::factory(fopen(__DIR__ . '/../TestData/compress_test', 'w')); + $this->assertFalse($body->compress()); + unset($body); + + unlink(__DIR__ . '/../TestData/compress_test'); + } + + public function testDeterminesContentType() + { + // Test using a string/temp stream + $body = EntityBody::factory('testing 123...testing 123'); + $this->assertNull($body->getContentType()); + + // Use a local file + $body = EntityBody::factory(fopen(__FILE__, 'r')); + $this->assertContains('text/x-', $body->getContentType()); + } + + public function testCreatesMd5Checksum() + { + $body = EntityBody::factory('testing 123...testing 123'); + $this->assertEquals(md5('testing 123...testing 123'), $body->getContentMd5()); + + $server = $this->getServer()->enqueue( + "HTTP/1.1 200 OK" . "\r\n" . + "Content-Length: 3" . "\r\n\r\n" . + "abc" + ); + + $body = EntityBody::factory(fopen($this->getServer()->getUrl(), 'r')); + $this->assertFalse($body->getContentMd5()); + } + + public function testSeeksToOriginalPosAfterMd5() + { + $body = EntityBody::factory('testing 123'); + $body->seek(4); + $this->assertEquals(md5('testing 123'), $body->getContentMd5()); + $this->assertEquals(4, $body->ftell()); + $this->assertEquals('ing 123', $body->read(1000)); + } + + public function testGetTypeFormBodyFactoring() + { + $body = EntityBody::factory(array('key1' => 'val1', 'key2' => 'val2')); + $this->assertEquals('key1=val1&key2=val2', (string) $body); + } + + public function testAllowsCustomRewind() + { + $body = EntityBody::factory('foo'); + $rewound = false; + $body->setRewindFunction(function ($body) use (&$rewound) { + $rewound = true; + return $body->seek(0); + }); + $body->seek(2); + $this->assertTrue($body->rewind()); + $this->assertTrue($rewound); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testCustomRewindFunctionMustBeCallable() + { + $body = EntityBody::factory(); + $body->setRewindFunction('foo'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php new file mode 100644 index 000000000..df3e4b791 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php @@ -0,0 +1,27 @@ +assertNull($e->getError()); + $this->assertNull($e->getErrorNo()); + $this->assertSame($e, $e->setError('test', 12)); + $this->assertEquals('test', $e->getError()); + $this->assertEquals(12, $e->getErrorNo()); + + $handle = new CurlHandle(curl_init(), array()); + $e->setCurlHandle($handle); + $this->assertSame($handle, $e->getCurlHandle()); + $handle->close(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php new file mode 100644 index 000000000..12cfd369e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php @@ -0,0 +1,66 @@ +setRequest($request); + $this->assertEquals($request, $e->getRequest()); + } + + /** + * @covers Guzzle\Http\Exception\BadResponseException + */ + public function testBadResponseException() + { + $e = new BadResponseException('Message'); + $response = new Response(200); + $e->setResponse($response); + $this->assertEquals($response, $e->getResponse()); + } + + /** + * @covers Guzzle\Http\Exception\BadResponseException::factory + */ + public function testCreatesGenericErrorExceptionOnError() + { + $request = new Request('GET', 'http://www.example.com'); + $response = new Response(307); + $e = BadResponseException::factory($request, $response); + $this->assertInstanceOf('Guzzle\Http\Exception\BadResponseException', $e); + } + + /** + * @covers Guzzle\Http\Exception\BadResponseException::factory + */ + public function testCreatesClientErrorExceptionOnClientError() + { + $request = new Request('GET', 'http://www.example.com'); + $response = new Response(404); + $e = BadResponseException::factory($request, $response); + $this->assertInstanceOf('Guzzle\Http\Exception\ClientErrorResponseException', $e); + } + + /** + * @covers Guzzle\Http\Exception\BadResponseException::factory + */ + public function testCreatesServerErrorExceptionOnServerError() + { + $request = new Request('GET', 'http://www.example.com'); + $response = new Response(503); + $e = BadResponseException::factory($request, $response); + $this->assertInstanceOf('Guzzle\Http\Exception\ServerErrorResponseException', $e); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php new file mode 100644 index 000000000..fa4ec2626 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php @@ -0,0 +1,51 @@ +addSuccessfulRequest($r1); + $e->addFailedRequest($r2); + $this->assertEquals(array($r1), $e->getSuccessfulRequests()); + $this->assertEquals(array($r2), $e->getSuccessfulRequests()); + $this->assertEquals(array($r1, $r2), $e->getAllRequests()); + $this->assertTrue($e->containsRequest($r1)); + $this->assertTrue($e->containsRequest($r2)); + $this->assertFalse($e->containsRequest(new Request('POST', '/foo'))); + } + + public function testCanSetRequests() + { + $s = array($r1 = new Request('GET', 'http://www.foo.com')); + $f = array($r2 = new Request('GET', 'http://www.foo.com')); + $e = new MultiTransferException(); + $e->setSuccessfulRequests($s); + $e->setFailedRequests($f); + $this->assertEquals(array($r1), $e->getSuccessfulRequests()); + $this->assertEquals(array($r2), $e->getSuccessfulRequests()); + } + + public function testAssociatesExceptionsWithRequests() + { + $r1 = new Request('GET', 'http://www.foo.com'); + $re1 = new \Exception('foo'); + $re2 = new \Exception('bar'); + $e = new MultiTransferException(); + $e->add($re2); + $e->addFailedRequestWithException($r1, $re1); + $this->assertSame($re1, $e->getExceptionForFailedRequest($r1)); + $this->assertNull($e->getExceptionForFailedRequest(new Request('POST', '/foo'))); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php new file mode 100644 index 000000000..cd6355f3b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php @@ -0,0 +1,47 @@ +decorated = EntityBody::factory('hello'); + $this->body = new IoEmittingEntityBody($this->decorated); + } + + public function testEmitsReadEvents() + { + $e = null; + $this->body->getEventDispatcher()->addListener('body.read', function ($event) use (&$e) { + $e = $event; + }); + $this->assertEquals('hel', $this->body->read(3)); + $this->assertEquals('hel', $e['read']); + $this->assertEquals(3, $e['length']); + $this->assertSame($this->body, $e['body']); + } + + public function testEmitsWriteEvents() + { + $e = null; + $this->body->getEventDispatcher()->addListener('body.write', function ($event) use (&$e) { + $e = $event; + }); + $this->body->seek(0, SEEK_END); + $this->assertEquals(5, $this->body->write('there')); + $this->assertEquals('there', $e['write']); + $this->assertEquals(5, $e['result']); + $this->assertSame($this->body, $e['body']); + $this->assertEquals('hellothere', (string) $this->body); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php new file mode 100644 index 000000000..9447d8c5d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php @@ -0,0 +1,136 @@ +mock = $this->getMockForAbstractClass('Guzzle\Http\Message\AbstractMessage'); + } + + public function tearDown() + { + $this->mock = $this->request = null; + } + + public function testGetParams() + { + $request = new Request('GET', 'http://example.com'); + $this->assertInstanceOf('Guzzle\\Common\\Collection', $request->getParams()); + } + + public function testAddHeaders() + { + $this->mock->setHeader('A', 'B'); + + $this->assertEquals($this->mock, $this->mock->addHeaders(array( + 'X-Data' => '123' + ))); + + $this->assertTrue($this->mock->hasHeader('X-Data') !== false); + $this->assertTrue($this->mock->hasHeader('A') !== false); + } + + public function testAllowsHeaderToSetAsHeader() + { + $h = new Header('A', 'B'); + $this->mock->setHeader('A', $h); + $this->assertSame($h, $this->mock->getHeader('A')); + } + + public function testGetHeader() + { + $this->mock->setHeader('Test', '123'); + $this->assertEquals('123', $this->mock->getHeader('Test')); + } + + public function testGetHeaders() + { + $this->assertSame($this->mock, $this->mock->setHeaders(array('a' => 'b', 'c' => 'd'))); + $h = $this->mock->getHeaders(); + $this->assertArrayHasKey('a', $h->toArray()); + $this->assertArrayHasKey('c', $h->toArray()); + $this->assertInstanceOf('Guzzle\Http\Message\Header\HeaderInterface', $h->get('a')); + $this->assertInstanceOf('Guzzle\Http\Message\Header\HeaderInterface', $h->get('c')); + } + + public function testGetHeaderLinesUsesGlue() + { + $this->mock->setHeaders(array('a' => 'b', 'c' => 'd')); + $this->mock->addHeader('a', 'e'); + $this->mock->getHeader('a')->setGlue('!'); + $this->assertEquals(array( + 'a: b! e', + 'c: d' + ), $this->mock->getHeaderLines()); + } + + public function testHasHeader() + { + $this->assertFalse($this->mock->hasHeader('Foo')); + $this->mock->setHeader('Foo', 'Bar'); + $this->assertEquals(true, $this->mock->hasHeader('Foo')); + $this->mock->setHeader('foo', 'yoo'); + $this->assertEquals(true, $this->mock->hasHeader('Foo')); + $this->assertEquals(true, $this->mock->hasHeader('foo')); + $this->assertEquals(false, $this->mock->hasHeader('bar')); + } + + public function testRemoveHeader() + { + $this->mock->setHeader('Foo', 'Bar'); + $this->assertEquals(true, $this->mock->hasHeader('Foo')); + $this->mock->removeHeader('Foo'); + $this->assertFalse($this->mock->hasHeader('Foo')); + } + + public function testReturnsNullWhenHeaderIsNotFound() + { + $this->assertNull($this->mock->getHeader('foo')); + } + + public function testAddingHeadersPreservesOriginalHeaderCase() + { + $this->mock->addHeaders(array( + 'test' => '123', + 'Test' => 'abc' + )); + $this->mock->addHeader('test', '456'); + $this->mock->addHeader('test', '789'); + + $header = $this->mock->getHeader('test'); + $this->assertContains('123', $header->toArray()); + $this->assertContains('456', $header->toArray()); + $this->assertContains('789', $header->toArray()); + $this->assertContains('abc', $header->toArray()); + } + + public function testCanStoreEmptyHeaders() + { + $this->mock->setHeader('Content-Length', 0); + $this->assertTrue($this->mock->hasHeader('Content-Length')); + $this->assertEquals(0, (string) $this->mock->getHeader('Content-Length')); + } + + public function testCanSetCustomHeaderFactory() + { + $f = new Header\HeaderFactory(); + $this->mock->setHeaderFactory($f); + $this->assertSame($f, $this->readAttribute($this->mock, 'headerFactory')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php new file mode 100644 index 000000000..191b02223 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php @@ -0,0 +1,434 @@ +client = new Client(); + } + + public function tearDown() + { + $this->client = null; + } + + public function testConstructorConfiguresRequest() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com', array( + 'X-Test' => '123' + )); + $request->setBody('Test'); + $this->assertEquals('123', $request->getHeader('X-Test')); + $this->assertNull($request->getHeader('Expect')); + } + + public function testCanSetBodyWithoutOverridingContentType() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com', array('Content-Type' => 'foooooo')); + $request->setBody('{"a":"b"}'); + $this->assertEquals('foooooo', $request->getHeader('Content-Type')); + } + + public function testRequestIncludesBodyInMessage() + { + + $request = RequestFactory::getInstance()->create('PUT', 'http://www.guzzle-project.com/', null, 'data'); + $this->assertEquals("PUT / HTTP/1.1\r\n" + . "Host: www.guzzle-project.com\r\n" + . "Content-Length: 4\r\n\r\n" + . "data", (string) $request); + } + + public function testRequestIncludesPostBodyInMessageOnlyWhenNoPostFiles() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/', null, array( + 'foo' => 'bar' + )); + $this->assertEquals("POST / HTTP/1.1\r\n" + . "Host: www.guzzle-project.com\r\n" + . "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n\r\n" + . "foo=bar", (string) $request); + + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/', null, array( + 'foo' => '@' . __FILE__ + )); + $this->assertEquals("POST / HTTP/1.1\r\n" + . "Host: www.guzzle-project.com\r\n" + . "Content-Type: multipart/form-data\r\n" + . "Expect: 100-Continue\r\n\r\n", (string) $request); + } + + public function testAddsPostFieldsAndSetsContentLength() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/', null, array( + 'data' => '123' + )); + $this->assertEquals("POST / HTTP/1.1\r\n" + . "Host: www.guzzle-project.com\r\n" + . "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n\r\n" + . "data=123", (string) $request); + } + + public function testAddsPostFilesAndSetsContentType() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.test.com/') + ->addPostFiles(array( + 'file' => __FILE__ + ))->addPostFields(array( + 'a' => 'b' + )); + $message = (string) $request; + $this->assertEquals('multipart/form-data', $request->getHeader('Content-Type')); + $this->assertEquals('100-Continue', $request->getHeader('Expect')); + } + + public function testRequestBodyContainsPostFiles() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.test.com/'); + $request->addPostFields(array( + 'test' => '123' + )); + $this->assertContains("\r\n\r\ntest=123", (string) $request); + } + + public function testRequestBodyAddsContentLength() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.test.com/'); + $request->setBody(EntityBody::factory('test')); + $this->assertEquals(4, (string) $request->getHeader('Content-Length')); + $this->assertFalse($request->hasHeader('Transfer-Encoding')); + } + + public function testRequestBodyDoesNotUseContentLengthWhenChunked() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.test.com/', array( + 'Transfer-Encoding' => 'chunked' + ), 'test'); + $this->assertNull($request->getHeader('Content-Length')); + $this->assertTrue($request->hasHeader('Transfer-Encoding')); + } + + public function testRequestHasMutableBody() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.guzzle-project.com/', null, 'data'); + $body = $request->getBody(); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $body); + $this->assertSame($body, $request->getBody()); + + $newBody = EntityBody::factory('foobar'); + $request->setBody($newBody); + $this->assertEquals('foobar', (string) $request->getBody()); + $this->assertSame($newBody, $request->getBody()); + } + + public function testSetPostFields() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/'); + $this->assertInstanceOf('Guzzle\\Http\\QueryString', $request->getPostFields()); + + $fields = new QueryString(array( + 'a' => 'b' + )); + $request->addPostFields($fields); + $this->assertEquals($fields->getAll(), $request->getPostFields()->getAll()); + $this->assertEquals(array(), $request->getPostFiles()); + } + + public function testSetPostFiles() + { + $request = RequestFactory::getInstance()->create('POST', $this->getServer()->getUrl()) + ->setClient(new Client()) + ->addPostFiles(array(__FILE__)) + ->addPostFields(array( + 'test' => 'abc' + )); + + $request->getCurlOptions()->set('debug', true); + + $this->assertEquals(array( + 'test' => 'abc' + ), $request->getPostFields()->getAll()); + + $files = $request->getPostFiles(); + $post = $files['file'][0]; + $this->assertEquals('file', $post->getFieldName()); + $this->assertContains('text/x-', $post->getContentType()); + $this->assertEquals(__FILE__, $post->getFilename()); + + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request->send(); + + $this->assertNotNull($request->getHeader('Content-Length')); + $this->assertContains('multipart/form-data; boundary=', (string) $request->getHeader('Content-Type'), '-> cURL must add the boundary'); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testSetPostFilesThrowsExceptionWhenFileIsNotFound() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFiles(array( + 'file' => 'filenotfound.ini' + )); + } + + /** + * @expectedException Guzzle\Http\Exception\RequestException + */ + public function testThrowsExceptionWhenNonStringsAreAddedToPost() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFile('foo', new \stdClass()); + } + + public function testAllowsContentTypeInPostUploads() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFile('foo', __FILE__, 'text/plain'); + + $this->assertEquals(array( + new PostFile('foo', __FILE__, 'text/plain') + ), $request->getPostFile('foo')); + } + + public function testGuessesContentTypeOfPostUpload() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFile('foo', __FILE__); + $file = $request->getPostFile('foo'); + $this->assertContains('text/x-', $file[0]->getContentType()); + } + + public function testAllowsContentDispositionFieldsInPostUploadsWhenSettingInBulk() + { + $postFile = new PostFile('foo', __FILE__, 'text/x-php'); + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFiles(array('foo' => $postFile)); + + $this->assertEquals(array($postFile), $request->getPostFile('foo')); + } + + public function testPostRequestsUseApplicationXwwwForUrlEncodedForArrays() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/'); + $request->setPostField('a', 'b'); + $this->assertContains("\r\n\r\na=b", (string) $request); + $this->assertEquals('application/x-www-form-urlencoded; charset=utf-8', $request->getHeader('Content-Type')); + } + + public function testProcessMethodAddsContentType() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/'); + $request->setPostField('a', 'b'); + $this->assertEquals('application/x-www-form-urlencoded; charset=utf-8', $request->getHeader('Content-Type')); + } + + public function testPostRequestsUseMultipartFormDataWithFiles() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/'); + $request->addPostFiles(array('file' => __FILE__)); + $this->assertEquals('multipart/form-data', $request->getHeader('Content-Type')); + } + + public function testCanSendMultipleRequestsUsingASingleRequestObject() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 201 Created\r\nContent-Length: 0\r\n\r\n", + )); + + // Send the first request + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()) + ->setBody('test') + ->setClient(new Client()); + $request->send(); + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + + // Send the second request + $request->setBody('abcdefg', 'application/json', false); + $request->send(); + $this->assertEquals(201, $request->getResponse()->getStatusCode()); + + // Ensure that the same request was sent twice with different bodies + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(2, count($requests)); + $this->assertEquals(4, (string) $requests[0]->getHeader('Content-Length')); + $this->assertEquals(7, (string) $requests[1]->getHeader('Content-Length')); + } + + public function testRemovingPostFieldRebuildsPostFields() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com'); + $request->setPostField('test', 'value'); + $request->removePostField('test'); + $this->assertNull($request->getPostField('test')); + } + + public function testUsesChunkedTransferWhenBodyLengthCannotBeDetermined() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $request->setBody(fopen($this->getServer()->getUrl(), 'r')); + $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding')); + $this->assertFalse($request->hasHeader('Content-Length')); + } + + /** + * @expectedException \Guzzle\Http\Exception\RequestException + */ + public function testThrowsExceptionWhenContentLengthCannotBeDeterminedAndUsingHttp1() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request->setProtocolVersion('1.0'); + $request->setBody(fopen($this->getServer()->getUrl(), 'r')); + } + + public function testAllowsNestedPostData() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFields(array( + 'a' => array('b', 'c') + )); + $this->assertEquals(array( + 'a' => array('b', 'c') + ), $request->getPostFields()->getAll()); + } + + public function testAllowsEmptyFields() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFields(array( + 'a' => '' + )); + $this->assertEquals(array( + 'a' => '' + ), $request->getPostFields()->getAll()); + } + + /** + * @expectedException \Guzzle\Http\Exception\RequestException + */ + public function testFailsOnInvalidFiles() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFiles(array( + 'a' => new \stdClass() + )); + } + + public function testHandlesEmptyStrings() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFields(array( + 'a' => '', + 'b' => null, + 'c' => 'Foo' + )); + $this->assertEquals(array( + 'a' => '', + 'b' => null, + 'c' => 'Foo' + ), $request->getPostFields()->getAll()); + } + + public function testHoldsPostFiles() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFile('foo', __FILE__); + $request->addPostFile(new PostFile('foo', __FILE__)); + + $this->assertArrayHasKey('foo', $request->getPostFiles()); + $foo = $request->getPostFile('foo'); + $this->assertEquals(2, count($foo)); + $this->assertEquals(__FILE__, $foo[0]->getFilename()); + $this->assertEquals(__FILE__, $foo[1]->getFilename()); + + $request->removePostFile('foo'); + $this->assertEquals(array(), $request->getPostFiles()); + } + + public function testAllowsAtPrefixWhenAddingPostFiles() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFiles(array( + 'foo' => '@' . __FILE__ + )); + $foo = $request->getPostFile('foo'); + $this->assertEquals(__FILE__, $foo[0]->getFilename()); + } + + public function testSetStateToTransferWithEmptyBodySetsContentLengthToZero() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->setState($request::STATE_TRANSFER); + $this->assertEquals('0', (string) $request->getHeader('Content-Length')); + } + + public function testSettingExpectHeaderCutoffChangesRequest() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $request->setHeader('Expect', '100-Continue'); + $request->setExpectHeaderCutoff(false); + $this->assertNull($request->getHeader('Expect')); + // There is not body, so remove the expect header + $request->setHeader('Expect', '100-Continue'); + $request->setExpectHeaderCutoff(10); + $this->assertNull($request->getHeader('Expect')); + // The size is less than the cutoff + $request->setBody('foo'); + $this->assertNull($request->getHeader('Expect')); + // The size is greater than the cutoff + $request->setBody('foobazbarbamboo'); + $this->assertNotNull($request->getHeader('Expect')); + } + + public function testStrictRedirectsCanBeSpecifiedOnEntityEnclosingRequests() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $request->configureRedirects(true); + $this->assertTrue($request->getParams()->get(RedirectPlugin::STRICT_REDIRECTS)); + } + + public function testCanDisableRedirects() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $request->configureRedirects(false, false); + $this->assertTrue($request->getParams()->get(RedirectPlugin::DISABLE)); + } + + public function testSetsContentTypeWhenSettingBodyByGuessingFromEntityBody() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/foo'); + $request->setBody(EntityBody::factory(fopen(__FILE__, 'r'))); + $this->assertEquals('text/x-php', (string) $request->getHeader('Content-Type')); + } + + public function testDoesNotCloneBody() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/foo'); + $request->setBody('test'); + $newRequest = clone $request; + $newRequest->setBody('foo'); + $this->assertInternalType('string', (string) $request->getBody()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php new file mode 100644 index 000000000..62ca5559d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php @@ -0,0 +1,29 @@ +createHeader('Foo', 'Bar'); + $this->assertInstanceOf('Guzzle\Http\Message\Header', $h); + $this->assertEquals('Foo', $h->getName()); + $this->assertEquals('Bar', (string) $h); + } + + public function testCreatesSpecificHeaders() + { + $f = new HeaderFactory(); + $h = $f->createHeader('Link', '; rel="test"'); + $this->assertInstanceOf('Guzzle\Http\Message\Header\Link', $h); + $this->assertEquals('Link', $h->getName()); + $this->assertEquals('; rel="test"', (string) $h); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php new file mode 100644 index 000000000..c834d1016 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php @@ -0,0 +1,63 @@ +; rel=front; type="image/jpeg", ; rel=back; type="image/jpeg", ; rel=side; type="image/jpeg"'); + $links = $link->getLinks(); + $this->assertEquals(array( + array( + 'rel' => 'front', + 'type' => 'image/jpeg', + 'url' => 'http:/.../front.jpeg', + ), + array( + 'rel' => 'back', + 'type' => 'image/jpeg', + 'url' => 'http://.../back.jpeg', + ), + array( + 'rel' => 'side', + 'type' => 'image/jpeg', + 'url' => 'http://.../side.jpeg?test=1' + ) + ), $links); + + $this->assertEquals(array( + 'rel' => 'back', + 'type' => 'image/jpeg', + 'url' => 'http://.../back.jpeg', + ), $link->getLink('back')); + + $this->assertTrue($link->hasLink('front')); + $this->assertFalse($link->hasLink('foo')); + } + + public function testCanAddLink() + { + $link = new Link('Link', '; rel=a; type="image/jpeg"'); + $link->addLink('http://test.com', 'test', array('foo' => 'bar')); + $this->assertEquals( + '; rel=a; type="image/jpeg", ; rel="test"; foo="bar"', + (string) $link + ); + } + + public function testCanParseLinksWithCommas() + { + $link = new Link('Link', '; rel="previous"; title="start, index"'); + $this->assertEquals(array( + array( + 'rel' => 'previous', + 'title' => 'start, index', + 'url' => 'http://example.com/TheBook/chapter1', + ) + ), $link->getLinks()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php new file mode 100644 index 000000000..a3f511bfc --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php @@ -0,0 +1,135 @@ +toArray(); + } + + foreach ($filteredHeaders as $k => $v) { + if ($k[0] == '_') { + // This header should be ignored + $ignore[] = str_replace('_', '', $k); + } elseif ($k[0] == '!') { + // This header must not be present + $absent[] = str_replace('!', '', $k); + } else { + $expected[$k] = $v; + } + } + + return $this->compareArray($expected, $actualHeaders, $ignore, $absent); + } + + /** + * Check if an array of HTTP headers matches another array of HTTP headers while taking * into account as a wildcard + * + * @param array $expected Expected HTTP headers (allows wildcard values) + * @param array|Collection $actual Actual HTTP header array + * @param array $ignore Headers to ignore from the comparison + * @param array $absent Array of headers that must not be present + * + * @return array|bool Returns an array of the differences or FALSE if none + */ + public function compareArray(array $expected, $actual, array $ignore = array(), array $absent = array()) + { + $differences = array(); + + // Add information about headers that were present but weren't supposed to be + foreach ($absent as $header) { + if ($this->hasKey($header, $actual)) { + $differences["++ {$header}"] = $actual[$header]; + unset($actual[$header]); + } + } + + // Check if expected headers are missing + foreach ($expected as $header => $value) { + if (!$this->hasKey($header, $actual)) { + $differences["- {$header}"] = $value; + } + } + + // Flip the ignore array so it works with the case insensitive helper + $ignore = array_flip($ignore); + // Allow case-insensitive comparisons in wildcards + $expected = array_change_key_case($expected); + + // Compare the expected and actual HTTP headers in no particular order + foreach ($actual as $key => $value) { + + // If this is to be ignored, the skip it + if ($this->hasKey($key, $ignore)) { + continue; + } + + // If the header was not expected + if (!$this->hasKey($key, $expected)) { + $differences["+ {$key}"] = $value; + continue; + } + + // Check values and take wildcards into account + $lkey = strtolower($key); + $pos = is_string($expected[$lkey]) ? strpos($expected[$lkey], '*') : false; + + foreach ((array) $actual[$key] as $v) { + if (($pos === false && $v != $expected[$lkey]) || $pos > 0 && substr($v, 0, $pos) != substr($expected[$lkey], 0, $pos)) { + $differences[$key] = "{$value} != {$expected[$lkey]}"; + } + } + } + + return empty($differences) ? false : $differences; + } + + /** + * Case insensitive check if an array have a key + * + * @param string $key Key to check + * @param array $array Array to check + * + * @return bool + */ + protected function hasKey($key, $array) + { + if ($array instanceof Collection) { + $keys = $array->getKeys(); + } else { + $keys = array_keys($array); + } + + foreach ($keys as $k) { + if (!strcasecmp($k, $key)) { + return true; + } + } + + return false; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php new file mode 100644 index 000000000..86c4fe866 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php @@ -0,0 +1,115 @@ + 'Foo' + ), array( + 'Content-Length' => 'Foo' + ), false), + + // Missing header + array(array( + 'X-Foo' => 'Bar' + ), array(), array( + '- X-Foo' => 'Bar' + )), + + // Extra headers is present + array(array( + 'X-Foo' => 'Bar' + ), array( + 'X-Foo' => 'Bar', + 'X-Baz' => 'Jar' + ), array( + '+ X-Baz' => 'Jar' + )), + + // Header is present but must be absent + array(array( + '!X-Foo' => '*' + ), array( + 'X-Foo' => 'Bar' + ), array( + '++ X-Foo' => 'Bar' + )), + + // Different values + array(array( + 'X-Foo' => 'Bar' + ), array( + 'X-Foo' => 'Baz' + ), array( + 'X-Foo' => 'Baz != Bar' + )), + + // Wildcard search passes + array(array( + 'X-Foo' => '*' + ), array( + 'X-Foo' => 'Bar' + ), false), + + // Wildcard search fails + array(array( + 'X-Foo' => '*' + ), array(), array( + '- X-Foo' => '*' + )), + + // Ignore extra header if present + array(array( + 'X-Foo' => '*', + '_X-Bar' => '*', + ), array( + 'X-Foo' => 'Baz', + 'X-Bar' => 'Jar' + ), false), + + // Ignore extra header if present and is not + array(array( + 'X-Foo' => '*', + '_X-Bar' => '*', + ), array( + 'X-Foo' => 'Baz' + ), false), + + // Case insensitive + array(array( + 'X-Foo' => '*', + '_X-Bar' => '*', + ), array( + 'x-foo' => 'Baz', + 'x-BAR' => 'baz' + ), false), + + // Case insensitive with collection + array(array( + 'X-Foo' => '*', + '_X-Bar' => '*', + ), new Collection(array( + 'x-foo' => 'Baz', + 'x-BAR' => 'baz' + )), false), + ); + } + + /** + * @dataProvider filterProvider + */ + public function testComparesHeaders($filters, $headers, $result) + { + $compare = new HeaderComparison(); + $this->assertEquals($result, $compare->compare($filters, $headers)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php new file mode 100644 index 000000000..c7502349f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php @@ -0,0 +1,162 @@ + array('foo', 'Foo'), + 'Zoo' => 'bar', + ); + + public function testStoresHeaderName() + { + $i = new Header('Zoo', $this->test); + $this->assertEquals('Zoo', $i->getName()); + } + + public function testConvertsToString() + { + $i = new Header('Zoo', $this->test); + $this->assertEquals('foo, Foo, bar', (string) $i); + $i->setGlue(';'); + $this->assertEquals('foo; Foo; bar', (string) $i); + } + + public function testNormalizesGluedHeaders() + { + $h = new Header('Zoo', array('foo, Faz', 'bar')); + $result = $h->normalize(true)->toArray(); + natsort($result); + $this->assertEquals(array('bar', 'foo', 'Faz'), $result); + } + + public function testCanSearchForValues() + { + $h = new Header('Zoo', $this->test); + $this->assertTrue($h->hasValue('foo')); + $this->assertTrue($h->hasValue('Foo')); + $this->assertTrue($h->hasValue('bar')); + $this->assertFalse($h->hasValue('moo')); + $this->assertFalse($h->hasValue('FoO')); + } + + public function testIsCountable() + { + $h = new Header('Zoo', $this->test); + $this->assertEquals(3, count($h)); + } + + public function testCanBeIterated() + { + $h = new Header('Zoo', $this->test); + $results = array(); + foreach ($h as $key => $value) { + $results[$key] = $value; + } + $this->assertEquals(array( + 'foo', 'Foo', 'bar' + ), $results); + } + + public function testAllowsFalseyValues() + { + // Allows 0 + $h = new Header('Foo', 0, ';'); + $this->assertEquals('0', (string) $h); + $this->assertEquals(1, count($h)); + $this->assertEquals(';', $h->getGlue()); + + // Does not add a null header by default + $h = new Header('Foo'); + $this->assertEquals('', (string) $h); + $this->assertEquals(0, count($h)); + + // Allows null array for a single null header + $h = new Header('Foo', array(null)); + $this->assertEquals('', (string) $h); + + // Allows empty string + $h = new Header('Foo', ''); + $this->assertEquals('', (string) $h); + $this->assertEquals(1, count($h)); + $this->assertEquals(1, count($h->normalize()->toArray())); + } + + public function testCanRemoveValues() + { + $h = new Header('Foo', array('Foo', 'baz', 'bar')); + $h->removeValue('bar'); + $this->assertTrue($h->hasValue('Foo')); + $this->assertFalse($h->hasValue('bar')); + $this->assertTrue($h->hasValue('baz')); + } + + public function testAllowsArrayInConstructor() + { + $h = new Header('Foo', array('Testing', '123', 'Foo=baz')); + $this->assertEquals(array('Testing', '123', 'Foo=baz'), $h->toArray()); + } + + public function parseParamsProvider() + { + $res1 = array( + array( + '' => '', + 'rel' => 'front', + 'type' => 'image/jpeg', + ), + array( + '' => '', + 'rel' => 'back', + 'type' => 'image/jpeg', + ), + ); + + return array( + array( + '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"', + $res1 + ), + array( + '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"', + $res1 + ), + array( + 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"', + array( + array('foo' => 'baz', 'bar' => '123'), + array('boo' => ''), + array('test' => '123'), + array('foobar' => 'foo;bar') + ) + ), + array( + '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"', + array( + array('' => '', 'rel' => 'side', 'type' => 'image/jpeg'), + array('' => '', 'rel' => 'side', 'type' => 'image/jpeg') + ) + ), + array( + '', + array() + ) + ); + } + + /** + * @dataProvider parseParamsProvider + */ + public function testParseParams($header, $result) + { + $response = new Response(200, array('Link' => $header)); + $this->assertEquals($result, $response->getHeader('Link')->parseParams()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php new file mode 100644 index 000000000..be048cb95 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php @@ -0,0 +1,88 @@ +assertEquals('foo', $file->getFieldName()); + $this->assertEquals(__FILE__, $file->getFilename()); + $this->assertEquals('boo', $file->getPostName()); + $this->assertEquals('x-foo', $file->getContentType()); + } + + public function testRemovesLeadingAtSymbolFromPath() + { + $file = new PostFile('foo', '@' . __FILE__); + $this->assertEquals(__FILE__, $file->getFilename()); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresFileIsReadable() + { + $file = new PostFile('foo', '/foo/baz/bar'); + } + + public function testCanChangeContentType() + { + $file = new PostFile('foo', '@' . __FILE__); + $file->setContentType('Boo'); + $this->assertEquals('Boo', $file->getContentType()); + } + + public function testCanChangeFieldName() + { + $file = new PostFile('foo', '@' . __FILE__); + $file->setFieldName('Boo'); + $this->assertEquals('Boo', $file->getFieldName()); + } + + public function testReturnsCurlValueString() + { + $file = new PostFile('foo', __FILE__); + if (version_compare(phpversion(), '5.5.0', '<')) { + $this->assertContains('@' . __FILE__ . ';filename=PostFileTest.php;type=text/x-', $file->getCurlValue()); + } else { + $c = $file->getCurlValue(); + $this->assertEquals(__FILE__, $c->getFilename()); + $this->assertEquals('PostFileTest.php', $c->getPostFilename()); + $this->assertContains('text/x-', $c->getMimeType()); + } + } + + public function testReturnsCurlValueStringAndPostname() + { + $file = new PostFile('foo', __FILE__, null, 'NewPostFileTest.php'); + if (version_compare(phpversion(), '5.5.0', '<')) { + $this->assertContains('@' . __FILE__ . ';filename=NewPostFileTest.php;type=text/x-', $file->getCurlValue()); + } else { + $c = $file->getCurlValue(); + $this->assertEquals(__FILE__, $c->getFilename()); + $this->assertEquals('NewPostFileTest.php', $c->getPostFilename()); + $this->assertContains('text/x-', $c->getMimeType()); + } + } + + public function testContentDispositionFilePathIsStripped() + { + $this->getServer()->flush(); + $client = new Client($this->getServer()->getUrl()); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = $client->post()->addPostFile('file', __FILE__); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(false); + $this->assertContains('POST / HTTP/1.1', $requests[0]); + $this->assertContains('Content-Disposition: form-data; name="file"; filename="PostFileTest.php"', $requests[0]); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php new file mode 100644 index 000000000..80b8d5415 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php @@ -0,0 +1,616 @@ +assertSame($factory, RequestFactory::getInstance()); + } + + public function testCreatesNewGetRequests() + { + $request = RequestFactory::getInstance()->create('GET', 'http://www.google.com/'); + $this->assertInstanceOf('Guzzle\\Http\\Message\\MessageInterface', $request); + $this->assertInstanceOf('Guzzle\\Http\\Message\\RequestInterface', $request); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Request', $request); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com/', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('/', $request->getPath()); + $this->assertEquals('/', $request->getResource()); + + // Create a GET request with a custom receiving body + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $b = EntityBody::factory(); + $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl(), null, $b); + $request->setClient(new Client()); + $response = $request->send(); + $this->assertSame($b, $response->getBody()); + } + + public function testCreatesPutRequests() + { + // Test using a string + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, 'Data'); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com/path?q=1&v=2', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('/path', $request->getPath()); + $this->assertEquals('/path?q=1&v=2', $request->getResource()); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody()); + $this->assertEquals('Data', (string) $request->getBody()); + unset($request); + + // Test using an EntityBody + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, EntityBody::factory('Data')); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('Data', (string) $request->getBody()); + + // Test using a resource + $resource = fopen('php://temp', 'w+'); + fwrite($resource, 'Data'); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, $resource); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('Data', (string) $request->getBody()); + + // Test using an object that can be cast as a string + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, Url::factory('http://www.example.com/')); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('http://www.example.com/', (string) $request->getBody()); + } + + public function testCreatesHeadAndDeleteRequests() + { + $request = RequestFactory::getInstance()->create('DELETE', 'http://www.test.com/'); + $this->assertEquals('DELETE', $request->getMethod()); + $request = RequestFactory::getInstance()->create('HEAD', 'http://www.test.com/'); + $this->assertEquals('HEAD', $request->getMethod()); + } + + public function testCreatesOptionsRequests() + { + $request = RequestFactory::getInstance()->create('OPTIONS', 'http://www.example.com/'); + $this->assertEquals('OPTIONS', $request->getMethod()); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + } + + public function testCreatesNewPutRequestWithBody() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, 'Data'); + $this->assertEquals('Data', (string) $request->getBody()); + } + + public function testCreatesNewPostRequestWithFields() + { + // Use an array + $request = RequestFactory::getInstance()->create('POST', 'http://www.google.com/path?q=1&v=2', null, array( + 'a' => 'b' + )); + $this->assertEquals(array('a' => 'b'), $request->getPostFields()->getAll()); + unset($request); + + // Use a collection + $request = RequestFactory::getInstance()->create('POST', 'http://www.google.com/path?q=1&v=2', null, new Collection(array( + 'a' => 'b' + ))); + $this->assertEquals(array('a' => 'b'), $request->getPostFields()->getAll()); + + // Use a QueryString + $request = RequestFactory::getInstance()->create('POST', 'http://www.google.com/path?q=1&v=2', null, new QueryString(array( + 'a' => 'b' + ))); + $this->assertEquals(array('a' => 'b'), $request->getPostFields()->getAll()); + + $request = RequestFactory::getInstance()->create('POST', 'http://www.test.com/', null, array( + 'a' => 'b', + 'file' => '@' . __FILE__ + )); + + $this->assertEquals(array( + 'a' => 'b' + ), $request->getPostFields()->getAll()); + + $files = $request->getPostFiles(); + $this->assertInstanceOf('Guzzle\Http\Message\PostFile', $files['file'][0]); + } + + public function testCreatesFromParts() + { + $parts = parse_url('http://michael:123@www.google.com:8080/path?q=1&v=2'); + + $request = RequestFactory::getInstance()->fromParts('PUT', $parts, null, 'Data'); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com:8080/path?q=1&v=2', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('www.google.com:8080', $request->getHeader('Host')); + $this->assertEquals('/path', $request->getPath()); + $this->assertEquals('/path?q=1&v=2', $request->getResource()); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody()); + $this->assertEquals('Data', (string) $request->getBody()); + $this->assertEquals('michael', $request->getUsername()); + $this->assertEquals('123', $request->getPassword()); + $this->assertEquals('8080', $request->getPort()); + $this->assertEquals(array( + 'scheme' => 'http', + 'host' => 'www.google.com', + 'port' => 8080, + 'path' => '/path', + 'query' => 'q=1&v=2', + ), parse_url($request->getUrl())); + } + + public function testCreatesFromMessage() + { + $auth = base64_encode('michael:123'); + $message = "PUT /path?q=1&v=2 HTTP/1.1\r\nHost: www.google.com:8080\r\nContent-Length: 4\r\nAuthorization: Basic {$auth}\r\n\r\nData"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com:8080/path?q=1&v=2', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('www.google.com:8080', $request->getHeader('Host')); + $this->assertEquals('/path', $request->getPath()); + $this->assertEquals('/path?q=1&v=2', $request->getResource()); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody()); + $this->assertEquals('Data', (string) $request->getBody()); + $this->assertEquals("Basic {$auth}", (string) $request->getHeader('Authorization')); + $this->assertEquals('8080', $request->getPort()); + + // Test passing a blank message returns false + $this->assertFalse($request = RequestFactory::getInstance()->fromMessage('')); + + // Test passing a url with no port + $message = "PUT /path?q=1&v=2 HTTP/1.1\r\nHost: www.google.com\r\nContent-Length: 4\r\nAuthorization: Basic {$auth}\r\n\r\nData"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com/path?q=1&v=2', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('/path', $request->getPath()); + $this->assertEquals('/path?q=1&v=2', $request->getResource()); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody()); + $this->assertEquals('Data', (string) $request->getBody()); + $this->assertEquals("Basic {$auth}", (string) $request->getHeader('Authorization')); + $this->assertEquals(80, $request->getPort()); + } + + public function testCreatesNewTraceRequest() + { + $request = RequestFactory::getInstance()->create('TRACE', 'http://www.google.com/'); + $this->assertFalse($request instanceof \Guzzle\Http\Message\EntityEnclosingRequest); + $this->assertEquals('TRACE', $request->getMethod()); + } + + public function testCreatesProperTransferEncodingRequests() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/', array( + 'Transfer-Encoding' => 'chunked' + ), 'hello'); + $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding')); + $this->assertFalse($request->hasHeader('Content-Length')); + } + + public function testProperlyDealsWithDuplicateHeaders() + { + $parser = new MessageParser(); + + $message = "POST / http/1.1\r\n" + . "DATE:Mon, 09 Sep 2011 23:36:00 GMT\r\n" + . "host:host.foo.com\r\n" + . "ZOO:abc\r\n" + . "ZOO:123\r\n" + . "ZOO:HI\r\n" + . "zoo:456\r\n\r\n"; + + $parts = $parser->parseRequest($message); + $this->assertEquals(array ( + 'DATE' => 'Mon, 09 Sep 2011 23:36:00 GMT', + 'host' => 'host.foo.com', + 'ZOO' => array('abc', '123', 'HI'), + 'zoo' => '456', + ), $parts['headers']); + + $request = RequestFactory::getInstance()->fromMessage($message); + + $this->assertEquals(array( + 'abc', '123', 'HI', '456' + ), $request->getHeader('zoo')->toArray()); + } + + public function testCreatesHttpMessagesWithBodiesAndNormalizesLineEndings() + { + $message = "POST / http/1.1\r\n" + . "Content-Type:application/x-www-form-urlencoded; charset=utf8\r\n" + . "Date:Mon, 09 Sep 2011 23:36:00 GMT\r\n" + . "Host:host.foo.com\r\n\r\n" + . "foo=bar"; + + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertEquals('application/x-www-form-urlencoded; charset=utf8', (string) $request->getHeader('Content-Type')); + $this->assertEquals('foo=bar', (string) $request->getBody()); + + $message = "POST / http/1.1\n" + . "Content-Type:application/x-www-form-urlencoded; charset=utf8\n" + . "Date:Mon, 09 Sep 2011 23:36:00 GMT\n" + . "Host:host.foo.com\n\n" + . "foo=bar"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertEquals('foo=bar', (string) $request->getBody()); + + $message = "PUT / HTTP/1.1\r\nContent-Length: 0\r\n\r\n"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertTrue($request->hasHeader('Content-Length')); + $this->assertEquals(0, (string) $request->getHeader('Content-Length')); + } + + public function testBugPathIncorrectlyHandled() + { + $message = "POST /foo\r\n\r\nBODY"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertSame('POST', $request->getMethod()); + $this->assertSame('/foo', $request->getPath()); + $this->assertSame('BODY', (string) $request->getBody()); + } + + public function testHandlesChunkedTransferEncoding() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.foo.com/', array( + 'Transfer-Encoding' => 'chunked' + ), 'Test'); + $this->assertFalse($request->hasHeader('Content-Length')); + $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding')); + + $request = RequestFactory::getInstance()->create('POST', 'http://www.foo.com/', array( + 'transfer-encoding' => 'chunked' + ), array( + 'foo' => 'bar' + )); + + $this->assertFalse($request->hasHeader('Content-Length')); + $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding')); + } + + public function testClonesRequestsWithMethodWithoutClient() + { + $f = RequestFactory::getInstance(); + $request = $f->create('GET', 'http://www.test.com', array('X-Foo' => 'Bar')); + $request->getParams()->replace(array('test' => '123')); + $request->getCurlOptions()->set('foo', 'bar'); + $cloned = $f->cloneRequestWithMethod($request, 'PUT'); + $this->assertEquals('PUT', $cloned->getMethod()); + $this->assertEquals('Bar', (string) $cloned->getHeader('X-Foo')); + $this->assertEquals('http://www.test.com', $cloned->getUrl()); + // Ensure params are cloned and cleaned up + $this->assertEquals(1, count($cloned->getParams()->getAll())); + $this->assertEquals('123', $cloned->getParams()->get('test')); + // Ensure curl options are cloned + $this->assertEquals('bar', $cloned->getCurlOptions()->get('foo')); + // Ensure event dispatcher is cloned + $this->assertNotSame($request->getEventDispatcher(), $cloned->getEventDispatcher()); + } + + public function testClonesRequestsWithMethodWithClient() + { + $f = RequestFactory::getInstance(); + $client = new Client(); + $request = $client->put('http://www.test.com', array('Content-Length' => 4), 'test'); + $cloned = $f->cloneRequestWithMethod($request, 'GET'); + $this->assertEquals('GET', $cloned->getMethod()); + $this->assertNull($cloned->getHeader('Content-Length')); + $this->assertEquals('http://www.test.com', $cloned->getUrl()); + $this->assertSame($request->getClient(), $cloned->getClient()); + } + + public function testClonesRequestsWithMethodWithClientWithEntityEnclosingChange() + { + $f = RequestFactory::getInstance(); + $client = new Client(); + $request = $client->put('http://www.test.com', array('Content-Length' => 4), 'test'); + $cloned = $f->cloneRequestWithMethod($request, 'POST'); + $this->assertEquals('POST', $cloned->getMethod()); + $this->assertEquals('test', (string) $cloned->getBody()); + } + + public function testCanDisableRedirects() + { + $this->getServer()->enqueue(array( + "HTTP/1.1 307\r\nLocation: " . $this->getServer()->getUrl() . "\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $response = $client->get('/', array(), array('allow_redirects' => false))->send(); + $this->assertEquals(307, $response->getStatusCode()); + } + + public function testCanAddCookies() + { + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/', array(), array('cookies' => array('Foo' => 'Bar'))); + $this->assertEquals('Bar', $request->getCookie('Foo')); + } + + public function testCanAddQueryString() + { + $request = RequestFactory::getInstance()->create('GET', 'http://foo.com', array(), null, array( + 'query' => array('Foo' => 'Bar') + )); + $this->assertEquals('Bar', $request->getQuery()->get('Foo')); + } + + public function testCanSetDefaultQueryString() + { + $request = new Request('GET', 'http://www.foo.com?test=abc'); + RequestFactory::getInstance()->applyOptions($request, array( + 'query' => array('test' => '123', 'other' => 't123') + ), RequestFactory::OPTIONS_AS_DEFAULTS); + $this->assertEquals('abc', $request->getQuery()->get('test')); + $this->assertEquals('t123', $request->getQuery()->get('other')); + } + + public function testCanAddBasicAuth() + { + $request = RequestFactory::getInstance()->create('GET', 'http://foo.com', array(), null, array( + 'auth' => array('michael', 'test') + )); + $this->assertEquals('michael', $request->getUsername()); + $this->assertEquals('test', $request->getPassword()); + } + + public function testCanAddDigestAuth() + { + $request = RequestFactory::getInstance()->create('GET', 'http://foo.com', array(), null, array( + 'auth' => array('michael', 'test', 'digest') + )); + $this->assertEquals(CURLAUTH_DIGEST, $request->getCurlOptions()->get(CURLOPT_HTTPAUTH)); + $this->assertEquals('michael', $request->getUsername()); + $this->assertEquals('test', $request->getPassword()); + } + + public function testCanAddEvents() + { + $foo = null; + $client = new Client(); + $client->addSubscriber(new MockPlugin(array(new Response(200)))); + $request = $client->get($this->getServer()->getUrl(), array(), array( + 'events' => array( + 'request.before_send' => function () use (&$foo) { $foo = true; } + ) + )); + $request->send(); + $this->assertTrue($foo); + } + + public function testCanAddEventsWithPriority() + { + $foo = null; + $client = new Client(); + $client->addSubscriber(new MockPlugin(array(new Response(200)))); + $request = $client->get($this->getServer()->getUrl(), array(), array( + 'events' => array( + 'request.before_send' => array(function () use (&$foo) { $foo = true; }, 100) + ) + )); + $request->send(); + $this->assertTrue($foo); + } + + public function testCanAddPlugins() + { + $mock = new MockPlugin(array( + new Response(200), + new Response(200) + )); + $client = new Client(); + $client->addSubscriber($mock); + $request = $client->get('/', array(), array( + 'plugins' => array($mock) + )); + $request->send(); + } + + public function testCanDisableExceptions() + { + $client = new Client(); + $request = $client->get('/', array(), array( + 'plugins' => array(new MockPlugin(array(new Response(500)))), + 'exceptions' => false + )); + $this->assertEquals(500, $request->send()->getStatusCode()); + } + + public function testCanDisableExceptionsWithErrorListener() + { + $client = new Client(); + $client->getEventDispatcher()->addListener('request.error', function () {}); + $request = $client->get('/', array(), array( + 'plugins' => array(new MockPlugin(array(new Response(500)))), + 'exceptions' => false + )); + $this->assertEquals(500, $request->send()->getStatusCode()); + } + + public function testCanChangeSaveToLocation() + { + $r = EntityBody::factory(); + $client = new Client(); + $request = $client->get('/', array(), array( + 'plugins' => array(new MockPlugin(array(new Response(200, array(), 'testing')))), + 'save_to' => $r + )); + $request->send(); + $this->assertEquals('testing', (string) $r); + } + + public function testCanSetProxy() + { + $client = new Client(); + $request = $client->get('/', array(), array('proxy' => '192.168.16.121')); + $this->assertEquals('192.168.16.121', $request->getCurlOptions()->get(CURLOPT_PROXY)); + } + + public function testCanSetHeadersOption() + { + $client = new Client(); + $request = $client->get('/', array(), array('headers' => array('Foo' => 'Bar'))); + $this->assertEquals('Bar', (string) $request->getHeader('Foo')); + } + + public function testCanSetDefaultHeadersOptions() + { + $request = new Request('GET', 'http://www.foo.com', array('Foo' => 'Bar')); + RequestFactory::getInstance()->applyOptions($request, array( + 'headers' => array('Foo' => 'Baz', 'Bam' => 't123') + ), RequestFactory::OPTIONS_AS_DEFAULTS); + $this->assertEquals('Bar', (string) $request->getHeader('Foo')); + $this->assertEquals('t123', (string) $request->getHeader('Bam')); + } + + public function testCanSetBodyOption() + { + $client = new Client(); + $request = $client->put('/', array(), null, array('body' => 'test')); + $this->assertEquals('test', (string) $request->getBody()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesBodyOption() + { + $client = new Client(); + $client->get('/', array(), array('body' => 'test')); + } + + public function testCanSetTimeoutOption() + { + $client = new Client(); + $request = $client->get('/', array(), array('timeout' => 1.5)); + $this->assertEquals(1500, $request->getCurlOptions()->get(CURLOPT_TIMEOUT_MS)); + } + + public function testCanSetConnectTimeoutOption() + { + $client = new Client(); + $request = $client->get('/', array(), array('connect_timeout' => 1.5)); + $this->assertEquals(1500, $request->getCurlOptions()->get(CURLOPT_CONNECTTIMEOUT_MS)); + } + + public function testCanSetDebug() + { + $client = new Client(); + $request = $client->get('/', array(), array('debug' => true)); + $this->assertTrue($request->getCurlOptions()->get(CURLOPT_VERBOSE)); + } + + public function testCanSetVerifyToOff() + { + $client = new Client(); + $request = $client->get('/', array(), array('verify' => false)); + $this->assertNull($request->getCurlOptions()->get(CURLOPT_CAINFO)); + $this->assertSame(0, $request->getCurlOptions()->get(CURLOPT_SSL_VERIFYHOST)); + $this->assertFalse($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)); + } + + public function testCanSetVerifyToOn() + { + $client = new Client(); + $request = $client->get('/', array(), array('verify' => true)); + $this->assertNotNull($request->getCurlOptions()->get(CURLOPT_CAINFO)); + $this->assertSame(2, $request->getCurlOptions()->get(CURLOPT_SSL_VERIFYHOST)); + $this->assertTrue($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)); + } + + public function testCanSetVerifyToPath() + { + $client = new Client(); + $request = $client->get('/', array(), array('verify' => '/foo.pem')); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_CAINFO)); + $this->assertSame(2, $request->getCurlOptions()->get(CURLOPT_SSL_VERIFYHOST)); + $this->assertTrue($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)); + } + + public function inputValidation() + { + return array_map(function ($option) { return array($option); }, array( + 'headers', 'query', 'cookies', 'auth', 'events', 'plugins', 'params' + )); + } + + /** + * @dataProvider inputValidation + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesInput($option) + { + $client = new Client(); + $client->get('/', array(), array($option => 'foo')); + } + + public function testCanAddRequestParams() + { + $client = new Client(); + $request = $client->put('/', array(), null, array('params' => array('foo' => 'test'))); + $this->assertEquals('test', $request->getParams()->get('foo')); + } + + public function testCanAddSslKey() + { + $client = new Client(); + $request = $client->get('/', array(), array('ssl_key' => '/foo.pem')); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLKEY)); + } + + public function testCanAddSslKeyPassword() + { + $client = new Client(); + $request = $client->get('/', array(), array('ssl_key' => array('/foo.pem', 'bar'))); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLKEY)); + $this->assertEquals('bar', $request->getCurlOptions()->get(CURLOPT_SSLKEYPASSWD)); + } + + public function testCanAddSslCert() + { + $client = new Client(); + $request = $client->get('/', array(), array('cert' => '/foo.pem')); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLCERT)); + } + + public function testCanAddSslCertPassword() + { + $client = new Client(); + $request = $client->get('/', array(), array('cert' => array('/foo.pem', 'bar'))); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLCERT)); + $this->assertEquals('bar', $request->getCurlOptions()->get(CURLOPT_SSLCERTPASSWD)); + } + + public function testCreatesBodyWithoutZeroString() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://test.com', array(), '0'); + $this->assertSame('0', (string) $request->getBody()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php new file mode 100644 index 000000000..5bf624829 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php @@ -0,0 +1,639 @@ +client = new Client($this->getServer()->getUrl()); + $this->request = $this->client->get(); + } + + public function tearDown() + { + unset($this->request); + unset($this->client); + } + + public function testConstructorBuildsRequestWithArrayHeaders() + { + // Test passing an array of headers + $request = new Request('GET', 'http://www.guzzle-project.com/', array( + 'foo' => 'bar' + )); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('http://www.guzzle-project.com/', $request->getUrl()); + $this->assertEquals('bar', $request->getHeader('foo')); + } + + public function testDescribesEvents() + { + $this->assertInternalType('array', Request::getAllEvents()); + } + + public function testConstructorBuildsRequestWithCollectionHeaders() + { + $request = new Request('GET', 'http://www.guzzle-project.com/', new Collection(array( + 'foo' => 'bar' + ))); + $this->assertEquals('bar', $request->getHeader('foo')); + } + + public function testConstructorBuildsRequestWithNoHeaders() + { + $request = new Request('GET', 'http://www.guzzle-project.com/', null); + $this->assertFalse($request->hasHeader('foo')); + } + + public function testConstructorHandlesNonBasicAuth() + { + $request = new Request('GET', 'http://www.guzzle-project.com/', array( + 'Authorization' => 'Foo bar' + )); + $this->assertNull($request->getUserName()); + $this->assertNull($request->getPassword()); + $this->assertEquals('Foo bar', (string) $request->getHeader('Authorization')); + } + + public function testRequestsCanBeConvertedToRawMessageStrings() + { + $auth = base64_encode('michael:123'); + $message = "PUT /path?q=1&v=2 HTTP/1.1\r\n" + . "Host: www.google.com\r\n" + . "Authorization: Basic {$auth}\r\n" + . "Content-Length: 4\r\n\r\nData"; + + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', array( + 'Authorization' => 'Basic ' . $auth + ), 'Data'); + + $this->assertEquals($message, $request->__toString()); + } + + /** + * Add authorization after the fact and see that it was put in the message + */ + public function testRequestStringsIncludeAuth() + { + $auth = base64_encode('michael:123'); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl(), null, 'Data') + ->setClient($this->client) + ->setAuth('michael', '123', CURLAUTH_BASIC); + $request->send(); + + $this->assertContains('Authorization: Basic ' . $auth, (string) $request); + } + + public function testGetEventDispatcher() + { + $d = $this->request->getEventDispatcher(); + $this->assertInstanceOf('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface', $d); + $this->assertEquals($d, $this->request->getEventDispatcher()); + } + + public function testRequestsManageClients() + { + $request = new Request('GET', 'http://test.com'); + $this->assertNull($request->getClient()); + $request->setClient($this->client); + $this->assertSame($this->client, $request->getClient()); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage A client must be set on the request + */ + public function testRequestsRequireClients() + { + $request = new Request('GET', 'http://test.com'); + $request->send(); + } + + public function testSend() + { + $response = new Response(200, array( + 'Content-Length' => 3 + ), 'abc'); + $this->request->setResponse($response, true); + $r = $this->request->send(); + + $this->assertSame($response, $r); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $this->request->getResponse()); + $this->assertSame($r, $this->request->getResponse()); + $this->assertEquals('complete', $this->request->getState()); + } + + public function testGetResponse() + { + $this->assertNull($this->request->getResponse()); + $response = new Response(200, array('Content-Length' => 3), 'abc'); + + $this->request->setResponse($response); + $this->assertEquals($response, $this->request->getResponse()); + + $client = new Client('http://www.google.com'); + $request = $client->get('http://www.google.com/'); + $request->setResponse($response, true); + $request->send(); + $requestResponse = $request->getResponse(); + $this->assertSame($response, $requestResponse); + + // Try again, making sure it's still the same response + $this->assertSame($requestResponse, $request->getResponse()); + + $response = new Response(204); + $request = $client->get(); + $request->setResponse($response, true); + $request->send(); + $requestResponse = $request->getResponse(); + $this->assertSame($response, $requestResponse); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $response->getBody()); + } + + public function testRequestThrowsExceptionOnBadResponse() + { + try { + $this->request->setResponse(new Response(404, array('Content-Length' => 3), 'abc'), true); + $this->request->send(); + $this->fail('Expected exception not thrown'); + } catch (BadResponseException $e) { + $this->assertInstanceOf('Guzzle\\Http\\Message\\RequestInterface', $e->getRequest()); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $e->getResponse()); + $this->assertContains('Client error response', $e->getMessage()); + } + } + + public function testManagesQuery() + { + $this->assertInstanceOf('Guzzle\\Http\\QueryString', $this->request->getQuery()); + $this->request->getQuery()->set('test', '123'); + $this->assertEquals('test=123', $this->request->getQuery(true)); + } + + public function testRequestHasMethod() + { + $this->assertEquals('GET', $this->request->getMethod()); + } + + public function testRequestHasScheme() + { + $this->assertEquals('http', $this->request->getScheme()); + $this->assertEquals($this->request, $this->request->setScheme('https')); + $this->assertEquals('https', $this->request->getScheme()); + } + + public function testRequestHasHost() + { + $this->assertEquals('127.0.0.1', $this->request->getHost()); + $this->assertEquals('127.0.0.1:8124', (string) $this->request->getHeader('Host')); + + $this->assertSame($this->request, $this->request->setHost('www2.google.com')); + $this->assertEquals('www2.google.com', $this->request->getHost()); + $this->assertEquals('www2.google.com:8124', (string) $this->request->getHeader('Host')); + + $this->assertSame($this->request, $this->request->setHost('www.test.com:8081')); + $this->assertEquals('www.test.com', $this->request->getHost()); + $this->assertEquals(8081, $this->request->getPort()); + } + + public function testRequestHasProtocol() + { + $this->assertEquals('1.1', $this->request->getProtocolVersion()); + $this->assertEquals($this->request, $this->request->setProtocolVersion('1.1')); + $this->assertEquals('1.1', $this->request->getProtocolVersion()); + $this->assertEquals($this->request, $this->request->setProtocolVersion('1.0')); + $this->assertEquals('1.0', $this->request->getProtocolVersion()); + } + + public function testRequestHasPath() + { + $this->assertEquals('/', $this->request->getPath()); + $this->assertEquals($this->request, $this->request->setPath('/index.html')); + $this->assertEquals('/index.html', $this->request->getPath()); + $this->assertEquals($this->request, $this->request->setPath('index.html')); + $this->assertEquals('/index.html', $this->request->getPath()); + } + + public function testPermitsFalsyComponents() + { + $request = new Request('GET', 'http://0/0?0'); + $this->assertSame('0', $request->getHost()); + $this->assertSame('/0', $request->getPath()); + $this->assertSame('0', $request->getQuery(true)); + + $request = new Request('GET', '0'); + $this->assertEquals('/0', $request->getPath()); + } + + public function testRequestHasPort() + { + $this->assertEquals(8124, $this->request->getPort()); + $this->assertEquals('127.0.0.1:8124', $this->request->getHeader('Host')); + + $this->assertEquals($this->request, $this->request->setPort('8080')); + $this->assertEquals('8080', $this->request->getPort()); + $this->assertEquals('127.0.0.1:8080', $this->request->getHeader('Host')); + + $this->request->setPort(80); + $this->assertEquals('127.0.0.1', $this->request->getHeader('Host')); + } + + public function testRequestHandlesAuthorization() + { + // Uninitialized auth + $this->assertEquals(null, $this->request->getUsername()); + $this->assertEquals(null, $this->request->getPassword()); + + // Set an auth + $this->assertSame($this->request, $this->request->setAuth('michael', '123')); + $this->assertEquals('michael', $this->request->getUsername()); + $this->assertEquals('123', $this->request->getPassword()); + + // Set an auth with blank password + $this->assertSame($this->request, $this->request->setAuth('michael', '')); + $this->assertEquals('michael', $this->request->getUsername()); + $this->assertEquals('', $this->request->getPassword()); + + // Remove the auth + $this->request->setAuth(false); + $this->assertEquals(null, $this->request->getUsername()); + $this->assertEquals(null, $this->request->getPassword()); + + // Make sure that the cURL based auth works too + $request = new Request('GET', $this->getServer()->getUrl()); + $request->setAuth('michael', 'password', CURLAUTH_DIGEST); + $this->assertEquals('michael:password', $request->getCurlOptions()->get(CURLOPT_USERPWD)); + $this->assertEquals(CURLAUTH_DIGEST, $request->getCurlOptions()->get(CURLOPT_HTTPAUTH)); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesAuth() + { + $this->request->setAuth('foo', 'bar', 'bam'); + } + + public function testGetResourceUri() + { + $this->assertEquals('/', $this->request->getResource()); + $this->request->setPath('/index.html'); + $this->assertEquals('/index.html', $this->request->getResource()); + $this->request->getQuery()->add('v', '1'); + $this->assertEquals('/index.html?v=1', $this->request->getResource()); + } + + public function testRequestHasMutableUrl() + { + $url = 'http://www.test.com:8081/path?q=123#fragment'; + $u = Url::factory($url); + $this->assertSame($this->request, $this->request->setUrl($url)); + $this->assertEquals($url, $this->request->getUrl()); + + $this->assertSame($this->request, $this->request->setUrl($u)); + $this->assertEquals($url, $this->request->getUrl()); + } + + public function testRequestHasState() + { + $this->assertEquals(RequestInterface::STATE_NEW, $this->request->getState()); + $this->request->setState(RequestInterface::STATE_TRANSFER); + $this->assertEquals(RequestInterface::STATE_TRANSFER, $this->request->getState()); + } + + public function testSetManualResponse() + { + $response = new Response(200, array( + 'Date' => 'Sat, 16 Oct 2010 17:27:14 GMT', + 'Expires' => '-1', + 'Cache-Control' => 'private, max-age=0', + 'Content-Type' => 'text/html; charset=ISO-8859-1', + ), 'response body'); + + $this->assertSame($this->request, $this->request->setResponse($response), '-> setResponse() must use a fluent interface'); + $this->assertEquals('complete', $this->request->getState(), '-> setResponse() must change the state of the request to complete'); + $this->assertSame($response, $this->request->getResponse(), '-> setResponse() must set the exact same response that was passed in to it'); + } + + public function testRequestCanHaveManuallySetResponseBody() + { + $file = __DIR__ . '/../../TestData/temp.out'; + if (file_exists($file)) { + unlink($file); + } + + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata"); + $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl()); + $request->setClient($this->client); + $entityBody = EntityBody::factory(fopen($file, 'w+')); + $request->setResponseBody($entityBody); + $response = $request->send(); + $this->assertSame($entityBody, $response->getBody()); + + $this->assertTrue(file_exists($file)); + $this->assertEquals('data', file_get_contents($file)); + unlink($file); + + $this->assertEquals('data', $response->getBody(true)); + } + + public function testHoldsCookies() + { + $this->assertNull($this->request->getCookie('test')); + + // Set a cookie + $this->assertSame($this->request, $this->request->addCookie('test', 'abc')); + $this->assertEquals('abc', $this->request->getCookie('test')); + + // Multiple cookies by setting the Cookie header + $this->request->setHeader('Cookie', '__utma=1.638370270.1344367610.1374365610.1944450276.2; __utmz=1.1346368610.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); hl=de; PHPSESSID=ak93pqashi5uubuoq8fjv60897'); + $this->assertEquals('1.638370270.1344367610.1374365610.1944450276.2', $this->request->getCookie('__utma')); + $this->assertEquals('1.1346368610.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)', $this->request->getCookie('__utmz')); + $this->assertEquals('de', $this->request->getCookie('hl')); + $this->assertEquals('ak93pqashi5uubuoq8fjv60897', $this->request->getCookie('PHPSESSID')); + + // Unset the cookies by setting the Cookie header to null + $this->request->setHeader('Cookie', null); + $this->assertNull($this->request->getCookie('test')); + $this->request->removeHeader('Cookie'); + + // Set and remove a cookie + $this->assertSame($this->request, $this->request->addCookie('test', 'abc')); + $this->assertEquals('abc', $this->request->getCookie('test')); + $this->assertSame($this->request, $this->request->removeCookie('test')); + $this->assertNull($this->request->getCookie('test')); + + // Remove the cookie header + $this->assertSame($this->request, $this->request->addCookie('test', 'abc')); + $this->request->removeHeader('Cookie'); + $this->assertEquals('', (string) $this->request->getHeader('Cookie')); + + // Remove a cookie value + $this->request->addCookie('foo', 'bar')->addCookie('baz', 'boo'); + $this->request->removeCookie('foo'); + $this->assertEquals(array( + 'baz' => 'boo' + ), $this->request->getCookies()); + + $this->request->addCookie('foo', 'bar'); + $this->assertEquals('baz=boo; foo=bar', (string) $this->request->getHeader('Cookie')); + } + + /** + * @expectedException \Guzzle\Http\Exception\RequestException + * @expectedExceptionMessage Error completing request + */ + public function testRequestThrowsExceptionWhenSetToCompleteWithNoResponse() + { + $this->request->setState(RequestInterface::STATE_COMPLETE); + } + + public function testClonedRequestsUseNewInternalState() + { + $p = new AsyncPlugin(); + $this->request->getEventDispatcher()->addSubscriber($p); + $h = $this->request->getHeader('Host'); + + $r = clone $this->request; + $this->assertEquals(RequestInterface::STATE_NEW, $r->getState()); + $this->assertNotSame($r->getQuery(), $this->request->getQuery()); + $this->assertNotSame($r->getCurlOptions(), $this->request->getCurlOptions()); + $this->assertNotSame($r->getEventDispatcher(), $this->request->getEventDispatcher()); + $this->assertEquals($r->getHeaders(), $this->request->getHeaders()); + $this->assertNotSame($h, $r->getHeader('Host')); + $this->assertNotSame($r->getParams(), $this->request->getParams()); + $this->assertTrue($this->request->getEventDispatcher()->hasListeners('request.sent')); + } + + public function testRecognizesBasicAuthCredentialsInUrls() + { + $this->request->setUrl('http://michael:test@test.com/'); + $this->assertEquals('michael', $this->request->getUsername()); + $this->assertEquals('test', $this->request->getPassword()); + } + + public function testRequestCanBeSentUsingCurl() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\nConnection: close\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\nConnection: close\r\n\r\ndata", + "HTTP/1.1 404 Not Found\r\nContent-Encoding: application/xml\r\nContent-Length: 48\r\n\r\nFile not found" + )); + + $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl()); + $request->setClient($this->client); + $response = $request->send(); + + $this->assertEquals('data', $response->getBody(true)); + $this->assertEquals(200, (int) $response->getStatusCode()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals(4, $response->getContentLength()); + $this->assertEquals('Thu, 01 Dec 1994 16:00:00 GMT', $response->getExpires()); + + // Test that the same handle can be sent twice without setting state to new + $response2 = $request->send(); + $this->assertNotSame($response, $response2); + + try { + $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl() . 'index.html'); + $request->setClient($this->client); + $response = $request->send(); + $this->fail('Request did not receive a 404 response'); + } catch (BadResponseException $e) { + } + + $requests = $this->getServer()->getReceivedRequests(true); + $messages = $this->getServer()->getReceivedRequests(false); + $port = $this->getServer()->getPort(); + + $userAgent = $this->client->getDefaultUserAgent(); + + $this->assertEquals('127.0.0.1:' . $port, $requests[0]->getHeader('Host')); + $this->assertEquals('127.0.0.1:' . $port, $requests[1]->getHeader('Host')); + $this->assertEquals('127.0.0.1:' . $port, $requests[2]->getHeader('Host')); + + $this->assertEquals('/', $requests[0]->getPath()); + $this->assertEquals('/', $requests[1]->getPath()); + $this->assertEquals('/index.html', $requests[2]->getPath()); + + $parts = explode("\r\n", $messages[0]); + $this->assertEquals('GET / HTTP/1.1', $parts[0]); + + $parts = explode("\r\n", $messages[1]); + $this->assertEquals('GET / HTTP/1.1', $parts[0]); + + $parts = explode("\r\n", $messages[2]); + $this->assertEquals('GET /index.html HTTP/1.1', $parts[0]); + } + + public function testThrowsExceptionsWhenUnsuccessfulResponseIsReceivedByDefault() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 404 Not found\r\nContent-Length: 0\r\n\r\n"); + + try { + $request = $this->client->get('/index.html'); + $response = $request->send(); + $this->fail('Request did not receive a 404 response'); + } catch (BadResponseException $e) { + $this->assertContains('Client error response', $e->getMessage()); + $this->assertContains('[status code] 404', $e->getMessage()); + $this->assertContains('[reason phrase] Not found', $e->getMessage()); + } + } + + public function testCanShortCircuitErrorHandling() + { + $request = $this->request; + $response = new Response(404); + $request->setResponse($response, true); + $out = ''; + $that = $this; + $request->getEventDispatcher()->addListener('request.error', function($event) use (&$out, $that) { + $out .= $event['request'] . "\n" . $event['response'] . "\n"; + $event->stopPropagation(); + }); + $request->send(); + $this->assertContains((string) $request, $out); + $this->assertContains((string) $request->getResponse(), $out); + $this->assertSame($response, $request->getResponse()); + } + + public function testCanOverrideUnsuccessfulResponses() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 404 NOT FOUND\r\n" . + "Content-Length: 0\r\n" . + "\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Length: 0\r\n" . + "\r\n" + )); + + $newResponse = null; + + $request = $this->request; + $request->getEventDispatcher()->addListener('request.error', function($event) use (&$newResponse) { + if ($event['response']->getStatusCode() == 404) { + $newRequest = clone $event['request']; + $newResponse = $newRequest->send(); + // Override the original response and bypass additional response processing + $event['response'] = $newResponse; + // Call $event['request']->setResponse($newResponse); to re-apply events + $event->stopPropagation(); + } + }); + + $request->send(); + + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $this->assertSame($newResponse, $request->getResponse()); + $this->assertEquals(2, count($this->getServer()->getReceivedRequests())); + } + + public function testCanRetrieveUrlObject() + { + $request = new Request('GET', 'http://www.example.com/foo?abc=d'); + $this->assertInstanceOf('Guzzle\Http\Url', $request->getUrl(true)); + $this->assertEquals('http://www.example.com/foo?abc=d', $request->getUrl()); + $this->assertEquals('http://www.example.com/foo?abc=d', (string) $request->getUrl(true)); + } + + public function testUnresolvedRedirectsReturnResponse() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 303 SEE OTHER\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Foo\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n" + )); + $request = $this->request; + $this->assertEquals(303, $request->send()->getStatusCode()); + $request->getParams()->set(RedirectPlugin::DISABLE, true); + $this->assertEquals(301, $request->send()->getStatusCode()); + } + + public function testCanSendCustomRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = $this->client->createRequest('PROPFIND', $this->getServer()->getUrl(), array( + 'Content-Type' => 'text/plain' + ), 'foo'); + $response = $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('PROPFIND', $requests[0]->getMethod()); + $this->assertEquals(3, (string) $requests[0]->getHeader('Content-Length')); + $this->assertEquals('foo', (string) $requests[0]->getBody()); + } + + /** + * @expectedException \PHPUnit_Framework_Error_Warning + */ + public function testEnsuresFileCanBeCreated() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); + $this->client->get('/')->setResponseBody('/wefwefefefefwewefwe/wefwefwefefwe/wefwefewfw.txt')->send(); + } + + public function testAllowsFilenameForDownloadingContent() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); + $name = sys_get_temp_dir() . '/foo.txt'; + $this->client->get('/')->setResponseBody($name)->send(); + $this->assertEquals('test', file_get_contents($name)); + unlink($name); + } + + public function testUsesCustomResponseBodyWhenItIsCustom() + { + $en = EntityBody::factory(); + $request = $this->client->get(); + $request->setResponseBody($en); + $request->setResponse(new Response(200, array(), 'foo')); + $this->assertEquals('foo', (string) $en); + } + + public function testCanChangePortThroughScheme() + { + $request = new Request('GET', 'http://foo.com'); + $request->setScheme('https'); + $this->assertEquals('https://foo.com', (string) $request->getUrl()); + $this->assertEquals('foo.com', $request->getHost()); + $request->setScheme('http'); + $this->assertEquals('http://foo.com', (string) $request->getUrl()); + $this->assertEquals('foo.com', $request->getHost()); + $request->setPort(null); + $this->assertEquals('http://foo.com', (string) $request->getUrl()); + $this->assertEquals('foo.com', $request->getHost()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php new file mode 100644 index 000000000..08b4df878 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php @@ -0,0 +1,677 @@ +response = new Response(200, new Collection(array( + 'Accept-Ranges' => 'bytes', + 'Age' => '12', + 'Allow' => 'GET, HEAD', + 'Cache-Control' => 'no-cache', + 'Content-Encoding' => 'gzip', + 'Content-Language' => 'da', + 'Content-Length' => '348', + 'Content-Location' => '/index.htm', + 'Content-Disposition' => 'attachment; filename=fname.ext', + 'Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ==', + 'Content-Range' => 'bytes 21010-47021/47022', + 'Content-Type' => 'text/html; charset=utf-8', + 'Date' => 'Tue, 15 Nov 1994 08:12:31 GMT', + 'ETag' => '737060cd8c284d8af7ad3082f209582d', + 'Expires' => 'Thu, 01 Dec 1994 16:00:00 GMT', + 'Last-Modified' => 'Tue, 15 Nov 1994 12:45:26 GMT', + 'Location' => 'http://www.w3.org/pub/WWW/People.html', + 'Pragma' => 'no-cache', + 'Proxy-Authenticate' => 'Basic', + 'Retry-After' => '120', + 'Server' => 'Apache/1.3.27 (Unix) (Red-Hat/Linux)', + 'Set-Cookie' => 'UserID=JohnDoe; Max-Age=3600; Version=1', + 'Trailer' => 'Max-Forwards', + 'Transfer-Encoding' => 'chunked', + 'Vary' => '*', + 'Via' => '1.0 fred, 1.1 nowhere.com (Apache/1.1)', + 'Warning' => '199 Miscellaneous warning', + 'WWW-Authenticate' => 'Basic' + )), 'body'); + } + + public function tearDown() + { + unset($this->response); + } + + public function testConstructor() + { + $params = new Collection(); + $body = EntityBody::factory(''); + $response = new Response(200, $params, $body); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals($body, $response->getBody()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", $response->getRawHeaders()); + + // Make sure Content-Length is set automatically + $response = new Response(200, $params); + $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", $response->getRawHeaders()); + + // Pass bodies to the response + $response = new Response(200, null, 'data'); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $response->getBody()); + $response = new Response(200, null, EntityBody::factory('data')); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $response->getBody()); + $this->assertEquals('data', $response->getBody(true)); + $response = new Response(200, null, '0'); + $this->assertSame('0', $response->getBody(true), 'getBody(true) should return "0" if response body is "0".'); + + // Make sure the proper exception is thrown + try { + //$response = new Response(200, null, array('foo' => 'bar')); + //$this->fail('Response did not throw exception when passing invalid body'); + } catch (HttpException $e) { + } + + // Ensure custom codes can be set + $response = new Response(2); + $this->assertEquals(2, $response->getStatusCode()); + $this->assertEquals('', $response->getReasonPhrase()); + + // Make sure the proper exception is thrown when sending invalid headers + try { + $response = new Response(200, 'adidas'); + $this->fail('Response did not throw exception when passing invalid $headers'); + } catch (BadResponseException $e) { + } + } + + public function test__toString() + { + $response = new Response(200); + $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", (string) $response); + + // Add another header + $response = new Response(200, array( + 'X-Test' => 'Guzzle' + )); + $this->assertEquals("HTTP/1.1 200 OK\r\nX-Test: Guzzle\r\n\r\n", (string) $response); + + $response = new Response(200, array( + 'Content-Length' => 4 + ), 'test'); + $this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", (string) $response); + } + + public function testFactory() + { + $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals(4, (string) $response->getContentLength()); + $this->assertEquals('test', $response->getBody(true)); + + // Make sure that automatic Content-Length works + $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); + $this->assertEquals(4, (string) $response->getContentLength()); + $this->assertEquals('test', $response->getBody(true)); + } + + public function testFactoryCanCreateHeadResponses() + { + $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n"); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals(4, (string) $response->getContentLength()); + $this->assertEquals('', $response->getBody(true)); + } + + public function testFactoryRequiresMessage() + { + $this->assertFalse(Response::fromMessage('')); + } + + public function testGetBody() + { + $body = EntityBody::factory(''); + $response = new Response(403, new Collection(), $body); + $this->assertEquals($body, $response->getBody()); + $response->setBody('foo'); + $this->assertEquals('foo', $response->getBody(true)); + } + + public function testManagesStatusCode() + { + $response = new Response(403); + $this->assertEquals(403, $response->getStatusCode()); + } + + public function testGetMessage() + { + $response = new Response(200, new Collection(array( + 'Content-Length' => 4 + )), 'body'); + + $this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nbody", $response->getMessage()); + } + + public function testGetRawHeaders() + { + $response = new Response(200, new Collection(array( + 'Keep-Alive' => 155, + 'User-Agent' => 'Guzzle', + 'Content-Length' => 4 + )), 'body'); + + $this->assertEquals("HTTP/1.1 200 OK\r\nKeep-Alive: 155\r\nUser-Agent: Guzzle\r\nContent-Length: 4\r\n\r\n", $response->getRawHeaders()); + } + + public function testHandlesStatusAndStatusCodes() + { + $response = new Response(200, new Collection(), 'body'); + $this->assertEquals('OK', $response->getReasonPhrase()); + + $this->assertSame($response, $response->setStatus(204)); + $this->assertEquals('No Content', $response->getReasonPhrase()); + $this->assertEquals(204, $response->getStatusCode()); + + $this->assertSame($response, $response->setStatus(204, 'Testing!')); + $this->assertEquals('Testing!', $response->getReasonPhrase()); + $this->assertEquals(204, $response->getStatusCode()); + + $response->setStatus(2000); + $this->assertEquals(2000, $response->getStatusCode()); + $this->assertEquals('', $response->getReasonPhrase()); + + $response->setStatus(200, 'Foo'); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('Foo', $response->getReasonPhrase()); + } + + public function testIsClientError() + { + $response = new Response(403); + $this->assertTrue($response->isClientError()); + $response = new Response(200); + $this->assertFalse($response->isClientError()); + } + + public function testIsError() + { + $response = new Response(403); + $this->assertTrue($response->isError()); + $response = new Response(200); + $this->assertFalse($response->isError()); + $response = new Response(500); + $this->assertTrue($response->isError()); + } + + public function testIsInformational() + { + $response = new Response(100); + $this->assertTrue($response->isInformational()); + $response = new Response(200); + $this->assertFalse($response->isInformational()); + } + + public function testIsRedirect() + { + $response = new Response(301); + $this->assertTrue($response->isRedirect()); + $response = new Response(200); + $this->assertFalse($response->isRedirect()); + } + + public function testIsServerError() + { + $response = new Response(500); + $this->assertTrue($response->isServerError()); + $response = new Response(400); + $this->assertFalse($response->isServerError()); + } + + public function testIsSuccessful() + { + $response = new Response(200); + $this->assertTrue($response->isSuccessful()); + $response = new Response(403); + $this->assertFalse($response->isSuccessful()); + } + + public function testGetAcceptRanges() + { + $this->assertEquals('bytes', $this->response->getAcceptRanges()); + } + + public function testCalculatesAge() + { + $this->assertEquals(12, $this->response->calculateAge()); + + $this->response->removeHeader('Age'); + $this->response->removeHeader('Date'); + $this->assertNull($this->response->calculateAge()); + + $this->response->setHeader('Date', gmdate(ClientInterface::HTTP_DATE, strtotime('-1 minute'))); + // If the test runs slowly, still pass with a +5 second allowance + $this->assertTrue($this->response->getAge() - 60 <= 5); + } + + public function testGetAllow() + { + $this->assertEquals('GET, HEAD', $this->response->getAllow()); + } + + public function testGetCacheControl() + { + $this->assertEquals('no-cache', $this->response->getCacheControl()); + } + + public function testGetContentEncoding() + { + $this->assertEquals('gzip', $this->response->getContentEncoding()); + } + + public function testGetContentLanguage() + { + $this->assertEquals('da', $this->response->getContentLanguage()); + } + + public function testGetContentLength() + { + $this->assertEquals('348', $this->response->getContentLength()); + } + + public function testGetContentLocation() + { + $this->assertEquals('/index.htm', $this->response->getContentLocation()); + } + + public function testGetContentDisposition() + { + $this->assertEquals('attachment; filename=fname.ext', $this->response->getContentDisposition()); + } + + public function testGetContentMd5() + { + $this->assertEquals('Q2hlY2sgSW50ZWdyaXR5IQ==', $this->response->getContentMd5()); + } + + public function testGetContentRange() + { + $this->assertEquals('bytes 21010-47021/47022', $this->response->getContentRange()); + } + + public function testGetContentType() + { + $this->assertEquals('text/html; charset=utf-8', $this->response->getContentType()); + } + + public function testGetDate() + { + $this->assertEquals('Tue, 15 Nov 1994 08:12:31 GMT', $this->response->getDate()); + } + + public function testGetEtag() + { + $this->assertEquals('737060cd8c284d8af7ad3082f209582d', $this->response->getEtag()); + } + + public function testGetExpires() + { + $this->assertEquals('Thu, 01 Dec 1994 16:00:00 GMT', $this->response->getExpires()); + } + + public function testGetLastModified() + { + $this->assertEquals('Tue, 15 Nov 1994 12:45:26 GMT', $this->response->getLastModified()); + } + + public function testGetLocation() + { + $this->assertEquals('http://www.w3.org/pub/WWW/People.html', $this->response->getLocation()); + } + + public function testGetPragma() + { + $this->assertEquals('no-cache', $this->response->getPragma()); + } + + public function testGetProxyAuthenticate() + { + $this->assertEquals('Basic', $this->response->getProxyAuthenticate()); + } + + public function testGetServer() + { + $this->assertEquals('Apache/1.3.27 (Unix) (Red-Hat/Linux)', $this->response->getServer()); + } + + public function testGetSetCookie() + { + $this->assertEquals('UserID=JohnDoe; Max-Age=3600; Version=1', $this->response->getSetCookie()); + } + + public function testGetMultipleSetCookie() + { + $this->response->addHeader('Set-Cookie', 'UserID=Mike; Max-Age=200'); + $this->assertEquals(array( + 'UserID=JohnDoe; Max-Age=3600; Version=1', + 'UserID=Mike; Max-Age=200', + ), $this->response->getHeader('Set-Cookie')->toArray()); + } + + public function testGetSetCookieNormalizesHeaders() + { + $this->response->addHeaders(array( + 'Set-Cooke' => 'boo', + 'set-cookie' => 'foo' + )); + + $this->assertEquals(array( + 'UserID=JohnDoe; Max-Age=3600; Version=1', + 'foo' + ), $this->response->getHeader('Set-Cookie')->toArray()); + + $this->response->addHeaders(array( + 'set-cookie' => 'fubu' + )); + $this->assertEquals( + array('UserID=JohnDoe; Max-Age=3600; Version=1', 'foo', 'fubu'), + $this->response->getHeader('Set-Cookie')->toArray() + ); + } + + public function testGetTrailer() + { + $this->assertEquals('Max-Forwards', $this->response->getTrailer()); + } + + public function testGetTransferEncoding() + { + $this->assertEquals('chunked', $this->response->getTransferEncoding()); + } + + public function testGetVary() + { + $this->assertEquals('*', $this->response->getVary()); + } + + public function testReturnsViaHeader() + { + $this->assertEquals('1.0 fred, 1.1 nowhere.com (Apache/1.1)', $this->response->getVia()); + } + public function testGetWarning() + { + $this->assertEquals('199 Miscellaneous warning', $this->response->getWarning()); + } + + public function testReturnsWwwAuthenticateHeader() + { + $this->assertEquals('Basic', $this->response->getWwwAuthenticate()); + } + + public function testReturnsConnectionHeader() + { + $this->assertEquals(null, $this->response->getConnection()); + $this->response->setHeader('Connection', 'close'); + $this->assertEquals('close', $this->response->getConnection()); + } + + public function testReturnsHeaders() + { + $this->assertEquals('Basic', $this->response->getHeader('WWW-Authenticate', null, true)); + $this->assertEquals('chunked', $this->response->getHeader('Transfer-Encoding', null, false)); + } + + public function testHasTransferInfo() + { + $stats = array ( + 'url' => 'http://www.google.com/', + 'content_type' => 'text/html; charset=ISO-8859-1', + 'http_code' => 200, + 'header_size' => 606, + 'request_size' => 53, + 'filetime' => -1, + 'ssl_verify_result' => 0, + 'redirect_count' => 0, + 'total_time' => 0.093284, + 'namelookup_time' => 0.001349, + 'connect_time' => 0.01635, + 'pretransfer_time' => 0.016358, + 'size_upload' => 0, + 'size_download' => 10330, + 'speed_download' => 110737, + 'speed_upload' => 0, + 'download_content_length' => -1, + 'upload_content_length' => 0, + 'starttransfer_time' => 0.07066, + 'redirect_time' => 0, + ); + + // Uninitialized state + $this->assertNull($this->response->getInfo('url')); + $this->assertEquals(array(), $this->response->getInfo()); + + // Set the stats + $this->response->setInfo($stats); + $this->assertEquals($stats, $this->response->getInfo()); + $this->assertEquals(606, $this->response->getInfo('header_size')); + $this->assertNull($this->response->getInfo('does_not_exist')); + } + + /** + * @return Response + */ + private function getResponse($code, array $headers = null, EntityBody $body = null) + { + return new Response($code, $headers, $body); + } + + public function testDeterminesIfItCanBeCached() + { + $this->assertTrue($this->getResponse(200)->canCache()); + $this->assertTrue($this->getResponse(410)->canCache()); + $this->assertFalse($this->getResponse(404)->canCache()); + $this->assertTrue($this->getResponse(200, array( + 'Cache-Control' => 'public' + ))->canCache()); + + // This has the no-store directive + $this->assertFalse($this->getResponse(200, array( + 'Cache-Control' => 'private, no-store' + ))->canCache()); + + // The body cannot be read, so it cannot be cached + $tmp = tempnam('/tmp', 'not-readable'); + $resource = fopen($tmp, 'w'); + $this->assertFalse($this->getResponse(200, array( + 'Transfer-Encoding' => 'chunked' + ), EntityBody::factory($resource, 10))->canCache()); + unlink($tmp); + + // The body is 0 length, cannot be read, so it can be cached + $tmp = tempnam('/tmp', 'not-readable'); + $resource = fopen($tmp, 'w'); + $this->assertTrue($this->getResponse(200, array(array( + 'Content-Length' => 0 + )), EntityBody::factory($resource, 0))->canCache()); + unlink($tmp); + } + + public function testDeterminesResponseMaxAge() + { + $this->assertEquals(null, $this->getResponse(200)->getMaxAge()); + + // Uses the response's s-maxage + $this->assertEquals(140, $this->getResponse(200, array( + 'Cache-Control' => 's-maxage=140' + ))->getMaxAge()); + + // Uses the response's max-age + $this->assertEquals(120, $this->getResponse(200, array( + 'Cache-Control' => 'max-age=120' + ))->getMaxAge()); + + // Uses the response's max-age + $this->assertEquals(120, $this->getResponse(200, array( + 'Cache-Control' => 'max-age=120', + 'Expires' => gmdate(ClientInterface::HTTP_DATE, strtotime('+1 day')) + ))->getMaxAge()); + + // Uses the Expires date + $this->assertGreaterThanOrEqual(82400, $this->getResponse(200, array( + 'Expires' => gmdate(ClientInterface::HTTP_DATE, strtotime('+1 day')) + ))->getMaxAge()); + + // Uses the Expires date + $this->assertGreaterThanOrEqual(82400, $this->getResponse(200, array( + 'Expires' => gmdate(ClientInterface::HTTP_DATE, strtotime('+1 day')) + ))->getMaxAge()); + } + + public function testDeterminesIfItCanValidate() + { + $response = new Response(200); + $this->assertFalse($response->canValidate()); + $response->setHeader('ETag', '123'); + $this->assertTrue($response->canValidate()); + $response->removeHeader('ETag'); + $this->assertFalse($response->canValidate()); + $response->setHeader('Last-Modified', '123'); + $this->assertTrue($response->canValidate()); + } + + public function testCalculatesFreshness() + { + $response = new Response(200); + $this->assertNull($response->isFresh()); + $this->assertNull($response->getFreshness()); + + $response->setHeader('Cache-Control', 'max-age=120'); + $response->setHeader('Age', 100); + $this->assertEquals(20, $response->getFreshness()); + $this->assertTrue($response->isFresh()); + + $response->setHeader('Age', 120); + $this->assertEquals(0, $response->getFreshness()); + $this->assertTrue($response->isFresh()); + + $response->setHeader('Age', 150); + $this->assertEquals(-30, $response->getFreshness()); + $this->assertFalse($response->isFresh()); + } + + public function testHandlesProtocols() + { + $this->assertSame($this->response, $this->response->setProtocol('HTTP', '1.0')); + $this->assertEquals('HTTP', $this->response->getProtocol()); + $this->assertEquals('1.0', $this->response->getProtocolVersion()); + } + + public function testComparesContentType() + { + $response = new Response(200, array( + 'Content-Type' => 'text/html; charset=ISO-8859-4' + )); + + $this->assertTrue($response->isContentType('text/html')); + $this->assertTrue($response->isContentType('TExT/html')); + $this->assertTrue($response->isContentType('charset=ISO-8859-4')); + $this->assertFalse($response->isContentType('application/xml')); + } + + public function testResponseDeterminesIfMethodIsAllowedBaseOnAllowHeader() + { + $response = new Response(200, array( + 'Allow' => 'OPTIONS, POST, deletE,GET' + )); + + $this->assertTrue($response->isMethodAllowed('get')); + $this->assertTrue($response->isMethodAllowed('GET')); + $this->assertTrue($response->isMethodAllowed('options')); + $this->assertTrue($response->isMethodAllowed('post')); + $this->assertTrue($response->isMethodAllowed('Delete')); + $this->assertFalse($response->isMethodAllowed('put')); + $this->assertFalse($response->isMethodAllowed('PUT')); + + $response = new Response(200); + $this->assertFalse($response->isMethodAllowed('get')); + } + + public function testParsesJsonResponses() + { + $response = new Response(200, array(), '{"foo": "bar"}'); + $this->assertEquals(array('foo' => 'bar'), $response->json()); + // Return array when null is a service response + $response = new Response(200); + $this->assertEquals(array(), $response->json()); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage Unable to parse response body into JSON: 4 + */ + public function testThrowsExceptionWhenFailsToParseJsonResponse() + { + $response = new Response(200, array(), '{"foo": "'); + $response->json(); + } + + public function testParsesXmlResponses() + { + $response = new Response(200, array(), 'bar'); + $this->assertEquals('bar', (string) $response->xml()->foo); + // Always return a SimpleXMLElement from the xml method + $response = new Response(200); + $this->assertEmpty((string) $response->xml()->foo); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage Unable to parse response body into XML: String could not be parsed as XML + */ + public function testThrowsExceptionWhenFailsToParseXmlResponse() + { + $response = new Response(200, array(), 'xml(); + } + + public function testResponseIsSerializable() + { + $response = new Response(200, array('Foo' => 'bar'), 'test'); + $r = unserialize(serialize($response)); + $this->assertEquals(200, $r->getStatusCode()); + $this->assertEquals('bar', (string) $r->getHeader('Foo')); + $this->assertEquals('test', (string) $r->getBody()); + } + + public function testPreventsComplexExternalEntities() + { + $xml = ']>&test;'; + $response = new Response(200, array(), $xml); + + $oldCwd = getcwd(); + chdir(__DIR__); + try { + $xml = $response->xml(); + chdir($oldCwd); + $this->markTestIncomplete('Did not throw the expected exception! XML resolved as: ' . $xml->asXML()); + } catch (\Exception $e) { + chdir($oldCwd); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php new file mode 100644 index 000000000..722845340 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php @@ -0,0 +1,31 @@ +assertEquals('text/x-php', Mimetypes::getInstance()->fromExtension('php')); + } + + public function testGetsFromFilename() + { + $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(__FILE__)); + } + + public function testGetsFromCaseInsensitiveFilename() + { + $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(strtoupper(__FILE__))); + } + + public function testReturnsNullWhenNoMatchFound() + { + $this->assertNull(Mimetypes::getInstance()->fromExtension('foobar')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php new file mode 100644 index 000000000..549d3edd9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php @@ -0,0 +1,30 @@ +aggregate($key, $value, $query); + $this->assertEquals(array('test%20123' => 'foo%20123,baz,bar'), $result); + } + + public function testEncodes() + { + $query = new QueryString(); + $query->useUrlEncoding(false); + $a = new Ag(); + $key = 'test 123'; + $value = array('foo 123', 'baz', 'bar'); + $result = $a->aggregate($key, $value, $query); + $this->assertEquals(array('test 123' => 'foo 123,baz,bar'), $result); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php new file mode 100644 index 000000000..6a4d9d95b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php @@ -0,0 +1,30 @@ +aggregate($key, $value, $query); + $this->assertEquals(array('facet%201' => array('size%20a', 'width%20b')), $result); + } + + public function testEncodes() + { + $query = new QueryString(); + $query->useUrlEncoding(false); + $a = new Ag(); + $key = 'facet 1'; + $value = array('size a', 'width b'); + $result = $a->aggregate($key, $value, $query); + $this->assertEquals(array('facet 1' => array('size a', 'width b')), $result); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php new file mode 100644 index 000000000..1e7f0c27a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php @@ -0,0 +1,32 @@ +useUrlEncoding(false); + $a = new Ag(); + $key = 't'; + $value = array( + 'v1' => 'a', + 'v2' => 'b', + 'v3' => array( + 'v4' => 'c', + 'v5' => 'd', + ) + ); + $result = $a->aggregate($key, $value, $query); + $this->assertEquals(array( + 't[v1]' => 'a', + 't[v2]' => 'b', + 't[v3][v4]' => 'c', + 't[v3][v5]' => 'd', + ), $result); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php new file mode 100644 index 000000000..948db442d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php @@ -0,0 +1,233 @@ +q = new QueryString(); + } + + public function testGetFieldSeparator() + { + $this->assertEquals('&', $this->q->getFieldSeparator()); + } + + public function testGetValueSeparator() + { + $this->assertEquals('=', $this->q->getValueSeparator()); + } + + public function testIsUrlEncoding() + { + $this->assertEquals('RFC 3986', $this->q->getUrlEncoding()); + $this->assertTrue($this->q->isUrlEncoding()); + $this->assertEquals('foo%20bar', $this->q->encodeValue('foo bar')); + + $this->q->useUrlEncoding(QueryString::FORM_URLENCODED); + $this->assertTrue($this->q->isUrlEncoding()); + $this->assertEquals(QueryString::FORM_URLENCODED, $this->q->getUrlEncoding()); + $this->assertEquals('foo+bar', $this->q->encodeValue('foo bar')); + + $this->assertSame($this->q, $this->q->useUrlEncoding(false)); + $this->assertFalse($this->q->isUrlEncoding()); + $this->assertFalse($this->q->isUrlEncoding()); + } + + public function testSetFieldSeparator() + { + $this->assertEquals($this->q, $this->q->setFieldSeparator('/')); + $this->assertEquals('/', $this->q->getFieldSeparator()); + } + + public function testSetValueSeparator() + { + $this->assertEquals($this->q, $this->q->setValueSeparator('/')); + $this->assertEquals('/', $this->q->getValueSeparator()); + } + + public function testUrlEncode() + { + $params = array( + 'test' => 'value', + 'test 2' => 'this is a test?', + 'test3' => array('v1', 'v2', 'v3'), + 'ሴ' => 'bar' + ); + $encoded = array( + 'test' => 'value', + 'test%202' => rawurlencode('this is a test?'), + 'test3%5B0%5D' => 'v1', + 'test3%5B1%5D' => 'v2', + 'test3%5B2%5D' => 'v3', + '%E1%88%B4' => 'bar' + ); + $this->q->replace($params); + $this->assertEquals($encoded, $this->q->urlEncode()); + + // Disable encoding + $testData = array('test 2' => 'this is a test'); + $this->q->replace($testData); + $this->q->useUrlEncoding(false); + $this->assertEquals($testData, $this->q->urlEncode()); + } + + public function testToString() + { + // Check with no parameters + $this->assertEquals('', $this->q->__toString()); + + $params = array( + 'test' => 'value', + 'test 2' => 'this is a test?', + 'test3' => array('v1', 'v2', 'v3'), + 'test4' => null, + ); + $this->q->replace($params); + $this->assertEquals('test=value&test%202=this%20is%20a%20test%3F&test3%5B0%5D=v1&test3%5B1%5D=v2&test3%5B2%5D=v3&test4', $this->q->__toString()); + $this->q->useUrlEncoding(false); + $this->assertEquals('test=value&test 2=this is a test?&test3[0]=v1&test3[1]=v2&test3[2]=v3&test4', $this->q->__toString()); + + // Use an alternative aggregator + $this->q->setAggregator(new CommaAggregator()); + $this->assertEquals('test=value&test 2=this is a test?&test3=v1,v2,v3&test4', $this->q->__toString()); + } + + public function testAllowsMultipleValuesPerKey() + { + $q = new QueryString(); + $q->add('facet', 'size'); + $q->add('facet', 'width'); + $q->add('facet.field', 'foo'); + // Use the duplicate aggregator + $q->setAggregator(new DuplicateAggregator()); + $this->assertEquals('facet=size&facet=width&facet.field=foo', $q->__toString()); + } + + public function testAllowsNestedQueryData() + { + $this->q->replace(array( + 'test' => 'value', + 't' => array( + 'v1' => 'a', + 'v2' => 'b', + 'v3' => array( + 'v4' => 'c', + 'v5' => 'd', + ) + ) + )); + + $this->q->useUrlEncoding(false); + $this->assertEquals('test=value&t[v1]=a&t[v2]=b&t[v3][v4]=c&t[v3][v5]=d', $this->q->__toString()); + } + + public function parseQueryProvider() + { + return array( + // Ensure that multiple query string values are allowed per value + array('q=a&q=b', array('q' => array('a', 'b'))), + // Ensure that PHP array style query string values are parsed + array('q[]=a&q[]=b', array('q' => array('a', 'b'))), + // Ensure that a single PHP array style query string value is parsed into an array + array('q[]=a', array('q' => array('a'))), + // Ensure that decimals are allowed in query strings + array('q.a=a&q.b=b', array( + 'q.a' => 'a', + 'q.b' => 'b' + )), + // Ensure that query string values are percent decoded + array('q%20a=a%20b', array('q a' => 'a b')), + // Ensure null values can be added + array('q&a', array('q' => false, 'a' => false)), + ); + } + + /** + * @dataProvider parseQueryProvider + */ + public function testParsesQueryStrings($query, $data) + { + $query = QueryString::fromString($query); + $this->assertEquals($data, $query->getAll()); + } + + public function testProperlyDealsWithDuplicateQueryStringValues() + { + $query = QueryString::fromString('foo=a&foo=b&?µ=c'); + $this->assertEquals(array('a', 'b'), $query->get('foo')); + $this->assertEquals('c', $query->get('?µ')); + } + + public function testAllowsBlankQueryStringValues() + { + $query = QueryString::fromString('foo'); + $this->assertEquals('foo', (string) $query); + $query->set('foo', QueryString::BLANK); + $this->assertEquals('foo', (string) $query); + } + + public function testAllowsFalsyQueryStringValues() + { + $query = QueryString::fromString('0'); + $this->assertEquals('0', (string) $query); + $query->set('0', QueryString::BLANK); + $this->assertSame('0', (string) $query); + } + + public function testFromStringIgnoresQuestionMark() + { + $query = QueryString::fromString('foo=baz&bar=boo'); + $this->assertEquals('foo=baz&bar=boo', (string) $query); + } + + public function testConvertsPlusSymbolsToSpaces() + { + $query = QueryString::fromString('var=foo+bar'); + $this->assertEquals('foo bar', $query->get('var')); + } + + public function testFromStringDoesntMangleZeroes() + { + $query = QueryString::fromString('var=0'); + $this->assertSame('0', $query->get('var')); + } + + public function testAllowsZeroValues() + { + $query = new QueryString(array( + 'foo' => 0, + 'baz' => '0', + 'bar' => null, + 'boo' => false, + 'bam' => '' + )); + $this->assertEquals('foo=0&baz=0&bar&boo&bam=', (string) $query); + } + + public function testFromStringDoesntStripTrailingEquals() + { + $query = QueryString::fromString('data=mF0b3IiLCJUZWFtIERldiJdfX0='); + $this->assertEquals('mF0b3IiLCJUZWFtIERldiJdfX0=', $query->get('data')); + } + + public function testGuessesIfDuplicateAggregatorShouldBeUsed() + { + $query = QueryString::fromString('test=a&test=b'); + $this->assertEquals('test=a&test=b', (string) $query); + } + + public function testGuessesIfDuplicateAggregatorShouldBeUsedAndChecksForPhpStyle() + { + $query = QueryString::fromString('test[]=a&test[]=b'); + $this->assertEquals('test%5B0%5D=a&test%5B1%5D=b', (string) $query); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php new file mode 100644 index 000000000..6bb3fed18 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php @@ -0,0 +1,81 @@ +decorated = EntityBody::factory(fopen(__FILE__, 'r')); + $this->body = new ReadLimitEntityBody($this->decorated, 10, 3); + } + + public function testReturnsSubsetWhenCastToString() + { + $body = EntityBody::factory('foo_baz_bar'); + $limited = new ReadLimitEntityBody($body, 3, 4); + $this->assertEquals('baz', (string) $limited); + } + + public function testReturnsSubsetOfEmptyBodyWhenCastToString() + { + $body = EntityBody::factory(''); + $limited = new ReadLimitEntityBody($body, 0, 10); + $this->assertEquals('', (string) $limited); + } + + public function testSeeksWhenConstructed() + { + $this->assertEquals(3, $this->body->ftell()); + } + + public function testAllowsBoundedSeek() + { + $this->body->seek(100); + $this->assertEquals(13, $this->body->ftell()); + $this->body->seek(0); + $this->assertEquals(3, $this->body->ftell()); + $this->assertEquals(false, $this->body->seek(1000, SEEK_END)); + } + + public function testReadsOnlySubsetOfData() + { + $data = $this->body->read(100); + $this->assertEquals(10, strlen($data)); + $this->assertFalse($this->body->read(1000)); + + $this->body->setOffset(10); + $newData = $this->body->read(100); + $this->assertEquals(10, strlen($newData)); + $this->assertNotSame($data, $newData); + } + + public function testClaimsConsumedWhenReadLimitIsReached() + { + $this->assertFalse($this->body->isConsumed()); + $this->body->read(1000); + $this->assertTrue($this->body->isConsumed()); + } + + public function testContentLengthIsBounded() + { + $this->assertEquals(10, $this->body->getContentLength()); + } + + public function testContentMd5IsBasedOnSubsection() + { + $this->assertNotSame($this->body->getContentMd5(), $this->decorated->getContentMd5()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php new file mode 100755 index 000000000..886236ddd --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php @@ -0,0 +1,277 @@ +getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + // Create a client that uses the default redirect behavior + $client = new Client($this->getServer()->getUrl()); + $history = new HistoryPlugin(); + $client->addSubscriber($history); + + $request = $client->get('/foo'); + $response = $request->send(); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertContains('/redirect2', $response->getEffectiveUrl()); + + // Ensure that two requests were sent + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('/foo', $requests[0]->getResource()); + $this->assertEquals('GET', $requests[0]->getMethod()); + $this->assertEquals('/redirect1', $requests[1]->getResource()); + $this->assertEquals('GET', $requests[1]->getMethod()); + $this->assertEquals('/redirect2', $requests[2]->getResource()); + $this->assertEquals('GET', $requests[2]->getMethod()); + + // Ensure that the redirect count was incremented + $this->assertEquals(2, $request->getParams()->get(RedirectPlugin::REDIRECT_COUNT)); + $this->assertCount(3, $history); + $requestHistory = $history->getAll(); + + $this->assertEquals(301, $requestHistory[0]['response']->getStatusCode()); + $this->assertEquals('/redirect1', (string) $requestHistory[0]['response']->getHeader('Location')); + $this->assertEquals(301, $requestHistory[1]['response']->getStatusCode()); + $this->assertEquals('/redirect2', (string) $requestHistory[1]['response']->getHeader('Location')); + $this->assertEquals(200, $requestHistory[2]['response']->getStatusCode()); + } + + public function testCanLimitNumberOfRedirects() + { + // Flush the server and queue up a redirect followed by a successful response + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect3\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect4\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect5\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect6\r\nContent-Length: 0\r\n\r\n" + )); + + try { + $client = new Client($this->getServer()->getUrl()); + $client->get('/foo')->send(); + $this->fail('Did not throw expected exception'); + } catch (TooManyRedirectsException $e) { + $this->assertContains( + "5 redirects were issued for this request:\nGET /foo HTTP/1.1\r\n", + $e->getMessage() + ); + } + } + + public function testDefaultBehaviorIsToRedirectWithGetForEntityEnclosingRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $client->post('/foo', array('X-Baz' => 'bar'), 'testing')->send(); + + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('GET', $requests[1]->getMethod()); + $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz')); + $this->assertEquals('GET', $requests[2]->getMethod()); + } + + public function testCanRedirectWithStrictRfcCompliance() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/foo', array('X-Baz' => 'bar'), 'testing'); + $request->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, true); + $request->send(); + + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('POST', $requests[1]->getMethod()); + $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz')); + $this->assertEquals('POST', $requests[2]->getMethod()); + } + + public function testRedirect303WithGet() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 303 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/foo'); + $request->send(); + + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('GET', $requests[1]->getMethod()); + } + + public function testRedirect303WithGetWithStrictRfcCompliance() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 303 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/foo'); + $request->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, true); + $request->send(); + + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('GET', $requests[1]->getMethod()); + } + + public function testRewindsStreamWhenRedirectingIfNeeded() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put(); + $request->configureRedirects(true); + $body = EntityBody::factory('foo'); + $body->read(1); + $request->setBody($body); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('foo', (string) $requests[0]->getBody()); + } + + /** + * @expectedException \Guzzle\Http\Exception\CouldNotRewindStreamException + */ + public function testThrowsExceptionWhenStreamCannotBeRewound() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n" + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put(); + $request->configureRedirects(true); + $body = EntityBody::factory(fopen($this->getServer()->getUrl(), 'r')); + $body->read(1); + $request->setBody($body)->send(); + } + + public function testRedirectsCanBeDisabledPerRequest() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array("HTTP/1.1 301 Foo\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n")); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put(); + $request->configureRedirects(false, 0); + $this->assertEquals(301, $request->send()->getStatusCode()); + } + + public function testCanRedirectWithNoLeadingSlashAndQuery() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: redirect?foo=bar\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('?foo=bar'); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals($this->getServer()->getUrl() . '?foo=bar', $requests[0]->getUrl()); + $this->assertEquals($this->getServer()->getUrl() . 'redirect?foo=bar', $requests[1]->getUrl()); + // Ensure that the history on the actual request is correct + $this->assertEquals($this->getServer()->getUrl() . '?foo=bar', $request->getUrl()); + } + + public function testRedirectWithStrictRfc386Compliance() + { + // Flush the server and queue up a redirect followed by a successful response + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/foo'); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('/redirect', $requests[1]->getResource()); + } + + public function testResetsHistoryEachSend() + { + // Flush the server and queue up a redirect followed by a successful response + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + + // Create a client that uses the default redirect behavior + $client = new Client($this->getServer()->getUrl()); + $history = new HistoryPlugin(); + $client->addSubscriber($history); + + $request = $client->get('/foo'); + $response = $request->send(); + $this->assertEquals(3, count($history)); + $this->assertTrue($request->getParams()->hasKey('redirect.count')); + $this->assertContains('/redirect2', $response->getEffectiveUrl()); + + $request->send(); + $this->assertFalse($request->getParams()->hasKey('redirect.count')); + } + + public function testHandlesRedirectsWithSpacesProperly() + { + // Flush the server and queue up a redirect followed by a successful response + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect 1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/foo'); + $request->send(); + $reqs = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('/redirect%201', $reqs[1]->getResource()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php new file mode 100644 index 000000000..94eb59a4d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php @@ -0,0 +1,191 @@ +port = $port ?: self::DEFAULT_PORT; + $this->client = new Client($this->getUrl()); + register_shutdown_function(array($this, 'stop')); + } + + /** + * Flush the received requests from the server + * @throws RuntimeException + */ + public function flush() + { + $this->client->delete('guzzle-server/requests')->send(); + } + + /** + * Queue an array of responses or a single response on the server. + * + * Any currently queued responses will be overwritten. Subsequent requests + * on the server will return queued responses in FIFO order. + * + * @param array|Response $responses A single or array of Responses to queue + * @throws BadResponseException + */ + public function enqueue($responses) + { + $data = array(); + foreach ((array) $responses as $response) { + + // Create the response object from a string + if (is_string($response)) { + $response = Response::fromMessage($response); + } elseif (!($response instanceof Response)) { + throw new BadResponseException('Responses must be strings or implement Response'); + } + + $data[] = array( + 'statusCode' => $response->getStatusCode(), + 'reasonPhrase' => $response->getReasonPhrase(), + 'headers' => $response->getHeaders()->toArray(), + 'body' => $response->getBody(true) + ); + } + + $request = $this->client->put('guzzle-server/responses', null, json_encode($data)); + $request->send(); + } + + /** + * Check if the server is running + * + * @return bool + */ + public function isRunning() + { + if ($this->running) { + return true; + } + + try { + $this->client->get('guzzle-server/perf', array(), array('timeout' => 5))->send(); + $this->running = true; + return true; + } catch (\Exception $e) { + return false; + } + } + + /** + * Get the URL to the server + * + * @return string + */ + public function getUrl() + { + return 'http://127.0.0.1:' . $this->getPort() . '/'; + } + + /** + * Get the port that the server is listening on + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Get all of the received requests + * + * @param bool $hydrate Set to TRUE to turn the messages into + * actual {@see RequestInterface} objects. If $hydrate is FALSE, + * requests will be returned as strings. + * + * @return array + * @throws RuntimeException + */ + public function getReceivedRequests($hydrate = false) + { + $response = $this->client->get('guzzle-server/requests')->send(); + $data = array_filter(explode(self::REQUEST_DELIMITER, $response->getBody(true))); + if ($hydrate) { + $data = array_map(function($message) { + return RequestFactory::getInstance()->fromMessage($message); + }, $data); + } + + return $data; + } + + /** + * Start running the node.js server in the background + */ + public function start() + { + if (!$this->isRunning()) { + exec('node ' . __DIR__ . \DIRECTORY_SEPARATOR + . 'server.js ' . $this->port + . ' >> /tmp/server.log 2>&1 &'); + // Wait at most 5 seconds for the server the setup before + // proceeding. + $start = time(); + while (!$this->isRunning() && time() - $start < 5); + if (!$this->running) { + throw new RuntimeException( + 'Unable to contact server.js. Have you installed node.js v0.5.0+? node must be in your path.' + ); + } + } + } + + /** + * Stop running the node.js server + */ + public function stop() + { + if (!$this->isRunning()) { + return false; + } + + $this->running = false; + $this->client->delete('guzzle-server')->send(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php new file mode 100644 index 000000000..091314bb7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php @@ -0,0 +1,67 @@ +assertTrue(class_exists('FooBazBar')); + $this->assertSame($client, $this->readAttribute('Guzzle\Http\StaticClient', 'client')); + } + + public function requestProvider() + { + return array_map( + function ($m) { return array($m); }, + array('GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS') + ); + } + + /** + * @dataProvider requestProvider + */ + public function testSendsRequests($method) + { + $mock = new MockPlugin(array(new Response(200))); + call_user_func('Guzzle\Http\StaticClient::' . $method, 'http://foo.com', array( + 'plugins' => array($mock) + )); + $requests = $mock->getReceivedRequests(); + $this->assertCount(1, $requests); + $this->assertEquals($method, $requests[0]->getMethod()); + } + + public function testCanCreateStreamsUsingDefaultFactory() + { + $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest")); + $stream = StaticClient::get($this->getServer()->getUrl(), array('stream' => true)); + $this->assertInstanceOf('Guzzle\Stream\StreamInterface', $stream); + $this->assertEquals('test', (string) $stream); + } + + public function testCanCreateStreamsUsingCustomFactory() + { + $stream = $this->getMockBuilder('Guzzle\Stream\StreamRequestFactoryInterface') + ->setMethods(array('fromRequest')) + ->getMockForAbstractClass(); + $resource = new Stream(fopen('php://temp', 'r+')); + $stream->expects($this->once()) + ->method('fromRequest') + ->will($this->returnValue($resource)); + $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest")); + $result = StaticClient::get($this->getServer()->getUrl(), array('stream' => $stream)); + $this->assertSame($resource, $result); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php new file mode 100644 index 000000000..28f26718b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php @@ -0,0 +1,303 @@ +assertEquals('', (string) $url); + } + + public function testPortIsDeterminedFromScheme() + { + $this->assertEquals(80, Url::factory('http://www.test.com/')->getPort()); + $this->assertEquals(443, Url::factory('https://www.test.com/')->getPort()); + $this->assertEquals(null, Url::factory('ftp://www.test.com/')->getPort()); + $this->assertEquals(8192, Url::factory('http://www.test.com:8192/')->getPort()); + } + + public function testCloneCreatesNewInternalObjects() + { + $u1 = Url::factory('http://www.test.com/'); + $u2 = clone $u1; + $this->assertNotSame($u1->getQuery(), $u2->getQuery()); + } + + public function testValidatesUrlPartsInFactory() + { + $url = Url::factory('/index.php'); + $this->assertEquals('/index.php', (string) $url); + $this->assertFalse($url->isAbsolute()); + + $url = 'http://michael:test@test.com:80/path/123?q=abc#test'; + $u = Url::factory($url); + $this->assertEquals('http://michael:test@test.com/path/123?q=abc#test', (string) $u); + $this->assertTrue($u->isAbsolute()); + } + + public function testAllowsFalsyUrlParts() + { + $url = Url::factory('http://0:50/0?0#0'); + $this->assertSame('0', $url->getHost()); + $this->assertEquals(50, $url->getPort()); + $this->assertSame('/0', $url->getPath()); + $this->assertEquals('0', (string) $url->getQuery()); + $this->assertSame('0', $url->getFragment()); + $this->assertEquals('http://0:50/0?0#0', (string) $url); + + $url = Url::factory(''); + $this->assertSame('', (string) $url); + + $url = Url::factory('0'); + $this->assertSame('0', (string) $url); + } + + public function testBuildsRelativeUrlsWithFalsyParts() + { + $url = Url::buildUrl(array( + 'host' => '0', + 'path' => '0', + )); + + $this->assertSame('//0/0', $url); + + $url = Url::buildUrl(array( + 'path' => '0', + )); + $this->assertSame('0', $url); + } + + public function testUrlStoresParts() + { + $url = Url::factory('http://test:pass@www.test.com:8081/path/path2/?a=1&b=2#fragment'); + $this->assertEquals('http', $url->getScheme()); + $this->assertEquals('test', $url->getUsername()); + $this->assertEquals('pass', $url->getPassword()); + $this->assertEquals('www.test.com', $url->getHost()); + $this->assertEquals(8081, $url->getPort()); + $this->assertEquals('/path/path2/', $url->getPath()); + $this->assertEquals('fragment', $url->getFragment()); + $this->assertEquals('a=1&b=2', (string) $url->getQuery()); + + $this->assertEquals(array( + 'fragment' => 'fragment', + 'host' => 'www.test.com', + 'pass' => 'pass', + 'path' => '/path/path2/', + 'port' => 8081, + 'query' => 'a=1&b=2', + 'scheme' => 'http', + 'user' => 'test' + ), $url->getParts()); + } + + public function testHandlesPathsCorrectly() + { + $url = Url::factory('http://www.test.com'); + $this->assertEquals('', $url->getPath()); + $url->setPath('test'); + $this->assertEquals('test', $url->getPath()); + + $url->setPath('/test/123/abc'); + $this->assertEquals(array('test', '123', 'abc'), $url->getPathSegments()); + + $parts = parse_url('http://www.test.com/test'); + $parts['path'] = ''; + $this->assertEquals('http://www.test.com', Url::buildUrl($parts)); + $parts['path'] = 'test'; + $this->assertEquals('http://www.test.com/test', Url::buildUrl($parts)); + } + + public function testAddsQueryStringIfPresent() + { + $this->assertEquals('?foo=bar', Url::buildUrl(array( + 'query' => 'foo=bar' + ))); + } + + public function testAddsToPath() + { + // Does nothing here + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(false)); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(null)); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(array())); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(new \stdClass())); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('')); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('/')); + $this->assertEquals('http://e.com/baz/foo', (string) Url::factory('http://e.com/baz/')->addPath('foo')); + $this->assertEquals('http://e.com/base/relative?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('relative')); + $this->assertEquals('http://e.com/base/relative?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('/relative')); + $this->assertEquals('http://e.com/base/0', (string) Url::factory('http://e.com/base')->addPath('0')); + $this->assertEquals('http://e.com/base/0/1', (string) Url::factory('http://e.com/base')->addPath('0')->addPath('1')); + } + + /** + * URL combination data provider + * + * @return array + */ + public function urlCombineDataProvider() + { + return array( + array('http://www.example.com/', 'http://www.example.com/', 'http://www.example.com/'), + array('http://www.example.com/path', '/absolute', 'http://www.example.com/absolute'), + array('http://www.example.com/path', '/absolute?q=2', 'http://www.example.com/absolute?q=2'), + array('http://www.example.com/path', 'more', 'http://www.example.com/path/more'), + array('http://www.example.com/path', 'more?q=1', 'http://www.example.com/path/more?q=1'), + array('http://www.example.com/', '?q=1', 'http://www.example.com/?q=1'), + array('http://www.example.com/path', 'http://test.com', 'http://test.com'), + array('http://www.example.com:8080/path', 'http://test.com', 'http://test.com'), + array('http://www.example.com:8080/path', '?q=2#abc', 'http://www.example.com:8080/path?q=2#abc'), + array('http://u:a@www.example.com/path', 'test', 'http://u:a@www.example.com/path/test'), + array('http://www.example.com/path', 'http://u:a@www.example.com/', 'http://u:a@www.example.com/'), + array('/path?q=2', 'http://www.test.com/', 'http://www.test.com/path?q=2'), + array('http://api.flickr.com/services/', 'http://www.flickr.com/services/oauth/access_token', 'http://www.flickr.com/services/oauth/access_token'), + array('http://www.example.com/?foo=bar', 'some/path', 'http://www.example.com/some/path?foo=bar'), + array('http://www.example.com/?foo=bar', 'some/path?boo=moo', 'http://www.example.com/some/path?boo=moo&foo=bar'), + array('http://www.example.com/some/', 'path?foo=bar&foo=baz', 'http://www.example.com/some/path?foo=bar&foo=baz'), + ); + } + + /** + * @dataProvider urlCombineDataProvider + */ + public function testCombinesUrls($a, $b, $c) + { + $this->assertEquals($c, (string) Url::factory($a)->combine($b)); + } + + public function testHasGettersAndSetters() + { + $url = Url::factory('http://www.test.com/'); + $this->assertEquals('example.com', $url->setHost('example.com')->getHost()); + $this->assertEquals('8080', $url->setPort(8080)->getPort()); + $this->assertEquals('/foo/bar', $url->setPath(array('foo', 'bar'))->getPath()); + $this->assertEquals('a', $url->setPassword('a')->getPassword()); + $this->assertEquals('b', $url->setUsername('b')->getUsername()); + $this->assertEquals('abc', $url->setFragment('abc')->getFragment()); + $this->assertEquals('https', $url->setScheme('https')->getScheme()); + $this->assertEquals('a=123', (string) $url->setQuery('a=123')->getQuery()); + $this->assertEquals('https://b:a@example.com:8080/foo/bar?a=123#abc', (string) $url); + $this->assertEquals('b=boo', (string) $url->setQuery(new QueryString(array( + 'b' => 'boo' + )))->getQuery()); + $this->assertEquals('https://b:a@example.com:8080/foo/bar?b=boo#abc', (string) $url); + } + + public function testSetQueryAcceptsArray() + { + $url = Url::factory('http://www.test.com'); + $url->setQuery(array('a' => 'b')); + $this->assertEquals('http://www.test.com?a=b', (string) $url); + } + + public function urlProvider() + { + return array( + array('/foo/..', '/'), + array('//foo//..', '/'), + array('/foo/../..', '/'), + array('/foo/../.', '/'), + array('/./foo/..', '/'), + array('/./foo', '/foo'), + array('/./foo/', '/foo/'), + array('/./foo/bar/baz/pho/../..', '/foo/bar'), + array('*', '*'), + array('/foo', '/foo'), + array('/abc/123/../foo/', '/abc/foo/'), + array('/a/b/c/./../../g', '/a/g'), + array('/b/c/./../../g', '/g'), + array('/b/c/./../../g', '/g'), + array('/c/./../../g', '/g'), + array('/./../../g', '/g'), + ); + } + + /** + * @dataProvider urlProvider + */ + public function testNormalizesPaths($path, $result) + { + $url = Url::factory('http://www.example.com/'); + $url->setPath($path)->normalizePath(); + $this->assertEquals($result, $url->getPath()); + } + + public function testSettingHostWithPortModifiesPort() + { + $url = Url::factory('http://www.example.com'); + $url->setHost('foo:8983'); + $this->assertEquals('foo', $url->getHost()); + $this->assertEquals(8983, $url->getPort()); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesUrlCanBeParsed() + { + Url::factory('foo:////'); + } + + public function testConvertsSpecialCharsInPathWhenCastingToString() + { + $url = Url::factory('http://foo.com/baz bar?a=b'); + $url->addPath('?'); + $this->assertEquals('http://foo.com/baz%20bar/%3F?a=b', (string) $url); + } + + /** + * @link http://tools.ietf.org/html/rfc3986#section-5.4.1 + */ + public function rfc3986UrlProvider() + { + $result = array( + array('g', 'http://a/b/c/g'), + array('./g', 'http://a/b/c/g'), + array('g/', 'http://a/b/c/g/'), + array('/g', 'http://a/g'), + array('?y', 'http://a/b/c/d;p?y'), + array('g?y', 'http://a/b/c/g?y'), + array('#s', 'http://a/b/c/d;p?q#s'), + array('g#s', 'http://a/b/c/g#s'), + array('g?y#s', 'http://a/b/c/g?y#s'), + array(';x', 'http://a/b/c/;x'), + array('g;x', 'http://a/b/c/g;x'), + array('g;x?y#s', 'http://a/b/c/g;x?y#s'), + array('', 'http://a/b/c/d;p?q'), + array('.', 'http://a/b/c'), + array('./', 'http://a/b/c/'), + array('..', 'http://a/b'), + array('../', 'http://a/b/'), + array('../g', 'http://a/b/g'), + array('../..', 'http://a/'), + array('../../', 'http://a/'), + array('../../g', 'http://a/g') + ); + + // This support was added in PHP 5.4.7: https://bugs.php.net/bug.php?id=62844 + if (version_compare(PHP_VERSION, '5.4.7', '>=')) { + $result[] = array('//g', 'http://g'); + } + + return $result; + } + + /** + * @dataProvider rfc3986UrlProvider + */ + public function testCombinesUrlsUsingRfc3986($relative, $result) + { + $a = Url::factory('http://a/b/c/d;p?q'); + $b = Url::factory($relative); + $this->assertEquals($result, trim((string) $a->combine($b, true), '=')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js new file mode 100644 index 000000000..4156f1aad --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js @@ -0,0 +1,146 @@ +/** + * Guzzle node.js test server to return queued responses to HTTP requests and + * expose a RESTful API for enqueueing responses and retrieving the requests + * that have been received. + * + * - Delete all requests that have been received: + * DELETE /guzzle-server/requests + * Host: 127.0.0.1:8124 + * + * - Enqueue responses + * PUT /guzzle-server/responses + * Host: 127.0.0.1:8124 + * + * [{ "statusCode": 200, "reasonPhrase": "OK", "headers": {}, "body": "" }] + * + * - Get the received requests + * GET /guzzle-server/requests + * Host: 127.0.0.1:8124 + * + * - Shutdown the server + * DELETE /guzzle-server + * Host: 127.0.0.1:8124 + * + * @package Guzzle PHP + * @license See the LICENSE file that was distributed with this source code. + */ + +var http = require("http"); + +/** + * Guzzle node.js server + * @class + */ +var GuzzleServer = function(port, log) { + + this.port = port; + this.log = log; + this.responses = []; + this.requests = []; + var that = this; + + var controlRequest = function(request, req, res) { + if (req.url == '/guzzle-server/perf') { + res.writeHead(200, "OK", {"Content-Length": 16}); + res.end("Body of response"); + } else if (req.method == "DELETE") { + if (req.url == "/guzzle-server/requests") { + // Clear the received requests + that.requests = []; + res.writeHead(200, "OK", { "Content-Length": 0 }); + res.end(); + if (this.log) { + console.log("Flushing requests"); + } + } else if (req.url == "/guzzle-server") { + // Shutdown the server + res.writeHead(200, "OK", { "Content-Length": 0, "Connection": "close" }); + res.end(); + if (this.log) { + console.log("Shutting down"); + } + that.server.close(); + } + } else if (req.method == "GET") { + if (req.url === "/guzzle-server/requests") { + // Get received requests + var data = that.requests.join("\n----[request]\n"); + res.writeHead(200, "OK", { "Content-Length": data.length }); + res.end(data); + if (that.log) { + console.log("Sending receiving requests"); + } + } + } else if (req.method == "PUT") { + if (req.url == "/guzzle-server/responses") { + if (that.log) { + console.log("Adding responses..."); + } + // Received response to queue + var data = request.split("\r\n\r\n")[1]; + if (!data) { + if (that.log) { + console.log("No response data was provided"); + } + res.writeHead(400, "NO RESPONSES IN REQUEST", { "Content-Length": 0 }); + } else { + that.responses = eval("(" + data + ")"); + if (that.log) { + console.log(that.responses); + } + res.writeHead(200, "OK", { "Content-Length": 0 }); + } + res.end(); + } + } + }; + + var receivedRequest = function(request, req, res) { + if (req.url.indexOf("/guzzle-server") === 0) { + controlRequest(request, req, res); + } else if (req.url.indexOf("/guzzle-server") == -1 && !that.responses.length) { + res.writeHead(500); + res.end("No responses in queue"); + } else { + var response = that.responses.shift(); + res.writeHead(response.statusCode, response.reasonPhrase, response.headers); + res.end(response.body); + that.requests.push(request); + } + }; + + this.start = function() { + + that.server = http.createServer(function(req, res) { + + var request = req.method + " " + req.url + " HTTP/" + req.httpVersion + "\r\n"; + for (var i in req.headers) { + request += i + ": " + req.headers[i] + "\r\n"; + } + request += "\r\n"; + + // Receive each chunk of the request body + req.addListener("data", function(chunk) { + request += chunk; + }); + + // Called when the request completes + req.addListener("end", function() { + receivedRequest(request, req, res); + }); + }); + that.server.listen(port, "127.0.0.1"); + + if (this.log) { + console.log("Server running at http://127.0.0.1:8124/"); + } + }; +}; + +// Get the port from the arguments +port = process.argv.length >= 3 ? process.argv[2] : 8124; +log = process.argv.length >= 4 ? process.argv[3] : false; + +// Start the server +server = new GuzzleServer(port, log); +server.start(); diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php new file mode 100644 index 000000000..990c0af66 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php @@ -0,0 +1,37 @@ +assertSame(Inflector::getDefault(), Inflector::getDefault()); + } + + public function testSnake() + { + $this->assertEquals('camel_case', Inflector::getDefault()->snake('camelCase')); + $this->assertEquals('camel_case', Inflector::getDefault()->snake('CamelCase')); + $this->assertEquals('camel_case_words', Inflector::getDefault()->snake('CamelCaseWords')); + $this->assertEquals('camel_case_words', Inflector::getDefault()->snake('CamelCase_words')); + $this->assertEquals('test', Inflector::getDefault()->snake('test')); + $this->assertEquals('test', Inflector::getDefault()->snake('test')); + $this->assertEquals('expect100_continue', Inflector::getDefault()->snake('Expect100Continue')); + } + + public function testCamel() + { + $this->assertEquals('CamelCase', Inflector::getDefault()->camel('camel_case')); + $this->assertEquals('CamelCaseWords', Inflector::getDefault()->camel('camel_case_words')); + $this->assertEquals('Test', Inflector::getDefault()->camel('test')); + $this->assertEquals('Expect100Continue', ucfirst(Inflector::getDefault()->camel('expect100_continue'))); + // Get from cache + $this->assertEquals('Test', Inflector::getDefault()->camel('test', false)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php new file mode 100644 index 000000000..f00b7fad8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php @@ -0,0 +1,46 @@ +getMock('Guzzle\Inflection\Inflector', array('snake', 'camel')); + $mock->expects($this->once())->method('snake')->will($this->returnValue('foo_bar')); + $mock->expects($this->once())->method('camel')->will($this->returnValue('FooBar')); + + $inflector = new MemoizingInflector($mock); + $this->assertEquals('foo_bar', $inflector->snake('FooBar')); + $this->assertEquals('foo_bar', $inflector->snake('FooBar')); + $this->assertEquals('FooBar', $inflector->camel('foo_bar')); + $this->assertEquals('FooBar', $inflector->camel('foo_bar')); + } + + public function testProtectsAgainstCacheOverflow() + { + $inflector = new MemoizingInflector(new Inflector(), 10); + for ($i = 1; $i < 11; $i++) { + $inflector->camel('foo_' . $i); + $inflector->snake('Foo' . $i); + } + + $cache = $this->readAttribute($inflector, 'cache'); + $this->assertEquals(10, count($cache['snake'])); + $this->assertEquals(10, count($cache['camel'])); + + $inflector->camel('baz!'); + $inflector->snake('baz!'); + + // Now ensure that 20% of the cache was removed (2), then the item was added + $cache = $this->readAttribute($inflector, 'cache'); + $this->assertEquals(9, count($cache['snake'])); + $this->assertEquals(9, count($cache['camel'])); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php new file mode 100644 index 000000000..ff2654cf6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php @@ -0,0 +1,45 @@ +getMock('Guzzle\Inflection\Inflector', array('snake', 'camel')); + $mock->expects($this->once())->method('snake')->with('Test')->will($this->returnValue('test')); + $mock->expects($this->once())->method('camel')->with('Test')->will($this->returnValue('Test')); + $inflector = new PreComputedInflector($mock, array('FooBar' => 'foo_bar'), array('foo_bar' => 'FooBar')); + $this->assertEquals('FooBar', $inflector->camel('foo_bar')); + $this->assertEquals('foo_bar', $inflector->snake('FooBar')); + $this->assertEquals('Test', $inflector->camel('Test')); + $this->assertEquals('test', $inflector->snake('Test')); + } + + public function testMirrorsPrecomputedValues() + { + $mock = $this->getMock('Guzzle\Inflection\Inflector', array('snake', 'camel')); + $mock->expects($this->never())->method('snake'); + $mock->expects($this->never())->method('camel'); + $inflector = new PreComputedInflector($mock, array('Zeep' => 'zeep'), array(), true); + $this->assertEquals('Zeep', $inflector->camel('zeep')); + $this->assertEquals('zeep', $inflector->snake('Zeep')); + } + + public function testMirrorsPrecomputedValuesByMerging() + { + $mock = $this->getMock('Guzzle\Inflection\Inflector', array('snake', 'camel')); + $mock->expects($this->never())->method('snake'); + $mock->expects($this->never())->method('camel'); + $inflector = new PreComputedInflector($mock, array('Zeep' => 'zeep'), array('foo' => 'Foo'), true); + $this->assertEquals('Zeep', $inflector->camel('zeep')); + $this->assertEquals('zeep', $inflector->snake('Zeep')); + $this->assertEquals('Foo', $inflector->camel('foo')); + $this->assertEquals('foo', $inflector->snake('Foo')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php new file mode 100644 index 000000000..8d6ae845a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php @@ -0,0 +1,29 @@ + 1, + 'b' => 2 + )); + $b = new \ArrayIterator(array()); + $c = new \ArrayIterator(array( + 'c' => 3, + 'd' => 4 + )); + $i = new AppendIterator(); + $i->append($a); + $i->append($b); + $i->append($c); + $this->assertEquals(array(1, 2, 3, 4), iterator_to_array($i, false)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php new file mode 100644 index 000000000..ec4c1294c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php @@ -0,0 +1,52 @@ +assertEquals(11, count($chunks)); + foreach ($chunks as $j => $chunk) { + $this->assertEquals(range($j * 10, min(100, $j * 10 + 9)), $chunk); + } + } + + public function testChunksIteratorWithOddValues() + { + $chunked = new ChunkedIterator(new \ArrayIterator(array(1, 2, 3, 4, 5)), 2); + $chunks = iterator_to_array($chunked, false); + $this->assertEquals(3, count($chunks)); + $this->assertEquals(array(1, 2), $chunks[0]); + $this->assertEquals(array(3, 4), $chunks[1]); + $this->assertEquals(array(5), $chunks[2]); + } + + public function testMustNotTerminateWithTraversable() + { + $traversable = simplexml_load_string('')->foo; + $chunked = new ChunkedIterator($traversable, 2); + $actual = iterator_to_array($chunked, false); + $this->assertCount(2, $actual); + } + + public function testSizeOfZeroMakesIteratorInvalid() { + $chunked = new ChunkedIterator(new \ArrayIterator(range(1, 5)), 0); + $chunked->rewind(); + $this->assertFalse($chunked->valid()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSizeLowerZeroThrowsException() { + new ChunkedIterator(new \ArrayIterator(range(1, 5)), -1); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php new file mode 100644 index 000000000..73b4f6987 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php @@ -0,0 +1,28 @@ +assertEquals(range(1, 99, 2), iterator_to_array($i, false)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesCallable() + { + $i = new FilterIterator(new \ArrayIterator(), new \stdClass()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php new file mode 100644 index 000000000..4de4a6bc1 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php @@ -0,0 +1,28 @@ +assertEquals(range(0, 1000, 10), iterator_to_array($i, false)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesCallable() + { + $i = new MapIterator(new \ArrayIterator(), new \stdClass()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php new file mode 100644 index 000000000..5bcf06fb0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php @@ -0,0 +1,28 @@ +append('a'); + $proxy->append('b'); + $this->assertEquals(array('a', 'b'), $i->getArrayCopy()); + $this->assertEquals(array('a', 'b'), $proxy->getArrayCopy()); + } + + public function testUsesInnerIterator() + { + $i = new MethodProxyIterator(new ChunkedIterator(new \ArrayIterator(array(1, 2, 3, 4, 5)), 2)); + $this->assertEquals(3, count(iterator_to_array($i, false))); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php new file mode 100644 index 000000000..a66882f6f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php @@ -0,0 +1,23 @@ +log('test', \LOG_NOTICE, '127.0.0.1'); + $this->assertEquals(array(array('message' => 'test', 'priority' => \LOG_NOTICE, 'extras' => '127.0.0.1')), $adapter->getLogs()); + } + + public function testClearLog() + { + $adapter = new ArrayLogAdapter(); + $adapter->log('test', \LOG_NOTICE, '127.0.0.1'); + $adapter->clearLogs(); + $this->assertEquals(array(), $adapter->getLogs()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php new file mode 100644 index 000000000..0177dc071 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php @@ -0,0 +1,30 @@ +adapter = new ClosureLogAdapter(function($message, $priority, $extras = null) use ($that, &$modified) { + $modified = array($message, $priority, $extras); + }); + $this->adapter->log('test', LOG_NOTICE, '127.0.0.1'); + $this->assertEquals(array('test', LOG_NOTICE, '127.0.0.1'), $modified); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testThrowsExceptionWhenNotCallable() + { + $this->adapter = new ClosureLogAdapter(123); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php new file mode 100644 index 000000000..3ff4b0737 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php @@ -0,0 +1,143 @@ +request = new EntityEnclosingRequest('POST', 'http://foo.com?q=test', array( + 'X-Foo' => 'bar', + 'Authorization' => 'Baz' + )); + $this->request->setBody(EntityBody::factory('Hello')); + + $this->response = new Response(200, array( + 'X-Test' => 'Abc' + ), 'Foo'); + + $this->handle = $this->getMockBuilder('Guzzle\Http\Curl\CurlHandle') + ->disableOriginalConstructor() + ->setMethods(array('getError', 'getErrorNo', 'getStderr', 'getInfo')) + ->getMock(); + + $this->handle->expects($this->any()) + ->method('getError') + ->will($this->returnValue('e')); + + $this->handle->expects($this->any()) + ->method('getErrorNo') + ->will($this->returnValue('123')); + + $this->handle->expects($this->any()) + ->method('getStderr') + ->will($this->returnValue('testing')); + + $this->handle->expects($this->any()) + ->method('getInfo') + ->will($this->returnValueMap(array( + array(CURLINFO_CONNECT_TIME, '123'), + array(CURLINFO_TOTAL_TIME, '456') + ))); + } + + public function logProvider() + { + return array( + // Uses the cache for the second time + array('{method} - {method}', 'POST - POST'), + array('{url}', 'http://foo.com?q=test'), + array('{port}', '80'), + array('{resource}', '/?q=test'), + array('{host}', 'foo.com'), + array('{hostname}', gethostname()), + array('{protocol}/{version}', 'HTTP/1.1'), + array('{code} {phrase}', '200 OK'), + array('{req_header_Foo}', ''), + array('{req_header_X-Foo}', 'bar'), + array('{req_header_Authorization}', 'Baz'), + array('{res_header_foo}', ''), + array('{res_header_X-Test}', 'Abc'), + array('{req_body}', 'Hello'), + array('{res_body}', 'Foo'), + array('{curl_stderr}', 'testing'), + array('{curl_error}', 'e'), + array('{curl_code}', '123'), + array('{connect_time}', '123'), + array('{total_time}', '456') + ); + } + + /** + * @dataProvider logProvider + */ + public function testFormatsMessages($template, $output) + { + $formatter = new MessageFormatter($template); + $this->assertEquals($output, $formatter->format($this->request, $this->response, $this->handle)); + } + + public function testFormatsRequestsAndResponses() + { + $formatter = new MessageFormatter(); + $formatter->setTemplate('{request}{response}'); + $this->assertEquals($this->request . $this->response, $formatter->format($this->request, $this->response)); + } + + public function testAddsTimestamp() + { + $formatter = new MessageFormatter('{ts}'); + $this->assertNotEmpty($formatter->format($this->request, $this->response)); + } + + public function testUsesResponseWhenNoHandleAndGettingCurlInformation() + { + $formatter = new MessageFormatter('{connect_time}/{total_time}'); + $response = $this->getMockBuilder('Guzzle\Http\Message\Response') + ->setConstructorArgs(array(200)) + ->setMethods(array('getInfo')) + ->getMock(); + $response->expects($this->exactly(2)) + ->method('getInfo') + ->will($this->returnValueMap(array( + array('connect_time', '1'), + array('total_time', '2'), + ))); + $this->assertEquals('1/2', $formatter->format($this->request, $response)); + } + + public function testUsesEmptyStringWhenNoHandleAndNoResponse() + { + $formatter = new MessageFormatter('{connect_time}/{total_time}'); + $this->assertEquals('/', $formatter->format($this->request)); + } + + public function testInjectsTotalTime() + { + $out = ''; + $formatter = new MessageFormatter('{connect_time}/{total_time}'); + $adapter = new ClosureLogAdapter(function ($m) use (&$out) { $out .= $m; }); + $log = new LogPlugin($adapter, $formatter); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHI"); + $client = new Client($this->getServer()->getUrl()); + $client->addSubscriber($log); + $client->get('/')->send(); + $this->assertNotEquals('/', $out); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php new file mode 100644 index 000000000..7b72dd6a0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php @@ -0,0 +1,25 @@ +pushHandler($handler); + $adapter = new PsrLogAdapter($log); + $adapter->log('test!', LOG_INFO); + $this->assertTrue($handler->hasInfoRecords()); + $this->assertSame($log, $adapter->getLogObject()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php new file mode 100644 index 000000000..1b6128365 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php @@ -0,0 +1,51 @@ +stream = fopen('php://temp', 'r+'); + $this->log = new Logger(); + $this->log->addWriter(new Stream($this->stream)); + $this->adapter = new Zf2LogAdapter($this->log); + + } + + public function testLogsMessagesToAdaptedObject() + { + // Test without a priority + $this->adapter->log('Zend_Test!', \LOG_NOTICE); + rewind($this->stream); + $contents = stream_get_contents($this->stream); + $this->assertEquals(1, substr_count($contents, 'Zend_Test!')); + + // Test with a priority + $this->adapter->log('Zend_Test!', \LOG_ALERT); + rewind($this->stream); + $contents = stream_get_contents($this->stream); + $this->assertEquals(2, substr_count($contents, 'Zend_Test!')); + } + + public function testExposesAdaptedLogObject() + { + $this->assertEquals($this->log, $this->adapter->getLogObject()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php new file mode 100644 index 000000000..3fb6527be --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php @@ -0,0 +1,21 @@ +command = $command; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php new file mode 100644 index 000000000..aabb15f9c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php @@ -0,0 +1,25 @@ +command = $command; + $this->response = $response; + $this->message = 'Error from ' . $response; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php new file mode 100644 index 000000000..97a197487 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php @@ -0,0 +1,11 @@ +multiHandle; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php new file mode 100644 index 000000000..11e22eb46 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php @@ -0,0 +1,65 @@ +events as $event) { + if ($event->getName() == $eventName) { + return true; + } + } + + return false; + } + + public function getLastEvent() + { + return end($this->events); + } + + public function count() + { + return count($this->events); + } + + public function getGrouped() + { + $events = array(); + foreach ($this->events as $event) { + if (!isset($events[$event->getName()])) { + $events[$event->getName()] = array(); + } + $events[$event->getName()][] = $event; + } + + return $events; + } + + public function getData($event, $key, $occurrence = 0) + { + $grouped = $this->getGrouped(); + if (isset($grouped[$event])) { + return $grouped[$event][$occurrence][$key]; + } + + return null; + } + + public function update(Event $event) + { + $this->events[] = $event; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php new file mode 100644 index 000000000..e011959bb --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php @@ -0,0 +1,7 @@ + 'allseeing-i.com', + 'path' => '/', + 'data' => array( + 'PHPSESSID' => '6c951590e7a9359bcedde25cda73e43c' + ), + 'max_age' => NULL, + 'expires' => 'Sat, 26-Jul-2008 17:00:42 GMT', + 'version' => NULL, + 'secure' => NULL, + 'discard' => NULL, + 'port' => NULL, + 'cookies' => array( + 'ASIHTTPRequestTestCookie' => 'This+is+the+value' + ), + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array('', false), + array('foo', false), + // Test setting a blank value for a cookie + array(array( + 'foo=', 'foo =', 'foo =;', 'foo= ;', 'foo =', 'foo= '), + array( + 'cookies' => array( + 'foo' => '' + ), + 'data' => array(), + 'discard' => null, + 'domain' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + // Test setting a value and removing quotes + array(array( + 'foo=1', 'foo =1', 'foo =1;', 'foo=1 ;', 'foo =1', 'foo= 1', 'foo = 1 ;', 'foo="1"', 'foo="1";', 'foo= "1";'), + array( + 'cookies' => array( + 'foo' => '1' + ), + 'data' => array(), + 'discard' => null, + 'domain' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + // Test setting multiple values + array(array( + 'foo=1; bar=2;', 'foo =1; bar = "2"', 'foo=1; bar=2'), + array( + 'cookies' => array( + 'foo' => '1', + 'bar' => '2', + ), + 'data' => array(), + 'discard' => null, + 'domain' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + // Tests getting the domain and path from a reference request + array(array( + 'foo=1; port="80,8081"; httponly', 'foo=1; port="80,8081"; domain=www.test.com; HttpOnly;', 'foo=1; ; domain=www.test.com; path=/path; port="80,8081"; HttpOnly;'), + array( + 'cookies' => array( + 'foo' => 1 + ), + 'data' => array(), + 'discard' => null, + 'domain' => 'www.test.com', + 'expires' => null, + 'max_age' => null, + 'path' => '/path', + 'port' => array('80', '8081'), + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => true + ), + 'http://www.test.com/path/' + ), + // Some of the following tests are based on http://framework.zend.com/svn/framework/standard/trunk/tests/Zend/Http/CookieTest.php + array( + 'justacookie=foo; domain=example.com', + array( + 'cookies' => array( + 'justacookie' => 'foo' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'expires=tomorrow; secure; path=/Space Out/; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=.example.com', + array( + 'cookies' => array( + 'expires' => 'tomorrow' + ), + 'domain' => '.example.com', + 'path' => '/Space Out/', + 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'data' => array(), + 'discard' => null, + 'port' => null, + 'secure' => true, + 'version' => null, + 'max_age' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'domain=unittests; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=example.com; path=/some value/', + array( + 'cookies' => array( + 'domain' => 'unittests' + ), + 'domain' => 'example.com', + 'path' => '/some value/', + 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'secure' => false, + 'data' => array(), + 'discard' => null, + 'max_age' => null, + 'port' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'path=indexAction; path=/; domain=.foo.com; expires=Tue, 21-Nov-2006 08:33:44 GMT', + array( + 'cookies' => array( + 'path' => 'indexAction' + ), + 'domain' => '.foo.com', + 'path' => '/', + 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'secure' => false, + 'data' => array(), + 'discard' => null, + 'max_age' => null, + 'port' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'secure=sha1; secure; SECURE; domain=some.really.deep.domain.com; version=1; Max-Age=86400', + array( + 'cookies' => array( + 'secure' => 'sha1' + ), + 'domain' => 'some.really.deep.domain.com', + 'path' => '/', + 'secure' => true, + 'data' => array(), + 'discard' => null, + 'expires' => time() + 86400, + 'max_age' => 86400, + 'port' => null, + 'version' => 1, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'PHPSESSID=123456789+abcd%2Cef; secure; discard; domain=.localdomain; path=/foo/baz; expires=Tue, 21-Nov-2006 08:33:44 GMT;', + array( + 'cookies' => array( + 'PHPSESSID' => '123456789+abcd%2Cef' + ), + 'domain' => '.localdomain', + 'path' => '/foo/baz', + 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'secure' => true, + 'data' => array(), + 'discard' => true, + 'max_age' => null, + 'port' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + // rfc6265#section-5.1.4 + array( + 'cookie=value', + array( + 'cookies' => array( + 'cookie' => 'value' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/some/path', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ), + 'http://example.com/some/path/test.html' + ), + array( + 'empty=path', + array( + 'cookies' => array( + 'empty' => 'path' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ), + 'http://example.com/test.html' + ), + array( + 'baz=qux', + array( + 'cookies' => array( + 'baz' => 'qux' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ), + 'http://example.com?query=here' + ), + array( + 'test=noSlashPath; path=someString', + array( + 'cookies' => array( + 'test' => 'noSlashPath' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/real/path', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ), + 'http://example.com/real/path/' + ), + ); + } + + /** + * @dataProvider cookieParserDataProvider + */ + public function testParseCookie($cookie, $parsed, $url = null) + { + $c = $this->cookieParserClass; + $parser = new $c(); + + $request = null; + if ($url) { + $url = Url::factory($url); + $host = $url->getHost(); + $path = $url->getPath(); + } else { + $host = ''; + $path = ''; + } + + foreach ((array) $cookie as $c) { + $p = $parser->parseCookie($c, $host, $path); + + // Remove expires values from the assertion if they are relatively equal by allowing a 5 minute difference + if ($p['expires'] != $parsed['expires']) { + if (abs($p['expires'] - $parsed['expires']) < 300) { + unset($p['expires']); + unset($parsed['expires']); + } + } + + if (is_array($parsed)) { + foreach ($parsed as $key => $value) { + $this->assertEquals($parsed[$key], $p[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true)); + } + + foreach ($p as $key => $value) { + $this->assertEquals($p[$key], $parsed[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true)); + } + } else { + $this->assertEquals($parsed, $p); + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php new file mode 100644 index 000000000..75d336fa5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php @@ -0,0 +1,22 @@ +parseCookie('foo=baz+bar', null, null, true); + $this->assertEquals(array( + 'foo' => 'baz bar' + ), $result['cookies']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php new file mode 100644 index 000000000..da58bb465 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php @@ -0,0 +1,225 @@ + 'GET', + 'protocol' => 'HTTP', + 'version' => '1.1', + 'request_url' => array( + 'scheme' => 'http', + 'host' => '', + 'port' => '', + 'path' => '/', + 'query' => '' + ), + 'headers' => array(), + 'body' => '' + )), + // Path and query string, multiple header values per header and case sensitive storage + array("HEAD /path?query=foo HTTP/1.0\r\nHost: example.com\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\nX-Foo: Baz\r\n\r\n", array( + 'method' => 'HEAD', + 'protocol' => 'HTTP', + 'version' => '1.0', + 'request_url' => array( + 'scheme' => 'http', + 'host' => 'example.com', + 'port' => '', + 'path' => '/path', + 'query' => 'query=foo' + ), + 'headers' => array( + 'Host' => 'example.com', + 'X-Foo' => array('foo', 'foo', 'Baz'), + 'x-foo' => 'Bar' + ), + 'body' => '' + )), + // Includes a body + array("PUT / HTTP/1.0\r\nhost: example.com:443\r\nContent-Length: 4\r\n\r\ntest", array( + 'method' => 'PUT', + 'protocol' => 'HTTP', + 'version' => '1.0', + 'request_url' => array( + 'scheme' => 'https', + 'host' => 'example.com', + 'port' => '443', + 'path' => '/', + 'query' => '' + ), + 'headers' => array( + 'host' => 'example.com:443', + 'Content-Length' => '4' + ), + 'body' => 'test' + )), + // Includes Authorization headers + array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nAuthorization: Basic {$auth}\r\n\r\n", array( + 'method' => 'GET', + 'protocol' => 'HTTP', + 'version' => '1.1', + 'request_url' => array( + 'scheme' => 'http', + 'host' => 'example.com', + 'port' => '8080', + 'path' => '/', + 'query' => '' + ), + 'headers' => array( + 'Host' => 'example.com:8080', + 'Authorization' => "Basic {$auth}" + ), + 'body' => '' + )), + // Include authorization header + array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nauthorization: Basic {$auth}\r\n\r\n", array( + 'method' => 'GET', + 'protocol' => 'HTTP', + 'version' => '1.1', + 'request_url' => array( + 'scheme' => 'http', + 'host' => 'example.com', + 'port' => '8080', + 'path' => '/', + 'query' => '' + ), + 'headers' => array( + 'Host' => 'example.com:8080', + 'authorization' => "Basic {$auth}" + ), + 'body' => '' + )), + ); + } + + public function responseProvider() + { + return array( + // Empty request + array('', false), + + array("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", array( + 'protocol' => 'HTTP', + 'version' => '1.1', + 'code' => '200', + 'reason_phrase' => 'OK', + 'headers' => array( + 'Content-Length' => 0 + ), + 'body' => '' + )), + array("HTTP/1.0 400 Bad Request\r\nContent-Length: 0\r\n\r\n", array( + 'protocol' => 'HTTP', + 'version' => '1.0', + 'code' => '400', + 'reason_phrase' => 'Bad Request', + 'headers' => array( + 'Content-Length' => 0 + ), + 'body' => '' + )), + array("HTTP/1.0 100 Continue\r\n\r\n", array( + 'protocol' => 'HTTP', + 'version' => '1.0', + 'code' => '100', + 'reason_phrase' => 'Continue', + 'headers' => array(), + 'body' => '' + )), + array("HTTP/1.1 204 No Content\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\n\r\n", array( + 'protocol' => 'HTTP', + 'version' => '1.1', + 'code' => '204', + 'reason_phrase' => 'No Content', + 'headers' => array( + 'X-Foo' => array('foo', 'foo'), + 'x-foo' => 'Bar' + ), + 'body' => '' + )), + array("HTTP/1.1 200 Ok that is great!\r\nContent-Length: 4\r\n\r\nTest", array( + 'protocol' => 'HTTP', + 'version' => '1.1', + 'code' => '200', + 'reason_phrase' => 'Ok that is great!', + 'headers' => array( + 'Content-Length' => 4 + ), + 'body' => 'Test' + )), + ); + } + + public function compareRequestResults($result, $expected) + { + if (!$result) { + $this->assertFalse($expected); + return; + } + + $this->assertEquals($result['method'], $expected['method']); + $this->assertEquals($result['protocol'], $expected['protocol']); + $this->assertEquals($result['version'], $expected['version']); + $this->assertEquals($result['request_url'], $expected['request_url']); + $this->assertEquals($result['body'], $expected['body']); + $this->compareHttpHeaders($result['headers'], $expected['headers']); + } + + public function compareResponseResults($result, $expected) + { + if (!$result) { + $this->assertFalse($expected); + return; + } + + $this->assertEquals($result['protocol'], $expected['protocol']); + $this->assertEquals($result['version'], $expected['version']); + $this->assertEquals($result['code'], $expected['code']); + $this->assertEquals($result['reason_phrase'], $expected['reason_phrase']); + $this->assertEquals($result['body'], $expected['body']); + $this->compareHttpHeaders($result['headers'], $expected['headers']); + } + + protected function normalizeHeaders($headers) + { + $normalized = array(); + foreach ($headers as $key => $value) { + $key = strtolower($key); + if (!isset($normalized[$key])) { + $normalized[$key] = $value; + } elseif (!is_array($normalized[$key])) { + $normalized[$key] = array($value); + } else { + $normalized[$key][] = $value; + } + } + + foreach ($normalized as $key => &$value) { + if (is_array($value)) { + sort($value); + } + } + + return $normalized; + } + + public function compareHttpHeaders($result, $expected) + { + // Aggregate all headers case-insensitively + $result = $this->normalizeHeaders($result); + $expected = $this->normalizeHeaders($expected); + $this->assertEquals($result, $expected); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php new file mode 100644 index 000000000..2f5222893 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php @@ -0,0 +1,58 @@ +compareRequestResults($parts, $parser->parseRequest($message)); + } + + /** + * @dataProvider responseProvider + */ + public function testParsesResponses($message, $parts) + { + $parser = new MessageParser(); + $this->compareResponseResults($parts, $parser->parseResponse($message)); + } + + public function testParsesRequestsWithMissingProtocol() + { + $parser = new MessageParser(); + $parts = $parser->parseRequest("GET /\r\nHost: Foo.com\r\n\r\n"); + $this->assertEquals('GET', $parts['method']); + $this->assertEquals('HTTP', $parts['protocol']); + $this->assertEquals('1.1', $parts['version']); + } + + public function testParsesRequestsWithMissingVersion() + { + $parser = new MessageParser(); + $parts = $parser->parseRequest("GET / HTTP\r\nHost: Foo.com\r\n\r\n"); + $this->assertEquals('GET', $parts['method']); + $this->assertEquals('HTTP', $parts['protocol']); + $this->assertEquals('1.1', $parts['version']); + } + + public function testParsesResponsesWithMissingReasonPhrase() + { + $parser = new MessageParser(); + $parts = $parser->parseResponse("HTTP/1.1 200\r\n\r\n"); + $this->assertEquals('200', $parts['code']); + $this->assertEquals('', $parts['reason_phrase']); + $this->assertEquals('HTTP', $parts['protocol']); + $this->assertEquals('1.1', $parts['version']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php new file mode 100644 index 000000000..6706e2063 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php @@ -0,0 +1,36 @@ +markTestSkipped('pecl_http is not available.'); + } + } + + /** + * @dataProvider requestProvider + */ + public function testParsesRequests($message, $parts) + { + $parser = new PeclHttpMessageParser(); + $this->compareRequestResults($parts, $parser->parseRequest($message)); + } + + /** + * @dataProvider responseProvider + */ + public function testParsesResponses($message, $parts) + { + $parser = new PeclHttpMessageParser(); + $this->compareResponseResults($parts, $parser->parseResponse($message)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php new file mode 100644 index 000000000..7675efb96 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php @@ -0,0 +1,33 @@ +registerParser('foo', $c); + $this->assertSame($c, $r->getParser('foo')); + } + + public function testReturnsNullWhenNotFound() + { + $r = new ParserRegistry(); + $this->assertNull($r->getParser('FOO')); + } + + public function testReturnsLazyLoadedDefault() + { + $r = new ParserRegistry(); + $c = $r->getParser('cookie'); + $this->assertInstanceOf('Guzzle\Parser\Cookie\CookieParser', $c); + $this->assertSame($c, $r->getParser('cookie')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php new file mode 100644 index 000000000..a05fc2e4d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php @@ -0,0 +1,113 @@ + 'value', + 'hello' => 'Hello World!', + 'empty' => '', + 'path' => '/foo/bar', + 'x' => '1024', + 'y' => '768', + 'null' => null, + 'list' => array('red', 'green', 'blue'), + 'keys' => array( + "semi" => ';', + "dot" => '.', + "comma" => ',' + ), + 'empty_keys' => array(), + ); + + return array_map(function($t) use ($params) { + $t[] = $params; + return $t; + }, array( + array('foo', 'foo'), + array('{var}', 'value'), + array('{hello}', 'Hello%20World%21'), + array('{+var}', 'value'), + array('{+hello}', 'Hello%20World!'), + array('{+path}/here', '/foo/bar/here'), + array('here?ref={+path}', 'here?ref=/foo/bar'), + array('X{#var}', 'X#value'), + array('X{#hello}', 'X#Hello%20World!'), + array('map?{x,y}', 'map?1024,768'), + array('{x,hello,y}', '1024,Hello%20World%21,768'), + array('{+x,hello,y}', '1024,Hello%20World!,768'), + array('{+path,x}/here', '/foo/bar,1024/here'), + array('{#x,hello,y}', '#1024,Hello%20World!,768'), + array('{#path,x}/here', '#/foo/bar,1024/here'), + array('X{.var}', 'X.value'), + array('X{.x,y}', 'X.1024.768'), + array('{/var}', '/value'), + array('{/var,x}/here', '/value/1024/here'), + array('{;x,y}', ';x=1024;y=768'), + array('{;x,y,empty}', ';x=1024;y=768;empty'), + array('{?x,y}', '?x=1024&y=768'), + array('{?x,y,empty}', '?x=1024&y=768&empty='), + array('?fixed=yes{&x}', '?fixed=yes&x=1024'), + array('{&x,y,empty}', '&x=1024&y=768&empty='), + array('{var:3}', 'val'), + array('{var:30}', 'value'), + array('{list}', 'red,green,blue'), + array('{list*}', 'red,green,blue'), + array('{keys}', 'semi,%3B,dot,.,comma,%2C'), + array('{keys*}', 'semi=%3B,dot=.,comma=%2C'), + array('{+path:6}/here', '/foo/b/here'), + array('{+list}', 'red,green,blue'), + array('{+list*}', 'red,green,blue'), + array('{+keys}', 'semi,;,dot,.,comma,,'), + array('{+keys*}', 'semi=;,dot=.,comma=,'), + array('{#path:6}/here', '#/foo/b/here'), + array('{#list}', '#red,green,blue'), + array('{#list*}', '#red,green,blue'), + array('{#keys}', '#semi,;,dot,.,comma,,'), + array('{#keys*}', '#semi=;,dot=.,comma=,'), + array('X{.var:3}', 'X.val'), + array('X{.list}', 'X.red,green,blue'), + array('X{.list*}', 'X.red.green.blue'), + array('X{.keys}', 'X.semi,%3B,dot,.,comma,%2C'), + array('X{.keys*}', 'X.semi=%3B.dot=..comma=%2C'), + array('{/var:1,var}', '/v/value'), + array('{/list}', '/red,green,blue'), + array('{/list*}', '/red/green/blue'), + array('{/list*,path:4}', '/red/green/blue/%2Ffoo'), + array('{/keys}', '/semi,%3B,dot,.,comma,%2C'), + array('{/keys*}', '/semi=%3B/dot=./comma=%2C'), + array('{;hello:5}', ';hello=Hello'), + array('{;list}', ';list=red,green,blue'), + array('{;list*}', ';list=red;list=green;list=blue'), + array('{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'), + array('{;keys*}', ';semi=%3B;dot=.;comma=%2C'), + array('{?var:3}', '?var=val'), + array('{?list}', '?list=red,green,blue'), + array('{?list*}', '?list=red&list=green&list=blue'), + array('{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'), + array('{?keys*}', '?semi=%3B&dot=.&comma=%2C'), + array('{&var:3}', '&var=val'), + array('{&list}', '&list=red,green,blue'), + array('{&list*}', '&list=red&list=green&list=blue'), + array('{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'), + array('{&keys*}', '&semi=%3B&dot=.&comma=%2C'), + array('{.null}', ''), + array('{.null,var}', '.value'), + array('X{.empty_keys*}', 'X'), + array('X{.empty_keys}', 'X'), + // Test that missing expansions are skipped + array('test{&missing*}', 'test'), + // Test that multiple expansions can be set + array('http://{var}/{var:2}{?keys*}', 'http://value/va?semi=%3B&dot=.&comma=%2C'), + // Test more complex query string stuff + array('http://www.test.com{+path}{?var,keys*}', 'http://www.test.com/foo/bar?var=value&semi=%3B&dot=.&comma=%2C') + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php new file mode 100644 index 000000000..633c5d539 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php @@ -0,0 +1,27 @@ +markTestSkipped('uri_template PECL extension must be installed to test PeclUriTemplate'); + } + } + + /** + * @dataProvider templateProvider + */ + public function testExpandsUriTemplates($template, $expansion, $params) + { + $uri = new PeclUriTemplate($template); + $this->assertEquals($expansion, $uri->expand($template, $params)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php new file mode 100644 index 000000000..5130d6f4b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php @@ -0,0 +1,106 @@ +assertEquals($expansion, $uri->expand($template, $params)); + } + + public function expressionProvider() + { + return array( + array( + '{+var*}', array( + 'operator' => '+', + 'values' => array( + array('value' => 'var', 'modifier' => '*') + ) + ), + ), + array( + '{?keys,var,val}', array( + 'operator' => '?', + 'values' => array( + array('value' => 'keys', 'modifier' => ''), + array('value' => 'var', 'modifier' => ''), + array('value' => 'val', 'modifier' => '') + ) + ), + ), + array( + '{+x,hello,y}', array( + 'operator' => '+', + 'values' => array( + array('value' => 'x', 'modifier' => ''), + array('value' => 'hello', 'modifier' => ''), + array('value' => 'y', 'modifier' => '') + ) + ) + ) + ); + } + + /** + * @dataProvider expressionProvider + */ + public function testParsesExpressions($exp, $data) + { + $template = new UriTemplate($exp); + + // Access the config object + $class = new \ReflectionClass($template); + $method = $class->getMethod('parseExpression'); + $method->setAccessible(true); + + $exp = substr($exp, 1, -1); + $this->assertEquals($data, $method->invokeArgs($template, array($exp))); + } + + /** + * @ticket https://github.com/guzzle/guzzle/issues/90 + */ + public function testAllowsNestedArrayExpansion() + { + $template = new UriTemplate(); + + $result = $template->expand('http://example.com{+path}{/segments}{?query,data*,foo*}', array( + 'path' => '/foo/bar', + 'segments' => array('one', 'two'), + 'query' => 'test', + 'data' => array( + 'more' => array('fun', 'ice cream') + ), + 'foo' => array( + 'baz' => array( + 'bar' => 'fizz', + 'test' => 'buzz' + ), + 'bam' => 'boo' + ) + )); + + $this->assertEquals('http://example.com/foo/bar/one,two?query=test&more%5B0%5D=fun&more%5B1%5D=ice%20cream&baz%5Bbar%5D=fizz&baz%5Btest%5D=buzz&bam=boo', $result); + } + + /** + * @ticket https://github.com/guzzle/guzzle/issues/426 + */ + public function testSetRegex() + { + $template = new UriTemplate(); + $template->setRegex('/\<\$(.+)\>/'); + $this->assertSame('/foo', $template->expand('/<$a>', array('a' => 'foo'))); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php new file mode 100644 index 000000000..16990a5a8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php @@ -0,0 +1,93 @@ +assertArrayHasKey('request.before_send', $events); + $this->assertArrayHasKey('request.exception', $events); + $this->assertArrayHasKey('curl.callback.progress', $events); + } + + public function testEnablesProgressCallbacks() + { + $p = new AsyncPlugin(); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com'); + $event = new Event(array( + 'request' => $request + )); + $p->onBeforeSend($event); + $this->assertEquals(true, $request->getCurlOptions()->get('progress')); + } + + public function testAddsTimesOutAfterSending() + { + $p = new AsyncPlugin(); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com'); + $handle = CurlHandle::factory($request); + $event = new Event(array( + 'request' => $request, + 'handle' => $handle->getHandle(), + 'uploaded' => 10, + 'upload_size' => 10, + 'downloaded' => 0 + )); + $p->onCurlProgress($event); + } + + public function testEnsuresRequestIsSet() + { + $p = new AsyncPlugin(); + $event = new Event(array( + 'uploaded' => 10, + 'upload_size' => 10, + 'downloaded' => 0 + )); + $p->onCurlProgress($event); + } + + public function testMasksCurlExceptions() + { + $p = new AsyncPlugin(); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com'); + $e = new CurlException('Error'); + $event = new Event(array( + 'request' => $request, + 'exception' => $e + )); + $p->onRequestTimeout($event); + $this->assertEquals(RequestInterface::STATE_COMPLETE, $request->getState()); + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $this->assertTrue($request->getResponse()->hasHeader('X-Guzzle-Async')); + } + + public function testEnsuresIntegration() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 204 FOO\r\nContent-Length: 4\r\n\r\ntest"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/', null, array( + 'foo' => 'bar' + )); + $request->getEventDispatcher()->addSubscriber(new AsyncPlugin()); + $request->send(); + $this->assertEquals('', $request->getResponse()->getBody(true)); + $this->assertTrue($request->getResponse()->hasHeader('X-Guzzle-Async')); + $received = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $received[0]->getMethod()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php new file mode 100644 index 000000000..72af26308 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php @@ -0,0 +1,86 @@ +getMockBuilder('Guzzle\Plugin\Backoff\AbstractBackoffStrategy') + ->setMethods(array('getDelay', 'makesDecision')) + ->getMockForAbstractClass(); + } + + public function testReturnsZeroWhenNoNextAndGotNull() + { + $request = new Request('GET', 'http://www.foo.com'); + $mock = $this->getMockStrategy(); + $mock->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(null)); + $this->assertEquals(0, $mock->getBackoffPeriod(0, $request)); + } + + public function testReturnsFalse() + { + $request = new Request('GET', 'http://www.foo.com'); + $mock = $this->getMockStrategy(); + $mock->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(false)); + $this->assertEquals(false, $mock->getBackoffPeriod(0, $request)); + } + + public function testReturnsNextValueWhenNullOrTrue() + { + $request = new Request('GET', 'http://www.foo.com'); + $mock = $this->getMockStrategy(); + $mock->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(null)); + $mock->expects($this->any())->method('makesDecision')->will($this->returnValue(false)); + + $mock2 = $this->getMockStrategy(); + $mock2->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(10)); + $mock2->expects($this->atLeastOnce())->method('makesDecision')->will($this->returnValue(true)); + $mock->setNext($mock2); + + $this->assertEquals(10, $mock->getBackoffPeriod(0, $request)); + } + + public function testReturnsFalseWhenNullAndNoNext() + { + $request = new Request('GET', 'http://www.foo.com'); + $s = new TruncatedBackoffStrategy(2); + $this->assertFalse($s->getBackoffPeriod(0, $request)); + } + + public function testHasNext() + { + $a = new TruncatedBackoffStrategy(2); + $b = new TruncatedBackoffStrategy(2); + $a->setNext($b); + $this->assertSame($b, $a->getNext()); + } + + public function testSkipsOtherDecisionsInChainWhenOneReturnsTrue() + { + $a = new CallbackBackoffStrategy(function () { return null; }, true); + $b = new CallbackBackoffStrategy(function () { return true; }, true); + $c = new CallbackBackoffStrategy(function () { return null; }, true); + $d = new CallbackBackoffStrategy(function () { return 10; }, false); + $a->setNext($b); + $b->setNext($c); + $c->setNext($d); + $this->assertEquals(10, $a->getBackoffPeriod(2, new Request('GET', 'http://www.foo.com'))); + } + + public function testReturnsZeroWhenDecisionMakerReturnsTrueButNoFurtherStrategiesAreInTheChain() + { + $a = new CallbackBackoffStrategy(function () { return null; }, true); + $b = new CallbackBackoffStrategy(function () { return true; }, true); + $a->setNext($b); + $this->assertSame(0, $a->getBackoffPeriod(2, new Request('GET', 'http://www.foo.com'))); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php new file mode 100644 index 000000000..a64dd826e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php @@ -0,0 +1,110 @@ +message = ''; + } + + public function testHasEventList() + { + $this->assertEquals(1, count(BackoffLogger::getSubscribedEvents())); + } + + public function testLogsEvents() + { + list($logPlugin, $request, $response) = $this->getMocks(); + + $response = $this->getMockBuilder('Guzzle\Http\Message\Response') + ->setConstructorArgs(array(503)) + ->setMethods(array('getInfo')) + ->getMock(); + + $response->expects($this->any()) + ->method('getInfo') + ->will($this->returnValue(2)); + + $handle = $this->getMockHandle(); + + $event = new Event(array( + 'request' => $request, + 'response' => $response, + 'retries' => 1, + 'delay' => 3, + 'handle' => $handle + )); + + $logPlugin->onRequestRetry($event); + $this->assertContains( + '] PUT http://www.example.com - 503 Service Unavailable - Retries: 1, Delay: 3, Time: 2, 2, cURL: 30 Foo', + $this->message + ); + } + + public function testCanSetTemplate() + { + $l = new BackoffLogger(new ClosureLogAdapter(function () {})); + $l->setTemplate('foo'); + $t = $this->readAttribute($l, 'formatter'); + $this->assertEquals('foo', $this->readAttribute($t, 'template')); + } + + /** + * @return array + */ + protected function getMocks() + { + $that = $this; + $logger = new ClosureLogAdapter(function ($message) use ($that) { + $that->message .= $message . "\n"; + }); + $logPlugin = new BackoffLogger($logger); + $response = new Response(503); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com', array( + 'Content-Length' => 3, + 'Foo' => 'Bar' + )); + + return array($logPlugin, $request, $response); + } + + /** + * @return CurlHandle + */ + protected function getMockHandle() + { + $handle = $this->getMockBuilder('Guzzle\Http\Curl\CurlHandle') + ->disableOriginalConstructor() + ->setMethods(array('getError', 'getErrorNo', 'getInfo')) + ->getMock(); + + $handle->expects($this->once()) + ->method('getError') + ->will($this->returnValue('Foo')); + + $handle->expects($this->once()) + ->method('getErrorNo') + ->will($this->returnValue(30)); + + $handle->expects($this->any()) + ->method('getInfo') + ->will($this->returnValue(2)); + + return $handle; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php new file mode 100644 index 000000000..496e49eb2 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php @@ -0,0 +1,297 @@ +retried = false; + } + + public static function getSubscribedEvents() + { + return array(BackoffPlugin::RETRY_EVENT => 'onRequestRetry'); + } + + public function onRequestRetry(Event $event) + { + $this->retried = $event; + } + + public function testHasEventList() + { + $this->assertEquals(1, count(BackoffPlugin::getAllEvents())); + } + + public function testCreatesDefaultExponentialBackoffPlugin() + { + $plugin = BackoffPlugin::getExponentialBackoff(3, array(204), array(10)); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\BackoffPlugin', $plugin); + $strategy = $this->readAttribute($plugin, 'strategy'); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\TruncatedBackoffStrategy', $strategy); + $this->assertEquals(3, $this->readAttribute($strategy, 'max')); + $strategy = $this->readAttribute($strategy, 'next'); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\HttpBackoffStrategy', $strategy); + $this->assertEquals(array(204 => true), $this->readAttribute($strategy, 'errorCodes')); + $strategy = $this->readAttribute($strategy, 'next'); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\CurlBackoffStrategy', $strategy); + $this->assertEquals(array(10 => true), $this->readAttribute($strategy, 'errorCodes')); + $strategy = $this->readAttribute($strategy, 'next'); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\ExponentialBackoffStrategy', $strategy); + } + + public function testDoesNotRetryUnlessStrategyReturnsNumber() + { + $request = new Request('GET', 'http://www.example.com'); + $request->setState('transfer'); + + $mock = $this->getMockBuilder('Guzzle\Plugin\Backoff\BackoffStrategyInterface') + ->setMethods(array('getBackoffPeriod')) + ->getMockForAbstractClass(); + + $mock->expects($this->once()) + ->method('getBackoffPeriod') + ->will($this->returnValue(false)); + + $plugin = new BackoffPlugin($mock); + $plugin->addSubscriber($this); + $plugin->onRequestSent(new Event(array('request' => $request))); + $this->assertFalse($this->retried); + } + + public function testUpdatesRequestForRetry() + { + $request = new Request('GET', 'http://www.example.com'); + $request->setState('transfer'); + $response = new Response(500); + $handle = $this->getMockBuilder('Guzzle\Http\Curl\CurlHandle')->disableOriginalConstructor()->getMock(); + $e = new CurlException(); + $e->setCurlHandle($handle); + + $plugin = new BackoffPlugin(new ConstantBackoffStrategy(10)); + $plugin->addSubscriber($this); + + $event = new Event(array( + 'request' => $request, + 'response' => $response, + 'exception' => $e + )); + + $plugin->onRequestSent($event); + $this->assertEquals(array( + 'request' => $request, + 'response' => $response, + 'handle' => $handle, + 'retries' => 1, + 'delay' => 10 + ), $this->readAttribute($this->retried, 'context')); + + $plugin->onRequestSent($event); + $this->assertEquals(array( + 'request' => $request, + 'response' => $response, + 'handle' => $handle, + 'retries' => 2, + 'delay' => 10 + ), $this->readAttribute($this->retried, 'context')); + } + + public function testDoesNothingWhenNotRetryingAndPollingRequest() + { + $request = new Request('GET', 'http://www.foo.com'); + $plugin = new BackoffPlugin(new ConstantBackoffStrategy(10)); + $plugin->onRequestPoll(new Event(array('request' => $request))); + } + + public function testRetriesRequests() + { + // Create a script to return several 500 and 503 response codes + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata" + )); + + $plugin = new BackoffPlugin( + new TruncatedBackoffStrategy(3, + new HttpBackoffStrategy(null, + new CurlBackoffStrategy(null, + new ConstantBackoffStrategy(0.05) + ) + ) + ) + ); + + $client = new Client($this->getServer()->getUrl()); + $client->getEventDispatcher()->addSubscriber($plugin); + $request = $client->get(); + $request->send(); + + // Make sure it eventually completed successfully + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $this->assertEquals('data', $request->getResponse()->getBody(true)); + + // Check that three requests were made to retry this request + $this->assertEquals(3, count($this->getServer()->getReceivedRequests(false))); + $this->assertEquals(2, $request->getParams()->get(BackoffPlugin::RETRY_PARAM)); + } + + /** + * @expectedException \Guzzle\Http\Exception\ServerErrorResponseException + */ + public function testFailsOnTruncation() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n" + )); + + $plugin = new BackoffPlugin( + new TruncatedBackoffStrategy(2, + new HttpBackoffStrategy(null, + new ConstantBackoffStrategy(0.05) + ) + ) + ); + + $client = new Client($this->getServer()->getUrl()); + $client->addSubscriber($plugin); + $client->get()->send(); + } + + public function testRetriesRequestsWhenInParallel() + { + // Create a script to return several 500 and 503 response codes + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata" + )); + + $plugin = new BackoffPlugin( + new HttpBackoffStrategy(null, + new TruncatedBackoffStrategy(3, + new CurlBackoffStrategy(null, + new ConstantBackoffStrategy(0.1) + ) + ) + ) + ); + $client = new Client($this->getServer()->getUrl()); + $client->getEventDispatcher()->addSubscriber($plugin); + $requests = array(); + for ($i = 0; $i < 5; $i++) { + $requests[] = $client->get(); + } + $client->send($requests); + + $this->assertEquals(15, count($this->getServer()->getReceivedRequests(false))); + } + + /** + * @covers Guzzle\Plugin\Backoff\BackoffPlugin + * @covers Guzzle\Http\Curl\CurlMulti + */ + public function testRetriesPooledRequestsUsingDelayAndPollingEvent() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata" + )); + // Need to sleep for some time ensure that the polling works correctly in the observer + $plugin = new BackoffPlugin(new HttpBackoffStrategy(null, + new TruncatedBackoffStrategy(1, + new ConstantBackoffStrategy(0.5)))); + + $client = new Client($this->getServer()->getUrl()); + $client->getEventDispatcher()->addSubscriber($plugin); + $request = $client->get(); + $request->send(); + // Make sure it eventually completed successfully + $this->assertEquals('data', $request->getResponse()->getBody(true)); + // Check that two requests were made to retry this request + $this->assertEquals(2, count($this->getServer()->getReceivedRequests(false))); + } + + public function testSeeksToBeginningOfRequestBodyWhenRetrying() + { + // Create a request with a body + $request = new EntityEnclosingRequest('PUT', 'http://www.example.com'); + $request->setBody('abc'); + // Set the retry time to be something that will be retried always + $request->getParams()->set(BackoffPlugin::DELAY_PARAM, 2); + // Seek to the end of the stream + $request->getBody()->seek(3); + $this->assertEquals('', $request->getBody()->read(1)); + // Create a plugin that does not delay when retrying + $plugin = new BackoffPlugin(new ConstantBackoffStrategy(0)); + $plugin->onRequestPoll($this->getMockEvent($request)); + // Ensure that the stream was seeked to 0 + $this->assertEquals('a', $request->getBody()->read(1)); + } + + public function testDoesNotSeekOnRequestsWithNoBodyWhenRetrying() + { + // Create a request with a body + $request = new EntityEnclosingRequest('PUT', 'http://www.example.com'); + $request->getParams()->set(BackoffPlugin::DELAY_PARAM, 2); + $plugin = new BackoffPlugin(new ConstantBackoffStrategy(0)); + $plugin->onRequestPoll($this->getMockEvent($request)); + } + + protected function getMockEvent(RequestInterface $request) + { + // Create a mock curl multi object + $multi = $this->getMockBuilder('Guzzle\Http\Curl\CurlMulti') + ->setMethods(array('remove', 'add')) + ->getMock(); + + // Create an event that is expected for the Poll event + $event = new Event(array( + 'request' => $request, + 'curl_multi' => $multi + )); + $event->setName(CurlMultiInterface::POLLING_REQUEST); + + return $event; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php new file mode 100644 index 000000000..c0ce10d5c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php @@ -0,0 +1,31 @@ +getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $strategy = new CallbackBackoffStrategy(function () { return 10; }, true); + $this->assertTrue($strategy->makesDecision()); + $this->assertEquals(10, $strategy->getBackoffPeriod(0, $request)); + // Ensure it chains correctly when null is returned + $strategy = new CallbackBackoffStrategy(function () { return null; }, false); + $this->assertFalse($strategy->makesDecision()); + $this->assertFalse($strategy->getBackoffPeriod(0, $request)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php new file mode 100644 index 000000000..703eb4a22 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php @@ -0,0 +1,20 @@ +assertFalse($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(3.5, $strategy->getBackoffPeriod(0, $request)); + $this->assertEquals(3.5, $strategy->getBackoffPeriod(1, $request)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php new file mode 100644 index 000000000..0a5c3e28d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php @@ -0,0 +1,36 @@ +assertNotEmpty(CurlBackoffStrategy::getDefaultFailureCodes()); + $strategy = new CurlBackoffStrategy(); + $this->assertTrue($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $e = new CurlException(); + $e->setError('foo', CURLE_BAD_CALLING_ORDER); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, null, $e)); + + foreach (CurlBackoffStrategy::getDefaultFailureCodes() as $code) { + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, null, $e->setError('foo', $code))); + } + } + + public function testIgnoresNonErrors() + { + $strategy = new CurlBackoffStrategy(); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, new Response(200))); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php new file mode 100644 index 000000000..09965bcbf --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php @@ -0,0 +1,23 @@ +assertFalse($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(1, $strategy->getBackoffPeriod(0, $request)); + $this->assertEquals(2, $strategy->getBackoffPeriod(1, $request)); + $this->assertEquals(4, $strategy->getBackoffPeriod(2, $request)); + $this->assertEquals(8, $strategy->getBackoffPeriod(3, $request)); + $this->assertEquals(16, $strategy->getBackoffPeriod(4, $request)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php new file mode 100644 index 000000000..ae68a4eb8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php @@ -0,0 +1,47 @@ +assertNotEmpty(HttpBackoffStrategy::getDefaultFailureCodes()); + $strategy = new HttpBackoffStrategy(); + $this->assertTrue($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + + $response = new Response(200); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response)); + $response->setStatus(400); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response)); + + foreach (HttpBackoffStrategy::getDefaultFailureCodes() as $code) { + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, $response->setStatus($code))); + } + } + + public function testAllowsCustomCodes() + { + $strategy = new HttpBackoffStrategy(array(204)); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $response = new Response(204); + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, $response)); + $response->setStatus(500); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response)); + } + + public function testIgnoresNonErrors() + { + $strategy = new HttpBackoffStrategy(); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php new file mode 100644 index 000000000..b4ce8e4af --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php @@ -0,0 +1,21 @@ +assertFalse($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request)); + $this->assertEquals(5, $strategy->getBackoffPeriod(1, $request)); + $this->assertEquals(10, $strategy->getBackoffPeriod(2, $request)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php new file mode 100644 index 000000000..dea5a6878 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php @@ -0,0 +1,32 @@ +assertEmpty(ReasonPhraseBackoffStrategy::getDefaultFailureCodes()); + $strategy = new ReasonPhraseBackoffStrategy(array('Foo', 'Internal Server Error')); + $this->assertTrue($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $response = new Response(200); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response)); + $response->setStatus(200, 'Foo'); + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, $response)); + } + + public function testIgnoresNonErrors() + { + $strategy = new ReasonPhraseBackoffStrategy(); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php new file mode 100644 index 000000000..5590dfb1c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php @@ -0,0 +1,30 @@ +assertTrue($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertFalse($strategy->getBackoffPeriod(0, $request)); + $this->assertFalse($strategy->getBackoffPeriod(1, $request)); + $this->assertFalse($strategy->getBackoffPeriod(2, $request)); + + $response = new Response(500); + $strategy->setNext(new HttpBackoffStrategy(null, new ConstantBackoffStrategy(10))); + $this->assertEquals(10, $strategy->getBackoffPeriod(0, $request, $response)); + $this->assertEquals(10, $strategy->getBackoffPeriod(1, $request, $response)); + $this->assertFalse($strategy->getBackoffPeriod(2, $request, $response)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php new file mode 100644 index 000000000..69da60a93 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php @@ -0,0 +1,441 @@ +assertInstanceOf('Guzzle\Plugin\Cache\CacheStorageInterface', $this->readAttribute($plugin, 'storage')); + } + + public function testAddsDefaultCollaborators() + { + $this->assertNotEmpty(CachePlugin::getSubscribedEvents()); + $plugin = new CachePlugin(array( + 'storage' => $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')->getMockForAbstractClass() + )); + $this->assertInstanceOf('Guzzle\Plugin\Cache\CacheStorageInterface', $this->readAttribute($plugin, 'storage')); + $this->assertInstanceOf( + 'Guzzle\Plugin\Cache\CanCacheStrategyInterface', + $this->readAttribute($plugin, 'canCache') + ); + $this->assertInstanceOf( + 'Guzzle\Plugin\Cache\RevalidationInterface', + $this->readAttribute($plugin, 'revalidation') + ); + } + + public function testAddsCallbackCollaborators() + { + $this->assertNotEmpty(CachePlugin::getSubscribedEvents()); + $plugin = new CachePlugin(array('can_cache' => function () {})); + $this->assertInstanceOf( + 'Guzzle\Plugin\Cache\CallbackCanCacheStrategy', + $this->readAttribute($plugin, 'canCache') + ); + } + + public function testCanPassCacheAsOnlyArgumentToConstructor() + { + $p = new CachePlugin(new DoctrineCacheAdapter(new ArrayCache())); + $p = new CachePlugin(new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache()))); + } + + public function testUsesCreatedCacheStorage() + { + $plugin = new CachePlugin(array( + 'adapter' => $this->getMockBuilder('Guzzle\Cache\CacheAdapterInterface')->getMockForAbstractClass() + )); + $this->assertInstanceOf('Guzzle\Plugin\Cache\CacheStorageInterface', $this->readAttribute($plugin, 'storage')); + } + + public function testUsesProvidedOptions() + { + $can = $this->getMockBuilder('Guzzle\Plugin\Cache\CanCacheStrategyInterface')->getMockForAbstractClass(); + $revalidate = $this->getMockBuilder('Guzzle\Plugin\Cache\RevalidationInterface')->getMockForAbstractClass(); + $plugin = new CachePlugin(array( + 'storage' => $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')->getMockForAbstractClass(), + 'can_cache' => $can, + 'revalidation' => $revalidate + )); + $this->assertSame($can, $this->readAttribute($plugin, 'canCache')); + $this->assertSame($revalidate, $this->readAttribute($plugin, 'revalidation')); + } + + public function satisfyProvider() + { + $req1 = new Request('GET', 'http://foo.com', array('Cache-Control' => 'no-cache')); + + return array( + // The response is too old to satisfy the request + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-age=20')), new Response(200, array('Age' => 100)), false, false), + // The response cannot satisfy the request because it is stale + array(new Request('GET', 'http://foo.com'), new Response(200, array('Cache-Control' => 'max-age=10', 'Age' => 100)), false, false), + // Allows the expired response to satisfy the request because of the max-stale + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale=15')), new Response(200, array('Cache-Control' => 'max-age=90', 'Age' => 100)), true, false), + // Max stale is > than the allowed staleness + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale=5')), new Response(200, array('Cache-Control' => 'max-age=90', 'Age' => 100)), false, false), + // Performs cache revalidation + array($req1, new Response(200), true, true), + // Performs revalidation due to ETag on the response and no cache-control on the request + array(new Request('GET', 'http://foo.com'), new Response(200, array( + 'ETag' => 'ABC', + 'Expires' => date('c', strtotime('+1 year')) + )), true, true), + ); + } + + /** + * @dataProvider satisfyProvider + */ + public function testChecksIfResponseCanSatisfyRequest($request, $response, $can, $revalidates) + { + $didRevalidate = false; + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')->getMockForAbstractClass(); + $revalidate = $this->getMockBuilder('Guzzle\Plugin\Cache\DefaultRevalidation') + ->setMethods(array('revalidate')) + ->setConstructorArgs(array($storage)) + ->getMockForAbstractClass(); + + $revalidate->expects($this->any()) + ->method('revalidate') + ->will($this->returnCallback(function () use (&$didRevalidate) { + $didRevalidate = true; + return true; + })); + + $plugin = new CachePlugin(array( + 'storage' => $storage, + 'revalidation' => $revalidate + )); + + $this->assertEquals($can, $plugin->canResponseSatisfyRequest($request, $response)); + $this->assertEquals($didRevalidate, $revalidates); + } + + public function satisfyFailedProvider() + { + return array( + // Neither has stale-if-error + array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100)), false), + // Request has stale-if-error + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50')), true), + // Request has valid stale-if-error + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=50')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50')), true), + // Request has expired stale-if-error + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=20')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50')), false), + // Response has permanent stale-if-error + array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error', )), true), + // Response has valid stale-if-error + array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=50')), true), + // Response has expired stale-if-error + array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=20')), false), + // Request has valid stale-if-error but response does not + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=50')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=20')), false), + // Response has valid stale-if-error but request does not + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=20')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=50')), false), + ); + } + + /** + * @dataProvider satisfyFailedProvider + */ + public function testChecksIfResponseCanSatisfyFailedRequest($request, $response, $can) + { + $plugin = new CachePlugin(); + + $this->assertEquals($can, $plugin->canResponseSatisfyFailedRequest($request, $response)); + } + + public function testDoesNothingWhenRequestIsNotCacheable() + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->never())->method('fetch'); + + $plugin = new CachePlugin(array( + 'storage' => $storage, + 'can_cache' => new CallbackCanCacheStrategy(function () { return false; }) + )); + + $plugin->onRequestBeforeSend(new Event(array( + 'request' => new Request('GET', 'http://foo.com') + ))); + } + + public function satisfiableProvider() + { + $date = new \DateTime('-10 seconds'); + + return array( + // Fresh response + array(new Response(200, array(), 'foo')), + // Stale response + array(new Response(200, array('Date' => $date->format('c'), 'Cache-Control' => 'max-age=5'), 'foo')) + ); + } + + /** + * @dataProvider satisfiableProvider + */ + public function testInjectsSatisfiableResponses($response) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + + $storage->expects($this->once())->method('fetch')->will($this->returnValue($response)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale')); + $plugin->onRequestBeforeSend(new Event(array('request' => $request))); + $plugin->onRequestSent(new Event(array('request' => $request, 'response' => $request->getResponse()))); + $this->assertEquals($response->getStatusCode(), $request->getResponse()->getStatusCode()); + $this->assertEquals((string) $response->getBody(), (string) $request->getResponse()->getBody()); + $this->assertTrue($request->getResponse()->hasHeader('Age')); + if ($request->getResponse()->isFresh() === false) { + $this->assertContains('110', (string) $request->getResponse()->getHeader('Warning')); + } + $this->assertSame( + sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), + (string) $request->getHeader('Via') + ); + $this->assertSame( + sprintf('%s GuzzleCache/%s',$request->getProtocolVersion(), Version::VERSION), + (string) $request->getResponse()->getHeader('Via') + ); + $this->assertTrue($request->getParams()->get('cache.lookup')); + $this->assertTrue($request->getParams()->get('cache.hit')); + $this->assertTrue($request->getResponse()->hasHeader('X-Cache-Lookup')); + $this->assertTrue($request->getResponse()->hasHeader('X-Cache')); + $this->assertEquals('HIT from GuzzleCache', (string) $request->getResponse()->getHeader('X-Cache')); + $this->assertEquals('HIT from GuzzleCache', (string) $request->getResponse()->getHeader('X-Cache-Lookup')); + } + + public function satisfiableOnErrorProvider() + { + $date = new \DateTime('-10 seconds'); + return array( + array( + new Response(200, array( + 'Date' => $date->format('c'), + 'Cache-Control' => 'max-age=5, stale-if-error' + ), 'foo'), + ) + ); + } + + /** + * @dataProvider satisfiableOnErrorProvider + */ + public function testInjectsSatisfiableResponsesOnError($cacheResponse) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->exactly(2))->method('fetch')->will($this->returnValue($cacheResponse)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale')); + $plugin->onRequestBeforeSend(new Event(array('request' => $request))); + $plugin->onRequestError( + $event = new Event(array( + 'request' => $request, + 'response' => $request->getResponse(), + )) + ); + $response = $event['response']; + $this->assertEquals($cacheResponse->getStatusCode(), $response->getStatusCode()); + $this->assertEquals((string) $cacheResponse->getBody(), (string) $response->getBody()); + $this->assertTrue($response->hasHeader('Age')); + if ($response->isFresh() === false) { + $this->assertContains('110', (string) $response->getHeader('Warning')); + } + $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $request->getHeader('Via')); + $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $response->getHeader('Via')); + $this->assertTrue($request->getParams()->get('cache.lookup')); + $this->assertSame('error', $request->getParams()->get('cache.hit')); + $this->assertTrue($response->hasHeader('X-Cache-Lookup')); + $this->assertTrue($response->hasHeader('X-Cache')); + $this->assertEquals('HIT from GuzzleCache', (string) $response->getHeader('X-Cache-Lookup')); + $this->assertEquals('HIT_ERROR from GuzzleCache', (string) $response->getHeader('X-Cache')); + } + + /** + * @dataProvider satisfiableOnErrorProvider + */ + public function testInjectsSatisfiableResponsesOnException($cacheResponse) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->exactly(2))->method('fetch')->will($this->returnValue($cacheResponse)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale')); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + $plugin->onRequestException( + new Event(array( + 'request' => $request, + 'response' => $request->getResponse(), + 'exception' => $this->getMock('Guzzle\Http\Exception\CurlException'), + )) + ); + $plugin->onRequestSent( + new Event(array( + 'request' => $request, + 'response' => $response = $request->getResponse(), + )) + ); + $this->assertEquals($cacheResponse->getStatusCode(), $response->getStatusCode()); + $this->assertEquals((string) $cacheResponse->getBody(), (string) $response->getBody()); + $this->assertTrue($response->hasHeader('Age')); + if ($response->isFresh() === false) { + $this->assertContains('110', (string) $response->getHeader('Warning')); + } + $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $request->getHeader('Via')); + $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $response->getHeader('Via')); + $this->assertTrue($request->getParams()->get('cache.lookup')); + $this->assertSame('error', $request->getParams()->get('cache.hit')); + $this->assertTrue($response->hasHeader('X-Cache-Lookup')); + $this->assertTrue($response->hasHeader('X-Cache')); + $this->assertEquals('HIT from GuzzleCache', (string) $response->getHeader('X-Cache-Lookup')); + $this->assertEquals('HIT_ERROR from GuzzleCache', (string) $response->getHeader('X-Cache')); + } + + public function unsatisfiableOnErrorProvider() + { + $date = new \DateTime('-10 seconds'); + + return array( + // no-store on request + array( + false, + array('Cache-Control' => 'no-store'), + new Response(200, array('Date' => $date->format('D, d M Y H:i:s T'), 'Cache-Control' => 'max-age=5, stale-if-error'), 'foo'), + ), + // request expired + array( + true, + array('Cache-Control' => 'stale-if-error=4'), + new Response(200, array('Date' => $date->format('D, d M Y H:i:s T'), 'Cache-Control' => 'max-age=5, stale-if-error'), 'foo'), + ), + // response expired + array( + true, + array('Cache-Control' => 'stale-if-error'), + new Response(200, array('Date' => $date->format('D, d M Y H:i:s T'), 'Cache-Control' => 'max-age=5, stale-if-error=4'), 'foo'), + ), + ); + } + + /** + * @dataProvider unsatisfiableOnErrorProvider + */ + public function testDoesNotInjectUnsatisfiableResponsesOnError($requestCanCache, $requestHeaders, $cacheResponse) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->exactly($requestCanCache ? 2 : 0))->method('fetch')->will($this->returnValue($cacheResponse)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', $requestHeaders); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + $plugin->onRequestError( + $event = new Event(array( + 'request' => $request, + 'response' => $response = $request->getResponse(), + )) + ); + + $this->assertSame($response, $event['response']); + } + + /** + * @dataProvider unsatisfiableOnErrorProvider + */ + public function testDoesNotInjectUnsatisfiableResponsesOnException($requestCanCache, $requestHeaders, $responseParts) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->exactly($requestCanCache ? 2 : 0))->method('fetch')->will($this->returnValue($responseParts)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', $requestHeaders); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + $plugin->onRequestException( + $event = new Event(array( + 'request' => $request, + 'response' => $response = $request->getResponse(), + 'exception' => $this->getMock('Guzzle\Http\Exception\CurlException'), + )) + ); + + $this->assertSame($response, $request->getResponse()); + } + + public function testCachesResponsesWhenCacheable() + { + $cache = new ArrayCache(); + $plugin = new CachePlugin($cache); + + $request = new Request('GET', 'http://foo.com'); + $response = new Response(200, array(), 'Foo'); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + $plugin->onRequestSent(new Event(array( + 'request' => $request, + 'response' => $response + ))); + $data = $this->readAttribute($cache, 'data'); + $this->assertNotEmpty($data); + } + + public function testPurgesRequests() + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('purge')) + ->getMockForAbstractClass(); + $storage->expects($this->atLeastOnce())->method('purge'); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', array('X-Foo' => 'Bar')); + $plugin->purge($request); + } + + public function testAutoPurgesRequests() + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('purge')) + ->getMockForAbstractClass(); + $storage->expects($this->atLeastOnce())->method('purge'); + $plugin = new CachePlugin(array('storage' => $storage, 'auto_purge' => true)); + $client = new Client(); + $request = $client->put('http://foo.com', array('X-Foo' => 'Bar')); + $request->addSubscriber($plugin); + $request->setResponse(new Response(200), true); + $request->send(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php new file mode 100644 index 000000000..f3d9bafe2 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php @@ -0,0 +1,72 @@ +assertTrue($c->canCacheRequest(new Request('DELETE', 'http://www.foo.com'))); + } + + /** + * The following is a bit of an integration test to ensure that the CachePlugin honors a + * custom can cache strategy. + */ + public function testIntegrationWithCachePlugin() + { + $c = new CallbackCanCacheStrategy( + function ($request) { return true; }, + function ($response) { return true; } + ); + + // Make a request and response that have no business being cached + $request = new Request('DELETE', 'http://www.foo.com'); + $response = Response::fromMessage( + "HTTP/1.1 200 OK\r\n" + . "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + . "Last-Modified: Wed, 09 Jan 2013 08:48:53 GMT\r\n" + . "Content-Length: 2\r\n" + . "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n\r\n" + . "hi" + ); + + $this->assertTrue($c->canCacheRequest($request)); + $this->assertTrue($c->canCacheResponse($response)); + + $s = $this->getMockBuilder('Guzzle\Plugin\Cache\DefaultCacheStorage') + ->setConstructorArgs(array(new DoctrineCacheAdapter(new ArrayCache()))) + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + + $s->expects($this->once()) + ->method('fetch') + ->will($this->returnValue($response)); + + $plugin = new CachePlugin(array('can_cache' => $c, 'storage' => $s)); + $plugin->onRequestBeforeSend(new Event(array('request' => $request))); + + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $this->assertEquals('hi', $request->getResponse()->getBody(true)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php new file mode 100644 index 000000000..701a0155f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php @@ -0,0 +1,193 @@ + 'application/json')); + $response = new Response(200, array( + 'Content-Type' => 'application/json', + 'Connection' => 'close', + 'X-Foo' => 'Bar', + 'Vary' => 'Accept' + ), 'test'); + $s->cache($request, $response); + $data = $this->readAttribute($a, 'data'); + + return array( + 'cache' => $a, + 'adapter' => $c, + 'storage' => $s, + 'request' => $request, + 'response' => $response, + 'serialized' => end($data) + ); + } + + public function testReturnsNullForCacheMiss() + { + $cache = $this->getCache(); + $this->assertNull($cache['storage']->fetch(new Request('GET', 'http://test.com'))); + } + + public function testCachesRequests() + { + $cache = $this->getCache(); + $foundRequest = $foundBody = $bodyKey = false; + foreach ($this->readAttribute($cache['cache'], 'data') as $key => $v) { + if (strpos($v, 'foo.com')) { + $foundRequest = true; + $data = unserialize($v); + $bodyKey = $data[0][3]; + $this->assertInternalType('integer', $data[0][4]); + $this->assertFalse(isset($data[0][0]['connection'])); + $this->assertEquals('foo.com', $data[0][0]['host']); + } elseif ($v == 'test') { + $foundBody = $key; + } + } + $this->assertContains($bodyKey, $foundBody); + $this->assertTrue($foundRequest); + } + + public function testFetchesResponse() + { + $cache = $this->getCache(); + $response = $cache['storage']->fetch($cache['request']); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($response->hasHeader('Connection')); + $this->assertEquals('Bar', (string) $response->getHeader('X-Foo')); + $this->assertEquals('test', (string) $response->getBody()); + $this->assertTrue(in_array($cache['serialized'], $this->readAttribute($cache['cache'], 'data'))); + } + + public function testDeletesRequestItemsAndBody() + { + $cache = $this->getCache(); + $cache['storage']->delete($cache['request']); + $this->assertFalse(in_array('test', $this->readAttribute($cache['cache'], 'data'))); + $this->assertFalse(in_array($cache['serialized'], $this->readAttribute($cache['cache'], 'data'))); + } + + public function testCachesMultipleRequestsWithVary() + { + $cache = $this->getCache(); + $cache['request']->setHeader('Accept', 'application/xml'); + $response = $cache['response']->setHeader('Content-Type', 'application/xml'); + $response->setBody('123'); + $cache['storage']->cache($cache['request'], $response); + $data = $this->readAttribute($cache['cache'], 'data'); + foreach ($data as $v) { + if (strpos($v, 'foo.com')) { + $u = unserialize($v); + $this->assertEquals(2, count($u)); + $this->assertEquals($u[0][0]['accept'], 'application/xml'); + $this->assertEquals($u[0][1]['content-type'], 'application/xml'); + $this->assertEquals($u[1][0]['accept'], 'application/json'); + $this->assertEquals($u[1][1]['content-type'], 'application/json'); + $this->assertNotSame($u[0][3], $u[1][3]); + break; + } + } + } + + public function testPurgeRemovesAllMethodCaches() + { + $cache = $this->getCache(); + foreach (array('HEAD', 'POST', 'PUT', 'DELETE') as $method) { + $request = RequestFactory::getInstance()->cloneRequestWithMethod($cache['request'], $method); + $cache['storage']->cache($request, $cache['response']); + } + $cache['storage']->purge('http://foo.com'); + $this->assertFalse(in_array('test', $this->readAttribute($cache['cache'], 'data'))); + $this->assertFalse(in_array($cache['serialized'], $this->readAttribute($cache['cache'], 'data'))); + $this->assertEquals( + array('DoctrineNamespaceCacheKey[]'), + array_keys($this->readAttribute($cache['cache'], 'data')) + ); + } + + public function testRemovesExpiredResponses() + { + $cache = $this->getCache(); + $request = new Request('GET', 'http://xyz.com'); + $response = new Response(200, array('Age' => 1000, 'Cache-Control' => 'max-age=-10000')); + $cache['storage']->cache($request, $response); + $this->assertNull($cache['storage']->fetch($request)); + $data = $this->readAttribute($cache['cache'], 'data'); + $this->assertFalse(in_array('xyz.com', $data)); + $this->assertTrue(in_array($cache['serialized'], $data)); + } + + public function testUsesVaryToDetermineResult() + { + $cache = $this->getCache(); + $this->assertInstanceOf('Guzzle\Http\Message\Response', $cache['storage']->fetch($cache['request'])); + $request = new Request('GET', 'http://foo.com', array('Accept' => 'application/xml')); + $this->assertNull($cache['storage']->fetch($request)); + } + + public function testEnsuresResponseIsStillPresent() + { + $cache = $this->getCache(); + $data = $this->readAttribute($cache['cache'], 'data'); + $key = array_search('test', $data); + $cache['cache']->delete(substr($key, 1, -4)); + $this->assertNull($cache['storage']->fetch($cache['request'])); + } + + public function staleProvider() + { + return array( + array( + new Request('GET', 'http://foo.com', array('Accept' => 'foo')), + new Response(200, array('Cache-Control' => 'stale-if-error=100', 'Vary' => 'Accept')) + ), + array( + new Request('GET', 'http://foo.com', array('Accept' => 'foo')), + new Response(200, array('Cache-Control' => 'stale-if-error', 'Vary' => 'Accept')) + ) + ); + } + + /** + * @dataProvider staleProvider + */ + public function testUsesStaleTimeDirectiveForTtd($request, $response) + { + $cache = $this->getCache(); + $cache['storage']->cache($request, $response); + $data = $this->readAttribute($cache['cache'], 'data'); + foreach ($data as $v) { + if (strpos($v, 'foo.com')) { + $u = unserialize($v); + $this->assertGreaterThan($u[1][4], $u[0][4]); + break; + } + } + } + + public function testCanFilterCacheKeys() + { + $cache = $this->getCache(); + $cache['request']->getQuery()->set('auth', 'foo'); + $this->assertNull($cache['storage']->fetch($cache['request'])); + $cache['request']->getParams()->set('cache.key_filter', 'auth'); + $this->assertNotNull($cache['storage']->fetch($cache['request'])); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php new file mode 100644 index 000000000..de4d182a2 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php @@ -0,0 +1,40 @@ +assertTrue($strategy->canCacheRequest($request)); + } + + public function testDoesNotCacheNoStore() + { + $strategy = new DefaultCanCacheStrategy(); + $request = new Request('GET', 'http://foo.com', array('cache-control' => 'no-store')); + $this->assertFalse($strategy->canCacheRequest($request)); + } + + public function testCanCacheResponse() + { + $response = $this->getMockBuilder('Guzzle\Http\Message\Response') + ->setMethods(array('canCache')) + ->setConstructorArgs(array(200)) + ->getMock(); + $response->expects($this->once()) + ->method('canCache') + ->will($this->returnValue(true)); + $strategy = new DefaultCanCacheStrategy(); + $this->assertTrue($strategy->canCacheResponse($response)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php new file mode 100644 index 000000000..0699cb266 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php @@ -0,0 +1,248 @@ +getHttpDate('-100 hours') . "\r\nContent-Length: 4\r\n\r\nData", + "HTTP/1.1 304 NOT MODIFIED\r\nCache-Control: max-age=2000000\r\nContent-Length: 0\r\n\r\n", + ), + // Forces revalidation that overwrites what is in cache + array( + false, + "\r\n", + "HTTP/1.1 200 OK\r\nCache-Control: must-revalidate, no-cache\r\nDate: " . $this->getHttpDate('-10 hours') . "\r\nContent-Length: 4\r\n\r\nData", + "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nDatas", + "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nDate: " . $this->getHttpDate('now') . "\r\n\r\nDatas" + ), + // Throws an exception during revalidation + array( + false, + "\r\n", + "HTTP/1.1 200 OK\r\nCache-Control: no-cache\r\nDate: " . $this->getHttpDate('-3 hours') . "\r\n\r\nData", + "HTTP/1.1 500 INTERNAL SERVER ERROR\r\nContent-Length: 0\r\n\r\n" + ), + // ETag mismatch + array( + false, + "\r\n", + "HTTP/1.1 200 OK\r\nCache-Control: no-cache\r\nETag: \"123\"\r\nDate: " . $this->getHttpDate('-10 hours') . "\r\n\r\nData", + "HTTP/1.1 304 NOT MODIFIED\r\nETag: \"123456\"\r\n\r\n", + ), + ); + } + + /** + * @dataProvider cacheRevalidationDataProvider + */ + public function testRevalidatesResponsesAgainstOriginServer($can, $request, $response, $validate = null, $result = null) + { + // Send some responses to the test server for cache validation + $server = $this->getServer(); + $server->flush(); + + if ($validate) { + $server->enqueue($validate); + } + + $request = RequestFactory::getInstance()->fromMessage("GET / HTTP/1.1\r\nHost: 127.0.0.1:" . $server->getPort() . "\r\n" . $request); + $response = Response::fromMessage($response); + $request->setClient(new Client()); + + $plugin = new CachePlugin(new DoctrineCacheAdapter(new ArrayCache())); + $this->assertEquals( + $can, + $plugin->canResponseSatisfyRequest($request, $response), + '-> ' . $request . "\n" . $response + ); + + if ($result) { + $result = Response::fromMessage($result); + $result->removeHeader('Date'); + $request->getResponse()->removeHeader('Date'); + $request->getResponse()->removeHeader('Connection'); + // Get rid of dates + $this->assertEquals((string) $result, (string) $request->getResponse()); + } + + if ($validate) { + $this->assertEquals(1, count($server->getReceivedRequests())); + } + } + + public function testHandles404RevalidationResponses() + { + $request = new Request('GET', 'http://foo.com'); + $request->setClient(new Client()); + $badResponse = new Response(404, array(), 'Oh no!'); + $badRequest = clone $request; + $badRequest->setResponse($badResponse, true); + $response = new Response(200, array(), 'foo'); + + // Seed the cache + $s = new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache())); + $s->cache($request, $response); + $this->assertNotNull($s->fetch($request)); + + $rev = $this->getMockBuilder('Guzzle\Plugin\Cache\DefaultRevalidation') + ->setConstructorArgs(array($s)) + ->setMethods(array('createRevalidationRequest')) + ->getMock(); + + $rev->expects($this->once()) + ->method('createRevalidationRequest') + ->will($this->returnValue($badRequest)); + + try { + $rev->revalidate($request, $response); + $this->fail('Should have thrown an exception'); + } catch (BadResponseException $e) { + $this->assertSame($badResponse, $e->getResponse()); + $this->assertNull($s->fetch($request)); + } + } + + public function testCanRevalidateWithPlugin() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n" . + "Date: Mon, 12 Nov 2012 03:06:37 GMT\r\n" . + "Cache-Control: private, s-maxage=0, max-age=0, must-revalidate\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Content-Length: 2\r\n\r\nhi", + "HTTP/1.0 304 Not Modified\r\n" . + "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Age: 6302\r\n\r\n", + "HTTP/1.0 304 Not Modified\r\n" . + "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Age: 6302\r\n\r\n", + )); + $client = new Client($this->getServer()->getUrl()); + $client->addSubscriber(new CachePlugin()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(3, count($this->getServer()->getReceivedRequests())); + } + + public function testCanHandleRevalidationFailures() + { + $client = new Client($this->getServer()->getUrl()); + $lm = gmdate('c', time() - 60); + $mock = new MockPlugin(array( + new Response(200, array( + 'Date' => $lm, + 'Cache-Control' => 'max-age=100, must-revalidate, stale-if-error=9999', + 'Last-Modified' => $lm, + 'Content-Length' => 2 + ), 'hi'), + new CurlException('Bleh'), + new CurlException('Bleh') + )); + $client->addSubscriber(new CachePlugin()); + $client->addSubscriber($mock); + $client->get()->send(); + $response = $client->get()->send(); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('hi', $response->getBody(true)); + $this->assertEquals(3, count($mock->getReceivedRequests())); + $this->assertEquals(0, count($mock->getQueue())); + } + + public function testCanHandleStaleIfErrorWhenRevalidating() + { + $lm = gmdate('c', time() - 60); + $mock = new MockPlugin(array( + new Response(200, array( + 'Date' => $lm, + 'Cache-Control' => 'must-revalidate, max-age=0, stale-if-error=1200', + 'Last-Modified' => $lm, + 'Content-Length' => 2 + ), 'hi'), + new CurlException('Oh no!'), + new CurlException('Oh no!') + )); + $cache = new CachePlugin(); + $client = new Client('http://www.example.com'); + $client->addSubscriber($cache); + $client->addSubscriber($mock); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $response = $client->get()->send(); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertCount(0, $mock); + $this->assertEquals('HIT from GuzzleCache', (string) $response->getHeader('X-Cache-Lookup')); + $this->assertEquals('HIT_ERROR from GuzzleCache', (string) $response->getHeader('X-Cache')); + } + + /** + * @group issue-437 + */ + public function testDoesNotTouchClosureListeners() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n" . + "Date: Mon, 12 Nov 2012 03:06:37 GMT\r\n" . + "Cache-Control: private, s-maxage=0, max-age=0, must-revalidate\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Content-Length: 2\r\n\r\nhi", + "HTTP/1.0 304 Not Modified\r\n" . + "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Age: 6302\r\n\r\n", + "HTTP/1.0 304 Not Modified\r\n" . + "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Age: 6302\r\n\r\n", + )); + $client = new Client($this->getServer()->getUrl()); + $client->addSubscriber(new CachePlugin()); + $client->getEventDispatcher()->addListener('command.after_send', function(){}); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + } + +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php new file mode 100644 index 000000000..9af80f255 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php @@ -0,0 +1,19 @@ +assertFalse($deny->revalidate(new Request('GET', 'http://foo.com'), new Response(200))); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php new file mode 100644 index 000000000..4bcc04bfa --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php @@ -0,0 +1,19 @@ +assertTrue($skip->revalidate(new Request('GET', 'http://foo.com'), new Response(200))); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php new file mode 100644 index 000000000..5d0f668a6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php @@ -0,0 +1,385 @@ +jar = new ArrayCookieJar(); + } + + protected function getTestCookies() + { + return array( + new Cookie(array('name' => 'foo', 'value' => 'bar', 'domain' => 'foo.com', 'path' => '/', 'discard' => true)), + new Cookie(array('name' => 'test', 'value' => '123', 'domain' => 'baz.com', 'path' => '/foo', 'expires' => 2)), + new Cookie(array('name' => 'you', 'value' => '123', 'domain' => 'bar.com', 'path' => '/boo', 'expires' => time() + 1000)) + ); + } + + /** + * Provides test data for cookie cookieJar retrieval + */ + public function getCookiesDataProvider() + { + return array( + array(array('foo', 'baz', 'test', 'muppet', 'googoo'), '', '', '', false), + array(array('foo', 'baz', 'muppet', 'googoo'), '', '', '', true), + array(array('googoo'), 'www.example.com', '', '', false), + array(array('muppet', 'googoo'), 'test.y.example.com', '', '', false), + array(array('foo', 'baz'), 'example.com', '', '', false), + array(array('muppet'), 'x.y.example.com', '/acme/', '', false), + array(array('muppet'), 'x.y.example.com', '/acme/test/', '', false), + array(array('googoo'), 'x.y.example.com', '/test/acme/test/', '', false), + array(array('foo', 'baz'), 'example.com', '', '', false), + array(array('baz'), 'example.com', '', 'baz', false), + ); + } + + public function testStoresAndRetrievesCookies() + { + $cookies = $this->getTestCookies(); + foreach ($cookies as $cookie) { + $this->assertTrue($this->jar->add($cookie)); + } + + $this->assertEquals(3, count($this->jar)); + $this->assertEquals(3, count($this->jar->getIterator())); + $this->assertEquals($cookies, $this->jar->all(null, null, null, false, false)); + } + + public function testRemovesExpiredCookies() + { + $cookies = $this->getTestCookies(); + foreach ($this->getTestCookies() as $cookie) { + $this->jar->add($cookie); + } + $this->jar->removeExpired(); + $this->assertEquals(array($cookies[0], $cookies[2]), $this->jar->all()); + } + + public function testRemovesTemporaryCookies() + { + $cookies = $this->getTestCookies(); + foreach ($this->getTestCookies() as $cookie) { + $this->jar->add($cookie); + } + $this->jar->removeTemporary(); + $this->assertEquals(array($cookies[2]), $this->jar->all()); + } + + public function testIsSerializable() + { + $this->assertEquals('[]', $this->jar->serialize()); + $this->jar->unserialize('[]'); + $this->assertEquals(array(), $this->jar->all()); + + $cookies = $this->getTestCookies(); + foreach ($this->getTestCookies() as $cookie) { + $this->jar->add($cookie); + } + + // Remove discard and expired cookies + $serialized = $this->jar->serialize(); + $data = json_decode($serialized, true); + $this->assertEquals(1, count($data)); + + $a = new ArrayCookieJar(); + $a->unserialize($serialized); + $this->assertEquals(1, count($a)); + } + + public function testRemovesSelectively() + { + $cookies = $this->getTestCookies(); + foreach ($this->getTestCookies() as $cookie) { + $this->jar->add($cookie); + } + + // Remove foo.com cookies + $this->jar->remove('foo.com'); + $this->assertEquals(2, count($this->jar)); + // Try again, removing no further cookies + $this->jar->remove('foo.com'); + $this->assertEquals(2, count($this->jar)); + + // Remove bar.com cookies with path of /boo + $this->jar->remove('bar.com', '/boo'); + $this->assertEquals(1, count($this->jar)); + + // Remove cookie by name + $this->jar->remove(null, null, 'test'); + $this->assertEquals(0, count($this->jar)); + } + + public function testDoesNotAddIncompleteCookies() + { + $this->assertEquals(false, $this->jar->add(new Cookie())); + $this->assertFalse($this->jar->add(new Cookie(array( + 'name' => 'foo' + )))); + $this->assertFalse($this->jar->add(new Cookie(array( + 'name' => false + )))); + $this->assertFalse($this->jar->add(new Cookie(array( + 'name' => true + )))); + $this->assertFalse($this->jar->add(new Cookie(array( + 'name' => 'foo', + 'domain' => 'foo.com' + )))); + } + + public function testDoesAddValidCookies() + { + $this->assertTrue($this->jar->add(new Cookie(array( + 'name' => 'foo', + 'domain' => 'foo.com', + 'value' => 0 + )))); + $this->assertTrue($this->jar->add(new Cookie(array( + 'name' => 'foo', + 'domain' => 'foo.com', + 'value' => 0.0 + )))); + $this->assertTrue($this->jar->add(new Cookie(array( + 'name' => 'foo', + 'domain' => 'foo.com', + 'value' => '0' + )))); + } + + public function testOverwritesCookiesThatAreOlderOrDiscardable() + { + $t = time() + 1000; + $data = array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'max_age' => '86400', + 'port' => array(80, 8080), + 'version' => '1', + 'secure' => true, + 'discard' => true, + 'expires' => $t + ); + + // Make sure that the discard cookie is overridden with the non-discard + $this->assertTrue($this->jar->add(new Cookie($data))); + + unset($data['discard']); + $this->assertTrue($this->jar->add(new Cookie($data))); + $this->assertEquals(1, count($this->jar)); + + $c = $this->jar->all(); + $this->assertEquals(false, $c[0]->getDiscard()); + + // Make sure it doesn't duplicate the cookie + $this->jar->add(new Cookie($data)); + $this->assertEquals(1, count($this->jar)); + + // Make sure the more future-ful expiration date supersede the other + $data['expires'] = time() + 2000; + $this->assertTrue($this->jar->add(new Cookie($data))); + $this->assertEquals(1, count($this->jar)); + $c = $this->jar->all(); + $this->assertNotEquals($t, $c[0]->getExpires()); + } + + public function testOverwritesCookiesThatHaveChanged() + { + $t = time() + 1000; + $data = array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'max_age' => '86400', + 'port' => array(80, 8080), + 'version' => '1', + 'secure' => true, + 'discard' => true, + 'expires' => $t + ); + + // Make sure that the discard cookie is overridden with the non-discard + $this->assertTrue($this->jar->add(new Cookie($data))); + + $data['value'] = 'boo'; + $this->assertTrue($this->jar->add(new Cookie($data))); + $this->assertEquals(1, count($this->jar)); + + // Changing the value plus a parameter also must overwrite the existing one + $data['value'] = 'zoo'; + $data['secure'] = false; + $this->assertTrue($this->jar->add(new Cookie($data))); + $this->assertEquals(1, count($this->jar)); + + $c = $this->jar->all(); + $this->assertEquals('zoo', $c[0]->getValue()); + } + + public function testAddsCookiesFromResponseWithNoRequest() + { + $response = new Response(200, array( + 'Set-Cookie' => array( + "fpc=d=.Hm.yh4.1XmJWjJfs4orLQzKzPImxklQoxXSHOZATHUSEFciRueW_7704iYUtsXNEXq0M92Px2glMdWypmJ7HIQl6XIUvrZimWjQ3vIdeuRbI.FNQMAfcxu_XN1zSx7l.AcPdKL6guHc2V7hIQFhnjRW0rxm2oHY1P4bGQxFNz7f.tHm12ZD3DbdMDiDy7TBXsuP4DM-&v=2; expires=Fri, 02-Mar-2019 02:17:40 GMT; path=/; domain=127.0.0.1", + "FPCK3=AgBNbvoQAGpGEABZLRAAbFsQAF1tEABkDhAAeO0=; expires=Sat, 02-Apr-2019 02:17:40 GMT; path=/; domain=127.0.0.1", + "CH=deleted; expires=Wed, 03-Mar-2010 02:17:39 GMT; path=/; domain=127.0.0.1", + "CH=AgBNbvoQAAEcEAApuhAAMJcQADQvEAAvGxAALe0QAD6uEAATwhAAC1AQAC8t; expires=Sat, 02-Apr-2019 02:17:40 GMT; path=/; domain=127.0.0.1" + ) + )); + + $this->jar->addCookiesFromResponse($response); + $this->assertEquals(3, count($this->jar)); + $this->assertEquals(1, count($this->jar->all(null, null, 'fpc'))); + $this->assertEquals(1, count($this->jar->all(null, null, 'FPCK3'))); + $this->assertEquals(1, count($this->jar->all(null, null, 'CH'))); + } + + public function testAddsCookiesFromResponseWithRequest() + { + $response = new Response(200, array( + 'Set-Cookie' => "fpc=d=.Hm.yh4.1XmJWjJfs4orLQzKzPImxklQoxXSHOZATHUSEFciRueW_7704iYUtsXNEXq0M92Px2glMdWypmJ7HIQl6XIUvrZimWjQ3vIdeuRbI.FNQMAfcxu_XN1zSx7l.AcPdKL6guHc2V7hIQFhnjRW0rxm2oHY1P4bGQxFNz7f.tHm12ZD3DbdMDiDy7TBXsuP4DM-&v=2; expires=Fri, 02-Mar-2019 02:17:40 GMT;" + )); + $request = new Request('GET', 'http://www.example.com'); + $this->jar->addCookiesFromResponse($response, $request); + $this->assertEquals(1, count($this->jar)); + } + + public function getMatchingCookiesDataProvider() + { + return array( + array('https://example.com', array(0)), + array('http://example.com', array()), + array('https://example.com:8912', array()), + array('https://foo.example.com', array(0)), + array('http://foo.example.com/test/acme/', array(4)) + ); + } + + /** + * @dataProvider getMatchingCookiesDataProvider + */ + public function testReturnsCookiesMatchingRequests($url, $cookies) + { + $bag = array( + new Cookie(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => 'example.com', + 'path' => '/', + 'max_age' => '86400', + 'port' => array(443, 8080), + 'version' => '1', + 'secure' => true + )), + new Cookie(array( + 'name' => 'baz', + 'value' => 'foobar', + 'domain' => 'example.com', + 'path' => '/', + 'max_age' => '86400', + 'port' => array(80, 8080), + 'version' => '1', + 'secure' => true + )), + new Cookie(array( + 'name' => 'test', + 'value' => '123', + 'domain' => 'www.foobar.com', + 'path' => '/path/', + 'discard' => true + )), + new Cookie(array( + 'name' => 'muppet', + 'value' => 'cookie_monster', + 'domain' => '.y.example.com', + 'path' => '/acme/', + 'comment' => 'Comment goes here...', + 'expires' => time() + 86400 + )), + new Cookie(array( + 'name' => 'googoo', + 'value' => 'gaga', + 'domain' => '.example.com', + 'path' => '/test/acme/', + 'max_age' => 1500, + 'version' => 2 + )) + ); + + foreach ($bag as $cookie) { + $this->jar->add($cookie); + } + + $request = new Request('GET', $url); + $results = $this->jar->getMatchingCookies($request); + $this->assertEquals(count($cookies), count($results)); + foreach ($cookies as $i) { + $this->assertContains($bag[$i], $results); + } + } + + /** + * @expectedException \Guzzle\Plugin\Cookie\Exception\InvalidCookieException + * @expectedExceptionMessage The cookie name must not contain invalid characters: abc:@123 + */ + public function testThrowsExceptionWithStrictMode() + { + $a = new ArrayCookieJar(); + $a->setStrictMode(true); + $a->add(new Cookie(array( + 'name' => 'abc:@123', + 'value' => 'foo', + 'domain' => 'bar' + ))); + } + + public function testRemoveExistingCookieIfEmpty() + { + // Add a cookie that should not be affected + $a = new Cookie(array( + 'name' => 'foo', + 'value' => 'nope', + 'domain' => 'foo.com', + 'path' => '/abc' + )); + $this->jar->add($a); + + $data = array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => 'foo.com', + 'path' => '/' + ); + + $b = new Cookie($data); + $this->assertTrue($this->jar->add($b)); + $this->assertEquals(2, count($this->jar)); + + // Try to re-set the same cookie with no value: assert that cookie is not added + $data['value'] = null; + $this->assertFalse($this->jar->add(new Cookie($data))); + // assert that original cookie has been deleted + $cookies = $this->jar->all('foo.com'); + $this->assertTrue(in_array($a, $cookies, true)); + $this->assertFalse(in_array($b, $cookies, true)); + $this->assertEquals(1, count($this->jar)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php new file mode 100644 index 000000000..ac9471fd8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php @@ -0,0 +1,63 @@ +file = tempnam('/tmp', 'file-cookies'); + } + + public function testLoadsFromFileFile() + { + $jar = new FileCookieJar($this->file); + $this->assertEquals(array(), $jar->all()); + unlink($this->file); + } + + public function testPersistsToFileFile() + { + $jar = new FileCookieJar($this->file); + $jar->add(new Cookie(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => 'foo.com', + 'expires' => time() + 1000 + ))); + $jar->add(new Cookie(array( + 'name' => 'baz', + 'value' => 'bar', + 'domain' => 'foo.com', + 'expires' => time() + 1000 + ))); + $jar->add(new Cookie(array( + 'name' => 'boo', + 'value' => 'bar', + 'domain' => 'foo.com', + ))); + + $this->assertEquals(3, count($jar)); + unset($jar); + + // Make sure it wrote to the file + $contents = file_get_contents($this->file); + $this->assertNotEmpty($contents); + + // Load the cookieJar from the file + $jar = new FileCookieJar($this->file); + + // Weeds out temporary and session cookies + $this->assertEquals(2, count($jar)); + unset($jar); + unlink($this->file); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php new file mode 100644 index 000000000..f8c175cce --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php @@ -0,0 +1,134 @@ +getMockBuilder('Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar') + ->setMethods(array('addCookiesFromResponse')) + ->getMock(); + + $mock->expects($this->exactly(1)) + ->method('addCookiesFromResponse') + ->with($response); + + $plugin = new CookiePlugin($mock); + $plugin->onRequestSent(new Event(array( + 'response' => $response + ))); + } + + public function testAddsCookiesToRequests() + { + $cookie = new Cookie(array( + 'name' => 'foo', + 'value' => 'bar' + )); + + $mock = $this->getMockBuilder('Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar') + ->setMethods(array('getMatchingCookies')) + ->getMock(); + + $mock->expects($this->once()) + ->method('getMatchingCookies') + ->will($this->returnValue(array($cookie))); + + $plugin = new CookiePlugin($mock); + + $client = new Client(); + $client->getEventDispatcher()->addSubscriber($plugin); + + $request = $client->get('http://www.example.com'); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + + $this->assertEquals('bar', $request->getCookie('foo')); + } + + public function testCookiesAreExtractedFromRedirectResponses() + { + $plugin = new CookiePlugin(new ArrayCookieJar()); + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 302 Moved Temporarily\r\n" . + "Set-Cookie: test=583551; expires=Wednesday, 23-Mar-2050 19:49:45 GMT; path=/\r\n" . + "Location: /redirect\r\n\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Length: 0\r\n\r\n" + )); + + $client = new Client($this->getServer()->getUrl()); + $client->getEventDispatcher()->addSubscriber($plugin); + + $client->get()->send(); + $request = $client->get(); + $request->send(); + $this->assertEquals('test=583551', $request->getHeader('Cookie')); + + $requests = $this->getServer()->getReceivedRequests(true); + // Confirm subsequent requests have the cookie. + $this->assertEquals('test=583551', $requests[2]->getHeader('Cookie')); + // Confirm the redirected request has the cookie. + $this->assertEquals('test=583551', $requests[1]->getHeader('Cookie')); + } + + public function testCookiesAreNotAddedWhenParamIsSet() + { + $jar = new ArrayCookieJar(); + $plugin = new CookiePlugin($jar); + + $jar->add(new Cookie(array( + 'domain' => 'example.com', + 'path' => '/', + 'name' => 'test', + 'value' => 'hi', + 'expires' => time() + 3600 + ))); + + $client = new Client('http://example.com'); + $client->getEventDispatcher()->addSubscriber($plugin); + + // Ensure that it is normally added + $request = $client->get(); + $request->setResponse(new Response(200), true); + $request->send(); + $this->assertEquals('hi', $request->getCookie('test')); + + // Now ensure that it is not added + $request = $client->get(); + $request->getParams()->set('cookies.disable', true); + $request->setResponse(new Response(200), true); + $request->send(); + $this->assertNull($request->getCookie('test')); + } + + public function testProvidesCookieJar() + { + $jar = new ArrayCookieJar(); + $plugin = new CookiePlugin($jar); + $this->assertSame($jar, $plugin->getCookieJar()); + } + + public function testEscapesCookieDomains() + { + $cookie = new Cookie(array('domain' => '/foo/^$[A-Z]+/')); + $this->assertFalse($cookie->matchesDomain('foo')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php new file mode 100644 index 000000000..9fb0b4310 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php @@ -0,0 +1,223 @@ +assertEquals('/', $cookie->getPath()); + $this->assertEquals(array(), $cookie->getPorts()); + } + + public function testConvertsDateTimeMaxAgeToUnixTimestamp() + { + $cookie = new Cookie(array( + 'expires' => 'November 20, 1984' + )); + $this->assertTrue(is_numeric($cookie->getExpires())); + } + + public function testAddsExpiresBasedOnMaxAge() + { + $t = time(); + $cookie = new Cookie(array( + 'max_age' => 100 + )); + $this->assertEquals($t + 100, $cookie->getExpires()); + } + + public function testHoldsValues() + { + $t = time(); + $data = array( + 'name' => 'foo', + 'value' => 'baz', + 'path' => '/bar', + 'domain' => 'baz.com', + 'expires' => $t, + 'max_age' => 100, + 'comment' => 'Hi', + 'comment_url' => 'foo.com', + 'port' => array(1, 2), + 'version' => 2, + 'secure' => true, + 'discard' => true, + 'http_only' => true, + 'data' => array( + 'foo' => 'baz', + 'bar' => 'bam' + ) + ); + + $cookie = new Cookie($data); + $this->assertEquals($data, $cookie->toArray()); + + $this->assertEquals('foo', $cookie->getName()); + $this->assertEquals('baz', $cookie->getValue()); + $this->assertEquals('baz.com', $cookie->getDomain()); + $this->assertEquals('/bar', $cookie->getPath()); + $this->assertEquals($t, $cookie->getExpires()); + $this->assertEquals(100, $cookie->getMaxAge()); + $this->assertEquals('Hi', $cookie->getComment()); + $this->assertEquals('foo.com', $cookie->getCommentUrl()); + $this->assertEquals(array(1, 2), $cookie->getPorts()); + $this->assertEquals(2, $cookie->getVersion()); + $this->assertTrue($cookie->getSecure()); + $this->assertTrue($cookie->getDiscard()); + $this->assertTrue($cookie->getHttpOnly()); + $this->assertEquals('baz', $cookie->getAttribute('foo')); + $this->assertEquals('bam', $cookie->getAttribute('bar')); + $this->assertEquals(array( + 'foo' => 'baz', + 'bar' => 'bam' + ), $cookie->getAttributes()); + + $cookie->setName('a') + ->setValue('b') + ->setPath('c') + ->setDomain('bar.com') + ->setExpires(10) + ->setMaxAge(200) + ->setComment('e') + ->setCommentUrl('f') + ->setPorts(array(80)) + ->setVersion(3) + ->setSecure(false) + ->setHttpOnly(false) + ->setDiscard(false) + ->setAttribute('snoop', 'dog'); + + $this->assertEquals('a', $cookie->getName()); + $this->assertEquals('b', $cookie->getValue()); + $this->assertEquals('c', $cookie->getPath()); + $this->assertEquals('bar.com', $cookie->getDomain()); + $this->assertEquals(10, $cookie->getExpires()); + $this->assertEquals(200, $cookie->getMaxAge()); + $this->assertEquals('e', $cookie->getComment()); + $this->assertEquals('f', $cookie->getCommentUrl()); + $this->assertEquals(array(80), $cookie->getPorts()); + $this->assertEquals(3, $cookie->getVersion()); + $this->assertFalse($cookie->getSecure()); + $this->assertFalse($cookie->getDiscard()); + $this->assertFalse($cookie->getHttpOnly()); + $this->assertEquals('dog', $cookie->getAttribute('snoop')); + } + + public function testDeterminesIfExpired() + { + $c = new Cookie(); + $c->setExpires(10); + $this->assertTrue($c->isExpired()); + $c->setExpires(time() + 10000); + $this->assertFalse($c->isExpired()); + } + + public function testMatchesPorts() + { + $cookie = new Cookie(); + // Always matches when nothing is set + $this->assertTrue($cookie->matchesPort(2)); + + $cookie->setPorts(array(1, 2)); + $this->assertTrue($cookie->matchesPort(2)); + $this->assertFalse($cookie->matchesPort(100)); + } + + public function testMatchesDomain() + { + $cookie = new Cookie(); + $this->assertTrue($cookie->matchesDomain('baz.com')); + + $cookie->setDomain('baz.com'); + $this->assertTrue($cookie->matchesDomain('baz.com')); + $this->assertFalse($cookie->matchesDomain('bar.com')); + + $cookie->setDomain('.baz.com'); + $this->assertTrue($cookie->matchesDomain('.baz.com')); + $this->assertTrue($cookie->matchesDomain('foo.baz.com')); + $this->assertFalse($cookie->matchesDomain('baz.bar.com')); + $this->assertTrue($cookie->matchesDomain('baz.com')); + + $cookie->setDomain('.127.0.0.1'); + $this->assertTrue($cookie->matchesDomain('127.0.0.1')); + + $cookie->setDomain('127.0.0.1'); + $this->assertTrue($cookie->matchesDomain('127.0.0.1')); + + $cookie->setDomain('.com.'); + $this->assertFalse($cookie->matchesDomain('baz.com')); + + $cookie->setDomain('.local'); + $this->assertTrue($cookie->matchesDomain('example.local')); + } + + public function testMatchesPath() + { + $cookie = new Cookie(); + $this->assertTrue($cookie->matchesPath('/foo')); + + $cookie->setPath('/foo'); + + // o The cookie-path and the request-path are identical. + $this->assertTrue($cookie->matchesPath('/foo')); + $this->assertFalse($cookie->matchesPath('/bar')); + + // o The cookie-path is a prefix of the request-path, and the first + // character of the request-path that is not included in the cookie- + // path is a %x2F ("/") character. + $this->assertTrue($cookie->matchesPath('/foo/bar')); + $this->assertFalse($cookie->matchesPath('/fooBar')); + + // o The cookie-path is a prefix of the request-path, and the last + // character of the cookie-path is %x2F ("/"). + $cookie->setPath('/foo/'); + $this->assertTrue($cookie->matchesPath('/foo/bar')); + $this->assertFalse($cookie->matchesPath('/fooBaz')); + $this->assertFalse($cookie->matchesPath('/foo')); + + } + + public function cookieValidateProvider() + { + return array( + array('foo', 'baz', 'bar', true), + array('0', '0', '0', true), + array('', 'baz', 'bar', 'The cookie name must not be empty'), + array('foo', '', 'bar', 'The cookie value must not be empty'), + array('foo', 'baz', '', 'The cookie domain must not be empty'), + array('foo\\', 'baz', '0', 'The cookie name must not contain invalid characters: foo\\'), + ); + } + + /** + * @dataProvider cookieValidateProvider + */ + public function testValidatesCookies($name, $value, $domain, $result) + { + $cookie = new Cookie(array( + 'name' => $name, + 'value' => $value, + 'domain' => $domain + )); + $this->assertSame($result, $cookie->validate()); + } + + public function testCreatesInvalidCharacterString() + { + $m = new \ReflectionMethod('Guzzle\Plugin\Cookie\Cookie', 'getInvalidCharacters'); + $m->setAccessible(true); + $p = new \ReflectionProperty('Guzzle\Plugin\Cookie\Cookie', 'invalidCharString'); + $p->setAccessible(true); + $p->setValue(''); + // Expects a string containing 51 invalid characters + $this->assertEquals(51, strlen($m->invoke($m))); + $this->assertContains('@', $m->invoke($m)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php new file mode 100644 index 000000000..2a4b49eb6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php @@ -0,0 +1,39 @@ +getEventDispatcher()->addSubscriber($plugin); + $request = $client->get('/'); + $this->assertEquals('michael', $request->getUsername()); + $this->assertEquals('test', $request->getPassword()); + Version::$emitWarnings = true; + } + + public function testAddsDigestAuthentication() + { + Version::$emitWarnings = false; + $plugin = new CurlAuthPlugin('julian', 'test', CURLAUTH_DIGEST); + $client = new Client('http://www.test.com/'); + $client->getEventDispatcher()->addSubscriber($plugin); + $request = $client->get('/'); + $this->assertEquals('julian', $request->getUsername()); + $this->assertEquals('test', $request->getPassword()); + $this->assertEquals('julian:test', $request->getCurlOptions()->get(CURLOPT_USERPWD)); + $this->assertEquals(CURLAUTH_DIGEST, $request->getCurlOptions()->get(CURLOPT_HTTPAUTH)); + Version::$emitWarnings = true; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php new file mode 100644 index 000000000..6f94186b5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php @@ -0,0 +1,137 @@ +flush(); + } + + public function setUp() + { + $mockError = 'Guzzle\Tests\Mock\ErrorResponseMock'; + $description = ServiceDescription::factory(array( + 'operations' => array( + 'works' => array( + 'httpMethod' => 'GET', + 'errorResponses' => array( + array('code' => 500, 'class' => $mockError), + array('code' => 503, 'reason' => 'foo', 'class' => $mockError), + array('code' => 200, 'reason' => 'Error!', 'class' => $mockError) + ) + ), + 'bad_class' => array( + 'httpMethod' => 'GET', + 'errorResponses' => array( + array('code' => 500, 'class' => 'Does\\Not\\Exist') + ) + ), + 'does_not_implement' => array( + 'httpMethod' => 'GET', + 'errorResponses' => array( + array('code' => 500, 'class' => __CLASS__) + ) + ), + 'no_errors' => array('httpMethod' => 'GET'), + 'no_class' => array( + 'httpMethod' => 'GET', + 'errorResponses' => array( + array('code' => 500) + ) + ), + ) + )); + $this->client = new Client($this->getServer()->getUrl()); + $this->client->setDescription($description); + } + + /** + * @expectedException \Guzzle\Http\Exception\ServerErrorResponseException + */ + public function testSkipsWhenErrorResponsesIsNotSet() + { + $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('no_errors')->execute(); + } + + public function testSkipsWhenErrorResponsesIsNotSetAndAllowsSuccess() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('no_errors')->execute(); + } + + /** + * @expectedException \Guzzle\Plugin\ErrorResponse\Exception\ErrorResponseException + * @expectedExceptionMessage Does\Not\Exist does not exist + */ + public function testEnsuresErrorResponseExists() + { + $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('bad_class')->execute(); + } + + /** + * @expectedException \Guzzle\Plugin\ErrorResponse\Exception\ErrorResponseException + * @expectedExceptionMessage must implement Guzzle\Plugin\ErrorResponse\ErrorResponseExceptionInterface + */ + public function testEnsuresErrorResponseImplementsInterface() + { + $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('does_not_implement')->execute(); + } + + public function testThrowsSpecificErrorResponseOnMatch() + { + try { + $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $command = $this->client->getCommand('works'); + $command->execute(); + $this->fail('Exception not thrown'); + } catch (ErrorResponseMock $e) { + $this->assertSame($command, $e->command); + $this->assertEquals(500, $e->response->getStatusCode()); + } + } + + /** + * @expectedException \Guzzle\Tests\Mock\ErrorResponseMock + */ + public function testThrowsWhenCodeAndPhraseMatch() + { + $this->getServer()->enqueue("HTTP/1.1 200 Error!\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('works')->execute(); + } + + public function testSkipsWhenReasonDoesNotMatch() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('works')->execute(); + } + + public function testSkipsWhenNoClassIsSet() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('no_class')->execute(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php new file mode 100644 index 000000000..41aa673fd --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php @@ -0,0 +1,140 @@ +get(); + $requests[$i]->setResponse(new Response(200), true); + $requests[$i]->send(); + $h->add($requests[$i]); + } + + return $requests; + } + + public function testDescribesSubscribedEvents() + { + $this->assertInternalType('array', HistoryPlugin::getSubscribedEvents()); + } + + public function testMaintainsLimitValue() + { + $h = new HistoryPlugin(); + $this->assertSame($h, $h->setLimit(10)); + $this->assertEquals(10, $h->getLimit()); + } + + public function testAddsRequests() + { + $h = new HistoryPlugin(); + $requests = $this->addRequests($h, 1); + $this->assertEquals(1, count($h)); + $i = $h->getIterator(); + $this->assertEquals(1, count($i)); + $this->assertEquals($requests[0], $i[0]); + } + + /** + * @depends testAddsRequests + */ + public function testMaintainsLimit() + { + $h = new HistoryPlugin(); + $h->setLimit(2); + $requests = $this->addRequests($h, 3); + $this->assertEquals(2, count($h)); + $i = 0; + foreach ($h as $request) { + if ($i > 0) { + $this->assertSame($requests[$i], $request); + } + } + } + + public function testReturnsLastRequest() + { + $h = new HistoryPlugin(); + $requests = $this->addRequests($h, 5); + $this->assertSame(end($requests), $h->getLastRequest()); + } + + public function testReturnsLastResponse() + { + $h = new HistoryPlugin(); + $requests = $this->addRequests($h, 5); + $this->assertSame(end($requests)->getResponse(), $h->getLastResponse()); + } + + public function testClearsHistory() + { + $h = new HistoryPlugin(); + $requests = $this->addRequests($h, 5); + $this->assertEquals(5, count($h)); + $h->clear(); + $this->assertEquals(0, count($h)); + } + + /** + * @depends testAddsRequests + */ + public function testUpdatesAddRequests() + { + $h = new HistoryPlugin(); + $client = new Client('http://127.0.0.1/'); + $client->getEventDispatcher()->addSubscriber($h); + + $request = $client->get(); + $request->setResponse(new Response(200), true); + $request->send(); + + $this->assertSame($request, $h->getLastRequest()); + } + + public function testCanCastToString() + { + $client = new Client('http://127.0.0.1/'); + $h = new HistoryPlugin(); + $client->getEventDispatcher()->addSubscriber($h); + + $mock = new MockPlugin(array( + new Response(301, array('Location' => '/redirect1', 'Content-Length' => 0)), + new Response(307, array('Location' => '/redirect2', 'Content-Length' => 0)), + new Response(200, array('Content-Length' => '2'), 'HI') + )); + + $client->getEventDispatcher()->addSubscriber($mock); + $request = $client->get(); + $request->send(); + $this->assertEquals(3, count($h)); + $this->assertEquals(3, count($mock->getReceivedRequests())); + + $h = str_replace("\r", '', $h); + $this->assertContains("> GET / HTTP/1.1\nHost: 127.0.0.1\nUser-Agent:", $h); + $this->assertContains("< HTTP/1.1 301 Moved Permanently\nLocation: /redirect1", $h); + $this->assertContains("< HTTP/1.1 307 Temporary Redirect\nLocation: /redirect2", $h); + $this->assertContains("< HTTP/1.1 200 OK\nContent-Length: 2\n\nHI", $h); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php new file mode 100644 index 000000000..ad663a53c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php @@ -0,0 +1,95 @@ +adapter = new ClosureLogAdapter(function ($message) { + echo $message; + }); + } + + public function testIgnoresCurlEventsWhenNotWiringBodies() + { + $p = new LogPlugin($this->adapter); + $this->assertNotEmpty($p->getSubscribedEvents()); + $event = new Event(array('request' => new Request('GET', 'http://foo.com'))); + $p->onCurlRead($event); + $p->onCurlWrite($event); + $p->onRequestBeforeSend($event); + } + + public function testLogsWhenComplete() + { + $output = ''; + $p = new LogPlugin(new ClosureLogAdapter(function ($message) use (&$output) { + $output = $message; + }), '{method} {resource} | {code} {res_body}'); + + $p->onRequestSent(new Event(array( + 'request' => new Request('GET', 'http://foo.com'), + 'response' => new Response(200, array(), 'Foo') + ))); + + $this->assertEquals('GET / | 200 Foo', $output); + } + + public function testWiresBodiesWhenNeeded() + { + $client = new Client($this->getServer()->getUrl()); + $plugin = new LogPlugin($this->adapter, '{req_body} | {res_body}', true); + $client->getEventDispatcher()->addSubscriber($plugin); + $request = $client->put(); + + // Send the response from the dummy server as the request body + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nsend"); + $stream = fopen($this->getServer()->getUrl(), 'r'); + $request->setBody(EntityBody::factory($stream, 4)); + + $tmpFile = tempnam(sys_get_temp_dir(), 'non_repeatable'); + $request->setResponseBody(EntityBody::factory(fopen($tmpFile, 'w'))); + + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nresponse"); + + ob_start(); + $request->send(); + $message = ob_get_clean(); + + unlink($tmpFile); + $this->assertContains("send", $message); + $this->assertContains("response", $message); + } + + public function testHasHelpfulStaticFactoryMethod() + { + $s = fopen('php://temp', 'r+'); + $client = new Client(); + $client->addSubscriber(LogPlugin::getDebugPlugin(true, $s)); + $request = $client->put('http://foo.com', array('Content-Type' => 'Foo'), 'Bar'); + $request->setresponse(new Response(200), true); + $request->send(); + rewind($s); + $contents = stream_get_contents($s); + $this->assertContains('# Request:', $contents); + $this->assertContainsIns('PUT / HTTP/1.1', $contents); + $this->assertContains('# Response:', $contents); + $this->assertContainsIns('HTTP/1.1 200 OK', $contents); + $this->assertContains('# Errors:', $contents); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php new file mode 100644 index 000000000..4bd411113 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php @@ -0,0 +1,97 @@ + array( + 'test' => array( + 'httpMethod' => 'PUT', + 'parameters' => array( + 'ContentMD5' => array(), + 'Body' => array( + 'location' => 'body' + ) + ) + ) + ) + )); + + $client = new Client(); + $client->setDescription($description); + + return $client; + } + + public function testHasEvents() + { + $this->assertNotEmpty(CommandContentMd5Plugin::getSubscribedEvents()); + } + + public function testValidatesMd5WhenParamExists() + { + $client = $this->getClient(); + $command = $client->getCommand('test', array( + 'Body' => 'Foo', + 'ContentMD5' => true + )); + $event = new Event(array('command' => $command)); + $request = $command->prepare(); + $plugin = new CommandContentMd5Plugin(); + $plugin->onCommandBeforeSend($event); + $this->assertEquals('E1bGfXrRY42Ba/uCLdLCXQ==', (string) $request->getHeader('Content-MD5')); + } + + public function testDoesNothingWhenNoPayloadExists() + { + $client = $this->getClient(); + $client->getDescription()->getOperation('test')->setHttpMethod('GET'); + $command = $client->getCommand('test'); + $event = new Event(array('command' => $command)); + $request = $command->prepare(); + $plugin = new CommandContentMd5Plugin(); + $plugin->onCommandBeforeSend($event); + $this->assertNull($request->getHeader('Content-MD5')); + } + + public function testAddsValidationToResponsesOfContentMd5() + { + $client = $this->getClient(); + $client->getDescription()->getOperation('test')->setHttpMethod('GET'); + $command = $client->getCommand('test', array( + 'ValidateMD5' => true + )); + $event = new Event(array('command' => $command)); + $request = $command->prepare(); + $plugin = new CommandContentMd5Plugin(); + $plugin->onCommandBeforeSend($event); + $listeners = $request->getEventDispatcher()->getListeners('request.complete'); + $this->assertNotEmpty($listeners); + } + + public function testIgnoresValidationWhenDisabled() + { + $client = $this->getClient(); + $client->getDescription()->getOperation('test')->setHttpMethod('GET'); + $command = $client->getCommand('test', array( + 'ValidateMD5' => false + )); + $event = new Event(array('command' => $command)); + $request = $command->prepare(); + $plugin = new CommandContentMd5Plugin(); + $plugin->onCommandBeforeSend($event); + $listeners = $request->getEventDispatcher()->getListeners('request.complete'); + $this->assertEmpty($listeners); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php new file mode 100644 index 000000000..482e92b28 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php @@ -0,0 +1,120 @@ +create('GET', 'http://www.test.com/'); + $request->getEventDispatcher()->addSubscriber($plugin); + + $body = 'abc'; + $hash = md5($body); + $response = new Response(200, array( + 'Content-MD5' => $hash, + 'Content-Length' => 3 + ), 'abc'); + + $request->dispatch('request.complete', array( + 'response' => $response + )); + + // Try again with no Content-MD5 + $response->removeHeader('Content-MD5'); + $request->dispatch('request.complete', array( + 'response' => $response + )); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testThrowsExceptionOnInvalidMd5() + { + $plugin = new Md5ValidatorPlugin(); + $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/'); + $request->getEventDispatcher()->addSubscriber($plugin); + + $request->dispatch('request.complete', array( + 'response' => new Response(200, array( + 'Content-MD5' => 'foobar', + 'Content-Length' => 3 + ), 'abc') + )); + } + + public function testSkipsWhenContentLengthIsTooLarge() + { + $plugin = new Md5ValidatorPlugin(false, 1); + $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/'); + $request->getEventDispatcher()->addSubscriber($plugin); + + $request->dispatch('request.complete', array( + 'response' => new Response(200, array( + 'Content-MD5' => 'foobar', + 'Content-Length' => 3 + ), 'abc') + )); + } + + public function testProperlyValidatesWhenUsingContentEncoding() + { + $plugin = new Md5ValidatorPlugin(true); + $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/'); + $request->getEventDispatcher()->addSubscriber($plugin); + + // Content-MD5 is the MD5 hash of the canonical content after all + // content-encoding has been applied. Because cURL will automatically + // decompress entity bodies, we need to re-compress it to calculate. + $body = EntityBody::factory('abc'); + $body->compress(); + $hash = $body->getContentMd5(); + $body->uncompress(); + + $response = new Response(200, array( + 'Content-MD5' => $hash, + 'Content-Encoding' => 'gzip' + ), 'abc'); + $request->dispatch('request.complete', array( + 'response' => $response + )); + $this->assertEquals('abc', $response->getBody(true)); + + // Try again with an unknown encoding + $response = new Response(200, array( + 'Content-MD5' => $hash, + 'Content-Encoding' => 'foobar' + ), 'abc'); + $request->dispatch('request.complete', array( + 'response' => $response + )); + + // Try again with compress + $body->compress('bzip2.compress'); + $response = new Response(200, array( + 'Content-MD5' => $body->getContentMd5(), + 'Content-Encoding' => 'compress' + ), 'abc'); + $request->dispatch('request.complete', array( + 'response' => $response + )); + + // Try again with encoding and disabled content-encoding checks + $request->getEventDispatcher()->removeSubscriber($plugin); + $plugin = new Md5ValidatorPlugin(false); + $request->getEventDispatcher()->addSubscriber($plugin); + $request->dispatch('request.complete', array( + 'response' => $response + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php new file mode 100644 index 000000000..3af8fefcc --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php @@ -0,0 +1,199 @@ +assertInternalType('array', MockPlugin::getSubscribedEvents()); + } + + public function testDescribesEvents() + { + $this->assertInternalType('array', MockPlugin::getAllEvents()); + } + + public function testCanBeTemporary() + { + $plugin = new MockPlugin(); + $this->assertFalse($plugin->isTemporary()); + $plugin = new MockPlugin(null, true); + $this->assertTrue($plugin->isTemporary()); + } + + public function testIsCountable() + { + $plugin = new MockPlugin(); + $plugin->addResponse(Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $this->assertEquals(1, count($plugin)); + } + + /** + * @depends testIsCountable + */ + public function testCanClearQueue() + { + $plugin = new MockPlugin(); + $plugin->addResponse(Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $plugin->clearQueue(); + $this->assertEquals(0, count($plugin)); + } + + public function testCanInspectQueue() + { + $plugin = new MockPlugin(); + $this->assertInternalType('array', $plugin->getQueue()); + $plugin->addResponse(Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $queue = $plugin->getQueue(); + $this->assertInternalType('array', $queue); + $this->assertEquals(1, count($queue)); + } + + public function testRetrievesResponsesFromFiles() + { + $response = MockPlugin::getMockFile(__DIR__ . '/../../TestData/mock_response'); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response); + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testThrowsExceptionWhenResponseFileIsNotFound() + { + MockPlugin::getMockFile('missing/filename'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidResponsesThrowAnException() + { + $p = new MockPlugin(); + $p->addResponse($this); + } + + public function testAddsResponseObjectsToQueue() + { + $p = new MockPlugin(); + $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $p->addResponse($response); + $this->assertEquals(array($response), $p->getQueue()); + } + + public function testAddsResponseFilesToQueue() + { + $p = new MockPlugin(); + $p->addResponse(__DIR__ . '/../../TestData/mock_response'); + $this->assertEquals(1, count($p)); + } + + /** + * @depends testAddsResponseFilesToQueue + */ + public function testAddsMockResponseToRequestFromClient() + { + $p = new MockPlugin(); + $response = MockPlugin::getMockFile(__DIR__ . '/../../TestData/mock_response'); + $p->addResponse($response); + + $client = new Client('http://127.0.0.1:123/'); + $client->getEventDispatcher()->addSubscriber($p, 9999); + $request = $client->get(); + $request->send(); + + $this->assertSame($response, $request->getResponse()); + $this->assertEquals(0, count($p)); + } + + /** + * @depends testAddsResponseFilesToQueue + * @expectedException \OutOfBoundsException + */ + public function testUpdateThrowsExceptionWhenEmpty() + { + $p = new MockPlugin(); + $p->onRequestBeforeSend(new Event()); + } + + /** + * @depends testAddsMockResponseToRequestFromClient + */ + public function testDetachesTemporaryWhenEmpty() + { + $p = new MockPlugin(null, true); + $p->addResponse(MockPlugin::getMockFile(__DIR__ . '/../../TestData/mock_response')); + $client = new Client('http://127.0.0.1:123/'); + $client->getEventDispatcher()->addSubscriber($p, 9999); + $request = $client->get(); + $request->send(); + + $this->assertFalse($this->hasSubscriber($client, $p)); + } + + public function testLoadsResponsesFromConstructor() + { + $p = new MockPlugin(array(new Response(200))); + $this->assertEquals(1, $p->count()); + } + + public function testStoresMockedRequests() + { + $p = new MockPlugin(array(new Response(200), new Response(200))); + $client = new Client('http://127.0.0.1:123/'); + $client->getEventDispatcher()->addSubscriber($p, 9999); + + $request1 = $client->get(); + $request1->send(); + $this->assertEquals(array($request1), $p->getReceivedRequests()); + + $request2 = $client->get(); + $request2->send(); + $this->assertEquals(array($request1, $request2), $p->getReceivedRequests()); + + $p->flush(); + $this->assertEquals(array(), $p->getReceivedRequests()); + } + + public function testReadsBodiesFromMockedRequests() + { + $p = new MockPlugin(array(new Response(200))); + $p->readBodies(true); + $client = new Client('http://127.0.0.1:123/'); + $client->getEventDispatcher()->addSubscriber($p, 9999); + + $body = EntityBody::factory('foo'); + $request = $client->put(); + $request->setBody($body); + $request->send(); + $this->assertEquals(3, $body->ftell()); + } + + public function testCanMockBadRequestExceptions() + { + $client = new Client('http://127.0.0.1:123/'); + $ex = new CurlException('Foo'); + $mock = new MockPlugin(array($ex)); + $client->addSubscriber($mock); + $request = $client->get('foo'); + + try { + $request->send(); + $this->fail('Did not dequeue an exception'); + } catch (CurlException $e) { + $this->assertSame($e, $ex); + $this->assertSame($request, $ex->getRequest()); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php new file mode 100644 index 000000000..3892fb62b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php @@ -0,0 +1,345 @@ + 'foo', + 'consumer_secret' => 'bar', + 'token' => 'count', + 'token_secret' => 'dracula' + ); + + protected function getRequest() + { + return RequestFactory::getInstance()->create('POST', 'http://www.test.com/path?a=b&c=d', null, array( + 'e' => 'f' + )); + } + + public function testSubscribesToEvents() + { + $events = OauthPlugin::getSubscribedEvents(); + $this->assertArrayHasKey('request.before_send', $events); + } + + public function testAcceptsConfigurationData() + { + $p = new OauthPlugin($this->config); + + // Access the config object + $class = new \ReflectionClass($p); + $property = $class->getProperty('config'); + $property->setAccessible(true); + $config = $property->getValue($p); + + $this->assertEquals('foo', $config['consumer_key']); + $this->assertEquals('bar', $config['consumer_secret']); + $this->assertEquals('count', $config['token']); + $this->assertEquals('dracula', $config['token_secret']); + $this->assertEquals('1.0', $config['version']); + $this->assertEquals('HMAC-SHA1', $config['signature_method']); + $this->assertEquals('header', $config['request_method']); + } + + public function testCreatesStringToSignFromPostRequest() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $signString = $p->getStringToSign($request, self::TIMESTAMP, self::NONCE); + + $this->assertContains('&e=f', rawurldecode($signString)); + + $expectedSignString = + // Method and URL + 'POST&http%3A%2F%2Fwww.test.com%2Fpath' . + // Sorted parameters from query string and body + '&a%3Db%26c%3Dd%26e%3Df%26oauth_consumer_key%3Dfoo' . + '%26oauth_nonce%3De7aa11195ca58349bec8b5ebe351d3497eb9e603%26' . + 'oauth_signature_method%3DHMAC-SHA1' . + '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_token%3Dcount%26oauth_version%3D1.0'; + + $this->assertEquals($expectedSignString, $signString); + } + + public function testCreatesStringToSignIgnoringPostFields() + { + $config = $this->config; + $config['disable_post_params'] = true; + $p = new OauthPlugin($config); + $request = $this->getRequest(); + $sts = rawurldecode($p->getStringToSign($request, self::TIMESTAMP, self::NONCE)); + $this->assertNotContains('&e=f', $sts); + } + + public function testCreatesStringToSignFromPostRequestWithCustomContentType() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $request->setHeader('Content-Type', 'Foo'); + $this->assertEquals( + // Method and URL + 'POST&http%3A%2F%2Fwww.test.com%2Fpath' . + // Sorted parameters from query string and body + '&a%3Db%26c%3Dd%26oauth_consumer_key%3Dfoo' . + '%26oauth_nonce%3D'. self::NONCE .'%26' . + 'oauth_signature_method%3DHMAC-SHA1' . + '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_token%3Dcount%26oauth_version%3D1.0', + $p->getStringToSign($request, self::TIMESTAMP, self::NONCE) + ); + } + + /** + * @depends testCreatesStringToSignFromPostRequest + */ + public function testConvertsBooleansToStrings() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $request->getQuery()->set('a', true); + $request->getQuery()->set('c', false); + $this->assertContains('&a%3Dtrue%26c%3Dfalse', $p->getStringToSign($request, self::TIMESTAMP, self::NONCE)); + } + + public function testCreatesStringToSignFromPostRequestWithNullValues() + { + $config = array( + 'consumer_key' => 'foo', + 'consumer_secret' => 'bar', + 'token' => null, + 'token_secret' => 'dracula' + ); + + $p = new OauthPlugin($config); + $request = $this->getRequest(); + $signString = $p->getStringToSign($request, self::TIMESTAMP, self::NONCE); + + $this->assertContains('&e=f', rawurldecode($signString)); + + $expectedSignString = // Method and URL + 'POST&http%3A%2F%2Fwww.test.com%2Fpath' . + // Sorted parameters from query string and body + '&a%3Db%26c%3Dd%26e%3Df%26oauth_consumer_key%3Dfoo' . + '%26oauth_nonce%3De7aa11195ca58349bec8b5ebe351d3497eb9e603%26' . + 'oauth_signature_method%3DHMAC-SHA1' . + '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_version%3D1.0'; + + $this->assertEquals($expectedSignString, $signString); + } + + /** + * @depends testCreatesStringToSignFromPostRequest + */ + public function testMultiDimensionalArray() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $request->getQuery()->set('a', array('b' => array('e' => 'f', 'c' => 'd'))); + $this->assertContains('a%255Bb%255D%255Bc%255D%3Dd%26a%255Bb%255D%255Be%255D%3Df%26c%3Dd%26e%3Df%26', $p->getStringToSign($request, self::TIMESTAMP, self::NONCE)); + } + + /** + * @depends testMultiDimensionalArray + */ + public function testMultiDimensionalArrayWithNonDefaultQueryAggregator() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $aggregator = new CommaAggregator(); + $query = $request->getQuery()->setAggregator($aggregator) + ->set('g', array('h', 'i', 'j')) + ->set('k', array('l')) + ->set('m', array('n', 'o')); + $this->assertContains('a%3Db%26c%3Dd%26e%3Df%26g%3Dh%2Ci%2Cj%26k%3Dl%26m%3Dn%2Co', $p->getStringToSign($request, self::TIMESTAMP, self::NONCE)); + } + + /** + * @depends testCreatesStringToSignFromPostRequest + */ + public function testSignsStrings() + { + $p = new OauthPlugin(array_merge($this->config, array( + 'signature_callback' => function($string, $key) { + return "_{$string}|{$key}_"; + } + ))); + $request = $this->getRequest(); + $sig = $p->getSignature($request, self::TIMESTAMP, self::NONCE); + $this->assertEquals( + '_POST&http%3A%2F%2Fwww.test.com%2Fpath&a%3Db%26c%3Dd%26e%3Df%26oauth_consumer_key%3Dfoo' . + '%26oauth_nonce%3D'. self::NONCE .'%26oauth_signature_method%3DHMAC-SHA1' . + '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_token%3Dcount%26oauth_version%3D1.0|' . + 'bar&dracula_', + base64_decode($sig) + ); + } + + /** + * Test that the Oauth is signed correctly and that extra strings haven't been added + * to the authorization header. + */ + public function testSignsOauthRequests() + { + $p = new OauthPlugin($this->config); + $event = new Event(array( + 'request' => $this->getRequest(), + 'timestamp' => self::TIMESTAMP + )); + $params = $p->onRequestBeforeSend($event); + + $this->assertTrue($event['request']->hasHeader('Authorization')); + + $authorizationHeader = (string)$event['request']->getHeader('Authorization'); + + $this->assertStringStartsWith('OAuth ', $authorizationHeader); + + $stringsToCheck = array( + 'oauth_consumer_key="foo"', + 'oauth_nonce="'.urlencode($params['oauth_nonce']).'"', + 'oauth_signature="'.urlencode($params['oauth_signature']).'"', + 'oauth_signature_method="HMAC-SHA1"', + 'oauth_timestamp="' . self::TIMESTAMP . '"', + 'oauth_token="count"', + 'oauth_version="1.0"', + ); + + $totalLength = strlen('OAuth '); + + //Separator is not used before first parameter. + $separator = ''; + + foreach ($stringsToCheck as $stringToCheck) { + $this->assertContains($stringToCheck, $authorizationHeader); + $totalLength += strlen($separator); + $totalLength += strlen($stringToCheck); + $separator = ', '; + } + + // Technically this test is not universally valid. It would be allowable to have extra \n characters + // in the Authorization header. However Guzzle does not do this, so we just perform a simple check + // on length to validate the Authorization header is composed of only the strings above. + $this->assertEquals($totalLength, strlen($authorizationHeader), 'Authorization has extra characters i.e. contains extra elements compared to stringsToCheck.'); + } + + public function testSignsOauthQueryStringRequest() + { + $config = array_merge( + $this->config, + array('request_method' => OauthPlugin::REQUEST_METHOD_QUERY) + ); + + $p = new OauthPlugin($config); + $event = new Event(array( + 'request' => $this->getRequest(), + 'timestamp' => self::TIMESTAMP + )); + $params = $p->onRequestBeforeSend($event); + + $this->assertFalse($event['request']->hasHeader('Authorization')); + + $stringsToCheck = array( + 'a=b', + 'c=d', + 'oauth_consumer_key=foo', + 'oauth_nonce='.urlencode($params['oauth_nonce']), + 'oauth_signature='.urlencode($params['oauth_signature']), + 'oauth_signature_method=HMAC-SHA1', + 'oauth_timestamp='.self::TIMESTAMP, + 'oauth_token=count', + 'oauth_version=1.0', + ); + + $queryString = (string) $event['request']->getQuery(); + + $totalLength = strlen('?'); + + //Separator is not used before first parameter. + $separator = ''; + + foreach ($stringsToCheck as $stringToCheck) { + $this->assertContains($stringToCheck, $queryString); + $totalLength += strlen($separator); + $totalLength += strlen($stringToCheck); + $separator = '&'; + } + + // Removes the last query string separator '&' + $totalLength -= 1; + + $this->assertEquals($totalLength, strlen($queryString), 'Query string has extra characters i.e. contains extra elements compared to stringsToCheck.'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidArgumentExceptionOnMethodError() + { + $config = array_merge( + $this->config, + array('request_method' => 'FakeMethod') + ); + + $p = new OauthPlugin($config); + $event = new Event(array( + 'request' => $this->getRequest(), + 'timestamp' => self::TIMESTAMP + )); + + $p->onRequestBeforeSend($event); + } + + public function testDoesNotAddFalseyValuesToAuthorization() + { + unset($this->config['token']); + $p = new OauthPlugin($this->config); + $event = new Event(array('request' => $this->getRequest(), 'timestamp' => self::TIMESTAMP)); + $p->onRequestBeforeSend($event); + $this->assertTrue($event['request']->hasHeader('Authorization')); + $this->assertNotContains('oauth_token=', (string) $event['request']->getHeader('Authorization')); + } + + public function testOptionalOauthParametersAreNotAutomaticallyAdded() + { + // The only required Oauth parameters are the consumer key and secret. That is enough credentials + // for signing oauth requests. + $config = array( + 'consumer_key' => 'foo', + 'consumer_secret' => 'bar', + ); + + $plugin = new OauthPlugin($config); + $event = new Event(array( + 'request' => $this->getRequest(), + 'timestamp' => self::TIMESTAMP + )); + + $timestamp = $plugin->getTimestamp($event); + $request = $event['request']; + $nonce = $plugin->generateNonce($request); + + $paramsToSign = $plugin->getParamsToSign($request, $timestamp, $nonce); + + $optionalParams = array( + 'callback' => 'oauth_callback', + 'token' => 'oauth_token', + 'verifier' => 'oauth_verifier', + 'token_secret' => 'token_secret' + ); + + foreach ($optionalParams as $optionName => $oauthName) { + $this->assertArrayNotHasKey($oauthName, $paramsToSign, "Optional Oauth param '$oauthName' was not set via config variable '$optionName', but it is listed in getParamsToSign()."); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php new file mode 100644 index 000000000..8b42fb839 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php @@ -0,0 +1,149 @@ +loader = $this->getMockBuilder('Guzzle\Service\AbstractConfigLoader') + ->setMethods(array('build')) + ->getMockForAbstractClass(); + } + + public function tearDown() + { + foreach ($this->cleanup as $file) { + unlink($file); + } + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testOnlyLoadsSupportedTypes() + { + $this->loader->load(new \stdClass()); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + * @expectedExceptionMessage Unable to open fooooooo.json + */ + public function testFileMustBeReadable() + { + $this->loader->load('fooooooo.json'); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + * @expectedExceptionMessage Unknown file extension + */ + public function testMustBeSupportedExtension() + { + $this->loader->load(dirname(__DIR__) . '/TestData/FileBody.txt'); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage Error loading JSON data from + */ + public function testJsonMustBeValue() + { + $filename = tempnam(sys_get_temp_dir(), 'json') . '.json'; + file_put_contents($filename, '{/{./{}foo'); + $this->cleanup[] = $filename; + $this->loader->load($filename); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + * @expectedExceptionMessage PHP files must return an array + */ + public function testPhpFilesMustReturnAnArray() + { + $filename = tempnam(sys_get_temp_dir(), 'php') . '.php'; + file_put_contents($filename, 'cleanup[] = $filename; + $this->loader->load($filename); + } + + public function testLoadsPhpFileIncludes() + { + $filename = tempnam(sys_get_temp_dir(), 'php') . '.php'; + file_put_contents($filename, ' "bar");'); + $this->cleanup[] = $filename; + $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0)); + $config = $this->loader->load($filename); + $this->assertEquals(array('foo' => 'bar'), $config); + } + + public function testCanCreateFromJson() + { + $file = dirname(__DIR__) . '/TestData/services/json1.json'; + // The build method will just return the config data + $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0)); + $data = $this->loader->load($file); + // Ensure that the config files were merged using the includes directives + $this->assertArrayHasKey('includes', $data); + $this->assertArrayHasKey('services', $data); + $this->assertInternalType('array', $data['services']['foo']); + $this->assertInternalType('array', $data['services']['abstract']); + $this->assertInternalType('array', $data['services']['mock']); + $this->assertEquals('bar', $data['services']['foo']['params']['baz']); + } + + public function testUsesAliases() + { + $file = dirname(__DIR__) . '/TestData/services/json1.json'; + $this->loader->addAlias('foo', $file); + // The build method will just return the config data + $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0)); + $data = $this->loader->load('foo'); + $this->assertEquals('bar', $data['services']['foo']['params']['baz']); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + * @expectedExceptionMessage Unable to open foo.json + */ + public function testCanRemoveAliases() + { + $file = dirname(__DIR__) . '/TestData/services/json1.json'; + $this->loader->addAlias('foo.json', $file); + $this->loader->removeAlias('foo.json'); + $this->loader->load('foo.json'); + } + + public function testCanLoadArraysWithIncludes() + { + $file = dirname(__DIR__) . '/TestData/services/json1.json'; + $config = array('includes' => array($file)); + // The build method will just return the config data + $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0)); + $data = $this->loader->load($config); + $this->assertEquals('bar', $data['services']['foo']['params']['baz']); + } + + public function testDoesNotEnterInfiniteLoop() + { + $prefix = $file = dirname(__DIR__) . '/TestData/description'; + $this->loader->load("{$prefix}/baz.json"); + $this->assertCount(4, $this->readAttribute($this->loader, 'loadedFiles')); + // Ensure that the internal list of loaded files is reset + $this->loader->load("{$prefix}/../test_service2.json"); + $this->assertCount(1, $this->readAttribute($this->loader, 'loadedFiles')); + // Ensure that previously loaded files will be reloaded when starting fresh + $this->loader->load("{$prefix}/baz.json"); + $this->assertCount(4, $this->readAttribute($this->loader, 'loadedFiles')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php new file mode 100644 index 000000000..f63070e38 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php @@ -0,0 +1,177 @@ + array( + 'abstract' => array( + 'params' => array( + 'access_key' => 'xyz', + 'secret' => 'abc', + ), + ), + 'foo' => array( + 'extends' => 'abstract', + 'params' => array( + 'baz' => 'bar', + ), + ), + 'mock' => array( + 'extends' => 'abstract', + 'params' => array( + 'username' => 'foo', + 'password' => 'baz', + 'subdomain' => 'bar', + ) + ) + ) + ); + + $builder = $arrayFactory->load($data); + + // Ensure that services were parsed + $this->assertTrue(isset($builder['mock'])); + $this->assertTrue(isset($builder['abstract'])); + $this->assertTrue(isset($builder['foo'])); + $this->assertFalse(isset($builder['jimmy'])); + } + + /** + * @expectedException Guzzle\Service\Exception\ServiceNotFoundException + * @expectedExceptionMessage foo is trying to extend a non-existent service: abstract + */ + public function testThrowsExceptionWhenExtendingNonExistentService() + { + $arrayFactory = new ServiceBuilderLoader(); + + $data = array( + 'services' => array( + 'foo' => array( + 'extends' => 'abstract' + ) + ) + ); + + $builder = $arrayFactory->load($data); + } + + public function testAllowsGlobalParameterOverrides() + { + $arrayFactory = new ServiceBuilderLoader(); + + $data = array( + 'services' => array( + 'foo' => array( + 'params' => array( + 'foo' => 'baz', + 'bar' => 'boo' + ) + ) + ) + ); + + $builder = $arrayFactory->load($data, array( + 'bar' => 'jar', + 'far' => 'car' + )); + + $compiled = json_decode($builder->serialize(), true); + $this->assertEquals(array( + 'foo' => 'baz', + 'bar' => 'jar', + 'far' => 'car' + ), $compiled['foo']['params']); + } + + public function tstDoesNotErrorOnCircularReferences() + { + $arrayFactory = new ServiceBuilderLoader(); + $arrayFactory->load(array( + 'services' => array( + 'too' => array('extends' => 'ball'), + 'ball' => array('extends' => 'too'), + ) + )); + } + + public function configProvider() + { + $foo = array( + 'extends' => 'bar', + 'class' => 'stdClass', + 'params' => array('a' => 'test', 'b' => '456') + ); + + return array( + array( + // Does not extend the existing `foo` service but overwrites it + array( + 'services' => array( + 'foo' => $foo, + 'bar' => array('params' => array('baz' => '123')) + ) + ), + array( + 'services' => array( + 'foo' => array('class' => 'Baz') + ) + ), + array( + 'services' => array( + 'foo' => array('class' => 'Baz'), + 'bar' => array('params' => array('baz' => '123')) + ) + ) + ), + array( + // Extends the existing `foo` service + array( + 'services' => array( + 'foo' => $foo, + 'bar' => array('params' => array('baz' => '123')) + ) + ), + array( + 'services' => array( + 'foo' => array( + 'extends' => 'foo', + 'params' => array('b' => '123', 'c' => 'def') + ) + ) + ), + array( + 'services' => array( + 'foo' => array( + 'extends' => 'bar', + 'class' => 'stdClass', + 'params' => array('a' => 'test', 'b' => '123', 'c' => 'def') + ), + 'bar' => array('params' => array('baz' => '123')) + ) + ) + ) + ); + } + + /** + * @dataProvider configProvider + */ + public function testCombinesConfigs($a, $b, $c) + { + $l = new ServiceBuilderLoader(); + $m = new \ReflectionMethod($l, 'mergeData'); + $m->setAccessible(true); + $this->assertEquals($c, $m->invoke($l, $a, $b)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php new file mode 100644 index 000000000..e1b3a1d49 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php @@ -0,0 +1,317 @@ + array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => 'michael', + 'password' => 'testing123', + 'subdomain' => 'michael', + ), + ), + 'billy.mock' => array( + 'alias' => 'Hello!', + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => 'billy', + 'password' => 'passw0rd', + 'subdomain' => 'billy', + ), + ), + 'billy.testing' => array( + 'extends' => 'billy.mock', + 'params' => array( + 'subdomain' => 'test.billy', + ), + ), + 'missing_params' => array( + 'extends' => 'billy.mock' + ) + ); + + public function testAllowsSerialization() + { + $builder = ServiceBuilder::factory($this->arrayData); + $cached = unserialize(serialize($builder)); + $this->assertEquals($cached, $builder); + } + + public function testDelegatesFactoryMethodToAbstractFactory() + { + $builder = ServiceBuilder::factory($this->arrayData); + $c = $builder->get('michael.mock'); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $c); + } + + /** + * @expectedException Guzzle\Service\Exception\ServiceNotFoundException + * @expectedExceptionMessage No service is registered as foobar + */ + public function testThrowsExceptionWhenGettingInvalidClient() + { + ServiceBuilder::factory($this->arrayData)->get('foobar'); + } + + public function testStoresClientCopy() + { + $builder = ServiceBuilder::factory($this->arrayData); + $client = $builder->get('michael.mock'); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client); + $this->assertEquals('http://127.0.0.1:8124/v1/michael', $client->getBaseUrl()); + $this->assertEquals($client, $builder->get('michael.mock')); + + // Get another client but throw this one away + $client2 = $builder->get('billy.mock', true); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client2); + $this->assertEquals('http://127.0.0.1:8124/v1/billy', $client2->getBaseUrl()); + + // Make sure the original client is still there and set + $this->assertTrue($client === $builder->get('michael.mock')); + + // Create a new billy.mock client that is stored + $client3 = $builder->get('billy.mock'); + + // Make sure that the stored billy.mock client is equal to the other stored client + $this->assertTrue($client3 === $builder->get('billy.mock')); + + // Make sure that this client is not equal to the previous throwaway client + $this->assertFalse($client2 === $builder->get('billy.mock')); + } + + public function testBuildersPassOptionsThroughToClients() + { + $s = new ServiceBuilder(array( + 'michael.mock' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'base_url' => 'http://www.test.com/', + 'subdomain' => 'michael', + 'password' => 'test', + 'username' => 'michael', + 'curl.curlopt_proxyport' => 8080 + ) + ) + )); + + $c = $s->get('michael.mock'); + $this->assertEquals(8080, $c->getConfig('curl.curlopt_proxyport')); + } + + public function testUsesTheDefaultBuilderWhenNoBuilderIsSpecified() + { + $s = new ServiceBuilder(array( + 'michael.mock' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'base_url' => 'http://www.test.com/', + 'subdomain' => 'michael', + 'password' => 'test', + 'username' => 'michael', + 'curl.curlopt_proxyport' => 8080 + ) + ) + )); + + $c = $s->get('michael.mock'); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $c); + } + + public function testUsedAsArray() + { + $b = ServiceBuilder::factory($this->arrayData); + $this->assertTrue($b->offsetExists('michael.mock')); + $this->assertFalse($b->offsetExists('not_there')); + $this->assertInstanceOf('Guzzle\Service\Client', $b['michael.mock']); + + unset($b['michael.mock']); + $this->assertFalse($b->offsetExists('michael.mock')); + + $b['michael.mock'] = new Client('http://www.test.com/'); + $this->assertInstanceOf('Guzzle\Service\Client', $b['michael.mock']); + } + + public function testFactoryCanCreateFromJson() + { + $tmp = sys_get_temp_dir() . '/test.js'; + file_put_contents($tmp, json_encode($this->arrayData)); + $b = ServiceBuilder::factory($tmp); + unlink($tmp); + $s = $b->get('billy.testing'); + $this->assertEquals('test.billy', $s->getConfig('subdomain')); + $this->assertEquals('billy', $s->getConfig('username')); + } + + public function testFactoryCanCreateFromArray() + { + $b = ServiceBuilder::factory($this->arrayData); + $s = $b->get('billy.testing'); + $this->assertEquals('test.billy', $s->getConfig('subdomain')); + $this->assertEquals('billy', $s->getConfig('username')); + } + + public function testFactoryDoesNotRequireParams() + { + $b = ServiceBuilder::factory($this->arrayData); + $s = $b->get('missing_params'); + $this->assertEquals('billy', $s->getConfig('username')); + } + + public function testBuilderAllowsReferencesBetweenClients() + { + $builder = ServiceBuilder::factory(array( + 'a' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'other_client' => '{b}', + 'username' => 'x', + 'password' => 'y', + 'subdomain' => 'z' + ) + ), + 'b' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => '1', + 'password' => '2', + 'subdomain' => '3' + ) + ) + )); + + $client = $builder['a']; + $this->assertEquals('x', $client->getConfig('username')); + $this->assertSame($builder['b'], $client->getConfig('other_client')); + $this->assertEquals('1', $builder['b']->getConfig('username')); + } + + public function testEmitsEventsWhenClientsAreCreated() + { + // Ensure that the client signals that it emits an event + $this->assertEquals(array('service_builder.create_client'), ServiceBuilder::getAllEvents()); + + // Create a test service builder + $builder = ServiceBuilder::factory(array( + 'a' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => 'test', + 'password' => '123', + 'subdomain' => 'z' + ) + ) + )); + + // Add an event listener to pick up client creation events + $emits = 0; + $builder->getEventDispatcher()->addListener('service_builder.create_client', function($event) use (&$emits) { + $emits++; + }); + + // Get the 'a' client by name + $client = $builder->get('a'); + + // Ensure that the event was emitted once, and that the client was present + $this->assertEquals(1, $emits); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client); + } + + public function testCanAddGlobalParametersToServicesOnLoad() + { + $builder = ServiceBuilder::factory($this->arrayData, array( + 'username' => 'fred', + 'new_value' => 'test' + )); + + $data = json_decode($builder->serialize(), true); + + foreach ($data as $service) { + $this->assertEquals('fred', $service['params']['username']); + $this->assertEquals('test', $service['params']['new_value']); + } + } + + public function testAddsGlobalPlugins() + { + $b = new ServiceBuilder($this->arrayData); + $b->addGlobalPlugin(new HistoryPlugin()); + $s = $b->get('michael.mock'); + $this->assertTrue($s->getEventDispatcher()->hasListeners('request.sent')); + } + + public function testCanGetData() + { + $b = new ServiceBuilder($this->arrayData); + $this->assertEquals($this->arrayData['michael.mock'], $b->getData('michael.mock')); + $this->assertNull($b->getData('ewofweoweofe')); + } + + public function testCanGetByAlias() + { + $b = new ServiceBuilder($this->arrayData); + $this->assertSame($b->get('billy.mock'), $b->get('Hello!')); + } + + public function testCanOverwriteParametersForThrowawayClients() + { + $b = new ServiceBuilder($this->arrayData); + + $c1 = $b->get('michael.mock'); + $this->assertEquals('michael', $c1->getConfig('username')); + + $c2 = $b->get('michael.mock', array('username' => 'jeremy')); + $this->assertEquals('jeremy', $c2->getConfig('username')); + } + + public function testGettingAThrowawayClientWithParametersDoesNotAffectGettingOtherClients() + { + $b = new ServiceBuilder($this->arrayData); + + $c1 = $b->get('michael.mock', array('username' => 'jeremy')); + $this->assertEquals('jeremy', $c1->getConfig('username')); + + $c2 = $b->get('michael.mock'); + $this->assertEquals('michael', $c2->getConfig('username')); + } + + public function testCanUseArbitraryData() + { + $b = new ServiceBuilder(); + $b['a'] = 'foo'; + $this->assertTrue(isset($b['a'])); + $this->assertEquals('foo', $b['a']); + unset($b['a']); + $this->assertFalse(isset($b['a'])); + } + + public function testCanRegisterServiceData() + { + $b = new ServiceBuilder(); + $b['a'] = array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => 'billy', + 'password' => 'passw0rd', + 'subdomain' => 'billy', + ) + ); + $this->assertTrue(isset($b['a'])); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $b['a']); + $client = $b['a']; + unset($b['a']); + $this->assertFalse(isset($b['a'])); + // Ensure that instantiated clients can be registered + $b['mock'] = $client; + $this->assertSame($client, $b['mock']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php new file mode 100644 index 000000000..b8245ad58 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php @@ -0,0 +1,43 @@ +getMockBuilder('Guzzle\Service\ConfigLoaderInterface') + ->setMethods(array('load')) + ->getMockForAbstractClass(); + $data = array('foo' => 'bar'); + $loader->expects($this->once()) + ->method('load') + ->will($this->returnValue($data)); + $cache = new CachingConfigLoader($loader, $cache); + $this->assertEquals($data, $cache->load('foo')); + $this->assertEquals($data, $cache->load('foo')); + } + + public function testDoesNotCacheArrays() + { + $cache = new DoctrineCacheAdapter(new ArrayCache()); + $loader = $this->getMockBuilder('Guzzle\Service\ConfigLoaderInterface') + ->setMethods(array('load')) + ->getMockForAbstractClass(); + $data = array('foo' => 'bar'); + $loader->expects($this->exactly(2)) + ->method('load') + ->will($this->returnValue($data)); + $cache = new CachingConfigLoader($loader, $cache); + $this->assertEquals($data, $cache->load(array())); + $this->assertEquals($data, $cache->load(array())); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php new file mode 100644 index 000000000..aee29ed8d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php @@ -0,0 +1,320 @@ +serviceTest = new ServiceDescription(array( + 'test_command' => new Operation(array( + 'doc' => 'documentationForCommand', + 'method' => 'DELETE', + 'class' => 'Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand', + 'args' => array( + 'bucket' => array( + 'required' => true + ), + 'key' => array( + 'required' => true + ) + ) + )) + )); + + $this->service = ServiceDescription::factory(__DIR__ . '/../TestData/test_service.json'); + } + + public function testAllowsCustomClientParameters() + { + $client = new Mock\MockClient(null, array( + Client::COMMAND_PARAMS => array(AbstractCommand::RESPONSE_PROCESSING => 'foo') + )); + $command = $client->getCommand('mock_command'); + $this->assertEquals('foo', $command->get(AbstractCommand::RESPONSE_PROCESSING)); + } + + public function testFactoryCreatesClient() + { + $client = Client::factory(array( + 'base_url' => 'http://www.test.com/', + 'test' => '123' + )); + + $this->assertEquals('http://www.test.com/', $client->getBaseUrl()); + $this->assertEquals('123', $client->getConfig('test')); + } + + public function testFactoryDoesNotRequireBaseUrl() + { + $client = Client::factory(); + } + + public function testDescribesEvents() + { + $this->assertInternalType('array', Client::getAllEvents()); + } + + public function testExecutesCommands() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client($this->getServer()->getUrl()); + $cmd = new MockCommand(); + $client->execute($cmd); + + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $cmd->getResponse()); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $cmd->getResult()); + $this->assertEquals(1, count($this->getServer()->getReceivedRequests(false))); + } + + public function testExecutesCommandsWithArray() + { + $client = new Client('http://www.test.com/'); + $client->getEventDispatcher()->addSubscriber(new MockPlugin(array( + new Response(200), + new Response(200) + ))); + + // Create a command set and a command + $set = array(new MockCommand(), new MockCommand()); + $client->execute($set); + + // Make sure it sent + $this->assertTrue($set[0]->isExecuted()); + $this->assertTrue($set[1]->isExecuted()); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testThrowsExceptionWhenInvalidCommandIsExecuted() + { + $client = new Client(); + $client->execute(new \stdClass()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testThrowsExceptionWhenMissingCommand() + { + $client = new Client(); + + $mock = $this->getMock('Guzzle\\Service\\Command\\Factory\\FactoryInterface'); + $mock->expects($this->any()) + ->method('factory') + ->with($this->equalTo('test')) + ->will($this->returnValue(null)); + + $client->setCommandFactory($mock); + $client->getCommand('test'); + } + + public function testCreatesCommandsUsingCommandFactory() + { + $mockCommand = new MockCommand(); + + $client = new Mock\MockClient(); + $mock = $this->getMock('Guzzle\\Service\\Command\\Factory\\FactoryInterface'); + $mock->expects($this->any()) + ->method('factory') + ->with($this->equalTo('foo')) + ->will($this->returnValue($mockCommand)); + + $client->setCommandFactory($mock); + + $command = $client->getCommand('foo', array('acl' => '123')); + $this->assertSame($mockCommand, $command); + $command = $client->getCommand('foo', array('acl' => '123')); + $this->assertSame($mockCommand, $command); + $this->assertSame($client, $command->getClient()); + } + + public function testOwnsServiceDescription() + { + $client = new Mock\MockClient(); + $this->assertNull($client->getDescription()); + + $description = $this->getMock('Guzzle\\Service\\Description\\ServiceDescription'); + $this->assertSame($client, $client->setDescription($description)); + $this->assertSame($description, $client->getDescription()); + } + + public function testOwnsResourceIteratorFactory() + { + $client = new Mock\MockClient(); + + $method = new \ReflectionMethod($client, 'getResourceIteratorFactory'); + $method->setAccessible(TRUE); + $rf1 = $method->invoke($client); + + $rf = $this->readAttribute($client, 'resourceIteratorFactory'); + $this->assertInstanceOf('Guzzle\\Service\\Resource\\ResourceIteratorClassFactory', $rf); + $this->assertSame($rf1, $rf); + + $rf = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock'); + $client->setResourceIteratorFactory($rf); + $this->assertNotSame($rf1, $rf); + } + + public function testClientResetsRequestsBeforeExecutingCommands() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHi", + "HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\nI" + )); + + $client = new Mock\MockClient($this->getServer()->getUrl()); + + $command = $client->getCommand('mock_command'); + $client->execute($command); + $client->execute($command); + $this->assertEquals('I', $command->getResponse()->getBody(true)); + } + + public function testClientCreatesIterators() + { + $client = new Mock\MockClient(); + + $iterator = $client->getIterator('mock_command', array( + 'foo' => 'bar' + ), array( + 'limit' => 10 + )); + + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + $this->assertEquals(10, $this->readAttribute($iterator, 'limit')); + + $command = $this->readAttribute($iterator, 'originalCommand'); + $this->assertEquals('bar', $command->get('foo')); + } + + public function testClientCreatesIteratorsWithNoOptions() + { + $client = new Mock\MockClient(); + $iterator = $client->getIterator('mock_command'); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } + + public function testClientCreatesIteratorsWithCommands() + { + $client = new Mock\MockClient(); + $command = new MockCommand(); + $iterator = $client->getIterator($command); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + $iteratorCommand = $this->readAttribute($iterator, 'originalCommand'); + $this->assertSame($command, $iteratorCommand); + } + + public function testClientHoldsInflector() + { + $client = new Mock\MockClient(); + $this->assertInstanceOf('Guzzle\Inflection\MemoizingInflector', $client->getInflector()); + + $inflector = new Inflector(); + $client->setInflector($inflector); + $this->assertSame($inflector, $client->getInflector()); + } + + public function testClientAddsGlobalCommandOptions() + { + $client = new Mock\MockClient('http://www.foo.com', array( + Client::COMMAND_PARAMS => array( + 'mesa' => 'bar' + ) + )); + $command = $client->getCommand('mock_command'); + $this->assertEquals('bar', $command->get('mesa')); + } + + public function testSupportsServiceDescriptionBaseUrls() + { + $description = new ServiceDescription(array('baseUrl' => 'http://foo.com')); + $client = new Client(); + $client->setDescription($description); + $this->assertEquals('http://foo.com', $client->getBaseUrl()); + } + + public function testMergesDefaultCommandParamsCorrectly() + { + $client = new Mock\MockClient('http://www.foo.com', array( + Client::COMMAND_PARAMS => array( + 'mesa' => 'bar', + 'jar' => 'jar' + ) + )); + $command = $client->getCommand('mock_command', array('jar' => 'test')); + $this->assertEquals('bar', $command->get('mesa')); + $this->assertEquals('test', $command->get('jar')); + } + + /** + * @expectedException \Guzzle\Http\Exception\BadResponseException + */ + public function testWrapsSingleCommandExceptions() + { + $client = new Mock\MockClient('http://foobaz.com'); + $mock = new MockPlugin(array(new Response(401))); + $client->addSubscriber($mock); + $client->execute(new MockCommand()); + } + + public function testWrapsMultipleCommandExceptions() + { + $client = new Mock\MockClient('http://foobaz.com'); + $mock = new MockPlugin(array(new Response(200), new Response(200), new Response(404), new Response(500))); + $client->addSubscriber($mock); + + $cmds = array(new MockCommand(), new MockCommand(), new MockCommand(), new MockCommand()); + try { + $client->execute($cmds); + } catch (CommandTransferException $e) { + $this->assertEquals(2, count($e->getFailedRequests())); + $this->assertEquals(2, count($e->getSuccessfulRequests())); + $this->assertEquals(2, count($e->getFailedCommands())); + $this->assertEquals(2, count($e->getSuccessfulCommands())); + + foreach ($e->getSuccessfulCommands() as $c) { + $this->assertTrue($c->getResponse()->isSuccessful()); + } + + foreach ($e->getFailedCommands() as $c) { + $this->assertFalse($c->getRequest()->getResponse()->isSuccessful()); + } + } + } + + public function testGetCommandAfterTwoSetDescriptions() + { + $service1 = ServiceDescription::factory(__DIR__ . '/../TestData/test_service.json'); + $service2 = ServiceDescription::factory(__DIR__ . '/../TestData/test_service_3.json'); + + $client = new Mock\MockClient(); + + $client->setDescription($service1); + $client->getCommand('foo_bar'); + $client->setDescription($service2); + $client->getCommand('baz_qux'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php new file mode 100644 index 000000000..1004fae66 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php @@ -0,0 +1,16 @@ +setDescription(ServiceDescription::factory(__DIR__ . '/../../TestData/test_service.json')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php new file mode 100644 index 000000000..d76224697 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php @@ -0,0 +1,54 @@ + function($command, $api) { + $command->set('testing', '123'); + $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/'); + return $request; + } + )); + + $client = $this->getServiceBuilder()->get('mock'); + $c->setClient($client)->prepare(); + $this->assertEquals('123', $c->get('testing')); + $this->assertEquals('http://www.test.com/', $c->getRequest()->getUrl()); + } + + /** + * @expectedException UnexpectedValueException + * @expectedExceptionMessage Closure command did not return a RequestInterface object + */ + public function testMustReturnRequest() + { + $c = new ClosureCommand(array( + 'closure' => function($command, $api) { + return false; + } + )); + + $client = $this->getServiceBuilder()->get('mock'); + $c->setClient($client)->prepare(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php new file mode 100644 index 000000000..b7173d486 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php @@ -0,0 +1,445 @@ +assertEquals('123', $command->get('test')); + $this->assertFalse($command->isPrepared()); + $this->assertFalse($command->isExecuted()); + } + + public function testDeterminesShortName() + { + $api = new Operation(array('name' => 'foobar')); + $command = new MockCommand(array(), $api); + $this->assertEquals('foobar', $command->getName()); + + $command = new MockCommand(); + $this->assertEquals('mock_command', $command->getName()); + + $command = new Sub(); + $this->assertEquals('sub.sub', $command->getName()); + } + + /** + * @expectedException RuntimeException + */ + public function testGetRequestThrowsExceptionBeforePreparation() + { + $command = new MockCommand(); + $command->getRequest(); + } + + public function testGetResponseExecutesCommandsWhenNeeded() + { + $response = new Response(200); + $client = $this->getClient(); + $this->setMockResponse($client, array($response)); + $command = new MockCommand(); + $command->setClient($client); + $this->assertSame($response, $command->getResponse()); + $this->assertSame($response, $command->getResponse()); + } + + public function testGetResultExecutesCommandsWhenNeeded() + { + $response = new Response(200); + $client = $this->getClient(); + $this->setMockResponse($client, array($response)); + $command = new MockCommand(); + $command->setClient($client); + $this->assertSame($response, $command->getResult()); + $this->assertSame($response, $command->getResult()); + } + + public function testSetClient() + { + $command = new MockCommand(); + $client = $this->getClient(); + + $command->setClient($client); + $this->assertEquals($client, $command->getClient()); + + unset($client); + unset($command); + + $command = new MockCommand(); + $client = $this->getClient(); + + $command->setClient($client)->prepare(); + $this->assertEquals($client, $command->getClient()); + $this->assertTrue($command->isPrepared()); + } + + public function testExecute() + { + $client = $this->getClient(); + $response = new Response(200, array( + 'Content-Type' => 'application/xml' + ), '123'); + $this->setMockResponse($client, array($response)); + $command = new MockCommand(); + $this->assertSame($command, $command->setClient($client)); + + // Returns the result of the command + $this->assertInstanceOf('SimpleXMLElement', $command->execute()); + + $this->assertTrue($command->isPrepared()); + $this->assertTrue($command->isExecuted()); + $this->assertSame($response, $command->getResponse()); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Request', $command->getRequest()); + // Make sure that the result was automatically set to a SimpleXMLElement + $this->assertInstanceOf('SimpleXMLElement', $command->getResult()); + $this->assertEquals('123', (string) $command->getResult()->data); + } + + public function testConvertsJsonResponsesToArray() + { + $client = $this->getClient(); + $this->setMockResponse($client, array( + new \Guzzle\Http\Message\Response(200, array( + 'Content-Type' => 'application/json' + ), '{ "key": "Hi!" }' + ) + )); + $command = new MockCommand(); + $command->setClient($client); + $command->execute(); + $this->assertEquals(array( + 'key' => 'Hi!' + ), $command->getResult()); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testConvertsInvalidJsonResponsesToArray() + { + $json = '{ "key": "Hi!" }invalid'; + // Some implementations of php-json extension are not strict enough + // and allow to parse invalid json ignoring invalid parts + // See https://github.com/remicollet/pecl-json-c/issues/5 + if (json_decode($json) && JSON_ERROR_NONE === json_last_error()) { + $this->markTestSkipped('php-pecl-json library regression issues'); + } + + $client = $this->getClient(); + $this->setMockResponse($client, array( + new \Guzzle\Http\Message\Response(200, array( + 'Content-Type' => 'application/json' + ), $json + ) + )); + $command = new MockCommand(); + $command->setClient($client); + $command->execute(); + } + + public function testProcessResponseIsNotXml() + { + $client = $this->getClient(); + $this->setMockResponse($client, array( + new Response(200, array( + 'Content-Type' => 'application/octet-stream' + ), 'abc,def,ghi') + )); + $command = new MockCommand(); + $client->execute($command); + + // Make sure that the result was not converted to XML + $this->assertFalse($command->getResult() instanceof \SimpleXMLElement); + } + + /** + * @expectedException RuntimeException + */ + public function testExecuteThrowsExceptionWhenNoClientIsSet() + { + $command = new MockCommand(); + $command->execute(); + } + + /** + * @expectedException RuntimeException + */ + public function testPrepareThrowsExceptionWhenNoClientIsSet() + { + $command = new MockCommand(); + $command->prepare(); + } + + public function testCommandsAllowsCustomRequestHeaders() + { + $command = new MockCommand(); + $command->getRequestHeaders()->set('test', '123'); + $this->assertInstanceOf('Guzzle\Common\Collection', $command->getRequestHeaders()); + $this->assertEquals('123', $command->getRequestHeaders()->get('test')); + + $command->setClient($this->getClient())->prepare(); + $this->assertEquals('123', (string) $command->getRequest()->getHeader('test')); + } + + public function testCommandsAllowsCustomRequestHeadersAsArray() + { + $command = new MockCommand(array(AbstractCommand::HEADERS_OPTION => array('Foo' => 'Bar'))); + $this->assertInstanceOf('Guzzle\Common\Collection', $command->getRequestHeaders()); + $this->assertEquals('Bar', $command->getRequestHeaders()->get('Foo')); + } + + private function getOperation() + { + return new Operation(array( + 'name' => 'foobar', + 'httpMethod' => 'POST', + 'class' => 'Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand', + 'parameters' => array( + 'test' => array( + 'default' => '123', + 'type' => 'string' + ) + ))); + } + + public function testCommandsUsesOperation() + { + $api = $this->getOperation(); + $command = new MockCommand(array(), $api); + $this->assertSame($api, $command->getOperation()); + $command->setClient($this->getClient())->prepare(); + $this->assertEquals('123', $command->get('test')); + $this->assertSame($api, $command->getOperation($api)); + } + + public function testCloneMakesNewRequest() + { + $client = $this->getClient(); + $command = new MockCommand(array(), $this->getOperation()); + $command->setClient($client); + + $command->prepare(); + $this->assertTrue($command->isPrepared()); + + $command2 = clone $command; + $this->assertFalse($command2->isPrepared()); + } + + public function testHasOnCompleteMethod() + { + $that = $this; + $called = 0; + + $testFunction = function($command) use (&$called, $that) { + $called++; + $that->assertInstanceOf('Guzzle\Service\Command\CommandInterface', $command); + }; + + $client = $this->getClient(); + $command = new MockCommand(array( + 'command.on_complete' => $testFunction + ), $this->getOperation()); + $command->setClient($client); + + $command->prepare()->setResponse(new Response(200), true); + $command->execute(); + $this->assertEquals(1, $called); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testOnCompleteMustBeCallable() + { + $client = $this->getClient(); + $command = new MockCommand(); + $command->setOnComplete('foo'); + } + + public function testCanSetResultManually() + { + $client = $this->getClient(); + $client->getEventDispatcher()->addSubscriber(new MockPlugin(array( + new Response(200) + ))); + $command = new MockCommand(); + $client->execute($command); + $command->setResult('foo!'); + $this->assertEquals('foo!', $command->getResult()); + } + + public function testCanInitConfig() + { + $command = $this->getMockBuilder('Guzzle\\Service\\Command\\AbstractCommand') + ->setConstructorArgs(array(array( + 'foo' => 'bar' + ), new Operation(array( + 'parameters' => array( + 'baz' => new Parameter(array( + 'default' => 'baaar' + )) + ) + )))) + ->getMockForAbstractClass(); + + $this->assertEquals('bar', $command['foo']); + $this->assertEquals('baaar', $command['baz']); + } + + public function testAddsCurlOptionsToRequestsWhenPreparing() + { + $command = new MockCommand(array( + 'foo' => 'bar', + 'curl.options' => array('CURLOPT_PROXYPORT' => 8080) + )); + $client = new Client(); + $command->setClient($client); + $request = $command->prepare(); + $this->assertEquals(8080, $request->getCurlOptions()->get(CURLOPT_PROXYPORT)); + } + + public function testIsInvokable() + { + $client = $this->getClient(); + $response = new Response(200); + $this->setMockResponse($client, array($response)); + $command = new MockCommand(); + $command->setClient($client); + // Returns the result of the command + $this->assertSame($response, $command()); + } + + public function testCreatesDefaultOperation() + { + $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')->getMockForAbstractClass(); + $this->assertInstanceOf('Guzzle\Service\Description\Operation', $command->getOperation()); + } + + public function testAllowsValidatorToBeInjected() + { + $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')->getMockForAbstractClass(); + $v = new SchemaValidator(); + $command->setValidator($v); + $this->assertSame($v, $this->readAttribute($command, 'validator')); + } + + public function testCanDisableValidation() + { + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + $v = $this->getMockBuilder('Guzzle\Service\Description\SchemaValidator') + ->setMethods(array('validate')) + ->getMock(); + $v->expects($this->never())->method('validate'); + $command->setValidator($v); + $command->set(AbstractCommand::DISABLE_VALIDATION, true); + $command->prepare(); + } + + public function testValidatorDoesNotUpdateNonDefaultValues() + { + $command = new MockCommand(array('test' => 123, 'foo' => 'bar')); + $command->setClient(new \Guzzle\Service\Client()); + $command->prepare(); + $this->assertEquals(123, $command->get('test')); + $this->assertEquals('bar', $command->get('foo')); + } + + public function testValidatorUpdatesDefaultValues() + { + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + $command->prepare(); + $this->assertEquals(123, $command->get('test')); + $this->assertEquals('abc', $command->get('_internal')); + } + + /** + * @expectedException \Guzzle\Service\Exception\ValidationException + * @expectedExceptionMessage [Foo] Baz + */ + public function testValidatesCommandBeforeSending() + { + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + $v = $this->getMockBuilder('Guzzle\Service\Description\SchemaValidator') + ->setMethods(array('validate', 'getErrors')) + ->getMock(); + $v->expects($this->any())->method('validate')->will($this->returnValue(false)); + $v->expects($this->any())->method('getErrors')->will($this->returnValue(array('[Foo] Baz', '[Bar] Boo'))); + $command->setValidator($v); + $command->prepare(); + } + + /** + * @expectedException \Guzzle\Service\Exception\ValidationException + * @expectedExceptionMessage Validation errors: [abc] must be of type string + */ + public function testValidatesAdditionalParameters() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'foo' => array( + 'parameters' => array( + 'baz' => array('type' => 'integer') + ), + 'additionalParameters' => array( + 'type' => 'string' + ) + ) + ) + )); + + $client = new Client(); + $client->setDescription($description); + $command = $client->getCommand('foo', array( + 'abc' => false, + 'command.headers' => array('foo' => 'bar') + )); + $command->prepare(); + } + + public function testCanAccessValidationErrorsFromCommand() + { + $validationErrors = array('[Foo] Baz', '[Bar] Boo'); + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + + $this->assertFalse($command->getValidationErrors()); + + $v = $this->getMockBuilder('Guzzle\Service\Description\SchemaValidator') + ->setMethods(array('validate', 'getErrors')) + ->getMock(); + $v->expects($this->any())->method('getErrors')->will($this->returnValue($validationErrors)); + $command->setValidator($v); + + $this->assertEquals($validationErrors, $command->getValidationErrors()); + } + + public function testCanChangeResponseBody() + { + $body = EntityBody::factory(); + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + $command->set(AbstractCommand::RESPONSE_BODY, $body); + $request = $command->prepare(); + $this->assertSame($body, $this->readAttribute($request, 'responseBody')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php new file mode 100644 index 000000000..b7a4682fc --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php @@ -0,0 +1,122 @@ +serializer = DefaultRequestSerializer::getInstance(); + $this->client = new Client('http://foo.com/baz'); + $this->operation = new Operation(array('httpMethod' => 'POST')); + $this->command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setConstructorArgs(array(array(), $this->operation)) + ->getMockForAbstractClass(); + $this->command->setClient($this->client); + } + + public function testAllowsCustomVisitor() + { + $this->serializer->addVisitor('custom', new HeaderVisitor()); + $this->command['test'] = '123'; + $this->operation->addParam(new Parameter(array('name' => 'test', 'location' => 'custom'))); + $request = $this->serializer->prepare($this->command); + $this->assertEquals('123', (string) $request->getHeader('test')); + } + + public function testUsesRelativePath() + { + $this->operation->setUri('bar'); + $request = $this->serializer->prepare($this->command); + $this->assertEquals('http://foo.com/baz/bar', (string) $request->getUrl()); + } + + public function testUsesRelativePathWithUriLocations() + { + $this->command['test'] = '123'; + $this->operation->setUri('bar/{test}'); + $this->operation->addParam(new Parameter(array('name' => 'test', 'location' => 'uri'))); + $request = $this->serializer->prepare($this->command); + $this->assertEquals('http://foo.com/baz/bar/123', (string) $request->getUrl()); + } + + public function testAllowsCustomFactory() + { + $f = new VisitorFlyweight(); + $serializer = new DefaultRequestSerializer($f); + $this->assertSame($f, $this->readAttribute($serializer, 'factory')); + } + + public function testMixedParams() + { + $this->operation->setUri('bar{?limit,fields}'); + $this->operation->addParam(new Parameter(array( + 'name' => 'limit', + 'location' => 'uri', + 'required' => false, + ))); + $this->operation->addParam(new Parameter(array( + 'name' => 'fields', + 'location' => 'uri', + 'required' => true, + ))); + + $this->command['fields'] = array('id', 'name'); + + $request = $this->serializer->prepare($this->command); + $this->assertEquals('http://foo.com/baz/bar?fields='.urlencode('id,name'), (string) $request->getUrl()); + } + + public function testValidatesAdditionalParameters() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'foo' => array( + 'httpMethod' => 'PUT', + 'parameters' => array( + 'bar' => array('location' => 'header') + ), + 'additionalParameters' => array( + 'location' => 'json' + ) + ) + ) + )); + + $client = new Client(); + $client->setDescription($description); + $command = $client->getCommand('foo'); + $command['bar'] = 'test'; + $command['hello'] = 'abc'; + $request = $command->prepare(); + $this->assertEquals('test', (string) $request->getHeader('bar')); + $this->assertEquals('{"hello":"abc"}', (string) $request->getBody()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php new file mode 100644 index 000000000..a6a02f951 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php @@ -0,0 +1,59 @@ +setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, array( + 'Content-Type' => 'application/xml' + ), 'Bar'), true); + $this->assertInstanceOf('SimpleXMLElement', $op->execute()); + } + + public function testParsesJsonResponses() + { + $op = new OperationCommand(array(), new Operation()); + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, array( + 'Content-Type' => 'application/json' + ), '{"Baz":"Bar"}'), true); + $this->assertEquals(array('Baz' => 'Bar'), $op->execute()); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testThrowsExceptionWhenParsingJsonFails() + { + $op = new OperationCommand(array(), new Operation()); + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, array('Content-Type' => 'application/json'), '{"Baz":ddw}'), true); + $op->execute(); + } + + public function testAddsContentTypeWhenExpectsIsSetOnCommand() + { + $op = new OperationCommand(array(), new Operation()); + $op['command.expects'] = 'application/json'; + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, null, '{"Baz":"Bar"}'), true); + $this->assertEquals(array('Baz' => 'Bar'), $op->execute()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php new file mode 100644 index 000000000..ab1041ad3 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php @@ -0,0 +1,76 @@ +client = new Client(); + + $map = new MapFactory(array( + 'test' => 'Guzzle\Tests\Service\Mock\Command\MockCommand', + 'test1' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand' + )); + + $this->factory = new AliasFactory($this->client, array( + 'foo' => 'test', + 'bar' => 'sub', + 'sub' => 'test1', + 'krull' => 'test3', + 'krull_2' => 'krull', + 'sub_2' => 'bar', + 'bad_link' => 'jarjar' + )); + + $map2 = new MapFactory(array( + 'test3' => 'Guzzle\Tests\Service\Mock\Command\Sub\Sub' + )); + + $this->client->setCommandFactory(new CompositeFactory(array($map, $this->factory, $map2))); + } + + public function aliasProvider() + { + return array( + array('foo', 'Guzzle\Tests\Service\Mock\Command\MockCommand', false), + array('bar', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', false), + array('sub', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', false), + array('sub_2', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', false), + array('krull', 'Guzzle\Tests\Service\Mock\Command\Sub\Sub', false), + array('krull_2', 'Guzzle\Tests\Service\Mock\Command\Sub\Sub', false), + array('missing', null, true), + array('bad_link', null, true) + ); + } + + /** + * @dataProvider aliasProvider + */ + public function testAliasesCommands($key, $result, $exception) + { + try { + $command = $this->client->getCommand($key); + if (is_null($result)) { + $this->assertNull($command); + } else { + $this->assertInstanceof($result, $command); + } + } catch (\Exception $e) { + if (!$exception) { + $this->fail('Got exception when it was not expected'); + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php new file mode 100644 index 000000000..b896dcfd6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php @@ -0,0 +1,124 @@ +getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testIsIterable() + { + $factory = new CompositeFactory(array($this->getFactory(), $this->getFactory())); + $this->assertEquals(2, count($factory)); + $this->assertEquals(2, count(iterator_to_array($factory->getIterator()))); + } + + public function testFindsFactories() + { + $f1 = $this->getFactory(); + $f2 = $this->getFactory('Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $factory = new CompositeFactory(array($f1, $f2)); + $this->assertNull($factory->find('foo')); + $this->assertNull($factory->find($this->getFactory())); + $this->assertSame($f1, $factory->find('Guzzle\\Service\\Command\\Factory\\MapFactory')); + $this->assertSame($f2, $factory->find('Guzzle\\Service\\Command\\Factory\\CompositeFactory')); + $this->assertSame($f1, $factory->find($f1)); + $this->assertSame($f2, $factory->find($f2)); + + $this->assertFalse($factory->has('foo')); + $this->assertTrue($factory->has('Guzzle\\Service\\Command\\Factory\\MapFactory')); + $this->assertTrue($factory->has('Guzzle\\Service\\Command\\Factory\\CompositeFactory')); + } + + public function testCreatesCommands() + { + $factory = new CompositeFactory(); + $this->assertNull($factory->factory('foo')); + + $f1 = $this->getFactory(); + $mockCommand1 = $this->getMockForAbstractClass('Guzzle\\Service\\Command\\AbstractCommand'); + + $f1->expects($this->once()) + ->method('factory') + ->with($this->equalTo('foo')) + ->will($this->returnValue($mockCommand1)); + + $factory = new CompositeFactory(array($f1)); + $this->assertSame($mockCommand1, $factory->factory('foo')); + } + + public function testAllowsRemovalOfFactories() + { + $f1 = $this->getFactory(); + $f2 = $this->getFactory(); + $f3 = $this->getFactory('Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $factories = array($f1, $f2, $f3); + $factory = new CompositeFactory($factories); + + $factory->remove('foo'); + $this->assertEquals($factories, $factory->getIterator()->getArrayCopy()); + + $factory->remove($f1); + $this->assertEquals(array($f2, $f3), $factory->getIterator()->getArrayCopy()); + + $factory->remove('Guzzle\\Service\\Command\\Factory\\MapFactory'); + $this->assertEquals(array($f3), $factory->getIterator()->getArrayCopy()); + + $factory->remove('Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $this->assertEquals(array(), $factory->getIterator()->getArrayCopy()); + + $factory->remove('foo'); + $this->assertEquals(array(), $factory->getIterator()->getArrayCopy()); + } + + public function testAddsFactoriesBeforeAndAtEnd() + { + $f1 = $this->getFactory(); + $f2 = $this->getFactory(); + $f3 = $this->getFactory('Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $f4 = $this->getFactory(); + + $factory = new CompositeFactory(); + + $factory->add($f1); + $this->assertEquals(array($f1), $factory->getIterator()->getArrayCopy()); + + $factory->add($f2); + $this->assertEquals(array($f1, $f2), $factory->getIterator()->getArrayCopy()); + + $factory->add($f3, $f2); + $this->assertEquals(array($f1, $f3, $f2), $factory->getIterator()->getArrayCopy()); + + $factory->add($f4, 'Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $this->assertEquals(array($f1, $f4, $f3, $f2), $factory->getIterator()->getArrayCopy()); + } + + public function testProvidesDefaultChainForClients() + { + $client = $this->getMock('Guzzle\\Service\\Client'); + $chain = CompositeFactory::getDefaultChain($client); + $a = $chain->getIterator()->getArrayCopy(); + $this->assertEquals(1, count($a)); + $this->assertInstanceOf('Guzzle\\Service\\Command\\Factory\\ConcreteClassFactory', $a[0]); + + $description = $this->getMock('Guzzle\\Service\\Description\\ServiceDescription'); + $client->expects($this->once()) + ->method('getDescription') + ->will($this->returnValue($description)); + $chain = CompositeFactory::getDefaultChain($client); + $a = $chain->getIterator()->getArrayCopy(); + $this->assertEquals(2, count($a)); + $this->assertInstanceOf('Guzzle\\Service\\Command\\Factory\\ServiceDescriptionFactory', $a[0]); + $this->assertInstanceOf('Guzzle\\Service\\Command\\Factory\\ConcreteClassFactory', $a[1]); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php new file mode 100644 index 000000000..766471822 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php @@ -0,0 +1,49 @@ + $prefix + )); + } + + $factory = new ConcreteClassFactory($client); + + if (is_null($result)) { + $this->assertNull($factory->factory($key)); + } else { + $this->assertInstanceof($result, $factory->factory($key)); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php new file mode 100644 index 000000000..ee720d1e7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php @@ -0,0 +1,37 @@ + 'Guzzle\Tests\Service\Mock\Command\MockCommand', + 'test1' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand' + )); + + if (is_null($result)) { + $this->assertNull($factory->factory($key)); + } else { + $this->assertInstanceof($result, $factory->factory($key)); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php new file mode 100644 index 000000000..337263481 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php @@ -0,0 +1,68 @@ +getDescription(); + + $factory = new ServiceDescriptionFactory($d); + $this->assertSame($d, $factory->getServiceDescription()); + + if (is_null($result)) { + $this->assertNull($factory->factory($key)); + } else { + $this->assertInstanceof($result, $factory->factory($key)); + } + } + + public function testUsesUcFirstIfNoExactMatch() + { + $d = $this->getDescription(); + $factory = new ServiceDescriptionFactory($d, new Inflector()); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('Test')); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('test')); + } + + public function testUsesInflectionIfNoExactMatch() + { + $d = $this->getDescription(); + $factory = new ServiceDescriptionFactory($d, new Inflector()); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('Binks')); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('binks')); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\MockCommand', $factory->factory('JarJar')); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\MockCommand', $factory->factory('jar_jar')); + } + + protected function getDescription() + { + return ServiceDescription::factory(array( + 'operations' => array( + 'jar_jar' => array('class' => 'Guzzle\Tests\Service\Mock\Command\MockCommand'), + 'binks' => array('class' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand'), + 'Test' => array('class' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand') + ) + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php new file mode 100644 index 000000000..46b472eb5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php @@ -0,0 +1,110 @@ +command = new MockCommand(); + $this->request = new EntityEnclosingRequest('POST', 'http://www.test.com/some/path.php'); + $this->validator = new SchemaValidator(); + } + + protected function getCommand($location) + { + $command = new OperationCommand(array(), $this->getNestedCommand($location)); + $command->setClient(new MockClient()); + + return $command; + } + + protected function getNestedCommand($location) + { + return new Operation(array( + 'httpMethod' => 'POST', + 'parameters' => array( + 'foo' => new Parameter(array( + 'type' => 'object', + 'location' => $location, + 'sentAs' => 'Foo', + 'required' => true, + 'properties' => array( + 'test' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'baz' => array( + 'type' => 'boolean', + 'default' => true + ), + 'jenga' => array( + 'type' => 'string', + 'default' => 'hello', + 'sentAs' => 'Jenga_Yall!', + 'filters' => array('strtoupper') + ) + ) + ), + 'bar' => array('default' => 123) + ), + 'additionalProperties' => array( + 'type' => 'string', + 'filters' => array('strtoupper'), + 'location' => $location + ) + )), + 'arr' => new Parameter(array( + 'type' => 'array', + 'location' => $location, + 'items' => array( + 'type' => 'string', + 'filters' => array('strtoupper') + ) + )), + ) + )); + } + + protected function getCommandWithArrayParamAndFilters() + { + $operation = new Operation(array( + 'httpMethod' => 'POST', + 'parameters' => array( + 'foo' => new Parameter(array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'Foo', + 'required' => true, + 'default' => 'bar', + 'filters' => array('strtoupper') + )), + 'arr' => new Parameter(array( + 'type' => 'array', + 'location' => 'query', + 'sentAs' => 'Arr', + 'required' => true, + 'default' => array(123, 456, 789), + 'filters' => array(array('method' => 'implode', 'args' => array(',', '@value'))) + )) + ) + )); + $command = new OperationCommand(array(), $operation); + $command->setClient(new MockClient()); + + return $command; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php new file mode 100644 index 000000000..2a95c452a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php @@ -0,0 +1,63 @@ +getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('123', (string) $this->request->getBody()); + $this->assertNull($this->request->getHeader('Expect')); + } + + public function testAddsExpectHeaderWhenSetToTrue() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + $param->setData('expect_header', true); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('123', (string) $this->request->getBody()); + } + + public function testCanDisableExpectHeader() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + $param->setData('expect_header', false); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertNull($this->request->getHeader('Expect')); + } + + public function testCanSetExpectHeaderBasedOnSize() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + // The body is less than the cutoff + $param->setData('expect_header', 5); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertNull($this->request->getHeader('Expect')); + // Now check when the body is greater than the cutoff + $param->setData('expect_header', 2); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('100-Continue', (string) $this->request->getHeader('Expect')); + } + + public function testAddsContentEncodingWhenSetOnBody() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + $body = EntityBody::factory('foo'); + $body->compress(); + $visitor->visit($this->command, $this->request, $param, $body); + $this->assertEquals('gzip', (string) $this->request->getHeader('Content-Encoding')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php new file mode 100644 index 000000000..7ea1ae913 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php @@ -0,0 +1,48 @@ +getNestedCommand('header')->getParam('foo')->setSentAs('test'); + $param->setAdditionalProperties(new Parameter(array())); + $visitor->visit($this->command, $this->request, $param, 'test'); + } + + public function testVisitsLocation() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('header')->getParam('foo')->setSentAs('test'); + $param->setAdditionalProperties(false); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('123', (string) $this->request->getHeader('test')); + } + + public function testVisitsMappedPrefixHeaders() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('header')->getParam('foo')->setSentAs('test'); + $param->setSentAs('x-foo-'); + $param->setAdditionalProperties(new Parameter(array( + 'type' => 'string' + ))); + $visitor->visit($this->command, $this->request, $param, array( + 'bar' => 'test', + 'baz' => '123' + )); + $this->assertEquals('test', (string) $this->request->getHeader('x-foo-bar')); + $this->assertEquals('123', (string) $this->request->getHeader('x-foo-baz')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php new file mode 100644 index 000000000..ea6782f53 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php @@ -0,0 +1,60 @@ +after($this->command, $this->request); + + $param = $this->getNestedCommand('json')->getParam('foo'); + $visitor->visit($this->command, $this->request, $param->setSentAs('test'), '123'); + $visitor->visit($this->command, $this->request, $param->setSentAs('test2'), 'abc'); + $visitor->after($this->command, $this->request); + $this->assertEquals('{"test":"123","test2":"abc"}', (string) $this->request->getBody()); + } + + public function testAddsJsonHeader() + { + $visitor = new Visitor(); + $visitor->setContentTypeHeader('application/json-foo'); + $param = $this->getNestedCommand('json')->getParam('foo'); + $visitor->visit($this->command, $this->request, $param->setSentAs('test'), '123'); + $visitor->after($this->command, $this->request); + $this->assertEquals('application/json-foo', (string) $this->request->getHeader('Content-Type')); + } + + public function testRecursivelyBuildsJsonBodies() + { + $command = $this->getCommand('json'); + $request = $command->prepare(); + $this->assertEquals('{"Foo":{"test":{"baz":true,"Jenga_Yall!":"HELLO"},"bar":123}}', (string) $request->getBody()); + } + + public function testAppliesFiltersToAdditionalProperties() + { + $command = $this->getCommand('json'); + $command->set('foo', array('not_set' => 'abc')); + $request = $command->prepare(); + $result = json_decode($request->getBody(), true); + $this->assertEquals('ABC', $result['Foo']['not_set']); + } + + public function testAppliesFiltersToArrayItemValues() + { + $command = $this->getCommand('json'); + $command->set('arr', array('a', 'b')); + $request = $command->prepare(); + $result = json_decode($request->getBody(), true); + $this->assertEquals(array('A', 'B'), $result['arr']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php new file mode 100644 index 000000000..540b41087 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php @@ -0,0 +1,33 @@ +getNestedCommand('postField')->getParam('foo'); + $visitor->visit($this->command, $this->request, $param->setSentAs('test'), '123'); + $this->assertEquals('123', (string) $this->request->getPostField('test')); + } + + public function testRecursivelyBuildsPostFields() + { + $command = $this->getCommand('postField'); + $request = $command->prepare(); + $visitor = new Visitor(); + $param = $command->getOperation()->getParam('foo'); + $visitor->visit($command, $request, $param, $command['foo']); + $visitor->after($command, $request); + $this->assertEquals( + 'Foo[test][baz]=1&Foo[test][Jenga_Yall!]=HELLO&Foo[bar]=123', + rawurldecode((string) $request->getPostFields()) + ); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php new file mode 100644 index 000000000..21e3cec33 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php @@ -0,0 +1,54 @@ +getNestedCommand('postFile')->getParam('foo'); + + // Test using a path to a file + $visitor->visit($this->command, $this->request, $param->setSentAs('test_3'), __FILE__); + $this->assertInternalType('array', $this->request->getPostFile('test_3')); + + // Test with a PostFile + $visitor->visit($this->command, $this->request, $param->setSentAs(null), new PostFile('baz', __FILE__)); + $this->assertInternalType('array', $this->request->getPostFile('baz')); + } + + public function testVisitsLocationWithMultipleFiles() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'DoPost' => array( + 'httpMethod' => 'POST', + 'parameters' => array( + 'foo' => array( + 'location' => 'postFile', + 'type' => array('string', 'array') + ) + ) + ) + ) + )); + $this->getServer()->flush(); + $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length:0\r\n\r\n")); + $client = new Client($this->getServer()->getUrl()); + $client->setDescription($description); + $command = $client->getCommand('DoPost', array('foo' => array(__FILE__, __FILE__))); + $command->execute(); + $received = $this->getServer()->getReceivedRequests(); + $this->assertContains('name="foo[0]";', $received[0]); + $this->assertContains('name="foo[1]";', $received[0]); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php new file mode 100644 index 000000000..607af7603 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php @@ -0,0 +1,48 @@ +getNestedCommand('query')->getParam('foo')->setSentAs('test'); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('123', $this->request->getQuery()->get('test')); + } + + /** + * @covers Guzzle\Service\Command\LocationVisitor\Request\QueryVisitor + * @covers Guzzle\Service\Command\LocationVisitor\Request\AbstractRequestVisitor::resolveRecursively + */ + public function testRecursivelyBuildsQueryStrings() + { + $command = $this->getCommand('query'); + $command->getOperation()->getParam('foo')->setSentAs('Foo'); + $request = $command->prepare(); + $this->assertEquals( + 'Foo[test][baz]=1&Foo[test][Jenga_Yall!]=HELLO&Foo[bar]=123', + rawurldecode($request->getQuery()) + ); + } + + /** + * @covers Guzzle\Service\Command\LocationVisitor\Request\AbstractRequestVisitor::resolveRecursively + */ + public function testFiltersAreAppliedToArrayParamType() + { + $command = $this->getCommandWithArrayParamAndFilters(); + $request = $command->prepare(); + $query = $request->getQuery(); + // param type 'string' + $this->assertEquals('BAR', $query->get('Foo')); + // param type 'array' + $this->assertEquals('123,456,789', $query->get('Arr')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php new file mode 100644 index 000000000..ff8cec599 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php @@ -0,0 +1,20 @@ +getNestedCommand('response_body')->getParam('foo'); + $visitor->visit($this->command, $this->request, $param, sys_get_temp_dir() . '/foo.txt'); + $body = $this->readAttribute($this->request, 'responseBody'); + $this->assertContains('/foo.txt', $body->getUri()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php new file mode 100644 index 000000000..beb58b00a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php @@ -0,0 +1,558 @@ + array( + 'xmlRoot' => array( + 'name' => 'test', + 'namespaces' => 'http://foo.com' + ) + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array('location' => 'xml', 'type' => 'string') + ) + ), + array('Foo' => 'test', 'Baz' => 'bar'), + 'testbar' + ), + // Ensure that the content-type is not added + array(array('parameters' => array('Foo' => array('location' => 'xml', 'type' => 'string'))), array(), ''), + // Test with adding attributes and no namespace + array( + array( + 'data' => array( + 'xmlRoot' => array( + 'name' => 'test' + ) + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string', 'data' => array('xmlAttribute' => true)) + ) + ), + array('Foo' => 'test', 'Baz' => 'bar'), + '' + ), + // Test adding with an array + array( + array( + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array( + 'type' => 'array', + 'location' => 'xml', + 'items' => array( + 'type' => 'numeric', + 'sentAs' => 'Bar' + ) + ) + ) + ), + array('Foo' => 'test', 'Baz' => array(1, 2)), + 'test12' + ), + // Test adding an object + array( + array( + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Bar' => array('type' => 'string'), + 'Bam' => array() + ) + ) + ) + ), + array('Foo' => 'test', 'Baz' => array('Bar' => 'abc', 'Bam' => 'foo')), + 'testabcfoo' + ), + // Add an array that contains an object + array( + array( + 'parameters' => array( + 'Baz' => array( + 'type' => 'array', + 'location' => 'xml', + 'items' => array( + 'type' => 'object', + 'sentAs' => 'Bar', + 'properties' => array('A' => array(), 'B' => array()) + ) + ) + ) + ), + array('Baz' => array( + array('A' => '1', 'B' => '2'), + array('A' => '3', 'B' => '4') + )), + '1234' + ), + // Add an object of attributes + array( + array( + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Bar' => array('type' => 'string', 'data' => array('xmlAttribute' => true)), + 'Bam' => array() + ) + ) + ) + ), + array('Foo' => 'test', 'Baz' => array('Bar' => 'abc', 'Bam' => 'foo')), + 'testfoo' + ), + // Check order doesn't matter + array( + array( + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Bar' => array('type' => 'string', 'data' => array('xmlAttribute' => true)), + 'Bam' => array() + ) + ) + ) + ), + array('Foo' => 'test', 'Baz' => array('Bam' => 'foo', 'Bar' => 'abc')), + 'testfoo' + ), + // Add values with custom namespaces + array( + array( + 'parameters' => array( + 'Foo' => array( + 'location' => 'xml', + 'type' => 'string', + 'data' => array( + 'xmlNamespace' => 'http://foo.com' + ) + ) + ) + ), + array('Foo' => 'test'), + 'test' + ), + // Add attributes with custom namespace prefix + array( + array( + 'parameters' => array( + 'Wrap' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Foo' => array( + 'type' => 'string', + 'sentAs' => 'xsi:baz', + 'data' => array( + 'xmlNamespace' => 'http://foo.com', + 'xmlAttribute' => true + ) + ) + ) + ), + ) + ), + array('Wrap' => array( + 'Foo' => 'test' + )), + '' + ), + // Add nodes with custom namespace prefix + array( + array( + 'parameters' => array( + 'Wrap' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Foo' => array( + 'type' => 'string', + 'sentAs' => 'xsi:Foo', + 'data' => array( + 'xmlNamespace' => 'http://foobar.com' + ) + ) + ) + ), + ) + ), + array('Wrap' => array( + 'Foo' => 'test' + )), + 'test' + ), + array( + array( + 'parameters' => array( + 'Foo' => array( + 'location' => 'xml', + 'type' => 'string', + 'data' => array( + 'xmlNamespace' => 'http://foo.com' + ) + ) + ) + ), + array('Foo' => '

    This is a title

    '), + 'This is a title]]>' + ), + // Flat array at top level + array( + array( + 'parameters' => array( + 'Bars' => array( + 'type' => 'array', + 'data' => array('xmlFlattened' => true), + 'location' => 'xml', + 'items' => array( + 'type' => 'object', + 'sentAs' => 'Bar', + 'properties' => array( + 'A' => array(), + 'B' => array() + ) + ) + ), + 'Boos' => array( + 'type' => 'array', + 'data' => array('xmlFlattened' => true), + 'location' => 'xml', + 'items' => array( + 'sentAs' => 'Boo', + 'type' => 'string' + ) + ) + ) + ), + array( + 'Bars' => array( + array('A' => '1', 'B' => '2'), + array('A' => '3', 'B' => '4') + ), + 'Boos' => array('test', '123') + ), + '1234test123' + ), + // Nested flat arrays + array( + array( + 'parameters' => array( + 'Delete' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Items' => array( + 'type' => 'array', + 'data' => array('xmlFlattened' => true), + 'items' => array( + 'type' => 'object', + 'sentAs' => 'Item', + 'properties' => array( + 'A' => array(), + 'B' => array() + ) + ) + ) + ) + ) + ) + ), + array( + 'Delete' => array( + 'Items' => array( + array('A' => '1', 'B' => '2'), + array('A' => '3', 'B' => '4') + ) + ) + ), + '1234' + ) + ); + } + + /** + * @dataProvider xmlProvider + */ + public function testSerializesXml(array $operation, array $input, $xml) + { + $operation = new Operation($operation); + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array($input, $operation)) + ->getMockForAbstractClass(); + $command->setClient(new Client('http://www.test.com/some/path.php')); + $request = $command->prepare(); + if (!empty($input)) { + $this->assertEquals('application/xml', (string) $request->getHeader('Content-Type')); + } else { + $this->assertNull($request->getHeader('Content-Type')); + } + $body = str_replace(array("\n", ""), '', (string) $request->getBody()); + $this->assertEquals($xml, $body); + } + + public function testAddsContentTypeAndTopLevelValues() + { + $operation = new Operation(array( + 'data' => array( + 'xmlRoot' => array( + 'name' => 'test', + 'namespaces' => array( + 'xsi' => 'http://foo.com' + ) + ) + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array('location' => 'xml', 'type' => 'string') + ) + )); + + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array( + 'Foo' => 'test', + 'Baz' => 'bar' + ), $operation)) + ->getMockForAbstractClass(); + + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals('application/xml', (string) $request->getHeader('Content-Type')); + $this->assertEquals( + '' . "\n" + . 'testbar' . "\n", + (string) $request->getBody() + ); + } + + public function testCanChangeContentType() + { + $visitor = new XmlVisitor(); + $visitor->setContentTypeHeader('application/foo'); + $this->assertEquals('application/foo', $this->readAttribute($visitor, 'contentType')); + } + + public function testCanAddArrayOfSimpleTypes() + { + $request = new EntityEnclosingRequest('POST', 'http://foo.com'); + $visitor = new XmlVisitor(); + $param = new Parameter(array( + 'type' => 'object', + 'location' => 'xml', + 'name' => 'Out', + 'properties' => array( + 'Nodes' => array( + 'required' => true, + 'type' => 'array', + 'min' => 1, + 'items' => array('type' => 'string', 'sentAs' => 'Node') + ) + ) + )); + + $param->setParent(new Operation(array( + 'data' => array( + 'xmlRoot' => array( + 'name' => 'Test', + 'namespaces' => array( + 'https://foo/' + ) + ) + ) + ))); + + $value = array('Nodes' => array('foo', 'baz')); + $this->assertTrue($this->validator->validate($param, $value)); + $visitor->visit($this->command, $request, $param, $value); + $visitor->after($this->command, $request); + + $this->assertEquals( + "\n" + . "foobaz\n", + (string) $request->getBody() + ); + } + + public function testCanAddMultipleNamespacesToRoot() + { + $operation = new Operation(array( + 'data' => array( + 'xmlRoot' => array( + 'name' => 'Hi', + 'namespaces' => array( + 'xsi' => 'http://foo.com', + 'foo' => 'http://foobar.com' + ) + ) + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string') + ) + )); + + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array( + 'Foo' => 'test' + ), $operation)) + ->getMockForAbstractClass(); + + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals('application/xml', (string) $request->getHeader('Content-Type')); + $this->assertEquals( + '' . "\n" + . 'test' . "\n", + (string) $request->getBody() + ); + } + + public function testValuesAreFiltered() + { + $operation = new Operation(array( + 'parameters' => array( + 'Foo' => array( + 'location' => 'xml', + 'type' => 'string', + 'filters' => array('strtoupper') + ), + 'Bar' => array( + 'location' => 'xml', + 'type' => 'object', + 'properties' => array( + 'Baz' => array( + 'filters' => array('strtoupper') + ) + ) + ) + ) + )); + + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array( + 'Foo' => 'test', + 'Bar' => array( + 'Baz' => 'abc' + ) + ), $operation)) + ->getMockForAbstractClass(); + + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals( + '' . "\n" + . 'TESTABC' . "\n", + (string) $request->getBody() + ); + } + + public function testSkipsNullValues() + { + $operation = new Operation(array( + 'parameters' => array( + 'Foo' => array( + 'location' => 'xml', + 'type' => 'string' + ), + 'Bar' => array( + 'location' => 'xml', + 'type' => 'object', + 'properties' => array( + 'Baz' => array(), + 'Bam' => array(), + ) + ), + 'Arr' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'string' + ) + ) + ) + )); + + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array( + 'Foo' => null, + 'Bar' => array( + 'Bar' => null, + 'Bam' => 'test' + ), + 'Arr' => array(null) + ), $operation)) + ->getMockForAbstractClass(); + + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals( + '' . "\n" + . 'test' . "\n", + (string) $request->getBody() + ); + } + + public function testAllowsXmlEncoding() + { + $operation = new Operation(array( + 'data' => array( + 'xmlEncoding' => 'UTF-8' + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml') + ) + )); + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array('Foo' => 'test'), $operation)) + ->getMockForAbstractClass(); + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals( + '' . "\n" + . 'test' . "\n", + (string) $request->getBody() + ); + } + + public function testAllowsSendingXmlPayloadIfNoXmlParamsWereSet() + { + $operation = new Operation(array( + 'httpMethod' => 'POST', + 'data' => array('xmlAllowEmpty' => true), + 'parameters' => array('Foo' => array('location' => 'xml')) + )); + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array(), $operation)) + ->getMockForAbstractClass(); + $command->setClient(new Client('http://foo.com')); + $request = $command->prepare(); + $this->assertEquals( + '' . "\n" + . '' . "\n", + (string) $request->getBody() + ); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php new file mode 100644 index 000000000..7b8600342 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php @@ -0,0 +1,29 @@ +value = array(); + $this->command = new MockCommand(); + $this->response = new Response(200, array( + 'X-Foo' => 'bar', + 'Content-Length' => 3, + 'Content-Type' => 'text/plain' + ), 'Foo'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php new file mode 100644 index 000000000..932e39bff --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php @@ -0,0 +1,21 @@ + 'body', 'name' => 'foo')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('Foo', (string) $this->value['foo']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php new file mode 100644 index 000000000..db54b1abb --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php @@ -0,0 +1,98 @@ + 'header', + 'name' => 'ContentType', + 'sentAs' => 'Content-Type' + )); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('text/plain', $this->value['ContentType']); + } + + public function testVisitsLocationWithFilters() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'header', + 'name' => 'Content-Type', + 'filters' => array('strtoupper') + )); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('TEXT/PLAIN', $this->value['Content-Type']); + } + + public function testVisitsMappedPrefixHeaders() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'header', + 'name' => 'Metadata', + 'sentAs' => 'X-Baz-', + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'string' + ) + )); + $response = new Response(200, array( + 'X-Baz-Test' => 'ABC', + 'X-Baz-Bar' => array('123', '456'), + 'Content-Length' => 3 + ), 'Foo'); + $visitor->visit($this->command, $response, $param, $this->value); + $this->assertEquals(array( + 'Metadata' => array( + 'Test' => 'ABC', + 'Bar' => array('123', '456') + ) + ), $this->value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownHeaders() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'header', + 'name' => 'Content-Type', + 'additionalParameters' => false + )); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('text/plain', $this->value['Content-Type']); + $this->assertArrayNotHasKey('X-Foo', $this->value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownPropertiesWithAliasing() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'header', + 'name' => 'ContentType', + 'sentAs' => 'Content-Type', + 'additionalParameters' => false + )); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('text/plain', $this->value['ContentType']); + $this->assertArrayNotHasKey('X-Foo', $this->value); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php new file mode 100644 index 000000000..4f8d30b1e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php @@ -0,0 +1,157 @@ +getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setMethods(array('getResponse')) + ->getMockForAbstractClass(); + $command->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue(new Response(200, null, '{"foo":"bar"}'))); + $result = array(); + $visitor->before($command, $result); + $this->assertEquals(array('foo' => 'bar'), $result); + } + + public function testVisitsLocation() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'array', + 'items' => array( + 'filters' => 'strtoupper', + 'type' => 'string' + ) + )); + $this->value = array('foo' => array('a', 'b', 'c')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('A', 'B', 'C'), $this->value['foo']); + } + + public function testRenamesTopLevelValues() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'sentAs' => 'Baz', + 'type' => 'string', + )); + $this->value = array('Baz' => 'test'); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => 'test'), $this->value); + } + + public function testRenamesDoesNotFailForNonExistentKey() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array( + 'bar' => array( + 'name' => 'bar', + 'sentAs' => 'baz', + ), + ), + )); + $this->value = array('foo' => array('unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('unknown' => 'Unknown')), $this->value); + } + + public function testTraversesObjectsAndAppliesFilters() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array( + 'foo' => array('filters' => 'strtoupper'), + 'bar' => array('filters' => 'strtolower') + ) + )); + $this->value = array('foo' => array('foo' => 'hello', 'bar' => 'THERE')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => 'HELLO', 'bar' => 'there'), $this->value['foo']); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownProperties() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'bar' => array( + 'type' => 'string', + 'name' => 'bar', + ), + ), + )); + $this->value = array('foo' => array('bar' => 15, 'unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('bar' => 15)), $this->value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownPropertiesWithAliasing() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'bar' => array( + 'name' => 'bar', + 'sentAs' => 'baz', + ), + ), + )); + $this->value = array('foo' => array('baz' => 15, 'unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('bar' => 15)), $this->value); + } + + public function testWalksAdditionalProperties() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'object', + 'properties' => array( + 'bar' => array( + 'type' => 'string', + 'filters' => array('base64_decode') + ) + ), + ), + )); + $this->value = array('foo' => array('baz' => array('bar' => 'Zm9v'))); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('foo', $this->value['foo']['baz']['bar']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php new file mode 100644 index 000000000..23cd40fed --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php @@ -0,0 +1,21 @@ + 'reasonPhrase', 'name' => 'phrase')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('OK', $this->value['phrase']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php new file mode 100644 index 000000000..7211a5801 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php @@ -0,0 +1,21 @@ + 'statusCode', 'name' => 'code')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(200, $this->value['code']); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php new file mode 100644 index 000000000..f87cec7cd --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php @@ -0,0 +1,431 @@ +getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setMethods(array('getResponse')) + ->getMockForAbstractClass(); + $command->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue(new Response(200, null, 'test'))); + $result = array(); + $visitor->before($command, $result); + $this->assertEquals(array('Bar' => 'test'), $result); + } + + public function testBeforeMethodParsesXmlWithNamespace() + { + $this->markTestSkipped("Response/XmlVisitor cannot accept 'xmlns' in response, see #368 (http://git.io/USa1mA)."); + + $visitor = new Visitor(); + $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setMethods(array('getResponse')) + ->getMockForAbstractClass(); + $command->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue(new Response(200, null, 'test'))); + $result = array(); + $visitor->before($command, $result); + $this->assertEquals(array('Bar' => 'test'), $result); + } + + public function testBeforeMethodParsesNestedXml() + { + $visitor = new Visitor(); + $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setMethods(array('getResponse')) + ->getMockForAbstractClass(); + $command->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue(new Response(200, null, 'test'))); + $result = array(); + $visitor->before($command, $result); + $this->assertEquals(array('Items' => array('Bar' => 'test')), $result); + } + + public function testCanExtractAndRenameTopLevelXmlValues() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'xml', + 'name' => 'foo', + 'sentAs' => 'Bar' + )); + $value = array('Bar' => 'test'); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertArrayHasKey('foo', $value); + $this->assertEquals('test', $value['foo']); + } + + public function testEnsuresRepeatedArraysAreInCorrectLocations() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'xml', + 'name' => 'foo', + 'sentAs' => 'Foo', + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'Bar' => array('type' => 'string'), + 'Baz' => array('type' => 'string'), + 'Bam' => array('type' => 'string') + ) + ) + )); + + $xml = new \SimpleXMLElement('12'); + $value = json_decode(json_encode($xml), true); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array( + 'foo' => array( + array ( + 'Bar' => '1', + 'Baz' => '2' + ) + ) + ), $value); + } + + public function testEnsuresFlatArraysAreFlat() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'xml', + 'name' => 'foo', + 'type' => 'array', + 'items' => array('type' => 'string') + )); + + $value = array('foo' => array('bar', 'baz')); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array('foo' => array('bar', 'baz')), $value); + + $value = array('foo' => 'bar'); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array('foo' => array('bar')), $value); + } + + public function xmlDataProvider() + { + $param = new Parameter(array( + 'location' => 'xml', + 'name' => 'Items', + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'name' => 'Item', + 'properties' => array( + 'Bar' => array('type' => 'string'), + 'Baz' => array('type' => 'string') + ) + ) + )); + + return array( + array($param, '12', array( + 'Items' => array( + array('Bar' => 1), + array('Bar' => 2) + ) + )), + array($param, '1', array( + 'Items' => array( + array('Bar' => 1) + ) + )), + array($param, '', array( + 'Items' => array() + )) + ); + } + + /** + * @dataProvider xmlDataProvider + */ + public function testEnsuresWrappedArraysAreInCorrectLocations($param, $xml, $result) + { + $visitor = new Visitor(); + $xml = new \SimpleXMLElement($xml); + $value = json_decode(json_encode($xml), true); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals($result, $value); + } + + public function testCanRenameValues() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'TerminatingInstances', + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'instancesSet', + 'items' => array( + 'name' => 'item', + 'type' => 'object', + 'sentAs' => 'item', + 'properties' => array( + 'InstanceId' => array( + 'type' => 'string', + 'sentAs' => 'instanceId', + ), + 'CurrentState' => array( + 'type' => 'object', + 'sentAs' => 'currentState', + 'properties' => array( + 'Code' => array( + 'type' => 'numeric', + 'sentAs' => 'code', + ), + 'Name' => array( + 'type' => 'string', + 'sentAs' => 'name', + ), + ), + ), + 'PreviousState' => array( + 'type' => 'object', + 'sentAs' => 'previousState', + 'properties' => array( + 'Code' => array( + 'type' => 'numeric', + 'sentAs' => 'code', + ), + 'Name' => array( + 'type' => 'string', + 'sentAs' => 'name', + ), + ), + ), + ), + ) + )); + + $value = array( + 'instancesSet' => array ( + 'item' => array ( + 'instanceId' => 'i-3ea74257', + 'currentState' => array( + 'code' => '32', + 'name' => 'shutting-down', + ), + 'previousState' => array( + 'code' => '16', + 'name' => 'running', + ), + ), + ) + ); + + $visitor->visit($this->command, $this->response, $param, $value); + + $this->assertEquals(array( + 'TerminatingInstances' => array( + array( + 'InstanceId' => 'i-3ea74257', + 'CurrentState' => array( + 'Code' => '32', + 'Name' => 'shutting-down', + ), + 'PreviousState' => array( + 'Code' => '16', + 'Name' => 'running', + ) + ) + ) + ), $value); + } + + public function testCanRenameAttributes() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'RunningQueues', + 'type' => 'array', + 'location' => 'xml', + 'items' => array( + 'type' => 'object', + 'sentAs' => 'item', + 'properties' => array( + 'QueueId' => array( + 'type' => 'string', + 'sentAs' => 'queue_id', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + 'CurrentState' => array( + 'type' => 'object', + 'properties' => array( + 'Code' => array( + 'type' => 'numeric', + 'sentAs' => 'code', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + 'Name' => array( + 'sentAs' => 'name', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + ), + ), + 'PreviousState' => array( + 'type' => 'object', + 'properties' => array( + 'Code' => array( + 'type' => 'numeric', + 'sentAs' => 'code', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + 'Name' => array( + 'sentAs' => 'name', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + ), + ), + ), + ) + )); + + $xml = ''; + $value = json_decode(json_encode(new \SimpleXMLElement($xml)), true); + $visitor->visit($this->command, $this->response, $param, $value); + + $this->assertEquals(array( + 'RunningQueues' => array( + array( + 'QueueId' => 'q-3ea74257', + 'CurrentState' => array( + 'Code' => '32', + 'Name' => 'processing', + ), + 'PreviousState' => array( + 'Code' => '16', + 'Name' => 'wait', + ), + ), + ) + ), $value); + } + + public function testAddsEmptyArraysWhenValueIsMissing() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'Foo', + 'type' => 'array', + 'location' => 'xml', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'Baz' => array('type' => 'array'), + 'Bar' => array( + 'type' => 'object', + 'properties' => array( + 'Baz' => array('type' => 'array'), + ) + ) + ) + ) + )); + + $value = array(); + $visitor->visit($this->command, $this->response, $param, $value); + + $value = array( + 'Foo' => array( + 'Bar' => array() + ) + ); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array( + 'Foo' => array( + array( + 'Bar' => array() + ) + ) + ), $value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownProperties() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'bar' => array( + 'type' => 'string', + 'name' => 'bar', + ), + ), + )); + $this->value = array('foo' => array('bar' => 15, 'unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('bar' => 15)), $this->value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownPropertiesWithAliasing() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'bar' => array( + 'name' => 'bar', + 'sentAs' => 'baz', + ), + ), + )); + $this->value = array('foo' => array('baz' => 15, 'unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('bar' => 15)), $this->value); + } + + public function testProperlyHandlesEmptyStringValues() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array( + 'bar' => array('type' => 'string') + ), + )); + $xml = ''; + $value = json_decode(json_encode(new \SimpleXMLElement($xml)), true); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array('foo' => array('bar' => '')), $value); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php new file mode 100644 index 000000000..a252ffe60 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php @@ -0,0 +1,53 @@ +assertInstanceOf('Guzzle\Service\Command\LocationVisitor\Request\JsonVisitor', $f->getRequestVisitor('json')); + $this->assertInstanceOf('Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor', $f->getResponseVisitor('json')); + } + + public function testCanUseCustomMappings() + { + $f = new VisitorFlyweight(array()); + $this->assertEquals(array(), $this->readAttribute($f, 'mappings')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage No request visitor has been mapped for foo + */ + public function testThrowsExceptionWhenRetrievingUnknownVisitor() + { + VisitorFlyweight::getInstance()->getRequestVisitor('foo'); + } + + public function testCachesVisitors() + { + $f = new VisitorFlyweight(); + $v1 = $f->getRequestVisitor('json'); + $this->assertSame($v1, $f->getRequestVisitor('json')); + } + + public function testAllowsAddingVisitors() + { + $f = new VisitorFlyweight(); + $j1 = new JsonRequestVisitor(); + $j2 = new JsonResponseVisitor(); + $f->addRequestVisitor('json', $j1); + $f->addResponseVisitor('json', $j2); + $this->assertSame($j1, $f->getRequestVisitor('json')); + $this->assertSame($j2, $f->getResponseVisitor('json')); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php new file mode 100644 index 000000000..95fb533af --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php @@ -0,0 +1,102 @@ +getRequestSerializer(); + $b = new DefaultRequestSerializer(VisitorFlyweight::getInstance()); + $operation->setRequestSerializer($b); + $this->assertNotSame($a, $operation->getRequestSerializer()); + } + + public function testPreparesRequestUsingSerializer() + { + $op = new OperationCommand(array(), new Operation()); + $op->setClient(new Client()); + $s = $this->getMockBuilder('Guzzle\Service\Command\RequestSerializerInterface') + ->setMethods(array('prepare')) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('prepare') + ->will($this->returnValue(new EntityEnclosingRequest('POST', 'http://foo.com'))); + $op->setRequestSerializer($s); + $op->prepare(); + } + + public function testParsesResponsesWithResponseParser() + { + $op = new OperationCommand(array(), new Operation()); + $p = $this->getMockBuilder('Guzzle\Service\Command\ResponseParserInterface') + ->setMethods(array('parse')) + ->getMockForAbstractClass(); + $p->expects($this->once()) + ->method('parse') + ->will($this->returnValue(array('foo' => 'bar'))); + $op->setResponseParser($p); + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200), true); + $this->assertEquals(array('foo' => 'bar'), $op->execute()); + } + + public function testParsesResponsesUsingModelParserWhenMatchingModelIsFound() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'foo' => array('responseClass' => 'bar', 'responseType' => 'model') + ), + 'models' => array( + 'bar' => array( + 'type' => 'object', + 'properties' => array( + 'Baz' => array('type' => 'string', 'location' => 'xml') + ) + ) + ) + )); + + $op = new OperationCommand(array(), $description->getOperation('foo')); + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, array( + 'Content-Type' => 'application/xml' + ), 'Bar'), true); + $result = $op->execute(); + $this->assertEquals(new Model(array('Baz' => 'Bar')), $result); + } + + public function testAllowsRawResponses() + { + $description = new ServiceDescription(array( + 'operations' => array('foo' => array('responseClass' => 'bar', 'responseType' => 'model')), + 'models' => array('bar' => array()) + )); + $op = new OperationCommand(array( + OperationCommand::RESPONSE_PROCESSING => OperationCommand::TYPE_RAW + ), $description->getOperation('foo')); + $op->setClient(new Client()); + $request = $op->prepare(); + $response = new Response(200, array( + 'Content-Type' => 'application/xml' + ), 'Bar'); + $request->setResponse($response, true); + $this->assertSame($response, $op->execute()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php new file mode 100644 index 000000000..69ba1fcea --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php @@ -0,0 +1,335 @@ +addVisitor('foo', $visitor); + $this->assertSame($visitor, $this->readAttribute($p, 'factory')->getResponseVisitor('foo')); + } + + public function testUsesParentParser() + { + $p = new OperationResponseParser(new VisitorFlyweight()); + $operation = new Operation(); + $operation->setServiceDescription(new ServiceDescription()); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($p)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/xml'), 'C'), true); + $this->assertInstanceOf('SimpleXMLElement', $op->execute()); + } + + public function testVisitsLocations() + { + $parser = new OperationResponseParser(new VisitorFlyweight(array())); + $parser->addVisitor('statusCode', new StatusCodeVisitor()); + $parser->addVisitor('reasonPhrase', new ReasonPhraseVisitor()); + $parser->addVisitor('json', new JsonVisitor()); + $op = new OperationCommand(array(), $this->getDescription()->getOperation('test')); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(201), true); + $result = $op->execute(); + $this->assertEquals(201, $result['code']); + $this->assertEquals('Created', $result['phrase']); + } + + public function testVisitsLocationsForJsonResponse() + { + $parser = OperationResponseParser::getInstance(); + $operation = $this->getDescription()->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array( + 'Content-Type' => 'application/json' + ), '{"baz":"bar","enigma":"123"}'), true); + $result = $op->execute(); + $this->assertEquals(array( + 'baz' => 'bar', + 'enigma' => '123', + 'code' => 200, + 'phrase' => 'OK' + ), $result->toArray()); + } + + public function testSkipsUnkownModels() + { + $parser = OperationResponseParser::getInstance(); + $operation = $this->getDescription()->getOperation('test'); + $operation->setResponseClass('Baz')->setResponseType('model'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(201), true); + $this->assertInstanceOf('Guzzle\Http\Message\Response', $op->execute()); + } + + public function testAllowsModelProcessingToBeDisabled() + { + $parser = OperationResponseParser::getInstance(); + $operation = $this->getDescription()->getOperation('test'); + $op = new OperationCommand(array('command.response_processing' => 'native'), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array( + 'Content-Type' => 'application/json' + ), '{"baz":"bar","enigma":"123"}'), true); + $result = $op->execute(); + $this->assertInstanceOf('Guzzle\Service\Resource\Model', $result); + $this->assertEquals(array( + 'baz' => 'bar', + 'enigma' => '123' + ), $result->toArray()); + } + + public function testCanInjectModelSchemaIntoModels() + { + $parser = new OperationResponseParser(VisitorFlyweight::getInstance(), true); + $desc = $this->getDescription(); + $operation = $desc->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array( + 'Content-Type' => 'application/json' + ), '{"baz":"bar","enigma":"123"}'), true); + $result = $op->execute(); + $this->assertSame($result->getStructure(), $desc->getModel('Foo')); + } + + public function testDoesNotParseXmlWhenNotUsingXmlVisitor() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Foo' => array( + 'type' => 'object', + 'properties' => array('baz' => array('location' => 'body')) + ) + ) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $brokenXml = '<><><>>>>'; + $op->prepare()->setResponse(new Response(200, array( + 'Content-Type' => 'application/xml' + ), $brokenXml), true); + $result = $op->execute(); + $this->assertEquals(array('baz'), $result->getKeys()); + $this->assertEquals($brokenXml, (string) $result['baz']); + } + + public function testVisitsAdditionalProperties() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Foo' => array( + 'type' => 'object', + 'properties' => array( + 'code' => array('location' => 'statusCode') + ), + 'additionalProperties' => array( + 'location' => 'json', + 'type' => 'object', + 'properties' => array( + 'a' => array( + 'type' => 'string', + 'filters' => 'strtoupper' + ) + ) + ) + ) + ) + )); + + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $json = '[{"a":"test"},{"a":"baz"}]'; + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), $json), true); + $result = $op->execute()->toArray(); + $this->assertEquals(array( + 'code' => 200, + array('a' => 'TEST'), + array('a' => 'BAZ') + ), $result); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testAdditionalPropertiesDisabledDiscardsData() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Foo' => array( + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'name' => array( + 'location' => 'json', + 'type' => 'string', + ), + 'nested' => array( + 'location' => 'json', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'width' => array( + 'type' => 'integer' + ) + ), + ), + 'code' => array('location' => 'statusCode') + ), + + ) + ) + )); + + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $json = '{"name":"test", "volume":2.0, "nested":{"width":10,"bogus":1}}'; + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), $json), true); + $result = $op->execute()->toArray(); + $this->assertEquals(array( + 'name' => 'test', + 'nested' => array( + 'width' => 10, + ), + 'code' => 200 + ), $result); + } + + public function testCreatesCustomResponseClassInterface() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Guzzle\Tests\Mock\CustomResponseModel')) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), 'hi!'), true); + $result = $op->execute(); + $this->assertInstanceOf('Guzzle\Tests\Mock\CustomResponseModel', $result); + $this->assertSame($op, $result->command); + } + + /** + * @expectedException \Guzzle\Service\Exception\ResponseClassException + * @expectedExceptionMessage must exist + */ + public function testEnsuresResponseClassExists() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo\Baz\Bar')) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), 'hi!'), true); + $op->execute(); + } + + /** + * @expectedException \Guzzle\Service\Exception\ResponseClassException + * @expectedExceptionMessage and implement + */ + public function testEnsuresResponseClassImplementsResponseClassInterface() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => __CLASS__)) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), 'hi!'), true); + $op->execute(); + } + + protected function getDescription() + { + return ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Foo' => array( + 'type' => 'object', + 'properties' => array( + 'baz' => array('type' => 'string', 'location' => 'json'), + 'code' => array('location' => 'statusCode'), + 'phrase' => array('location' => 'reasonPhrase'), + ) + ) + ) + )); + } + + public function testCanAddListenerToParseDomainObjects() + { + $client = new Client(); + $client->setDescription(ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'FooBazBar')) + ))); + $foo = new \stdClass(); + $client->getEventDispatcher()->addListener('command.parse_response', function ($e) use ($foo) { + $e['result'] = $foo; + }); + $command = $client->getCommand('test'); + $command->prepare()->setResponse(new Response(200), true); + $result = $command->execute(); + $this->assertSame($result, $foo); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/501 + */ + public function testAdditionalPropertiesWithRefAreResolved() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Baz' => array('type' => 'string'), + 'Foo' => array( + 'type' => 'object', + 'additionalProperties' => array('$ref' => 'Baz', 'location' => 'json') + ) + ) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $json = '{"a":"a","b":"b","c":"c"}'; + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), $json), true); + $result = $op->execute()->toArray(); + $this->assertEquals(array('a' => 'a', 'b' => 'b', 'c' => 'c'), $result); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php new file mode 100644 index 000000000..ae33b6925 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php @@ -0,0 +1,308 @@ + 'test', + 'summary' => 'doc', + 'notes' => 'notes', + 'documentationUrl' => 'http://www.example.com', + 'httpMethod' => 'POST', + 'uri' => '/api/v1', + 'responseClass' => 'array', + 'responseNotes' => 'returns the json_decoded response', + 'deprecated' => true, + 'parameters' => array( + 'key' => array( + 'required' => true, + 'type' => 'string', + 'maxLength' => 10 + ), + 'key_2' => array( + 'required' => true, + 'type' => 'integer', + 'default' => 10 + ) + ) + )); + + $this->assertEquals('test', $c->getName()); + $this->assertEquals('doc', $c->getSummary()); + $this->assertEquals('http://www.example.com', $c->getDocumentationUrl()); + $this->assertEquals('POST', $c->getHttpMethod()); + $this->assertEquals('/api/v1', $c->getUri()); + $this->assertEquals('array', $c->getResponseClass()); + $this->assertEquals('returns the json_decoded response', $c->getResponseNotes()); + $this->assertTrue($c->getDeprecated()); + $this->assertEquals('Guzzle\\Service\\Command\\OperationCommand', $c->getClass()); + $this->assertEquals(array( + 'key' => new Parameter(array( + 'name' => 'key', + 'required' => true, + 'type' => 'string', + 'maxLength' => 10, + 'parent' => $c + )), + 'key_2' => new Parameter(array( + 'name' => 'key_2', + 'required' => true, + 'type' => 'integer', + 'default' => 10, + 'parent' => $c + )) + ), $c->getParams()); + + $this->assertEquals(new Parameter(array( + 'name' => 'key_2', + 'required' => true, + 'type' => 'integer', + 'default' => 10, + 'parent' => $c + )), $c->getParam('key_2')); + + $this->assertNull($c->getParam('afefwef')); + $this->assertArrayNotHasKey('parent', $c->getParam('key_2')->toArray()); + } + + public function testAllowsConcreteCommands() + { + $c = new Operation(array( + 'name' => 'test', + 'class' => 'Guzzle\\Service\\Command\ClosureCommand', + 'parameters' => array( + 'p' => new Parameter(array( + 'name' => 'foo' + )) + ) + )); + $this->assertEquals('Guzzle\\Service\\Command\ClosureCommand', $c->getClass()); + } + + public function testConvertsToArray() + { + $data = array( + 'name' => 'test', + 'class' => 'Guzzle\\Service\\Command\ClosureCommand', + 'summary' => 'test', + 'documentationUrl' => 'http://www.example.com', + 'httpMethod' => 'PUT', + 'uri' => '/', + 'parameters' => array('p' => array('name' => 'foo')) + ); + $c = new Operation($data); + $toArray = $c->toArray(); + unset($data['name']); + $this->assertArrayHasKey('parameters', $toArray); + $this->assertInternalType('array', $toArray['parameters']); + + // Normalize the array + unset($data['parameters']); + unset($toArray['parameters']); + + $data['responseType'] = 'primitive'; + $data['responseClass'] = 'array'; + $this->assertEquals($data, $toArray); + } + + public function testDeterminesIfHasParam() + { + $command = $this->getTestCommand(); + $this->assertTrue($command->hasParam('data')); + $this->assertFalse($command->hasParam('baz')); + } + + public function testReturnsParamNames() + { + $command = $this->getTestCommand(); + $this->assertEquals(array('data'), $command->getParamNames()); + } + + protected function getTestCommand() + { + return new Operation(array( + 'parameters' => array( + 'data' => new Parameter(array( + 'type' => 'string' + )) + ) + )); + } + + public function testCanBuildUpCommands() + { + $c = new Operation(array()); + $c->setName('foo') + ->setClass('Baz') + ->setDeprecated(false) + ->setSummary('summary') + ->setDocumentationUrl('http://www.foo.com') + ->setHttpMethod('PUT') + ->setResponseNotes('oh') + ->setResponseClass('string') + ->setUri('/foo/bar') + ->addParam(new Parameter(array( + 'name' => 'test' + ))); + + $this->assertEquals('foo', $c->getName()); + $this->assertEquals('Baz', $c->getClass()); + $this->assertEquals(false, $c->getDeprecated()); + $this->assertEquals('summary', $c->getSummary()); + $this->assertEquals('http://www.foo.com', $c->getDocumentationUrl()); + $this->assertEquals('PUT', $c->getHttpMethod()); + $this->assertEquals('oh', $c->getResponseNotes()); + $this->assertEquals('string', $c->getResponseClass()); + $this->assertEquals('/foo/bar', $c->getUri()); + $this->assertEquals(array('test'), $c->getParamNames()); + } + + public function testCanRemoveParams() + { + $c = new Operation(array()); + $c->addParam(new Parameter(array('name' => 'foo'))); + $this->assertTrue($c->hasParam('foo')); + $c->removeParam('foo'); + $this->assertFalse($c->hasParam('foo')); + } + + public function testAddsNameToParametersIfNeeded() + { + $command = new Operation(array('parameters' => array('foo' => new Parameter(array())))); + $this->assertEquals('foo', $command->getParam('foo')->getName()); + } + + public function testContainsApiErrorInformation() + { + $command = $this->getOperation(); + $this->assertEquals(1, count($command->getErrorResponses())); + $arr = $command->toArray(); + $this->assertEquals(1, count($arr['errorResponses'])); + $command->addErrorResponse(400, 'Foo', 'Baz\\Bar'); + $this->assertEquals(2, count($command->getErrorResponses())); + $command->setErrorResponses(array()); + $this->assertEquals(0, count($command->getErrorResponses())); + } + + public function testHasNotes() + { + $o = new Operation(array('notes' => 'foo')); + $this->assertEquals('foo', $o->getNotes()); + $o->setNotes('bar'); + $this->assertEquals('bar', $o->getNotes()); + } + + public function testHasData() + { + $o = new Operation(array('data' => array('foo' => 'baz', 'bar' => 123))); + $o->setData('test', false); + $this->assertEquals('baz', $o->getData('foo')); + $this->assertEquals(123, $o->getData('bar')); + $this->assertNull($o->getData('wfefwe')); + $this->assertEquals(array( + 'parameters' => array(), + 'class' => 'Guzzle\Service\Command\OperationCommand', + 'data' => array('foo' => 'baz', 'bar' => 123, 'test' => false), + 'responseClass' => 'array', + 'responseType' => 'primitive' + ), $o->toArray()); + } + + public function testHasServiceDescription() + { + $s = new ServiceDescription(); + $o = new Operation(array(), $s); + $this->assertSame($s, $o->getServiceDescription()); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesResponseType() + { + $o = new Operation(array('responseClass' => 'array', 'responseType' => 'foo')); + } + + public function testInfersResponseType() + { + $o = $this->getOperation(); + $o->setServiceDescription(new ServiceDescription(array('models' => array('Foo' => array())))); + $this->assertEquals('primitive', $o->getResponseType()); + $this->assertEquals('primitive', $o->setResponseClass('boolean')->getResponseType()); + $this->assertEquals('primitive', $o->setResponseClass('array')->getResponseType()); + $this->assertEquals('primitive', $o->setResponseClass('integer')->getResponseType()); + $this->assertEquals('primitive', $o->setResponseClass('string')->getResponseType()); + $this->assertEquals('class', $o->setResponseClass('foo')->getResponseType()); + $this->assertEquals('class', $o->setResponseClass(__CLASS__)->getResponseType()); + $this->assertEquals('model', $o->setResponseClass('Foo')->getResponseType()); + } + + public function testHasResponseType() + { + // infers in the constructor + $o = new Operation(array('responseClass' => 'array')); + $this->assertEquals('primitive', $o->getResponseType()); + // Infers when set + $o = new Operation(); + $this->assertEquals('primitive', $o->getResponseType()); + $this->assertEquals('model', $o->setResponseType('model')->getResponseType()); + } + + public function testHasAdditionalParameters() + { + $o = new Operation(array( + 'additionalParameters' => array( + 'type' => 'string', 'name' => 'binks' + ), + 'parameters' => array( + 'foo' => array('type' => 'integer') + ) + )); + $this->assertEquals('string', $o->getAdditionalParameters()->getType()); + $arr = $o->toArray(); + $this->assertEquals(array( + 'type' => 'string' + ), $arr['additionalParameters']); + } + + /** + * @return Operation + */ + protected function getOperation() + { + return new Operation(array( + 'name' => 'OperationTest', + 'class' => get_class($this), + 'parameters' => array( + 'test' => array('type' => 'object'), + 'bool_1' => array('default' => true, 'type' => 'boolean'), + 'bool_2' => array('default' => false), + 'float' => array('type' => 'numeric'), + 'int' => array('type' => 'integer'), + 'date' => array('type' => 'string'), + 'timestamp' => array('type' => 'string'), + 'string' => array('type' => 'string'), + 'username' => array('type' => 'string', 'required' => true, 'filters' => 'strtolower'), + 'test_function' => array('type' => 'string', 'filters' => __CLASS__ . '::strtoupper') + ), + 'errorResponses' => array( + array('code' => 503, 'reason' => 'InsufficientCapacity', 'class' => 'Guzzle\\Exception\\RuntimeException') + ) + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php new file mode 100644 index 000000000..b9c162aae --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php @@ -0,0 +1,411 @@ + 'foo', + 'type' => 'bar', + 'required' => true, + 'default' => '123', + 'description' => '456', + 'minLength' => 2, + 'maxLength' => 5, + 'location' => 'body', + 'static' => 'static!', + 'filters' => array('trim', 'json_encode') + ); + + public function testCreatesParamFromArray() + { + $p = new Parameter($this->data); + $this->assertEquals('foo', $p->getName()); + $this->assertEquals('bar', $p->getType()); + $this->assertEquals(true, $p->getRequired()); + $this->assertEquals('123', $p->getDefault()); + $this->assertEquals('456', $p->getDescription()); + $this->assertEquals(2, $p->getMinLength()); + $this->assertEquals(5, $p->getMaxLength()); + $this->assertEquals('body', $p->getLocation()); + $this->assertEquals('static!', $p->getStatic()); + $this->assertEquals(array('trim', 'json_encode'), $p->getFilters()); + } + + public function testCanConvertToArray() + { + $p = new Parameter($this->data); + unset($this->data['name']); + $this->assertEquals($this->data, $p->toArray()); + } + + public function testUsesStatic() + { + $d = $this->data; + $d['default'] = 'booboo'; + $d['static'] = true; + $p = new Parameter($d); + $this->assertEquals('booboo', $p->getValue('bar')); + } + + public function testUsesDefault() + { + $d = $this->data; + $d['default'] = 'foo'; + $d['static'] = null; + $p = new Parameter($d); + $this->assertEquals('foo', $p->getValue(null)); + } + + public function testReturnsYourValue() + { + $d = $this->data; + $d['static'] = null; + $p = new Parameter($d); + $this->assertEquals('foo', $p->getValue('foo')); + } + + public function testZeroValueDoesNotCauseDefaultToBeReturned() + { + $d = $this->data; + $d['default'] = '1'; + $d['static'] = null; + $p = new Parameter($d); + $this->assertEquals('0', $p->getValue('0')); + } + + public function testFiltersValues() + { + $d = $this->data; + $d['static'] = null; + $d['filters'] = 'strtoupper'; + $p = new Parameter($d); + $this->assertEquals('FOO', $p->filter('foo')); + } + + public function testConvertsBooleans() + { + $p = new Parameter(array('type' => 'boolean')); + $this->assertEquals(true, $p->filter('true')); + $this->assertEquals(false, $p->filter('false')); + } + + public function testUsesArrayByDefaultForFilters() + { + $d = $this->data; + $d['filters'] = null; + $p = new Parameter($d); + $this->assertEquals(array(), $p->getFilters()); + } + + public function testAllowsSimpleLocationValue() + { + $p = new Parameter(array('name' => 'myname', 'location' => 'foo', 'sentAs' => 'Hello')); + $this->assertEquals('foo', $p->getLocation()); + $this->assertEquals('Hello', $p->getSentAs()); + } + + public function testParsesTypeValues() + { + $p = new Parameter(array('type' => 'foo')); + $this->assertEquals('foo', $p->getType()); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage A [method] value must be specified for each complex filter + */ + public function testValidatesComplexFilters() + { + $p = new Parameter(array('filters' => array(array('args' => 'foo')))); + } + + public function testCanBuildUpParams() + { + $p = new Parameter(array()); + $p->setName('foo') + ->setDescription('c') + ->setFilters(array('d')) + ->setLocation('e') + ->setSentAs('f') + ->setMaxLength(1) + ->setMinLength(1) + ->setMinimum(2) + ->setMaximum(2) + ->setMinItems(3) + ->setMaxItems(3) + ->setRequired(true) + ->setStatic(true) + ->setDefault('h') + ->setType('i'); + + $p->addFilter('foo'); + + $this->assertEquals('foo', $p->getName()); + $this->assertEquals('h', $p->getDefault()); + $this->assertEquals('c', $p->getDescription()); + $this->assertEquals(array('d', 'foo'), $p->getFilters()); + $this->assertEquals('e', $p->getLocation()); + $this->assertEquals('f', $p->getSentAs()); + $this->assertEquals(1, $p->getMaxLength()); + $this->assertEquals(1, $p->getMinLength()); + $this->assertEquals(2, $p->getMaximum()); + $this->assertEquals(2, $p->getMinimum()); + $this->assertEquals(3, $p->getMaxItems()); + $this->assertEquals(3, $p->getMinItems()); + $this->assertEquals(true, $p->getRequired()); + $this->assertEquals(true, $p->getStatic()); + $this->assertEquals('i', $p->getType()); + } + + public function testAllowsNestedShape() + { + $command = $this->getServiceBuilder()->get('mock')->getCommand('mock_command')->getOperation(); + $param = new Parameter(array( + 'parent' => $command, + 'name' => 'foo', + 'type' => 'object', + 'location' => 'query', + 'properties' => array( + 'foo' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'baz' => array( + 'name' => 'baz', + 'type' => 'bool', + ) + ) + ), + 'bar' => array( + 'name' => 'bar', + 'default' => '123' + ) + ) + )); + + $this->assertSame($command, $param->getParent()); + $this->assertNotEmpty($param->getProperties()); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $param->getProperty('foo')); + $this->assertSame($param, $param->getProperty('foo')->getParent()); + $this->assertSame($param->getProperty('foo'), $param->getProperty('foo')->getProperty('baz')->getParent()); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $param->getProperty('bar')); + $this->assertSame($param, $param->getProperty('bar')->getParent()); + + $array = $param->toArray(); + $this->assertInternalType('array', $array['properties']); + $this->assertArrayHasKey('foo', $array['properties']); + $this->assertArrayHasKey('bar', $array['properties']); + } + + public function testAllowsComplexFilters() + { + $that = $this; + $param = new Parameter(array()); + $param->setFilters(array(array('method' => function ($a, $b, $c, $d) use ($that, $param) { + $that->assertEquals('test', $a); + $that->assertEquals('my_value!', $b); + $that->assertEquals('bar', $c); + $that->assertSame($param, $d); + return 'abc' . $b; + }, 'args' => array('test', '@value', 'bar', '@api')))); + $this->assertEquals('abcmy_value!', $param->filter('my_value!')); + } + + public function testCanChangeParentOfNestedParameter() + { + $param1 = new Parameter(array('name' => 'parent')); + $param2 = new Parameter(array('name' => 'child')); + $param2->setParent($param1); + $this->assertSame($param1, $param2->getParent()); + } + + public function testCanRemoveFromNestedStructure() + { + $param1 = new Parameter(array('name' => 'parent')); + $param2 = new Parameter(array('name' => 'child')); + $param1->addProperty($param2); + $this->assertSame($param1, $param2->getParent()); + $this->assertSame($param2, $param1->getProperty('child')); + + // Remove a single child from the structure + $param1->removeProperty('child'); + $this->assertNull($param1->getProperty('child')); + // Remove the entire structure + $param1->addProperty($param2); + $param1->removeProperty('child'); + $this->assertNull($param1->getProperty('child')); + } + + public function testAddsAdditionalProperties() + { + $p = new Parameter(array( + 'type' => 'object', + 'additionalProperties' => array('type' => 'string') + )); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $p->getAdditionalProperties()); + $this->assertNull($p->getAdditionalProperties()->getAdditionalProperties()); + $p = new Parameter(array('type' => 'object')); + $this->assertTrue($p->getAdditionalProperties()); + } + + public function testAddsItems() + { + $p = new Parameter(array( + 'type' => 'array', + 'items' => array('type' => 'string') + )); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $p->getItems()); + $out = $p->toArray(); + $this->assertEquals('array', $out['type']); + $this->assertInternalType('array', $out['items']); + } + + public function testHasExtraProperties() + { + $p = new Parameter(); + $this->assertEquals(array(), $p->getData()); + $p->setData(array('foo' => 'bar')); + $this->assertEquals('bar', $p->getData('foo')); + $p->setData('baz', 'boo'); + $this->assertEquals(array('foo' => 'bar', 'baz' => 'boo'), $p->getData()); + } + + public function testCanRetrieveKnownPropertiesUsingDataMethod() + { + $p = new Parameter(); + $this->assertEquals(null, $p->getData('foo')); + $p->setName('test'); + $this->assertEquals('test', $p->getData('name')); + } + + public function testHasInstanceOf() + { + $p = new Parameter(); + $this->assertNull($p->getInstanceOf()); + $p->setInstanceOf('Foo'); + $this->assertEquals('Foo', $p->getInstanceOf()); + } + + public function testHasPattern() + { + $p = new Parameter(); + $this->assertNull($p->getPattern()); + $p->setPattern('/[0-9]+/'); + $this->assertEquals('/[0-9]+/', $p->getPattern()); + } + + public function testHasEnum() + { + $p = new Parameter(); + $this->assertNull($p->getEnum()); + $p->setEnum(array('foo', 'bar')); + $this->assertEquals(array('foo', 'bar'), $p->getEnum()); + } + + public function testSerializesItems() + { + $p = new Parameter(array( + 'type' => 'object', + 'additionalProperties' => array('type' => 'string') + )); + $this->assertEquals(array( + 'type' => 'object', + 'additionalProperties' => array('type' => 'string') + ), $p->toArray()); + } + + public function testResolvesRefKeysRecursively() + { + $description = new ServiceDescription(array( + 'models' => array( + 'JarJar' => array('type' => 'string', 'default' => 'Mesa address tha senate!'), + 'Anakin' => array('type' => 'array', 'items' => array('$ref' => 'JarJar')) + ) + )); + $p = new Parameter(array('$ref' => 'Anakin', 'description' => 'added'), $description); + $this->assertEquals(array( + 'type' => 'array', + 'items' => array('type' => 'string', 'default' => 'Mesa address tha senate!'), + 'description' => 'added' + ), $p->toArray()); + } + + public function testResolvesExtendsRecursively() + { + $jarJar = array('type' => 'string', 'default' => 'Mesa address tha senate!', 'description' => 'a'); + $anakin = array('type' => 'array', 'items' => array('extends' => 'JarJar', 'description' => 'b')); + $description = new ServiceDescription(array( + 'models' => array('JarJar' => $jarJar, 'Anakin' => $anakin) + )); + // Description attribute will be updated, and format added + $p = new Parameter(array('extends' => 'Anakin', 'format' => 'date'), $description); + $this->assertEquals(array( + 'type' => 'array', + 'format' => 'date', + 'items' => array( + 'type' => 'string', + 'default' => 'Mesa address tha senate!', + 'description' => 'b' + ) + ), $p->toArray()); + } + + public function testHasKeyMethod() + { + $p = new Parameter(array('name' => 'foo', 'sentAs' => 'bar')); + $this->assertEquals('bar', $p->getWireName()); + $p->setSentAs(null); + $this->assertEquals('foo', $p->getWireName()); + } + + public function testIncludesNameInToArrayWhenItemsAttributeHasName() + { + $p = new Parameter(array( + 'type' => 'array', + 'name' => 'Abc', + 'items' => array( + 'name' => 'Foo', + 'type' => 'object' + ) + )); + $result = $p->toArray(); + $this->assertEquals(array( + 'type' => 'array', + 'items' => array( + 'name' => 'Foo', + 'type' => 'object', + 'additionalProperties' => true + ) + ), $result); + } + + public function dateTimeProvider() + { + $d = 'October 13, 2012 16:15:46 UTC'; + + return array( + array($d, 'date-time', '2012-10-13T16:15:46Z'), + array($d, 'date', '2012-10-13'), + array($d, 'timestamp', strtotime($d)), + array(new \DateTime($d), 'timestamp', strtotime($d)) + ); + } + + /** + * @dataProvider dateTimeProvider + */ + public function testAppliesFormat($d, $format, $result) + { + $p = new Parameter(); + $p->setFormat($format); + $this->assertEquals($format, $p->getFormat()); + $this->assertEquals($result, $p->filter($d)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php new file mode 100644 index 000000000..eb3619bea --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php @@ -0,0 +1,61 @@ +assertEquals($result, SchemaFormatter::format($format, $value)); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesDateTimeInput() + { + SchemaFormatter::format('date-time', false); + } + + public function testEnsuresTimestampsAreIntegers() + { + $t = time(); + $result = SchemaFormatter::format('timestamp', $t); + $this->assertSame($t, $result); + $this->assertInternalType('int', $result); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php new file mode 100644 index 000000000..4d6cc8728 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php @@ -0,0 +1,326 @@ +validator = new SchemaValidator(); + } + + public function testValidatesArrayListsAreNumericallyIndexed() + { + $value = array(array(1)); + $this->assertFalse($this->validator->validate($this->getComplexParam(), $value)); + $this->assertEquals( + array('[Foo][0] must be an array of properties. Got a numerically indexed array.'), + $this->validator->getErrors() + ); + } + + public function testValidatesArrayListsContainProperItems() + { + $value = array(true); + $this->assertFalse($this->validator->validate($this->getComplexParam(), $value)); + $this->assertEquals( + array('[Foo][0] must be of type object'), + $this->validator->getErrors() + ); + } + + public function testAddsDefaultValuesInLists() + { + $value = array(array()); + $this->assertTrue($this->validator->validate($this->getComplexParam(), $value)); + $this->assertEquals(array(array('Bar' => true)), $value); + } + + public function testMergesDefaultValuesInLists() + { + $value = array( + array('Baz' => 'hello!'), + array('Bar' => false) + ); + $this->assertTrue($this->validator->validate($this->getComplexParam(), $value)); + $this->assertEquals(array( + array( + 'Baz' => 'hello!', + 'Bar' => true + ), + array('Bar' => false) + ), $value); + } + + public function testCorrectlyConvertsParametersToArrayWhenArraysArePresent() + { + $param = $this->getComplexParam(); + $result = $param->toArray(); + $this->assertInternalType('array', $result['items']); + $this->assertEquals('array', $result['type']); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $param->getItems()); + } + + public function testAllowsInstanceOf() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'instanceOf' => get_class($this) + )); + $this->assertTrue($this->validator->validate($p, $this)); + $this->assertFalse($this->validator->validate($p, $p)); + $this->assertEquals(array('[foo] must be an instance of ' . __CLASS__), $this->validator->getErrors()); + } + + public function testEnforcesInstanceOfOnlyWhenObject() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => array('object', 'string'), + 'instanceOf' => get_class($this) + )); + $this->assertTrue($this->validator->validate($p, $this)); + $s = 'test'; + $this->assertTrue($this->validator->validate($p, $s)); + } + + public function testConvertsObjectsToArraysWhenToArrayInterface() + { + $o = $this->getMockBuilder('Guzzle\Common\ToArrayInterface') + ->setMethods(array('toArray')) + ->getMockForAbstractClass(); + $o->expects($this->once()) + ->method('toArray') + ->will($this->returnValue(array( + 'foo' => 'bar' + ))); + $p = new Parameter(array( + 'name' => 'test', + 'type' => 'object', + 'properties' => array( + 'foo' => array('required' => 'true') + ) + )); + $this->assertTrue($this->validator->validate($p, $o)); + } + + public function testMergesValidationErrorsInPropertiesWithParent() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array( + 'bar' => array('type' => 'string', 'required' => true, 'description' => 'This is what it does'), + 'test' => array('type' => 'string', 'minLength' => 2, 'maxLength' => 5), + 'test2' => array('type' => 'string', 'minLength' => 2, 'maxLength' => 2), + 'test3' => array('type' => 'integer', 'minimum' => 100), + 'test4' => array('type' => 'integer', 'maximum' => 10), + 'test5' => array('type' => 'array', 'maxItems' => 2), + 'test6' => array('type' => 'string', 'enum' => array('a', 'bc')), + 'test7' => array('type' => 'string', 'pattern' => '/[0-9]+/'), + 'test8' => array('type' => 'number'), + 'baz' => array( + 'type' => 'array', + 'minItems' => 2, + 'required' => true, + "items" => array("type" => "string") + ) + ) + )); + + $value = array( + 'test' => 'a', + 'test2' => 'abc', + 'baz' => array(false), + 'test3' => 10, + 'test4' => 100, + 'test5' => array(1, 3, 4), + 'test6' => 'Foo', + 'test7' => 'abc', + 'test8' => 'abc' + ); + + $this->assertFalse($this->validator->validate($p, $value)); + $this->assertEquals(array ( + '[foo][bar] is a required string: This is what it does', + '[foo][baz] must contain 2 or more elements', + '[foo][baz][0] must be of type string', + '[foo][test2] length must be less than or equal to 2', + '[foo][test3] must be greater than or equal to 100', + '[foo][test4] must be less than or equal to 10', + '[foo][test5] must contain 2 or fewer elements', + '[foo][test6] must be one of "a" or "bc"', + '[foo][test7] must match the following regular expression: /[0-9]+/', + '[foo][test8] must be of type number', + '[foo][test] length must be greater than or equal to 2', + ), $this->validator->getErrors()); + } + + public function testHandlesNullValuesInArraysWithDefaults() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'bar' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'foo' => array('default' => 'hi') + ) + ) + ) + )); + $value = array(); + $this->assertTrue($this->validator->validate($p, $value)); + $this->assertEquals(array('bar' => array('foo' => 'hi')), $value); + } + + public function testFailsWhenNullValuesInArraysWithNoDefaults() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'bar' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array('foo' => array('type' => 'string')) + ) + ) + )); + $value = array(); + $this->assertFalse($this->validator->validate($p, $value)); + $this->assertEquals(array('[foo][bar] is a required object'), $this->validator->getErrors()); + } + + public function testChecksTypes() + { + $p = new SchemaValidator(); + $r = new \ReflectionMethod($p, 'determineType'); + $r->setAccessible(true); + $this->assertEquals('any', $r->invoke($p, 'any', 'hello')); + $this->assertEquals(false, $r->invoke($p, 'foo', 'foo')); + $this->assertEquals('string', $r->invoke($p, 'string', 'hello')); + $this->assertEquals(false, $r->invoke($p, 'string', false)); + $this->assertEquals('integer', $r->invoke($p, 'integer', 1)); + $this->assertEquals(false, $r->invoke($p, 'integer', 'abc')); + $this->assertEquals('numeric', $r->invoke($p, 'numeric', 1)); + $this->assertEquals('numeric', $r->invoke($p, 'numeric', '1')); + $this->assertEquals('number', $r->invoke($p, 'number', 1)); + $this->assertEquals('number', $r->invoke($p, 'number', '1')); + $this->assertEquals(false, $r->invoke($p, 'numeric', 'a')); + $this->assertEquals('boolean', $r->invoke($p, 'boolean', true)); + $this->assertEquals('boolean', $r->invoke($p, 'boolean', false)); + $this->assertEquals(false, $r->invoke($p, 'boolean', 'false')); + $this->assertEquals('null', $r->invoke($p, 'null', null)); + $this->assertEquals(false, $r->invoke($p, 'null', 'abc')); + $this->assertEquals('array', $r->invoke($p, 'array', array())); + $this->assertEquals(false, $r->invoke($p, 'array', 'foo')); + } + + public function testValidatesFalseAdditionalProperties() + { + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array('bar' => array('type' => 'string')), + 'additionalProperties' => false + )); + $value = array('test' => '123'); + $this->assertFalse($this->validator->validate($param, $value)); + $this->assertEquals(array('[foo][test] is not an allowed property'), $this->validator->getErrors()); + $value = array('bar' => '123'); + $this->assertTrue($this->validator->validate($param, $value)); + } + + public function testAllowsUndefinedAdditionalProperties() + { + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array('bar' => array('type' => 'string')) + )); + $value = array('test' => '123'); + $this->assertTrue($this->validator->validate($param, $value)); + } + + public function testValidatesAdditionalProperties() + { + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array('bar' => array('type' => 'string')), + 'additionalProperties' => array('type' => 'integer') + )); + $value = array('test' => 'foo'); + $this->assertFalse($this->validator->validate($param, $value)); + $this->assertEquals(array('[foo][test] must be of type integer'), $this->validator->getErrors()); + } + + public function testValidatesAdditionalPropertiesThatArrayArrays() + { + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'array', + 'items' => array('type' => 'string') + ) + )); + $value = array('test' => array(true)); + $this->assertFalse($this->validator->validate($param, $value)); + $this->assertEquals(array('[foo][test][0] must be of type string'), $this->validator->getErrors()); + } + + public function testIntegersCastToStringWhenTypeMismatch() + { + $param = new Parameter(array('name' => 'test', 'type' => 'string')); + $value = 12; + $this->assertTrue($this->validator->validate($param, $value)); + $this->assertEquals('12', $value); + } + + public function testRequiredMessageIncludesType() + { + $param = new Parameter(array('name' => 'test', 'type' => array('string', 'boolean'), 'required' => true)); + $value = null; + $this->assertFalse($this->validator->validate($param, $value)); + $this->assertEquals(array('[test] is a required string or boolean'), $this->validator->getErrors()); + } + + protected function getComplexParam() + { + return new Parameter(array( + 'name' => 'Foo', + 'type' => 'array', + 'required' => true, + 'min' => 1, + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'Baz' => array( + 'type' => 'string', + ), + 'Bar' => array( + 'required' => true, + 'type' => 'boolean', + 'default' => true + ) + ) + ) + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php new file mode 100644 index 000000000..bbfd1d6df --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php @@ -0,0 +1,177 @@ + true, + 'baz' => array('bar'), + 'apiVersion' => '123', + 'operations' => array() + )); + + $this->assertEquals(true, $d->getData('foo')); + $this->assertEquals(array('bar'), $d->getData('baz')); + $this->assertEquals('123', $d->getApiVersion()); + } + + public function testAllowsDeepNestedInheritance() + { + $d = ServiceDescription::factory(array( + 'operations' => array( + 'abstract' => array( + 'httpMethod' => 'HEAD', + 'parameters' => array( + 'test' => array('type' => 'string', 'required' => true) + ) + ), + 'abstract2' => array('uri' => '/test', 'extends' => 'abstract'), + 'concrete' => array('extends' => 'abstract2'), + 'override' => array('extends' => 'abstract', 'httpMethod' => 'PUT'), + 'override2' => array('extends' => 'override', 'httpMethod' => 'POST', 'uri' => '/') + ) + )); + + $c = $d->getOperation('concrete'); + $this->assertEquals('/test', $c->getUri()); + $this->assertEquals('HEAD', $c->getHttpMethod()); + $params = $c->getParams(); + $param = $params['test']; + $this->assertEquals('string', $param->getType()); + $this->assertTrue($param->getRequired()); + + // Ensure that merging HTTP method does not make an array + $this->assertEquals('PUT', $d->getOperation('override')->getHttpMethod()); + $this->assertEquals('POST', $d->getOperation('override2')->getHttpMethod()); + $this->assertEquals('/', $d->getOperation('override2')->getUri()); + } + + /** + * @expectedException RuntimeException + */ + public function testThrowsExceptionWhenExtendingMissingCommand() + { + ServiceDescription::factory(array( + 'operations' => array( + 'concrete' => array( + 'extends' => 'missing' + ) + ) + )); + } + + public function testAllowsMultipleInheritance() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'a' => array( + 'httpMethod' => 'GET', + 'parameters' => array( + 'a1' => array( + 'default' => 'foo', + 'required' => true, + 'prepend' => 'hi' + ) + ) + ), + 'b' => array( + 'extends' => 'a', + 'parameters' => array( + 'b2' => array() + ) + ), + 'c' => array( + 'parameters' => array( + 'a1' => array( + 'default' => 'bar', + 'required' => true, + 'description' => 'test' + ), + 'c3' => array() + ) + ), + 'd' => array( + 'httpMethod' => 'DELETE', + 'extends' => array('b', 'c'), + 'parameters' => array( + 'test' => array() + ) + ) + ) + )); + + $command = $description->getOperation('d'); + $this->assertEquals('DELETE', $command->getHttpMethod()); + $this->assertContains('a1', $command->getParamNames()); + $this->assertContains('b2', $command->getParamNames()); + $this->assertContains('c3', $command->getParamNames()); + $this->assertContains('test', $command->getParamNames()); + + $this->assertTrue($command->getParam('a1')->getRequired()); + $this->assertEquals('bar', $command->getParam('a1')->getDefault()); + $this->assertEquals('test', $command->getParam('a1')->getDescription()); + } + + public function testAddsOtherFields() + { + $description = ServiceDescription::factory(array( + 'operations' => array(), + 'description' => 'Foo', + 'apiVersion' => 'bar' + )); + $this->assertEquals('Foo', $description->getDescription()); + $this->assertEquals('bar', $description->getApiVersion()); + } + + public function testCanLoadNestedExtends() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'root' => array( + 'class' => 'foo' + ), + 'foo' => array( + 'extends' => 'root', + 'parameters' => array( + 'baz' => array('type' => 'string') + ) + ), + 'foo_2' => array( + 'extends' => 'foo', + 'parameters' => array( + 'bar' => array('type' => 'string') + ) + ), + 'foo_3' => array( + 'class' => 'bar', + 'parameters' => array( + 'bar2' => array('type' => 'string') + ) + ), + 'foo_4' => array( + 'extends' => array('foo_2', 'foo_3'), + 'parameters' => array( + 'bar3' => array('type' => 'string') + ) + ) + ) + )); + + $this->assertTrue($description->hasOperation('foo_4')); + $foo4 = $description->getOperation('foo_4'); + $this->assertTrue($foo4->hasParam('baz')); + $this->assertTrue($foo4->hasParam('bar')); + $this->assertTrue($foo4->hasParam('bar2')); + $this->assertTrue($foo4->hasParam('bar3')); + $this->assertEquals('bar', $foo4->getClass()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php new file mode 100644 index 000000000..ff2545235 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php @@ -0,0 +1,240 @@ +serviceData = array( + 'test_command' => new Operation(array( + 'name' => 'test_command', + 'description' => 'documentationForCommand', + 'httpMethod' => 'DELETE', + 'class' => 'Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand', + 'parameters' => array( + 'bucket' => array('required' => true), + 'key' => array('required' => true) + ) + )) + ); + } + + /** + * @covers Guzzle\Service\Description\ServiceDescription::factory + * @covers Guzzle\Service\Description\ServiceDescriptionLoader::build + */ + public function testFactoryDelegatesToConcreteFactories() + { + $jsonFile = __DIR__ . '/../../TestData/test_service.json'; + $this->assertInstanceOf('Guzzle\Service\Description\ServiceDescription', ServiceDescription::factory($jsonFile)); + } + + public function testConstructor() + { + $service = new ServiceDescription(array('operations' => $this->serviceData)); + $this->assertEquals(1, count($service->getOperations())); + $this->assertFalse($service->hasOperation('foobar')); + $this->assertTrue($service->hasOperation('test_command')); + } + + public function testIsSerializable() + { + $service = new ServiceDescription(array('operations' => $this->serviceData)); + $data = serialize($service); + $d2 = unserialize($data); + $this->assertEquals(serialize($service), serialize($d2)); + } + + public function testSerializesParameters() + { + $service = new ServiceDescription(array( + 'operations' => array( + 'foo' => new Operation(array('parameters' => array('foo' => array('type' => 'string')))) + ) + )); + $serialized = serialize($service); + $this->assertContains('"parameters":{"foo":', $serialized); + $service = unserialize($serialized); + $this->assertTrue($service->getOperation('foo')->hasParam('foo')); + } + + public function testAllowsForJsonBasedArrayParamsFunctionalTest() + { + $description = new ServiceDescription(array( + 'operations' => array( + 'test' => new Operation(array( + 'httpMethod' => 'PUT', + 'parameters' => array( + 'data' => array( + 'required' => true, + 'filters' => 'json_encode', + 'location' => 'body' + ) + ) + )) + ) + )); + $client = new Client(); + $client->setDescription($description); + $command = $client->getCommand('test', array( + 'data' => array( + 'foo' => 'bar' + ) + )); + + $request = $command->prepare(); + $this->assertEquals('{"foo":"bar"}', (string) $request->getBody()); + } + + public function testContainsModels() + { + $d = new ServiceDescription(array( + 'operations' => array('foo' => array()), + 'models' => array( + 'Tag' => array('type' => 'object'), + 'Person' => array('type' => 'object') + ) + )); + $this->assertTrue($d->hasModel('Tag')); + $this->assertTrue($d->hasModel('Person')); + $this->assertFalse($d->hasModel('Foo')); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $d->getModel('Tag')); + $this->assertNull($d->getModel('Foo')); + $this->assertContains('"models":{', serialize($d)); + $this->assertEquals(array('Tag', 'Person'), array_keys($d->getModels())); + } + + public function testCanAddModels() + { + $d = new ServiceDescription(array()); + $this->assertFalse($d->hasModel('Foo')); + $d->addModel(new Parameter(array('name' => 'Foo'))); + $this->assertTrue($d->hasModel('Foo')); + } + + public function testHasAttributes() + { + $d = new ServiceDescription(array( + 'operations' => array(), + 'name' => 'Name', + 'description' => 'Description', + 'apiVersion' => '1.24' + )); + + $this->assertEquals('Name', $d->getName()); + $this->assertEquals('Description', $d->getDescription()); + $this->assertEquals('1.24', $d->getApiVersion()); + + $s = serialize($d); + $this->assertContains('"name":"Name"', $s); + $this->assertContains('"description":"Description"', $s); + $this->assertContains('"apiVersion":"1.24"', $s); + + $d = unserialize($s); + $this->assertEquals('Name', $d->getName()); + $this->assertEquals('Description', $d->getDescription()); + $this->assertEquals('1.24', $d->getApiVersion()); + } + + public function testPersistsCustomAttributes() + { + $data = array( + 'operations' => array('foo' => array('class' => 'foo', 'parameters' => array())), + 'name' => 'Name', + 'description' => 'Test', + 'apiVersion' => '1.24', + 'auth' => 'foo', + 'keyParam' => 'bar' + ); + $d = new ServiceDescription($data); + $d->setData('hello', 'baz'); + $this->assertEquals('foo', $d->getData('auth')); + $this->assertEquals('baz', $d->getData('hello')); + $this->assertEquals('bar', $d->getData('keyParam')); + // responseClass and responseType are added by default + $data['operations']['foo']['responseClass'] = 'array'; + $data['operations']['foo']['responseType'] = 'primitive'; + $this->assertEquals($data + array('hello' => 'baz'), json_decode($d->serialize(), true)); + } + + public function testHasToArray() + { + $data = array( + 'operations' => array(), + 'name' => 'Name', + 'description' => 'Test' + ); + $d = new ServiceDescription($data); + $arr = $d->toArray(); + $this->assertEquals('Name', $arr['name']); + $this->assertEquals('Test', $arr['description']); + } + + public function testReturnsNullWhenRetrievingMissingOperation() + { + $s = new ServiceDescription(array()); + $this->assertNull($s->getOperation('foo')); + } + + public function testCanAddOperations() + { + $s = new ServiceDescription(array()); + $this->assertFalse($s->hasOperation('Foo')); + $s->addOperation(new Operation(array('name' => 'Foo'))); + $this->assertTrue($s->hasOperation('Foo')); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesOperationTypes() + { + $s = new ServiceDescription(array( + 'operations' => array('foo' => new \stdClass()) + )); + } + + public function testHasBaseUrl() + { + $description = new ServiceDescription(array('baseUrl' => 'http://foo.com')); + $this->assertEquals('http://foo.com', $description->getBaseUrl()); + $description->setBaseUrl('http://foobar.com'); + $this->assertEquals('http://foobar.com', $description->getBaseUrl()); + } + + public function testCanUseBasePath() + { + $description = new ServiceDescription(array('basePath' => 'http://foo.com')); + $this->assertEquals('http://foo.com', $description->getBaseUrl()); + } + + public function testModelsHaveNames() + { + $desc = array( + 'models' => array( + 'date' => array('type' => 'string'), + 'user'=> array( + 'type' => 'object', + 'properties' => array( + 'dob' => array('$ref' => 'date') + ) + ) + ) + ); + + $s = ServiceDescription::factory($desc); + $this->assertEquals('date', $s->getModel('date')->getName()); + $this->assertEquals('dob', $s->getModel('user')->getProperty('dob')->getName()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php new file mode 100644 index 000000000..be0d4ac8b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php @@ -0,0 +1,66 @@ +addSuccessfulCommand($c1)->addFailedCommand($c2); + $this->assertSame(array($c1), $e->getSuccessfulCommands()); + $this->assertSame(array($c2), $e->getFailedCommands()); + $this->assertSame(array($c1, $c2), $e->getAllCommands()); + } + + public function testConvertsMultiExceptionIntoCommandTransfer() + { + $r1 = new Request('GET', 'http://foo.com'); + $r2 = new Request('GET', 'http://foobaz.com'); + $e = new MultiTransferException('Test', 123); + $e->addSuccessfulRequest($r1)->addFailedRequest($r2); + $ce = CommandTransferException::fromMultiTransferException($e); + + $this->assertInstanceOf('Guzzle\Service\Exception\CommandTransferException', $ce); + $this->assertEquals('Test', $ce->getMessage()); + $this->assertEquals(123, $ce->getCode()); + $this->assertSame(array($r1), $ce->getSuccessfulRequests()); + $this->assertSame(array($r2), $ce->getFailedRequests()); + } + + public function testCanRetrieveExceptionForCommand() + { + $r1 = new Request('GET', 'http://foo.com'); + $e1 = new \Exception('foo'); + $c1 = $this->getMockBuilder('Guzzle\Tests\Service\Mock\Command\MockCommand') + ->setMethods(array('getRequest')) + ->getMock(); + $c1->expects($this->once())->method('getRequest')->will($this->returnValue($r1)); + + $e = new MultiTransferException('Test', 123); + $e->addFailedRequestWithException($r1, $e1); + $ce = CommandTransferException::fromMultiTransferException($e); + $ce->addFailedCommand($c1); + + $this->assertSame($e1, $ce->getExceptionForFailedCommand($c1)); + } + + public function testAddsNonRequestExceptions() + { + $e = new MultiTransferException(); + $e->add(new \Exception('bar')); + $e->addFailedRequestWithException(new Request('GET', 'http://www.foo.com'), new \Exception('foo')); + $ce = CommandTransferException::fromMultiTransferException($e); + $this->assertEquals(2, count($ce)); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php new file mode 100644 index 000000000..6455295ad --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php @@ -0,0 +1,15 @@ +assertEquals($items, $e->getCommands()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php new file mode 100644 index 000000000..ef789d8a9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php @@ -0,0 +1,17 @@ +setErrors($errors); + $this->assertEquals($errors, $e->getErrors()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php new file mode 100644 index 000000000..4ab423e85 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php @@ -0,0 +1,31 @@ + 'iterable_command', + 'parameters' => array( + 'page_size' => array('type' => 'integer'), + 'next_token' => array('type' => 'string') + ) + )); + } + + protected function build() + { + $this->request = $this->client->createRequest('GET'); + + // Add the next token and page size query string values + $this->request->getQuery()->set('next_token', $this->get('next_token')); + + if ($this->get('page_size')) { + $this->request->getQuery()->set('page_size', $this->get('page_size')); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php new file mode 100644 index 000000000..831a7e795 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php @@ -0,0 +1,32 @@ + get_called_class() == __CLASS__ ? 'mock_command' : 'sub.sub', + 'httpMethod' => 'POST', + 'parameters' => array( + 'test' => array( + 'default' => 123, + 'required' => true, + 'doc' => 'Test argument' + ), + '_internal' => array( + 'default' => 'abc' + ), + 'foo' => array('filters' => array('strtoupper')) + ) + )); + } + + protected function build() + { + $this->request = $this->client->createRequest(); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php new file mode 100644 index 000000000..72ae1f6a7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php @@ -0,0 +1,30 @@ + 'other_command', + 'parameters' => array( + 'test' => array( + 'default' => '123', + 'required' => true, + 'doc' => 'Test argument' + ), + 'other' => array(), + 'arg' => array('type' => 'string'), + 'static' => array('static' => true, 'default' => 'this is static') + ) + )); + } + + protected function build() + { + $this->request = $this->client->getRequest('HEAD'); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php new file mode 100644 index 000000000..d348480cf --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php @@ -0,0 +1,7 @@ + '{scheme}://127.0.0.1:8124/{api_version}/{subdomain}', + 'scheme' => 'http', + 'api_version' => 'v1' + ), array('username', 'password', 'subdomain')); + + return new self($config->get('base_url'), $config); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php new file mode 100644 index 000000000..8faf4123a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php @@ -0,0 +1,42 @@ +nextToken) { + $this->command->set('next_token', $this->nextToken); + } + + $this->command->set('page_size', (int) $this->calculatePageSize()); + $this->command->execute(); + + $data = json_decode($this->command->getResponse()->getBody(true), true); + + $this->nextToken = $data['next_token']; + + return $data['resources']; + } + + public function next() + { + $this->calledNext++; + parent::next(); + } + + public function getResources() + { + return $this->resources; + } + + public function getIteratedCount() + { + return $this->iteratedCount; + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php new file mode 100644 index 000000000..41c207372 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php @@ -0,0 +1,37 @@ +assertFalse($factory->canBuild($cmd)); + $factory->build($cmd); + } + + public function testBuildsResourceIterators() + { + $f1 = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock\Model'); + $factory = new CompositeResourceIteratorFactory(array()); + $factory->addFactory($f1); + $command = new MockCommand(); + $iterator = $factory->build($command, array('client.namespace' => 'Guzzle\Tests\Service\Mock')); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php new file mode 100644 index 000000000..d166e9261 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php @@ -0,0 +1,40 @@ +build(new MockCommand()); + } + + public function testBuildsResourceIterators() + { + $factory = new MapResourceIteratorFactory(array( + 'mock_command' => 'Guzzle\Tests\Service\Mock\Model\MockCommandIterator' + )); + $iterator = $factory->build(new MockCommand()); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } + + public function testUsesWildcardMappings() + { + $factory = new MapResourceIteratorFactory(array( + '*' => 'Guzzle\Tests\Service\Mock\Model\MockCommandIterator' + )); + $iterator = $factory->build(new MockCommand()); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php new file mode 100644 index 000000000..7214133e5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php @@ -0,0 +1,65 @@ + 'object')); + $model = new Model(array('foo' => 'bar'), $param); + $this->assertSame($param, $model->getStructure()); + $this->assertEquals('bar', $model->get('foo')); + $this->assertEquals('bar', $model['foo']); + } + + public function testCanBeUsedWithoutStructure() + { + $model = new Model(array( + 'Foo' => 'baz', + 'Bar' => array( + 'Boo' => 'Bam' + ) + )); + $transform = function ($key, $value) { + return ($value && is_array($value)) ? new Collection($value) : $value; + }; + $model = $model->map($transform); + $this->assertInstanceOf('Guzzle\Common\Collection', $model->getPath('Bar')); + } + + public function testAllowsFiltering() + { + $model = new Model(array( + 'Foo' => 'baz', + 'Bar' => 'a' + )); + $model = $model->filter(function ($i, $v) { + return $v[0] == 'a'; + }); + $this->assertEquals(array('Bar' => 'a'), $model->toArray()); + } + + public function testDoesNotIncludeEmptyStructureInString() + { + $model = new Model(array('Foo' => 'baz')); + $str = (string) $model; + $this->assertContains('Debug output of model', $str); + $this->assertNotContains('Model structure', $str); + } + + public function testDoesIncludeModelStructureInString() + { + $model = new Model(array('Foo' => 'baz'), new Parameter(array('name' => 'Foo'))); + $str = (string) $model; + $this->assertContains('Debug output of Foo model', $str); + $this->assertContains('Model structure', $str); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php new file mode 100644 index 000000000..7b061b537 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php @@ -0,0 +1,41 @@ +registerNamespace('Baz'); + $command = new MockCommand(); + $factory->build($command); + } + + public function testBuildsResourceIterators() + { + $factory = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock\Model'); + $command = new MockCommand(); + $iterator = $factory->build($command, array('client.namespace' => 'Guzzle\Tests\Service\Mock')); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } + + public function testChecksIfCanBuild() + { + $factory = new ResourceIteratorClassFactory('Guzzle\Tests\Service'); + $this->assertFalse($factory->canBuild(new MockCommand())); + $factory = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock\Model'); + $this->assertTrue($factory->canBuild(new MockCommand())); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php new file mode 100644 index 000000000..573fb6d6e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php @@ -0,0 +1,184 @@ +assertInternalType('array', ResourceIterator::getAllEvents()); + } + + public function testConstructorConfiguresDefaults() + { + $ri = $this->getMockForAbstractClass('Guzzle\\Service\\Resource\\ResourceIterator', array( + $this->getServiceBuilder()->get('mock')->getCommand('iterable_command'), + array( + 'limit' => 10, + 'page_size' => 3 + ) + ), 'MockIterator'); + + $this->assertEquals(false, $ri->getNextToken()); + $this->assertEquals(false, $ri->current()); + } + + public function testSendsRequestsForNextSetOfResources() + { + // Queue up an array of responses for iterating + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"g\", \"h\", \"i\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 41\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"j\"] }" + )); + + // Create a new resource iterator using the IterableCommand mock + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'), array( + 'page_size' => 3 + )); + + // Ensure that no requests have been sent yet + $this->assertEquals(0, count($this->getServer()->getReceivedRequests(false))); + + //$this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i', 'j'), $ri->toArray()); + $ri->toArray(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(3, count($requests)); + + $this->assertEquals(3, $requests[0]->getQuery()->get('page_size')); + $this->assertEquals(3, $requests[1]->getQuery()->get('page_size')); + $this->assertEquals(3, $requests[2]->getQuery()->get('page_size')); + + // Reset and resend + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"g\", \"h\", \"i\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 41\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"j\"] }", + )); + + $d = array(); + foreach ($ri as $data) { + $d[] = $data; + } + $this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i', 'j'), $d); + } + + public function testCalculatesPageSize() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"g\", \"h\", \"i\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"j\", \"k\"] }" + )); + + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'), array( + 'page_size' => 3, + 'limit' => 7 + )); + + $this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i', 'j'), $ri->toArray()); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(3, count($requests)); + $this->assertEquals(3, $requests[0]->getQuery()->get('page_size')); + $this->assertEquals(3, $requests[1]->getQuery()->get('page_size')); + $this->assertEquals(1, $requests[2]->getQuery()->get('page_size')); + } + + public function testUseAsArray() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"g\", \"h\", \"i\"] }" + )); + + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + + // Ensure that the key is never < 0 + $this->assertEquals(0, $ri->key()); + $this->assertEquals(0, count($ri)); + + // Ensure that the iterator can be used as KVP array + $data = array(); + foreach ($ri as $key => $value) { + $data[$key] = $value; + } + + // Ensure that the iterate is countable + $this->assertEquals(6, count($ri)); + $this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i'), $data); + } + + public function testBailsWhenSendReturnsNoResults() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [] }" + )); + + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + + // Ensure that the iterator can be used as KVP array + $data = $ri->toArray(); + + // Ensure that the iterate is countable + $this->assertEquals(3, count($ri)); + $this->assertEquals(array('d', 'e', 'f'), $data); + + $this->assertEquals(2, $ri->getRequestCount()); + } + + public function testHoldsDataOptions() + { + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + $this->assertNull($ri->get('foo')); + $this->assertSame($ri, $ri->set('foo', 'bar')); + $this->assertEquals('bar', $ri->get('foo')); + } + + public function testSettingLimitOrPageSizeClearsData() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }" + )); + + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + $ri->toArray(); + $this->assertNotEmpty($this->readAttribute($ri, 'resources')); + + $ri->setLimit(10); + $this->assertEmpty($this->readAttribute($ri, 'resources')); + + $ri->toArray(); + $this->assertNotEmpty($this->readAttribute($ri, 'resources')); + $ri->setPageSize(10); + $this->assertEmpty($this->readAttribute($ri, 'resources')); + } + + public function testWorksWithCustomAppendIterator() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }" + )); + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + $a = new \Guzzle\Iterator\AppendIterator(); + $a->append($ri); + $results = iterator_to_array($a, false); + $this->assertEquals(4, $ri->calledNext); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php new file mode 100644 index 000000000..083aaa0d9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php @@ -0,0 +1,172 @@ +client = new Client($this->getServer()->getUrl()); + $this->factory = new PhpStreamRequestFactory(); + } + + public function testOpensValidStreamByCreatingContext() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + $request = $this->client->get('/'); + $stream = $this->factory->fromRequest($request); + $this->assertEquals('hi', (string) $stream); + $headers = $this->factory->getLastResponseHeaders(); + $this->assertContains('HTTP/1.1 200 OK', $headers); + $this->assertContains('Content-Length: 2', $headers); + $this->assertSame($headers, $stream->getCustomData('response_headers')); + $this->assertEquals(2, $stream->getSize()); + } + + public function testOpensValidStreamByPassingContextAndMerging() + { + $request = $this->client->get('/'); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createContext', 'createStream')) + ->getMock(); + $this->factory->expects($this->never()) + ->method('createContext'); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + + $context = array('http' => array('method' => 'HEAD', 'ignore_errors' => false)); + $this->factory->fromRequest($request, stream_context_create($context)); + $options = stream_context_get_options($this->readAttribute($this->factory, 'context')); + $this->assertEquals('HEAD', $options['http']['method']); + $this->assertFalse($options['http']['ignore_errors']); + $this->assertEquals('1.1', $options['http']['protocol_version']); + } + + public function testAppliesProxySettings() + { + $request = $this->client->get('/'); + $request->getCurlOptions()->set(CURLOPT_PROXY, 'tcp://foo.com'); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createStream')) + ->getMock(); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + $this->factory->fromRequest($request); + $options = stream_context_get_options($this->readAttribute($this->factory, 'context')); + $this->assertEquals('tcp://foo.com', $options['http']['proxy']); + } + + public function testAddsPostFields() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + $request = $this->client->post('/', array('Foo' => 'Bar'), array('foo' => 'baz bar')); + $stream = $this->factory->fromRequest($request); + $this->assertEquals('hi', (string) $stream); + + $headers = $this->factory->getLastResponseHeaders(); + $this->assertContains('HTTP/1.1 200 OK', $headers); + $this->assertContains('Content-Length: 2', $headers); + $this->assertSame($headers, $stream->getCustomData('response_headers')); + + $received = $this->getServer()->getReceivedRequests(); + $this->assertEquals(1, count($received)); + $this->assertContains('POST / HTTP/1.1', $received[0]); + $this->assertContains('host: ', $received[0]); + $this->assertContains('user-agent: Guzzle/', $received[0]); + $this->assertContains('foo: Bar', $received[0]); + $this->assertContains('content-length: 13', $received[0]); + $this->assertContains('foo=baz%20bar', $received[0]); + } + + public function testAddsBody() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + $request = $this->client->put('/', array('Foo' => 'Bar'), 'Testing...123'); + $stream = $this->factory->fromRequest($request); + $this->assertEquals('hi', (string) $stream); + + $headers = $this->factory->getLastResponseHeaders(); + $this->assertContains('HTTP/1.1 200 OK', $headers); + $this->assertContains('Content-Length: 2', $headers); + $this->assertSame($headers, $stream->getCustomData('response_headers')); + + $received = $this->getServer()->getReceivedRequests(); + $this->assertEquals(1, count($received)); + $this->assertContains('PUT / HTTP/1.1', $received[0]); + $this->assertContains('host: ', $received[0]); + $this->assertContains('user-agent: Guzzle/', $received[0]); + $this->assertContains('foo: Bar', $received[0]); + $this->assertContains('content-length: 13', $received[0]); + $this->assertContains('Testing...123', $received[0]); + } + + public function testCanDisableSslValidation() + { + $request = $this->client->get('/'); + $request->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createStream')) + ->getMock(); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + $this->factory->fromRequest($request); + $options = stream_context_get_options($this->readAttribute($this->factory, 'context')); + $this->assertFalse($options['ssl']['verify_peer']); + } + + public function testUsesSslValidationByDefault() + { + $request = $this->client->get('/'); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createStream')) + ->getMock(); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + $this->factory->fromRequest($request); + $options = stream_context_get_options($this->readAttribute($this->factory, 'context')); + $this->assertTrue($options['ssl']['verify_peer']); + $this->assertSame($request->getCurlOptions()->get(CURLOPT_CAINFO), $options['ssl']['cafile']); + } + + public function testBasicAuthAddsUserAndPassToUrl() + { + $request = $this->client->get('/'); + $request->setAuth('Foo', 'Bar'); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createStream')) + ->getMock(); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + $this->factory->fromRequest($request); + $this->assertContains('Foo:Bar@', (string) $this->readAttribute($this->factory, 'url')); + } + + public function testCanCreateCustomStreamClass() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + $request = $this->client->get('/'); + $stream = $this->factory->fromRequest($request, array(), array('stream_class' => 'Guzzle\Http\EntityBody')); + $this->assertInstanceOf('Guzzle\Http\EntityBody', $stream); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php new file mode 100644 index 000000000..4973f252e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php @@ -0,0 +1,189 @@ +assertEquals($handle, $stream->getStream()); + $this->assertTrue($stream->isReadable()); + $this->assertTrue($stream->isWritable()); + $this->assertTrue($stream->isLocal()); + $this->assertTrue($stream->isSeekable()); + $this->assertEquals('PHP', $stream->getWrapper()); + $this->assertEquals('TEMP', $stream->getStreamType()); + $this->assertEquals(4, $stream->getSize()); + $this->assertEquals('php://temp', $stream->getUri()); + $this->assertEquals(array(), $stream->getWrapperData()); + $this->assertFalse($stream->isConsumed()); + unset($stream); + } + + public function testCanModifyStream() + { + $handle1 = fopen('php://temp', 'r+'); + $handle2 = fopen('php://temp', 'r+'); + $stream = new Stream($handle1); + $this->assertSame($handle1, $stream->getStream()); + $stream->setStream($handle2, 10); + $this->assertEquals(10, $stream->getSize()); + $this->assertSame($handle2, $stream->getStream()); + } + + public function testStreamClosesHandleOnDestruct() + { + $handle = fopen('php://temp', 'r'); + $stream = new Stream($handle); + unset($stream); + $this->assertFalse(is_resource($handle)); + } + + public function testConvertsToString() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertEquals('data', (string) $stream); + unset($stream); + + $handle = fopen(__DIR__ . '/../TestData/FileBody.txt', 'r'); + $stream = new Stream($handle); + $this->assertEquals('', (string) $stream); + unset($stream); + } + + public function testConvertsToStringAndRestoresCursorPos() + { + $handle = fopen('php://temp', 'w+'); + $stream = new Stream($handle); + $stream->write('foobazbar'); + $stream->seek(3); + $this->assertEquals('foobazbar', (string) $stream); + $this->assertEquals(3, $stream->ftell()); + } + + public function testIsConsumed() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertFalse($stream->isConsumed()); + $stream->read(4); + $this->assertTrue($stream->isConsumed()); + } + + public function testAllowsSettingManualSize() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $stream->setSize(10); + $this->assertEquals(10, $stream->getSize()); + unset($stream); + } + + public function testWrapsStream() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertTrue($stream->isSeekable()); + $this->assertTrue($stream->isReadable()); + $this->assertTrue($stream->seek(0)); + $this->assertEquals('da', $stream->read(2)); + $this->assertEquals('ta', $stream->read(2)); + $this->assertTrue($stream->seek(0)); + $this->assertEquals('data', $stream->read(4)); + $stream->write('_appended'); + $stream->seek(0); + $this->assertEquals('data_appended', $stream->read(13)); + } + + public function testGetSize() + { + $size = filesize(__DIR__ . '/../../../bootstrap.php'); + $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r'); + $stream = new Stream($handle); + $this->assertEquals($handle, $stream->getStream()); + $this->assertEquals($size, $stream->getSize()); + $this->assertEquals($size, $stream->getSize()); + unset($stream); + + // Make sure that false is returned when the size cannot be determined + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $handle = fopen('http://127.0.0.1:' . $this->getServer()->getPort(), 'r'); + $stream = new Stream($handle); + $this->assertEquals(false, $stream->getSize()); + unset($stream); + } + + public function testEnsuresSizeIsConsistent() + { + $h = fopen('php://temp', 'r+'); + fwrite($h, 'foo'); + $stream = new Stream($h); + $this->assertEquals(3, $stream->getSize()); + $stream->write('test'); + $this->assertEquals(7, $stream->getSize()); + fclose($h); + } + + public function testAbstractsMetaData() + { + $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r'); + $stream = new Stream($handle); + $this->assertEquals('plainfile', $stream->getMetaData('wrapper_type')); + $this->assertEquals(null, $stream->getMetaData('wrapper_data')); + $this->assertInternalType('array', $stream->getMetaData()); + } + + public function testDoesNotAttemptToWriteToReadonlyStream() + { + $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r'); + $stream = new Stream($handle); + $this->assertEquals(0, $stream->write('foo')); + } + + public function testProvidesStreamPosition() + { + $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r'); + $stream = new Stream($handle); + $stream->read(2); + $this->assertSame(ftell($handle), $stream->ftell()); + $this->assertEquals(2, $stream->ftell()); + } + + public function testRewindIsSeekZero() + { + $stream = new Stream(fopen('php://temp', 'w+')); + $stream->write('foobazbar'); + $this->assertTrue($stream->rewind()); + $this->assertEquals('foobazbar', $stream->read(9)); + } + + public function testCanDetachStream() + { + $r = fopen('php://temp', 'w+'); + $stream = new Stream($r); + $stream->detachStream(); + $this->assertNull($stream->getStream()); + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/FileBody.txt b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/FileBody.txt new file mode 100644 index 000000000..e69de29bb diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json new file mode 100644 index 000000000..c354ed788 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json @@ -0,0 +1,3 @@ +{ + "includes": ["foo.json"] +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json new file mode 100644 index 000000000..765237b3e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json @@ -0,0 +1,3 @@ +{ + "includes": ["foo.json", "bar.json"] +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json new file mode 100644 index 000000000..cee5005b8 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json @@ -0,0 +1,8 @@ +{ + "includes": ["recursive.json"], + "operations": { + "abstract": { + "httpMethod": "POST" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json new file mode 100644 index 000000000..c354ed788 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json @@ -0,0 +1,3 @@ +{ + "includes": ["foo.json"] +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response new file mode 100644 index 000000000..b6938a28c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response @@ -0,0 +1,3 @@ +HTTP/1.1 200 OK +Content-Length: 0 + diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json new file mode 100644 index 000000000..7b2a9dad4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json @@ -0,0 +1,18 @@ +{ + "includes": [ "json2.json" ], + "services": { + "abstract": { + "access_key": "xyz", + "secret": "abc" + }, + "mock": { + "class": "Guzzle\\Tests\\Service\\Mock\\MockClient", + "extends": "abstract", + "params": { + "username": "foo", + "password": "baz", + "subdomain": "bar" + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json new file mode 100644 index 000000000..08e5566f0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json @@ -0,0 +1,11 @@ +{ + "services": { + "foo": { + "class": "Guzzle\\Tests\\Service\\Mock\\MockClient", + "extends": "abstract", + "params": { + "baz": "bar" + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json new file mode 100644 index 000000000..25452e4a4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json @@ -0,0 +1,71 @@ +{ + "abstract": { + "access_key": "xyz", + "secret": "abc" + }, + "mock": { + "class": "Guzzle\\Tests\\Service\\Mock\\MockClient", + "extends": "abstract", + "params": { + "username": "foo", + "password": "baz", + "subdomain": "bar" + } + }, + + "test.abstract.aws": { + "params": { + "access_key": "12345", + "secret_key": "abcd" + } + }, + + "test.s3": { + "class": "Guzzle\\Service\\Aws\\S3Client", + "extends": "test.abstract.aws", + "params": { + "devpay_product_token": "", + "devpay_user_token": "" + } + }, + + "test.simple_db": { + "class": "Guzzle\\Service\\Aws\\SimpleDb\\SimpleDbClient", + "extends": "test.abstract.aws" + }, + + "test.sqs": { + "class": "Guzzle\\Service\\Aws\\Sqs\\SqsClient", + "extends": "test.abstract.aws" + }, + + "test.centinel": { + "class": "Guzzle\\Service\\CardinalCommerce\\Centinel.CentinelClient", + "params": { + "password": "test", + "processor_id": "123", + "merchant_id": "456" + } + }, + + "test.mws": { + "class": "Guzzle\\Service\\Mws\\MwsClient", + "extends": "test.abstract.aws", + "params": { + "merchant_id": "ABCDE", + "marketplace_id": "FGHIJ", + "application_name": "GuzzleTest", + "application_version": "0.1", + "base_url": "https://mws.amazonservices.com" + } + }, + + "mock": { + "class": "Guzzle\\Tests\\Service\\Mock\\MockClient", + "params": { + "username": "test_user", + "password": "****", + "subdomain": "test" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json new file mode 100644 index 000000000..01557ca11 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json @@ -0,0 +1,40 @@ +{ + "includes": [ "test_service2.json" ], + "operations": { + "test": { + "uri": "/path" + }, + "concrete": { + "extends": "abstract" + }, + "foo_bar": { + "uri": "/testing", + "parameters": { + "other": { + "location": "json", + "location_key": "Other" + }, + "test": { + "type": "object", + "location": "json", + "properties": { + "baz": { + "type": "boolean", + "default": true + }, + "bar": { + "type": "string", + "filters": [ + { + "method": "strtolower", + "args": ["test", "@value"] + }, + "strtoupper" + ] + } + } + } + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json new file mode 100644 index 000000000..66dd9ef7e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json @@ -0,0 +1,7 @@ +{ + "operations": { + "abstract": { + "uri": "/abstract" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json new file mode 100644 index 000000000..ae2ae0bd4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json @@ -0,0 +1,40 @@ +{ + "includes": [ "test_service2.json" ], + "operations": { + "test": { + "uri": "/path" + }, + "concrete": { + "extends": "abstract" + }, + "baz_qux": { + "uri": "/testing", + "parameters": { + "other": { + "location": "json", + "location_key": "Other" + }, + "test": { + "type": "object", + "location": "json", + "properties": { + "baz": { + "type": "boolean", + "default": true + }, + "bar": { + "type": "string", + "filters": [ + { + "method": "strtolower", + "args": ["test", "@value"] + }, + "strtoupper" + ] + } + } + } + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/bootstrap.php b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/bootstrap.php new file mode 100644 index 000000000..28908d310 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/tests/bootstrap.php @@ -0,0 +1,10 @@ += 5.3.2 with [cURL](http://php.net/manual/en/book.curl.php) extension, +* [Guzzle](https://github.com/guzzle/guzzle) library, +* (optional) PHPUnit to run tests. + +## Autoload + +The new version of `php-github-api` using [Composer](http://getcomposer.org). +The first step to use `php-github-api` is to download composer: + +```bash +$ curl -s http://getcomposer.org/installer | php +``` + +Then we have to install our dependencies using: +```bash +$ php composer.phar install +``` +Now we can use autoloader from Composer by: + +```json +{ + "require": { + "knplabs/github-api": "~1.2" + } +} +``` + +> `php-github-api` follows the PSR-0 convention names for its classes, which means you can easily integrate `php-github-api` classes loading in your own autoloader. + +## Using Laravel? + +[Laravel GitHub](https://github.com/GrahamCampbell/Laravel-GitHub) by [Graham Campbell](https://github.com/GrahamCampbell) might interest you. + +## Basic usage of `php-github-api` client + +```php +api('user')->repositories('ornicar'); +``` + +From `$client` object, you can access to all GitHub. + +## Cache usage + +```php + '/tmp/github-api-cache')) +); + +// Or select directly which cache you want to use +$client = new \Github\HttpClient\CachedHttpClient(); +$client->setCache( + // Built in one, or any cache implementing this interface: + // Github\HttpClient\Cache\CacheInterface + new \Github\HttpClient\Cache\FilesystemCache('/tmp/github-api-cache') +); + +$client = new \Github\Client($client); +``` + +Using cache, the client will get cached responses if resources haven't changed since last time, +**without** reaching the `X-Rate-Limit` [imposed by github](http://developer.github.com/v3/#rate-limiting). + + +## Documentation + +See the [`doc` directory](doc/) for more detailed documentation. + +## License + +`php-github-api` is licensed under the MIT License - see the LICENSE file for details + +## Credits + +### Sponsored by + +[![KnpLabs Team](http://knplabs.com/front/images/knp-labs-logo.png)](http://knplabs.com) + +### Contributors + +- Thanks to [Thibault Duplessis aka. ornicar](http://github.com/ornicar) for his first version of this library. +- Thanks to [Joseph Bielawski aka. stloyd](http://github.com/stloyd) for his contributions and support. +- Thanks to [noloh](http://github.com/noloh) for his contribution on the Object API. +- Thanks to [bshaffer](http://github.com/bshaffer) for his contribution on the Repo API. +- Thanks to [Rolf van de Krol](http://github.com/rolfvandekrol) for his countless contributions. +- Thanks to [Nicolas Pastorino](http://github.com/jeanvoye) for his contribution on the Pull Request API. +- Thanks to [Edoardo Rivello](http://github.com/erivello) for his contribution on the Gists API. + +Thanks to GitHub for the high quality API and documentation. diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/composer.json b/modules/devshop/devshop_github/vendor/knplabs/github-api/composer.json new file mode 100644 index 000000000..dc16d3e77 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/composer.json @@ -0,0 +1,38 @@ +{ + "name": "knplabs/github-api", + "type": "library", + "description": "GitHub API v3 client", + "homepage": "https://github.com/KnpLabs/php-github-api", + "keywords": ["github", "gh", "api", "gist"], + "license": "MIT", + "authors": [ + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + }, + { + "name": "Thibault Duplessis", + "email": "thibault.duplessis@gmail.com", + "homepage": "http://ornicar.github.com" + } + ], + "require": { + "php": ">=5.3.2", + "ext-curl": "*", + "guzzle/guzzle": "~3.7" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "knplabs/gaufrette": "Needed for optional Gaufrette cache" + }, + "autoload": { + "psr-0": { "Github\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/AbstractApi.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/AbstractApi.php new file mode 100644 index 000000000..e7209936e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/AbstractApi.php @@ -0,0 +1,204 @@ + + */ +abstract class AbstractApi implements ApiInterface +{ + /** + * The client. + * + * @var Client + */ + protected $client; + + /** + * Number of items per page (GitHub pagination). + * + * @var null|int + */ + protected $perPage; + + /** + * @param Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + public function configure() + { + } + + /** + * @return null|int + */ + public function getPerPage() + { + return $this->perPage; + } + + /** + * @param null|int $perPage + */ + public function setPerPage($perPage) + { + $this->perPage = (null === $perPage ? $perPage : (int) $perPage); + + return $this; + } + + /** + * Send a GET request with query parameters. + * + * @param string $path Request path. + * @param array $parameters GET parameters. + * @param array $requestHeaders Request Headers. + * + * @return \Guzzle\Http\EntityBodyInterface|mixed|string + */ + protected function get($path, array $parameters = array(), $requestHeaders = array()) + { + if (null !== $this->perPage && !isset($parameters['per_page'])) { + $parameters['per_page'] = $this->perPage; + } + if (array_key_exists('ref', $parameters) && is_null($parameters['ref'])) { + unset($parameters['ref']); + } + $response = $this->client->getHttpClient()->get($path, $parameters, $requestHeaders); + + return ResponseMediator::getContent($response); + } + + /** + * Send a HEAD request with query parameters. + * + * @param string $path Request path. + * @param array $parameters HEAD parameters. + * @param array $requestHeaders Request headers. + * + * @return \Guzzle\Http\Message\Response + */ + protected function head($path, array $parameters = array(), $requestHeaders = array()) + { + if (array_key_exists('ref', $parameters) && is_null($parameters['ref'])) { + unset($parameters['ref']); + } + + $response = $this->client->getHttpClient()->request($path, null, 'HEAD', $requestHeaders, array( + 'query' => $parameters + )); + + return $response; + } + + /** + * Send a POST request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + */ + protected function post($path, array $parameters = array(), $requestHeaders = array()) + { + return $this->postRaw( + $path, + $this->createJsonBody($parameters), + $requestHeaders + ); + } + + /** + * Send a POST request with raw data. + * + * @param string $path Request path. + * @param $body Request body. + * @param array $requestHeaders Request headers. + * + * @return \Guzzle\Http\EntityBodyInterface|mixed|string + */ + protected function postRaw($path, $body, $requestHeaders = array()) + { + $response = $this->client->getHttpClient()->post( + $path, + $body, + $requestHeaders + ); + + return ResponseMediator::getContent($response); + } + + /** + * Send a PATCH request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + */ + protected function patch($path, array $parameters = array(), $requestHeaders = array()) + { + $response = $this->client->getHttpClient()->patch( + $path, + $this->createJsonBody($parameters), + $requestHeaders + ); + + return ResponseMediator::getContent($response); + } + + /** + * Send a PUT request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + */ + protected function put($path, array $parameters = array(), $requestHeaders = array()) + { + $response = $this->client->getHttpClient()->put( + $path, + $this->createJsonBody($parameters), + $requestHeaders + ); + + return ResponseMediator::getContent($response); + } + + /** + * Send a DELETE request with JSON-encoded parameters. + * + * @param string $path Request path. + * @param array $parameters POST parameters to be JSON encoded. + * @param array $requestHeaders Request headers. + */ + protected function delete($path, array $parameters = array(), $requestHeaders = array()) + { + $response = $this->client->getHttpClient()->delete( + $path, + $this->createJsonBody($parameters), + $requestHeaders + ); + + return ResponseMediator::getContent($response); + } + + /** + * Create a JSON encoded version of an array of parameters. + * + * @param array $parameters Request parameters + * + * @return null|string + */ + protected function createJsonBody(array $parameters) + { + return (count($parameters) === 0) ? null : json_encode($parameters, empty($parameters) ? JSON_FORCE_OBJECT : 0); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/ApiInterface.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/ApiInterface.php new file mode 100644 index 000000000..893f538e1 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/ApiInterface.php @@ -0,0 +1,16 @@ + + */ +interface ApiInterface +{ + + public function getPerPage(); + + public function setPerPage($perPage); +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Authorizations.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Authorizations.php new file mode 100644 index 000000000..014e08b44 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Authorizations.php @@ -0,0 +1,45 @@ +get('authorizations'); + } + + public function show($number) + { + return $this->get('authorizations/'.rawurlencode($number)); + } + + public function create(array $params, $OTPCode = null) + { + $headers = null === $OTPCode ? array() : array('X-GitHub-OTP' => $OTPCode); + + return $this->post('authorizations', $params, $headers); + } + + public function update($id, array $params) + { + return $this->patch('authorizations/'.rawurlencode($id), $params); + } + + public function remove($id) + { + return $this->delete('authorizations/'.rawurlencode($id)); + } + + public function check($id, $token) + { + return $this->get('authorizations/'.rawurlencode($id).'/tokens/'.rawurlencode($token)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser.php new file mode 100644 index 000000000..3ab571d47 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser.php @@ -0,0 +1,154 @@ + + * @author Felipe Valtl de Mello + */ +class CurrentUser extends AbstractApi +{ + public function show() + { + return $this->get('user'); + } + + public function update(array $params) + { + return $this->patch('user', $params); + } + + /** + * @return Emails + */ + public function emails() + { + return new Emails($this->client); + } + + /** + * @return Followers + */ + public function follow() + { + return new Followers($this->client); + } + + public function followers($page = 1) + { + return $this->get('user/followers', array( + 'page' => $page + )); + } + + /** + * @link http://developer.github.com/v3/issues/#list-issues + * + * @param array $params + * @param bool $includeOrgIssues + * + * @return array + */ + public function issues(array $params = array(), $includeOrgIssues = true) + { + return $this->get($includeOrgIssues ? 'issues' : 'user/issues', array_merge(array('page' => 1), $params)); + } + + /** + * @return DeployKeys + */ + public function keys() + { + return new DeployKeys($this->client); + } + + /** + * @return Notifications + */ + public function notifications() + { + return new Notifications($this->client); + } + + /** + * @link http://developer.github.com/v3/orgs/#list-user-organizations + * + * @return array + */ + public function organizations() + { + return $this->get('user/orgs'); + } + + /** + * @link https://developer.github.com/v3/orgs/teams/#list-user-teams + * + * @return array + */ + public function teams() + { + return $this->get('user/teams'); + } + + /** + * @link http://developer.github.com/v3/repos/#list-your-repositories + * + * @param string $type role in the repository + * @param string $sort sort by + * @param string $direction direction of sort, ask or desc + * + * @return array + */ + public function repositories($type = 'owner', $sort = 'full_name', $direction = 'asc') + { + return $this->get('user/repos', array( + $type, + $sort, + $direction + )); + } + + /** + * @return Watchers + */ + public function watchers() + { + return new Watchers($this->client); + } + + /** + * @deprecated Use watchers() instead + */ + public function watched($page = 1) + { + return $this->get('user/watched', array( + 'page' => $page + )); + } + + /** + * @return Starring + */ + public function starring() + { + return new Starring($this->client); + } + + /** + * @deprecated Use starring() instead + */ + public function starred($page = 1) + { + return $this->get('user/starred', array( + 'page' => $page + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/DeployKeys.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/DeployKeys.php new file mode 100644 index 000000000..d4f8b080d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/DeployKeys.php @@ -0,0 +1,94 @@ + + */ +class DeployKeys extends AbstractApi +{ + /** + * List deploy keys for the authenticated user. + * + * @link http://developer.github.com/v3/repos/keys/ + * + * @return array + */ + public function all() + { + return $this->get('user/keys'); + } + + /** + * Shows deploy key for the authenticated user. + * + * @link http://developer.github.com/v3/repos/keys/ + * + * @param string $id + * + * @return array + */ + public function show($id) + { + return $this->get('user/keys/'.rawurlencode($id)); + } + + /** + * Adds deploy key for the authenticated user. + * + * @link http://developer.github.com/v3/repos/keys/ + * + * @param array $params + * + * @throws MissingArgumentException + * + * @return array + */ + public function create(array $params) + { + if (!isset($params['title'], $params['key'])) { + throw new MissingArgumentException(array('title', 'key')); + } + + return $this->post('user/keys', $params); + } + + /** + * Updates deploy key for the authenticated user. + * + * @link http://developer.github.com/v3/repos/keys/ + * + * @param string $id + * @param array $params + * + * @throws MissingArgumentException + * + * @return array + */ + public function update($id, array $params) + { + if (!isset($params['title'], $params['key'])) { + throw new MissingArgumentException(array('title', 'key')); + } + + return $this->patch('user/keys/'.rawurlencode($id), $params); + } + + /** + * Removes deploy key for the authenticated user. + * + * @link http://developer.github.com/v3/repos/keys/ + * + * @param string $id + * + * @return array + */ + public function remove($id) + { + return $this->delete('user/keys/'.rawurlencode($id)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Emails.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Emails.php new file mode 100644 index 000000000..587aa12c7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Emails.php @@ -0,0 +1,69 @@ + + */ +class Emails extends AbstractApi +{ + /** + * List emails for the authenticated user. + * + * @link http://developer.github.com/v3/users/emails/ + * + * @return array + */ + public function all() + { + return $this->get('user/emails'); + } + + /** + * Adds one or more email for the authenticated user. + * + * @link http://developer.github.com/v3/users/emails/ + * + * @param string|array $emails + * + * @throws InvalidArgumentException + * + * @return array + */ + public function add($emails) + { + if (is_string($emails)) { + $emails = array($emails); + } elseif (0 === count($emails)) { + throw new InvalidArgumentException(); + } + + return $this->post('user/emails', $emails); + } + + /** + * Removes one or more email for the authenticated user. + * + * @link http://developer.github.com/v3/users/emails/ + * + * @param string|array $emails + * + * @throws InvalidArgumentException + * + * @return array + */ + public function remove($emails) + { + if (is_string($emails)) { + $emails = array($emails); + } elseif (0 === count($emails)) { + throw new InvalidArgumentException(); + } + + return $this->delete('user/emails', $emails); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Followers.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Followers.php new file mode 100644 index 000000000..303637740 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Followers.php @@ -0,0 +1,70 @@ + + */ +class Followers extends AbstractApi +{ + /** + * List followed users by the authenticated user. + * + * @link http://developer.github.com/v3/repos/followers/ + * + * @param int $page + * + * @return array + */ + public function all($page = 1) + { + return $this->get('user/following', array( + 'page' => $page + )); + } + + /** + * Check that the authenticated user follows a user. + * + * @link http://developer.github.com/v3/repos/followers/ + * + * @param string $username the username to follow + * + * @return array + */ + public function check($username) + { + return $this->get('user/following/'.rawurlencode($username)); + } + + /** + * Make the authenticated user follow a user. + * + * @link http://developer.github.com/v3/repos/followers/ + * + * @param string $username the username to follow + * + * @return array + */ + public function follow($username) + { + return $this->put('user/following/'.rawurlencode($username)); + } + + /** + * Make the authenticated user un-follow a user. + * + * @link http://developer.github.com/v3/repos/followers/ + * + * @param string $username the username to un-follow + * + * @return array + */ + public function unfollow($username) + { + return $this->delete('user/following/'.rawurlencode($username)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Notifications.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Notifications.php new file mode 100644 index 000000000..92dc0a570 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Notifications.php @@ -0,0 +1,130 @@ + + */ +class Notifications extends AbstractApi +{ + /** + * List all notifications for the authenticated user. + * + * @link http://developer.github.com/v3/activity/notifications/#list-your-notifications + * + * @param array $params + * + * @return array + */ + public function all(array $params = array()) + { + return $this->get('notifications', $params); + } + + /** + * List all notifications for the authenticated user in selected repository. + * + * @link http://developer.github.com/v3/activity/notifications/#list-your-notifications-in-a-repository + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param array $params + * + * @return array + */ + public function allInRepository($username, $repository, array $params = array()) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/notifications', $params); + } + + /** + * @link http://developer.github.com/v3/activity/notifications/#mark-as-read + * + * @param array $params + * + * @return array + */ + public function markAsReadAll(array $params = array()) + { + return $this->put('notifications', $params); + } + + /** + * @link http://developer.github.com/v3/activity/notifications/#mark-notifications-as-read-in-a-repository + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param array $params + * + * @return array + */ + public function markAsReadInRepository($username, $repository, array $params = array()) + { + return $this->put('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/notifications', $params); + } + + /** + * @link http://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read + * + * @param string $id the notification number + * @param array $params + * + * @return array + */ + public function markAsRead($id, array $params) + { + return $this->patch('notifications/threads/'.rawurlencode($id), $params); + } + + /** + * @link http://developer.github.com/v3/activity/notifications/#view-a-single-thread + * + * @param string $id the notification number + * + * @return array + */ + public function show($id) + { + return $this->get('notifications/threads/'.rawurlencode($id)); + } + + /** + * @link http://developer.github.com/v3/activity/notifications/#get-a-thread-subscription + * + * @param string $id the notification number + * + * @return array + */ + public function showSubscription($id) + { + return $this->get('notifications/threads/'.rawurlencode($id).'/subscription'); + } + + /** + * @link http://developer.github.com/v3/activity/notifications/#set-a-thread-subscription + * + * @param string $id the notification number + * @param array $params + * + * @return array + */ + public function createSubscription($id, array $params) + { + return $this->put('notifications/threads/'.rawurlencode($id).'/subscription', $params); + } + + /** + * @link http://developer.github.com/v3/activity/notifications/#delete-a-thread-subscription + * + * @param string $id the notification number + * + * @return array + */ + public function removeSubscription($id) + { + return $this->delete('notifications/threads/'.rawurlencode($id).'/subscription'); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Starring.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Starring.php new file mode 100644 index 000000000..0e75da2c5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Starring.php @@ -0,0 +1,73 @@ + + */ +class Starring extends AbstractApi +{ + /** + * List repositories starred by the authenticated user. + * + * @link https://developer.github.com/v3/activity/starring/ + * + * @param int $page + * + * @return array + */ + public function all($page = 1) + { + return $this->get('user/starred', array( + 'page' => $page + )); + } + + /** + * Check that the authenticated user starres a repository. + * + * @link https://developer.github.com/v3/activity/starring/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function check($username, $repository) + { + return $this->get('user/starred/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Make the authenticated user star a repository. + * + * @link https://developer.github.com/v3/activity/starring/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function star($username, $repository) + { + return $this->put('user/starred/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Make the authenticated user unstar a repository. + * + * @link https://developer.github.com/v3/activity/starring + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function unstar($username, $repository) + { + return $this->delete('user/starred/'.rawurlencode($username).'/'.rawurlencode($repository)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Watchers.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Watchers.php new file mode 100644 index 000000000..7f7b55457 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/CurrentUser/Watchers.php @@ -0,0 +1,74 @@ + + * @revised Felipe Valtl de Mello + */ +class Watchers extends AbstractApi +{ + /** + * List repositories watched by the authenticated user. + * + * @link https://developer.github.com/v3/activity/watching/ + * + * @param int $page + * + * @return array + */ + public function all($page = 1) + { + return $this->get('user/subscriptions', array( + 'page' => $page + )); + } + + /** + * Check that the authenticated user watches a repository. + * + * @link https://developer.github.com/v3/activity/watching/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function check($username, $repository) + { + return $this->get('user/subscriptions/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Make the authenticated user watch a repository. + * + * @link https://developer.github.com/v3/activity/watching/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function watch($username, $repository) + { + return $this->put('user/subscriptions/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Make the authenticated user unwatch a repository. + * + * @link https://developer.github.com/v3/activity/watching/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function unwatch($username, $repository) + { + return $this->delete('user/subscriptions/'.rawurlencode($username).'/'.rawurlencode($repository)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Deployment.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Deployment.php new file mode 100644 index 000000000..f8f3dbac9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Deployment.php @@ -0,0 +1,84 @@ +get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/deployments', $params); + } + + /** + * Create a new deployment for the given username and repo. + * @link https://developer.github.com/v3/repos/deployments/#create-a-deployment + * + * Important: Once a deployment is created, it cannot be updated. Changes are indicated by creating new statuses. + * @see updateStatus + * + * @param string $username the username + * @param string $repository the repository + * @param array $params the new deployment data + * @return array information about the deployment + * + * @throws MissingArgumentException + */ + public function create($username, $repository, array $params) + { + if (!isset($params['ref'])) { + throw new MissingArgumentException(array('ref')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/deployments', $params); + } + + /** + * Updates a deployment by creating a new status update. + * @link https://developer.github.com/v3/repos/deployments/#create-a-deployment-status + * + * @param string $username the username + * @param string $repository the repository + * @param string $id the deployment number + * @param array $params The information about the deployment update. + * Must include a "state" field of pending, success, error, or failure. + * May also be given a target_url and description, ßee link for more details. + * @return array information about the deployment + * + * @throws MissingArgumentException + */ + public function updateStatus($username, $repository, $id, array $params) + { + if (!isset($params['state'])) { + throw new MissingArgumentException(array('state')); + } + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/deployments/'.rawurlencode($id).'/statuses', $params); + } + + /** + * Gets all of the status updates tied to a given deployment. + * + * @param string $username the username + * @param string $repository the repository + * @param int $id the deployment identifier + * @return array the deployment statuses + */ + public function getStatuses($username, $repository, $id) { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/deployments/'.rawurlencode($id).'/statuses'); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise.php new file mode 100644 index 000000000..c23171a66 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise.php @@ -0,0 +1,50 @@ + + * @author Guillermo A. Fisher + */ +class Enterprise extends AbstractApi +{ + /** + * @return Stats + */ + public function stats() + { + return new Stats($this->client); + } + + /** + * @return License + */ + public function license() + { + return new License($this->client); + } + + /** + * @return ManagementConsole + */ + public function console() + { + return new ManagementConsole($this->client); + } + + /** + * @return UserAdmin + */ + public function userAdmin() + { + return new UserAdmin($this->client); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/License.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/License.php new file mode 100644 index 000000000..de14f23ea --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/License.php @@ -0,0 +1,20 @@ +get('enterprise/settings/license'); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/ManagementConsole.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/ManagementConsole.php new file mode 100644 index 000000000..8554425f3 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/ManagementConsole.php @@ -0,0 +1,77 @@ +getWithLicenseHash('/setup/api/configcheck', $hash); + } + + /** + * Retrieves your installation’s settings. + * + * @link https://developer.github.com/v3/enterprise/management_console/#retrieve-settings + * + * @param string $hash md5 hash of your license + * + * @return array array of settings + */ + public function settings($hash) + { + return $this->getWithLicenseHash('/setup/api/settings', $hash); + } + + /** + * Checks your installation’s maintenance status. + * + * @link https://developer.github.com/v3/enterprise/management_console/#check-maintenance-status + * + * @param string $hash md5 hash of your license + * + * @return array array of maintenance status information + */ + public function maintenance($hash) + { + return $this->getWithLicenseHash('/setup/api/maintenance', $hash); + } + + /** + * Retrieves your installation’s authorized SSH keys. + * + * @link https://developer.github.com/v3/enterprise/management_console/#retrieve-authorized-ssh-keys + * + * @param string $hash md5 hash of your license + * + * @return array array of authorized keys + */ + public function keys($hash) + { + return $this->getWithLicenseHash('/setup/api/settings/authorized-keys', $hash); + } + + /** + * Sends an authenticated GET request. + * + * @param string $uri the request URI + * @param string $hash md5 hash of your license + * + * @return \Guzzle\Http\EntityBodyInterface|mixed|string + */ + protected function getWithLicenseHash($uri, $hash) + { + return $this->get($uri, array('license_md5' => rawurlencode($hash))); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/Stats.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/Stats.php new file mode 100644 index 000000000..7af932945 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/Stats.php @@ -0,0 +1,128 @@ +show('issues'); + } + + /** + * Returns the number of active and inactive hooks. + * + * @return array array with totals of active and inactive hooks + */ + public function hooks() + { + return $this->show('hooks'); + } + + /** + * Returns the number of open and closed milestones. + * + * @return array array with totals of open and closed milestones + */ + public function milestones() + { + return $this->show('milestones'); + } + + /** + * Returns the number of organizations, teams, team members, and disabled organizations. + * + * @return array array with totals of organizations, teams, team members, and disabled organizations + */ + public function orgs() + { + return $this->show('orgs'); + } + + /** + * Returns the number of comments on issues, pull requests, commits, and gists. + * + * @return array array with totals of comments on issues, pull requests, commits, and gists + */ + public function comments() + { + return $this->show('comments'); + } + + /** + * Returns the number of GitHub Pages sites. + * + * @return array array with totals of GitHub Pages sites + */ + public function pages() + { + return $this->show('pages'); + } + + /** + * Returns the number of suspended and admin users. + * + * @return array array with totals of suspended and admin users + */ + public function users() + { + return $this->show('users'); + } + + /** + * Returns the number of private and public gists. + * + * @return array array with totals of private and public gists + */ + public function gists() + { + return $this->show('gists'); + } + + /** + * Returns the number of merged, mergeable, and unmergeable pull requests. + * + * @return array array with totals of merged, mergeable, and unmergeable pull requests + */ + public function pulls() + { + return $this->show('pulls'); + } + + /** + * Returns the number of organization-owned repositories, root repositories, forks, pushed commits, and wikis. + * + * @return array array with totals of organization-owned repositories, root repositories, forks, pushed commits, and wikis + */ + public function repos() + { + return $this->show('repos'); + } + + /** + * Returns all of the statistics. + * + * @return array array with all of the statistics + */ + public function all() + { + return $this->show('all'); + } + + /** + * @param string $type The type of statistics to show + * + * @return array + */ + public function show($type) + { + return $this->get('enterprise/stats/' . rawurlencode($type)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/UserAdmin.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/UserAdmin.php new file mode 100644 index 000000000..5a14114bc --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Enterprise/UserAdmin.php @@ -0,0 +1,36 @@ +put('users/'.rawurldecode($username).'/suspended', array('Content-Length' => 0)); + } + + /** + * Unsuspend a user. + * + * @link https://developer.github.com/v3/users/administration/#unsuspend-a-user + * + * @param string $username + * + * @return array + */ + public function unsuspend($username) + { + return $this->delete('users/'.rawurldecode($username).'/suspended'); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Gist/Comments.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Gist/Comments.php new file mode 100644 index 000000000..2d8896500 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Gist/Comments.php @@ -0,0 +1,37 @@ + + */ +class Comments extends AbstractApi +{ + public function all($gist) + { + return $this->get('gists/'.rawurlencode($gist).'/comments'); + } + + public function show($gist, $comment) + { + return $this->get('gists/'.rawurlencode($gist).'/comments/'.rawurlencode($comment)); + } + + public function create($gist, $body) + { + return $this->post('gists/'.rawurlencode($gist).'/comments', array($body)); + } + + public function update($gist, $comment_id, $body) + { + return $this->patch('gists/'.rawurlencode($gist).'/comments/'.rawurlencode($comment_id), array($body)); + } + + public function remove($gist, $comment) + { + return $this->delete('gists/'.rawurlencode($gist).'/comments/'.rawurlencode($comment)); + } +} \ No newline at end of file diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Gists.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Gists.php new file mode 100644 index 000000000..eb24f0b3f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Gists.php @@ -0,0 +1,88 @@ + + * @author Edoardo Rivello + */ +class Gists extends AbstractApi +{ + public function all($type = null) + { + if (!in_array($type, array('public', 'starred'))) { + return $this->get('gists'); + } + + return $this->get('gists/'.rawurlencode($type)); + } + + public function show($number) + { + return $this->get('gists/'.rawurlencode($number)); + } + + public function create(array $params) + { + if (!isset($params['files']) || (!is_array($params['files']) || 0 === count($params['files']))) { + throw new MissingArgumentException('files'); + } + + $params['public'] = (bool) $params['public']; + + return $this->post('gists', $params); + } + + public function update($id, array $params) + { + return $this->patch('gists/'.rawurlencode($id), $params); + } + + public function commits($id) + { + return $this->get('gists/'.rawurlencode($id).'/commits'); + } + + public function fork($id) + { + return $this->post('gists/'.rawurlencode($id).'/fork'); + } + + public function remove($id) + { + return $this->delete('gists/'.rawurlencode($id)); + } + + public function check($id) + { + return $this->get('gists/'.rawurlencode($id).'/star'); + } + + public function star($id) + { + return $this->put('gists/'.rawurlencode($id).'/star'); + } + + public function unstar($id) + { + return $this->delete('gists/'.rawurlencode($id).'/star'); + } + + /** + * Get a gist's comments. + * + * @link http://developer.github.com/v3/gists/comments/ + * + * @return Comments + */ + public function comments() + { + return new Comments($this->client); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData.php new file mode 100644 index 000000000..21395a8ba --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData.php @@ -0,0 +1,58 @@ + + */ +class GitData extends AbstractApi +{ + /** + * @return Blobs + */ + public function blobs() + { + return new Blobs($this->client); + } + + /** + * @return Commits + */ + public function commits() + { + return new Commits($this->client); + } + + /** + * @return References + */ + public function references() + { + return new References($this->client); + } + + /** + * @return Tags + */ + public function tags() + { + return new Tags($this->client); + } + + /** + * @return Trees + */ + public function trees() + { + return new Trees($this->client); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Blobs.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Blobs.php new file mode 100644 index 000000000..2ff844db2 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Blobs.php @@ -0,0 +1,38 @@ + + */ +class Blobs extends AbstractApi +{ + public function configure($bodyType = null) + { + if ('raw' == $bodyType) { + $this->client->setHeaders(array( + 'Accept' => sprintf('application/vnd.github.%s.raw', $this->client->getOption('api_version')) + )); + } + } + + public function show($username, $repository, $sha) + { + $response = $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/blobs/'.rawurlencode($sha)); + + return $response; + } + + public function create($username, $repository, array $params) + { + if (!isset($params['content'], $params['encoding'])) { + throw new MissingArgumentException(array('content', 'encoding')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/blobs', $params); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Commits.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Commits.php new file mode 100644 index 000000000..1c161e1d7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Commits.php @@ -0,0 +1,27 @@ + + */ +class Commits extends AbstractApi +{ + public function show($username, $repository, $sha) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/commits/'.rawurlencode($sha)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['message'], $params['tree'], $params['parents'])) { + throw new MissingArgumentException(array('message', 'tree', 'parents')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/commits', $params); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/References.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/References.php new file mode 100644 index 000000000..70d8fa37a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/References.php @@ -0,0 +1,56 @@ + + */ +class References extends AbstractApi +{ + public function all($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs'); + } + + public function branches($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/heads'); + } + + public function tags($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/tags'); + } + + public function show($username, $repository, $reference) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/'.rawurlencode($reference)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['ref'], $params['sha'])) { + throw new MissingArgumentException(array('ref', 'sha')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs', $params); + } + + public function update($username, $repository, $reference, array $params) + { + if (!isset($params['sha'])) { + throw new MissingArgumentException('sha'); + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/'.rawurlencode($reference), $params); + } + + public function remove($username, $repository, $reference) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/'.rawurlencode($reference)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Tags.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Tags.php new file mode 100644 index 000000000..b27dddd12 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Tags.php @@ -0,0 +1,40 @@ + + */ +class Tags extends AbstractApi +{ + public function all($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/refs/tags'); + } + + public function show($username, $repository, $sha) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/tags/'.rawurlencode($sha)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['tag'], $params['message'], $params['object'], $params['type'])) { + throw new MissingArgumentException(array('tag', 'message', 'object', 'type')); + } + + if (!isset($params['tagger'])) { + throw new MissingArgumentException('tagger'); + } + + if (!isset($params['tagger']['name'], $params['tagger']['email'], $params['tagger']['date'])) { + throw new MissingArgumentException(array('tagger.name', 'tagger.email', 'tagger.date')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/tags', $params); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Trees.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Trees.php new file mode 100644 index 000000000..92bd21ac4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/GitData/Trees.php @@ -0,0 +1,42 @@ + + */ +class Trees extends AbstractApi +{ + public function show($username, $repository, $sha, $recursive = false) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/trees/'.rawurlencode($sha), array('recursive' => $recursive ? 1 : null)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['tree']) || !is_array($params['tree'])) { + throw new MissingArgumentException('tree'); + } + + if (!isset($params['tree'][0])) { + $params['tree'] = array($params['tree']); + } + + foreach ($params['tree'] as $key => $tree) { + if (!isset($tree['path'], $tree['mode'], $tree['type'])) { + throw new MissingArgumentException(array("tree.$key.path", "tree.$key.mode", "tree.$key.type")); + } + + // If `sha` is not set, `content` is required + if (!isset($tree['sha']) && !isset($tree['content'])) { + throw new MissingArgumentException("tree.$key.content"); + } + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/git/trees', $params); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue.php new file mode 100644 index 000000000..c1b9a6e79 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue.php @@ -0,0 +1,182 @@ + + * @author Joseph Bielawski + */ +class Issue extends AbstractApi +{ + /** + * List issues by username, repo and state. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $username the username + * @param string $repository the repository + * @param array $params the additional parameters like milestone, assignees, labels, sort, direction + * + * @return array list of issues found + */ + public function all($username, $repository, array $params = array()) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues', array_merge(array('page' => 1), $params)); + } + + /** + * Search issues by username, repo, state and keyword. + * + * @link http://developer.github.com/v3/search/#search-issues + * + * @param string $username the username + * @param string $repository the repository + * @param string $state the issue state, can be open or closed + * @param string $keyword the keyword to filter issues by + * + * @return array list of issues found + */ + public function find($username, $repository, $state, $keyword) + { + if (!in_array($state, array('open', 'closed'))) { + $state = 'open'; + } + + return $this->get('legacy/issues/search/'.rawurlencode($username).'/'.rawurlencode($repository).'/'.rawurlencode($state).'/'.rawurlencode($keyword)); + } + + /** + * List issues by organization. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $organization the organization + * @param string $state the issue state, can be open or closed + * @param array $params the additional parameters like milestone, assignees, labels, sort, direction + * + * @return array list of issues found + */ + public function org($organization, $state, array $params = array()) + { + if (!in_array($state, array('open', 'closed'))) { + $state = 'open'; + } + + return $this->get('orgs/'.rawurlencode($organization).'/issues', array_merge(array('page' => 1, 'state' => $state), $params)); + } + + /** + * Get extended information about an issue by its username, repo and number. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $username the username + * @param string $repository the repository + * @param string $id the issue number + * + * @return array information about the issue + */ + public function show($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($id)); + } + + /** + * Create a new issue for the given username and repo. + * The issue is assigned to the authenticated user. Requires authentication. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $username the username + * @param string $repository the repository + * @param array $params the new issue data + * + * @throws MissingArgumentException + * + * @return array information about the issue + */ + public function create($username, $repository, array $params) + { + if (!isset($params['title'], $params['body'])) { + throw new MissingArgumentException(array('title', 'body')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues', $params); + } + + /** + * Update issue information's by username, repo and issue number. Requires authentication. + * + * @link http://developer.github.com/v3/issues/ + * + * @param string $username the username + * @param string $repository the repository + * @param string $id the issue number + * @param array $params key=>value user attributes to update. + * key can be title or body + * + * @return array information about the issue + */ + public function update($username, $repository, $id, array $params) + { + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($id), $params); + } + + /** + * List an issue comments. + * + * @link http://developer.github.com/v3/issues/comments/ + * + * @return Comments + */ + public function comments() + { + return new Comments($this->client); + } + + /** + * List all project events. + * + * @link http://developer.github.com/v3/issues/events/ + * + * @return Events + */ + public function events() + { + return new Events($this->client); + } + + /** + * List all project labels. + * + * @link http://developer.github.com/v3/issues/labels/ + * + * @return Labels + */ + public function labels() + { + return new Labels($this->client); + } + + /** + * List all project milestones. + * + * @link http://developer.github.com/v3/issues/milestones/ + * + * @return Milestones + */ + public function milestones() + { + return new Milestones($this->client); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Comments.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Comments.php new file mode 100644 index 000000000..ae6370fc6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Comments.php @@ -0,0 +1,70 @@ + + */ +class Comments extends AbstractApi +{ + public function configure($bodyType = null) + { + switch ($bodyType) { + case 'raw': + $header = sprintf('Accept: application/vnd.github.%s.raw+json', $this->client->getOption('api_version')); + break; + + case 'text': + $header = sprintf('Accept: application/vnd.github.%s.text+json', $this->client->getOption('api_version')); + break; + + case 'html': + $header = sprintf('Accept: application/vnd.github.%s.html+json', $this->client->getOption('api_version')); + break; + + default: + $header = sprintf('Accept: application/vnd.github.%s.full+json', $this->client->getOption('api_version')); + } + + $this->client->setHeaders(array($header)); + } + + public function all($username, $repository, $issue, $page = 1) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/comments', array( + 'page' => $page + )); + } + + public function show($username, $repository, $comment) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/comments/'.rawurlencode($comment)); + } + + public function create($username, $repository, $issue, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/comments', $params); + } + + public function update($username, $repository, $comment, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/comments/'.rawurlencode($comment), $params); + } + + public function remove($username, $repository, $comment) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/comments/'.rawurlencode($comment)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Events.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Events.php new file mode 100644 index 000000000..17bcef839 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Events.php @@ -0,0 +1,30 @@ + + */ +class Events extends AbstractApi +{ + public function all($username, $repository, $issue = null, $page = 1) + { + if (null !== $issue) { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/events', array( + 'page' => $page + )); + } + + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/events', array( + 'page' => $page + )); + } + + public function show($username, $repository, $event) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/events/'.rawurlencode($event)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Labels.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Labels.php new file mode 100644 index 000000000..bdce14371 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Labels.php @@ -0,0 +1,76 @@ + + */ +class Labels extends AbstractApi +{ + public function all($username, $repository, $issue = null) + { + if ($issue === null) { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels'); + } + + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels'); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException('name'); + } + if (!isset($params['color'])) { + $params['color'] = 'FFFFFF'; + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels', $params); + } + + public function deleteLabel($username, $repository, $label) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label)); + } + + public function update($username, $repository, $label, $newName, $color) + { + $params = array( + 'name' => $newName, + 'color' => $color, + ); + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label), $params); + } + + public function add($username, $repository, $issue, $labels) + { + if (is_string($labels)) { + $labels = array($labels); + } elseif (0 === count($labels)) { + throw new InvalidArgumentException(); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels', $labels); + } + + public function replace($username, $repository, $issue, array $params) + { + return $this->put('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels', $params); + } + + public function remove($username, $repository, $issue, $label) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels/'.rawurlencode($label)); + } + + public function clear($username, $repository, $issue) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/issues/'.rawurlencode($issue).'/labels'); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Milestones.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Milestones.php new file mode 100644 index 000000000..a2e945a98 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Issue/Milestones.php @@ -0,0 +1,64 @@ + + */ +class Milestones extends AbstractApi +{ + public function all($username, $repository, array $params = array()) + { + if (isset($params['state']) && !in_array($params['state'], array('open', 'closed', 'all'))) { + $params['state'] = 'open'; + } + if (isset($params['sort']) && !in_array($params['sort'], array('due_date', 'completeness'))) { + $params['sort'] = 'due_date'; + } + if (isset($params['direction']) && !in_array($params['direction'], array('asc', 'desc'))) { + $params['direction'] = 'desc'; + } + + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones', array_merge(array('page' => 1, 'state' => 'open', 'sort' => 'due_date', 'direction' => 'desc'), $params)); + } + + public function show($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones/'.rawurlencode($id)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['title'])) { + throw new MissingArgumentException('title'); + } + if (isset($params['state']) && !in_array($params['state'], array('open', 'closed'))) { + $params['state'] = 'open'; + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones', $params); + } + + public function update($username, $repository, $milestone, array $params) + { + if (isset($params['state']) && !in_array($params['state'], array('open', 'closed'))) { + $params['state'] = 'open'; + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones/'.rawurlencode($milestone), $params); + } + + public function remove($username, $repository, $milestone) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones/'.rawurlencode($milestone)); + } + + public function labels($username, $repository, $milestone) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/milestones/'.rawurlencode($milestone).'/labels'); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Markdown.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Markdown.php new file mode 100644 index 000000000..f97a142df --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Markdown.php @@ -0,0 +1,48 @@ + + */ +class Markdown extends AbstractApi +{ + /** + * @param string $text + * @param string $mode + * @param string $context + * + * @return string + */ + public function render($text, $mode = 'markdown', $context = null) + { + if (!in_array($mode, array('gfm', 'markdown'))) { + $mode = 'markdown'; + } + + $params = array( + 'text' => $text, + 'mode' => $mode + ); + if (null !== $context && 'gfm' === $mode) { + $params['context'] = $context; + } + + return $this->post('markdown', $params); + } + + /** + * @param string $file + * + * @return string + */ + public function renderRaw($file) + { + return $this->post('markdown/raw', array( + 'file' => $file + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Meta.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Meta.php new file mode 100644 index 000000000..73a43301f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Meta.php @@ -0,0 +1,22 @@ + + */ +class Meta extends AbstractApi +{ + /** + * Get the ip address of the hook and git servers for the GitHub.com service. + * + * @return array Informations about the service of GitHub.com + */ + public function service() + { + return $this->get('meta'); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Notification.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Notification.php new file mode 100644 index 000000000..c351927ec --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Notification.php @@ -0,0 +1,60 @@ + + */ +class Notification extends AbstractApi +{ + /** + * Get a listing of notifications. + * + * @link https://developer.github.com/v3/activity/notifications/ + * + * @param bool $includingRead + * @param bool $participating + * @param DateTime|null $since + * + * @return array array of notifications + */ + public function all($includingRead = false, $participating = false, DateTime $since = null) + { + $parameters = array( + 'all' => $includingRead, + 'participating' => $participating + ); + + if($since !== null) { + $parameters['since'] = $since->format(DateTime::ISO8601); + } + + return $this->get('notifications', $parameters); + } + + /** + * Marks all notifications as read from the current date + * Optionally give DateTime to mark as read before that date. + * + * @link https://developer.github.com/v3/activity/notifications/#mark-as-read + * + * @param DateTime|null $since + */ + public function markRead(DateTime $since = null) + { + $parameters = array(); + + if($since !== null) { + $parameters['last_read_at'] = $since->format(DateTime::ISO8601); + } + + $this->put('notifications', $parameters); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Organization.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Organization.php new file mode 100644 index 000000000..37322b218 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Organization.php @@ -0,0 +1,68 @@ + + * @author Joseph Bielawski + */ +class Organization extends AbstractApi +{ + /** + * Get extended information about an organization by its name. + * + * @link http://developer.github.com/v3/orgs/#get + * + * @param string $organization the organization to show + * + * @return array informations about the organization + */ + public function show($organization) + { + return $this->get('orgs/'.rawurlencode($organization)); + } + + public function update($organization, array $params) + { + return $this->patch('orgs/'.rawurlencode($organization), $params); + } + + /** + * List all repositories across all the organizations that you can access. + * + * @link http://developer.github.com/v3/repos/#list-organization-repositories + * + * @param string $organization the user name + * @param string $type the type of repositories + * + * @return array the repositories + */ + public function repositories($organization, $type = 'all') + { + return $this->get('orgs/'.rawurlencode($organization).'/repos', array( + 'type' => $type + )); + } + + /** + * @return Members + */ + public function members() + { + return new Members($this->client); + } + + /** + * @return Teams + */ + public function teams() + { + return new Teams($this->client); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Organization/Members.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Organization/Members.php new file mode 100644 index 000000000..7358ed276 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Organization/Members.php @@ -0,0 +1,53 @@ + + */ +class Members extends AbstractApi +{ + public function all($organization, $type = null, $filter = 'all') + { + $parameters = array(); + $path = 'orgs/'.rawurlencode($organization).'/'; + if (null === $type) { + $path .= 'members'; + if (null !== $filter) { + $parameters['filter'] = $filter; + } + } else { + $path .= 'public_members'; + } + + return $this->get($path, $parameters); + } + + public function show($organization, $username) + { + return $this->get('orgs/'.rawurlencode($organization).'/members/'.rawurlencode($username)); + } + + public function check($organization, $username) + { + return $this->get('orgs/'.rawurlencode($organization).'/public_members/'.rawurlencode($username)); + } + + public function publicize($organization, $username) + { + return $this->put('orgs/'.rawurlencode($organization).'/public_members/'.rawurlencode($username)); + } + + public function conceal($organization, $username) + { + return $this->delete('orgs/'.rawurlencode($organization).'/public_members/'.rawurlencode($username)); + } + + public function remove($organization, $username) + { + return $this->delete('orgs/'.rawurlencode($organization).'/members/'.rawurlencode($username)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Organization/Teams.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Organization/Teams.php new file mode 100644 index 000000000..6acca32a0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Organization/Teams.php @@ -0,0 +1,95 @@ + + */ +class Teams extends AbstractApi +{ + public function all($organization) + { + return $this->get('orgs/'.rawurlencode($organization).'/teams'); + } + + public function create($organization, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException('name'); + } + if (isset($params['repo_names']) && !is_array($params['repo_names'])) { + $params['repo_names'] = array($params['repo_names']); + } + if (isset($params['permission']) && !in_array($params['permission'], array('pull', 'push', 'admin'))) { + $params['permission'] = 'pull'; + } + + return $this->post('orgs/'.rawurlencode($organization).'/teams', $params); + } + + public function show($team) + { + return $this->get('teams/'.rawurlencode($team)); + } + + public function update($team, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException('name'); + } + if (isset($params['permission']) && !in_array($params['permission'], array('pull', 'push', 'admin'))) { + $params['permission'] = 'pull'; + } + + return $this->patch('teams/'.rawurlencode($team), $params); + } + + public function remove($team) + { + return $this->delete('teams/'.rawurlencode($team)); + } + + public function members($team) + { + return $this->get('teams/'.rawurlencode($team).'/members'); + } + + public function check($team, $username) + { + return $this->get('teams/'.rawurlencode($team).'/memberships/'.rawurlencode($username)); + } + + public function addMember($team, $username) + { + return $this->put('teams/'.rawurlencode($team).'/memberships/'.rawurlencode($username)); + } + + public function removeMember($team, $username) + { + return $this->delete('teams/'.rawurlencode($team).'/memberships/'.rawurlencode($username)); + } + + public function repositories($team) + { + return $this->get('teams/'.rawurlencode($team).'/repos'); + } + + public function repository($team, $username, $repository) + { + return $this->get('teams/'.rawurlencode($team).'/repos/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + public function addRepository($team, $username, $repository) + { + return $this->put('teams/'.rawurlencode($team).'/repos/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + public function removeRepository($team, $username, $repository) + { + return $this->delete('teams/'.rawurlencode($team).'/repos/'.rawurlencode($username).'/'.rawurlencode($repository)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/PullRequest.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/PullRequest.php new file mode 100755 index 000000000..14db3bc7a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/PullRequest.php @@ -0,0 +1,124 @@ + + */ +class PullRequest extends AbstractApi +{ + /** + * Get a listing of a project's pull requests by the username, repository and (optionally) state. + * + * @link http://developer.github.com/v3/pulls/ + * + * @param string $username the username + * @param string $repository the repository + * @param array $params a list of extra parameters. + * + * @return array array of pull requests for the project + */ + public function all($username, $repository, array $params = array()) + { + $parameters = array_merge(array( + 'page' => 1, + 'per_page' => 30 + ), $params); + + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls', $parameters); + } + + /** + * Show all details of a pull request, including the discussions. + * + * @link http://developer.github.com/v3/pulls/ + * + * @param string $username the username + * @param string $repository the repository + * @param string $id the ID of the pull request for which details are retrieved + * + * @return array array of pull requests for the project + */ + public function show($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id)); + } + + public function commits($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id).'/commits'); + } + + public function files($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id).'/files'); + } + + public function comments() + { + return new Comments($this->client); + } + + /** + * Create a pull request. + * + * @link http://developer.github.com/v3/pulls/ + * + * @param string $username the username + * @param string $repository the repository + * @param array $params A String of the branch or commit SHA that you want your changes to be pulled to. + * A String of the branch or commit SHA of your changes. Typically this will be a branch. + * If the branch is in a fork of the original repository, specify the username first: + * "my-user:some-branch". The String title of the Pull Request. The String body of + * the Pull Request. The issue number. Used when title and body is not set. + * + * @throws MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + // Two ways to create PR, using issue or title + if (!isset($params['issue']) && !isset($params['title'])) { + throw new MissingArgumentException(array('issue', 'title')); + } + + if (!isset($params['base'], $params['head'])) { + throw new MissingArgumentException(array('base', 'head')); + } + + // If `issue` is not sent, then `body` must be sent + if (!isset($params['issue']) && !isset($params['body'])) { + throw new MissingArgumentException(array('issue', 'body')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls', $params); + } + + public function update($username, $repository, $id, array $params) + { + if (isset($params['state']) && !in_array($params['state'], array('open', 'closed'))) { + $params['state'] = 'open'; + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id), $params); + } + + public function merged($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id).'/merge'); + } + + public function merge($username, $repository, $id, $message = '') + { + return $this->put('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($id).'/merge', array( + 'commit_message' => $message + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/PullRequest/Comments.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/PullRequest/Comments.php new file mode 100644 index 000000000..dec30fb05 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/PullRequest/Comments.php @@ -0,0 +1,51 @@ + + */ +class Comments extends AbstractApi +{ + public function all($username, $repository, $pullRequest) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($pullRequest).'/comments'); + } + + public function show($username, $repository, $comment) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/comments/'.rawurlencode($comment)); + } + + public function create($username, $repository, $pullRequest, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + // If `in_reply_to` is set, other options are not necessary anymore + if (!isset($params['in_reply_to']) && !isset($params['commit_id'], $params['path'], $params['position'])) { + throw new MissingArgumentException(array('commit_id', 'path', 'position')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/'.rawurlencode($pullRequest).'/comments', $params); + } + + public function update($username, $repository, $comment, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/comments/'.rawurlencode($comment), $params); + } + + public function remove($username, $repository, $comment) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/pulls/comments/'.rawurlencode($comment)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repo.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repo.php new file mode 100644 index 000000000..fcafd4b25 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repo.php @@ -0,0 +1,466 @@ + + * @author Thibault Duplessis + */ +class Repo extends AbstractApi +{ + /** + * Search repositories by keyword. + * + * @link http://developer.github.com/v3/search/#search-repositories + * + * @param string $keyword the search query + * @param array $params + * + * @return array list of found repositories + */ + public function find($keyword, array $params = array()) + { + return $this->get('legacy/repos/search/'.rawurlencode($keyword), array_merge(array('start_page' => 1), $params)); + } + + /** + * Get the last year of commit activity for a repository grouped by week. + * + * @link http://developer.github.com/v3/repos/statistics/#commit-activity + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array commit activity grouped by week + */ + public function activity($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/stats/commit_activity'); + } + + /** + * Get contributor commit statistics for a repository. + * + * @link http://developer.github.com/v3/repos/statistics/#contributors + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array list of contributors and their commit statistics + */ + public function statistics($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/stats/contributors'); + } + + /** + * List all repositories for an organization. + * + * @link http://developer.github.com/v3/repos/#list-organization-repositories + * + * @param string $organization the name of the organization + * @param array $params + * + * @return array list of organization repositories + */ + public function org($organization, array $params = array()) + { + return $this->get('orgs/'.$organization.'/repos', array_merge(array('start_page' => 1), $params)); + } + + /** + * Get extended information about a repository by its username and repository name. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array informations about the repository + */ + public function show($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Create repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $name name of the repository + * @param string $description repository description + * @param string $homepage homepage url + * @param bool $public `true` for public, `false` for private + * @param null|string $organization username of organization if applicable + * @param bool $hasIssues `true` to enable issues for this repository, `false` to disable them + * @param bool $hasWiki `true` to enable the wiki for this repository, `false` to disable it + * @param bool $hasDownloads `true` to enable downloads for this repository, `false` to disable them + * @param int $teamId The id of the team that will be granted access to this repository. This is only valid when creating a repo in an organization. + * @param bool $autoInit `true` to create an initial commit with empty README, `false` for no initial commit + * + * @return array returns repository data + */ + public function create( + $name, + $description = '', + $homepage = '', + $public = true, + $organization = null, + $hasIssues = false, + $hasWiki = false, + $hasDownloads = false, + $teamId = null, + $autoInit = false + ) { + $path = null !== $organization ? 'orgs/'.$organization.'/repos' : 'user/repos'; + + $parameters = array( + 'name' => $name, + 'description' => $description, + 'homepage' => $homepage, + 'private' => !$public, + 'has_issues' => $hasIssues, + 'has_wiki' => $hasWiki, + 'has_downloads' => $hasDownloads, + 'auto_init' => $autoInit + ); + + if ($organization && $teamId) { + $parameters['team_id'] = $teamId; + } + + return $this->post($path, $parameters); + } + + /** + * Set information of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param array $values the key => value pairs to post + * + * @return array informations about the repository + */ + public function update($username, $repository, array $values) + { + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository), $values); + } + + /** + * Delete a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return mixed null on success, array on error with 'message' + */ + public function remove($username, $repository) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository)); + } + + /** + * Get the readme content for a repository by its username and repository name. + * + * @link http://developer.github.com/v3/repos/contents/#get-the-readme + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array the readme content + */ + public function readme($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/readme'); + } + + /** + * Manage the collaborators of a repository. + * + * @link http://developer.github.com/v3/repos/collaborators/ + * + * @return Collaborators + */ + public function collaborators() + { + return new Collaborators($this->client); + } + + /** + * Manage the comments of a repository. + * + * @link http://developer.github.com/v3/repos/comments/ + * + * @return Comments + */ + public function comments() + { + return new Comments($this->client); + } + + /** + * Manage the commits of a repository. + * + * @link http://developer.github.com/v3/repos/commits/ + * + * @return Commits + */ + public function commits() + { + return new Commits($this->client); + } + + /** + * Manage the content of a repository. + * + * @link http://developer.github.com/v3/repos/contents/ + * + * @return Contents + */ + public function contents() + { + return new Contents($this->client); + } + + /** + * Manage the content of a repository. + * + * @link http://developer.github.com/v3/repos/downloads/ + * + * @return Downloads + */ + public function downloads() + { + return new Downloads($this->client); + } + + /** + * Manage the releases of a repository (Currently Undocumented). + * + * @link http://developer.github.com/v3/repos/ + * + * @return Releases + */ + public function releases() + { + return new Releases($this->client); + } + + /** + * Manage the deploy keys of a repository. + * + * @link http://developer.github.com/v3/repos/keys/ + * + * @return DeployKeys + */ + public function keys() + { + return new DeployKeys($this->client); + } + + /** + * Manage the forks of a repository. + * + * @link http://developer.github.com/v3/repos/forks/ + * + * @return Forks + */ + public function forks() + { + return new Forks($this->client); + } + + /** + * Manage the hooks of a repository. + * + * @link http://developer.github.com/v3/issues/jooks/ + * + * @return Hooks + */ + public function hooks() + { + return new Hooks($this->client); + } + + /** + * Manage the labels of a repository. + * + * @link http://developer.github.com/v3/issues/labels/ + * + * @return Labels + */ + public function labels() + { + return new Labels($this->client); + } + + /** + * Manage the statuses of a repository. + * + * @link http://developer.github.com/v3/repos/statuses/ + * + * @return Statuses + */ + public function statuses() + { + return new Statuses($this->client); + } + + /** + * Get the branch(es) of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the username + * @param string $repository the name of the repository + * @param string $branch the name of the branch + * + * @return array list of the repository branches + */ + public function branches($username, $repository, $branch = null) + { + $url = 'repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/branches'; + if (null !== $branch) { + $url .= '/'.rawurlencode($branch); + } + + return $this->get($url); + } + + /** + * Get the contributors of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param bool $includingAnonymous by default, the list only shows GitHub users. + * You can include non-users too by setting this to true + * + * @return array list of the repo contributors + */ + public function contributors($username, $repository, $includingAnonymous = false) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contributors', array( + 'anon' => $includingAnonymous ?: null + )); + } + + /** + * Get the language breakdown of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array list of the languages + */ + public function languages($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/languages'); + } + + /** + * Get the tags of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * + * @return array list of the repository tags + */ + public function tags($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/tags'); + } + + /** + * Get the teams of a repository. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array list of the languages + */ + public function teams($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/teams'); + } + + /** + * @deprecated see subscribers method + * + * @param string $username + * @param string $repository + * @param int $page + * + * @return array + */ + public function watchers($username, $repository, $page = 1) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/watchers', array( + 'page' => $page + )); + } + + /** + * @param string $username + * @param string $repository + * @param int $page + * + * @return array + */ + public function subscribers($username, $repository, $page = 1) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/subscribers', array( + 'page' => $page + )); + } + + /** + * Perform a merge. + * + * @link http://developer.github.com/v3/repos/merging/ + * + * @param string $username + * @param string $repository + * @param string $base The name of the base branch that the head will be merged into. + * @param string $head The head to merge. This can be a branch name or a commit SHA1. + * @param string $message Commit message to use for the merge commit. If omitted, a default message will be used. + * + * @return array|null + */ + public function merge($username, $repository, $base, $head, $message = null) + { + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/merges', array( + 'base' => $base, + 'head' => $head, + 'commit_message' => $message + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Assets.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Assets.php new file mode 100644 index 000000000..dbd448c7e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Assets.php @@ -0,0 +1,124 @@ + + */ +class Assets extends AbstractApi +{ + /** + * Get all release's assets in selected repository + * GET /repos/:owner/:repo/releases/:id/assets. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the release + * + * @return array + */ + public function all($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id).'/assets'); + } + + /** + * Get an asset in selected repository's release + * GET /repos/:owner/:repo/releases/assets/:id. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the asset + * + * @return array + */ + public function show($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/assets/'.rawurlencode($id)); + } + + /** + * Create an asset for selected repository's release + * POST /repos/:owner/:repo/releases/:id/assets?name=:filename. + * + * Creating an asset requires support for server name indentification (SNI) + * so this must be supported by your PHP version. + * + * @see http://developer.github.com/v3/repos/releases/#upload-a-release-asset + * @see http://php.net/manual/en/openssl.constsni.php + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the release + * @param string $name the filename for the asset + * @param string $contentType the content type for the asset + * @param string $content the content of the asset + * + * @throws MissingArgumentException + * @throws ErrorException + * + * @return array + */ + public function create($username, $repository, $id, $name, $contentType, $content) + { + if (!defined('OPENSSL_TLSEXT_SERVER_NAME') || !OPENSSL_TLSEXT_SERVER_NAME) { + throw new ErrorException('Asset upload support requires Server Name Indication. This is not supported by your PHP version. See http://php.net/manual/en/openssl.constsni.php.'); + } + + // Asset creation requires a separate endpoint, uploads.github.com. + // Change the base url for the HTTP client temporarily while we execute + // this request. + $baseUrl = $this->client->getHttpClient()->client->getBaseUrl(); + $this->client->getHttpClient()->client->setBaseUrl('https://uploads.github.com/'); + + $response = $this->postRaw('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id).'/assets?name='.$name, $content, array('Content-Type' => $contentType)); + + // Reset the base url. + $this->client->getHttpClient()->client->setBaseUrl($baseUrl); + + return $response; + } + + /** + * Edit an asset in selected repository's release + * PATCH /repos/:owner/:repo/releases/assets/:id. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the asset + * @param array $params request parameters + * + * @throws MissingArgumentException + * + * @return array + */ + public function edit($username, $repository, $id, array $params) + { + if (!isset($params['name'])) { + throw new MissingArgumentException('name'); + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/assets/'.rawurlencode($id), $params); + } + + /** + * Delete an asset in selected repository's release + * DELETE /repos/:owner/:repo/releases/assets/:id. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the asset + * + * @return array + */ + public function remove($username, $repository, $id) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/assets/'.rawurlencode($id)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Collaborators.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Collaborators.php new file mode 100644 index 000000000..89a950685 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Collaborators.php @@ -0,0 +1,32 @@ + + */ +class Collaborators extends AbstractApi +{ + public function all($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/collaborators'); + } + + public function check($username, $repository, $collaborator) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/collaborators/'.rawurlencode($collaborator)); + } + + public function add($username, $repository, $collaborator) + { + return $this->put('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/collaborators/'.rawurlencode($collaborator)); + } + + public function remove($username, $repository, $collaborator) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/collaborators/'.rawurlencode($collaborator)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Comments.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Comments.php new file mode 100644 index 000000000..cce0e5059 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Comments.php @@ -0,0 +1,72 @@ + + */ +class Comments extends AbstractApi +{ + public function configure($bodyType = null) + { + switch ($bodyType) { + case 'raw': + $header = sprintf('Accept: application/vnd.github.%s.raw+json', $this->client->getOption('api_version')); + break; + + case 'text': + $header = sprintf('Accept: application/vnd.github.%s.text+json', $this->client->getOption('api_version')); + break; + + case 'html': + $header = sprintf('Accept: application/vnd.github.%s.html+json', $this->client->getOption('api_version')); + break; + + default: + $header = sprintf('Accept: application/vnd.github.%s.full+json', $this->client->getOption('api_version')); + } + + $this->client->setHeaders(array($header)); + } + + public function all($username, $repository, $sha = null) + { + if (null === $sha) { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/comments'); + } + + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha).'/comments'); + } + + public function show($username, $repository, $comment) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/comments/'.rawurlencode($comment)); + } + + public function create($username, $repository, $sha, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha).'/comments', $params); + } + + public function update($username, $repository, $comment, array $params) + { + if (!isset($params['body'])) { + throw new MissingArgumentException('body'); + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/comments/'.rawurlencode($comment), $params); + } + + public function remove($username, $repository, $comment) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/comments/'.rawurlencode($comment)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Commits.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Commits.php new file mode 100644 index 000000000..909849868 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Commits.php @@ -0,0 +1,31 @@ + + */ +class Commits extends AbstractApi +{ + public function all($username, $repository, array $params) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits', $params); + } + + public function compare($username, $repository, $base, $head, $mediaType = null) + { + $headers = array(); + if (null !== $mediaType) { + $headers['Accept'] = $mediaType; + } + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/compare/'.rawurlencode($base).'...'.rawurlencode($head), array(), $headers); + } + + public function show($username, $repository, $sha) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Contents.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Contents.php new file mode 100644 index 000000000..c98898af6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Contents.php @@ -0,0 +1,274 @@ + + */ +class Contents extends AbstractApi +{ + /** + * Get content of README file in a repository. + * + * @link http://developer.github.com/v3/repos/contents/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param null|string $reference reference to a branch or commit + * + * @return array information for README file + */ + public function readme($username, $repository, $reference = null) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/readme', array( + 'ref' => $reference + )); + } + + /** + * Get contents of any file or directory in a repository. + * + * @link http://developer.github.com/v3/repos/contents/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param null|string $path path to file or directory + * @param null|string $reference reference to a branch or commit + * + * @return array information for file | information for each item in directory + */ + public function show($username, $repository, $path = null, $reference = null) + { + $url = 'repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents'; + if (null !== $path) { + $url .= '/'.rawurlencode($path); + } + + return $this->get($url, array( + 'ref' => $reference + )); + } + + /** + * Creates a new file in a repository. + * + * @link http://developer.github.com/v3/repos/contents/#create-a-file + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path to file + * @param string $content contents of the new file + * @param string $message the commit message + * @param null|string $branch name of a branch + * @param null|array $committer information about the committer + * + * @throws MissingArgumentException + * + * @return array information about the new file + */ + public function create($username, $repository, $path, $content, $message, $branch = null, array $committer = null) + { + $url = 'repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents/'.rawurlencode($path); + + $parameters = array( + 'content' => base64_encode($content), + 'message' => $message, + ); + + if (null !== $branch) { + $parameters['branch'] = $branch; + } + + if (null !== $committer) { + if (!isset($committer['name'], $committer['email'])) { + throw new MissingArgumentException(array('name', 'email')); + } + $parameters['committer'] = $committer; + } + + return $this->put($url, $parameters); + } + + /** + * Checks that a given path exists in a repository. + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path of file to check + * @param null|string $reference reference to a branch or commit + * + * @return bool + */ + public function exists($username, $repository, $path, $reference = null) + { + $url = 'repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents'; + + if (null !== $path) { + $url .= '/'.rawurlencode($path); + } + + try { + $response = $this->head($url, array( + 'ref' => $reference + )); + + if ($response->getStatusCode() != 200) { + return false; + } + } catch (TwoFactorAuthenticationRequiredException $ex) { + throw $ex; + } catch (\Exception $ex) { + return false; + } + + return true; + } + + /** + * Updates the contents of a file in a repository. + * + * @link http://developer.github.com/v3/repos/contents/#update-a-file + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path to file + * @param string $content contents of the new file + * @param string $message the commit message + * @param string $sha blob SHA of the file being replaced + * @param null|string $branch name of a branch + * @param null|array $committer information about the committer + * + * @throws MissingArgumentException + * + * @return array information about the updated file + */ + public function update($username, $repository, $path, $content, $message, $sha, $branch = null, array $committer = null) + { + $url = 'repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents/'.rawurlencode($path); + + $parameters = array( + 'content' => base64_encode($content), + 'message' => $message, + 'sha' => $sha, + ); + + if (null !== $branch) { + $parameters['branch'] = $branch; + } + + if (null !== $committer) { + if (!isset($committer['name'], $committer['email'])) { + throw new MissingArgumentException(array('name', 'email')); + } + $parameters['committer'] = $committer; + } + + return $this->put($url, $parameters); + } + + + /** + * Deletes a file from a repository. + * + * @link http://developer.github.com/v3/repos/contents/#delete-a-file + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path to file + * @param string $message the commit message + * @param string $sha blob SHA of the file being deleted + * @param null|string $branch name of a branch + * @param null|array $committer information about the committer + * + * @throws MissingArgumentException + * + * @return array information about the updated file + */ + public function rm($username, $repository, $path, $message, $sha, $branch = null, array $committer = null) + { + $url = 'repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/contents/'.rawurlencode($path); + + $parameters = array( + 'message' => $message, + 'sha' => $sha, + ); + + if (null !== $branch) { + $parameters['branch'] = $branch; + } + + if (null !== $committer) { + if (!isset($committer['name'], $committer['email'])) { + throw new MissingArgumentException(array('name', 'email')); + } + $parameters['committer'] = $committer; + } + + return $this->delete($url, $parameters); + } + + /** + * Get content of archives in a repository. + * + * @link http://developer.github.com/v3/repos/contents/ + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $format format of archive: tarball or zipball + * @param null|string $reference reference to a branch or commit + * + * @return array information for archives + */ + public function archive($username, $repository, $format, $reference = null) + { + if (!in_array($format, array('tarball', 'zipball'))) { + $format = 'tarball'; + } + + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/'.rawurlencode($format). + ((null !== $reference) ? ('/'.rawurlencode($reference)) : '')); + } + + /** + * Get the contents of a file in a repository. + * + * @param string $username the user who owns the repository + * @param string $repository the name of the repository + * @param string $path path to file + * @param null|string $reference reference to a branch or commit + * + * @throws InvalidArgumentException If $path is not a file or if its encoding is different from base64 + * @throws ErrorException If $path doesn't include a 'content' index + * + * @return null|string content of file, or null in case of base64_decode failure + */ + public function download($username, $repository, $path, $reference = null) + { + $file = $this->show($username, $repository, $path, $reference); + + if (!isset($file['type']) || 'file' !== $file['type']) { + throw new InvalidArgumentException(sprintf('Path "%s" is not a file.', $path)); + } + + if (!isset($file['content'])) { + throw new ErrorException(sprintf('Unable to access "content" for file "%s" (possible keys: "%s").', $path, implode(', ', array_keys($file)))); + } + + if (!isset($file['encoding'])) { + throw new InvalidArgumentException(sprintf('Can\'t decode content of file "%s", as no encoding is defined.', $path)); + } + + if ('base64' !== $file['encoding']) { + throw new InvalidArgumentException(sprintf('Encoding "%s" of file "%s" is not supported.', $file['encoding'], $path)); + } + + return base64_decode($file['content']) ?: null; + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/DeployKeys.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/DeployKeys.php new file mode 100644 index 000000000..390835798 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/DeployKeys.php @@ -0,0 +1,46 @@ + + */ +class DeployKeys extends AbstractApi +{ + public function all($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys'); + } + + public function show($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys/'.rawurlencode($id)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['title'], $params['key'])) { + throw new MissingArgumentException(array('title', 'key')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys', $params); + } + + public function update($username, $repository, $id, array $params) + { + if (!isset($params['title'], $params['key'])) { + throw new MissingArgumentException(array('title', 'key')); + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys/'.rawurlencode($id), $params); + } + + public function remove($username, $repository, $id) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/keys/'.rawurlencode($id)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Downloads.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Downloads.php new file mode 100644 index 000000000..63177e499 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Downloads.php @@ -0,0 +1,59 @@ + + */ +class Downloads extends AbstractApi +{ + /** + * List downloads in selected repository. + * + * @link http://developer.github.com/v3/repos/downloads/#list-downloads-for-a-repository + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function all($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/downloads'); + } + + /** + * Get a download in selected repository. + * + * @link http://developer.github.com/v3/repos/downloads/#get-a-single-download + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the download file + * + * @return array + */ + public function show($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/downloads/'.rawurlencode($id)); + } + + /** + * Delete a download in selected repository. + * + * @link http://developer.github.com/v3/repos/downloads/#delete-a-download + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the download file + * + * @return array + */ + public function remove($username, $repository, $id) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/downloads/'.rawurlencode($id)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Forks.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Forks.php new file mode 100644 index 000000000..c16596185 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Forks.php @@ -0,0 +1,26 @@ + + */ +class Forks extends AbstractApi +{ + public function all($username, $repository, array $params = array()) + { + if (isset($params['sort']) && !in_array($params['sort'], array('newest', 'oldest', 'watchers'))) { + $params['sort'] = 'newest'; + } + + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/forks', array_merge(array('page' => 1), $params)); + } + + public function create($username, $repository, array $params = array()) + { + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/forks', $params); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Hooks.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Hooks.php new file mode 100644 index 000000000..f4866ad0a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Hooks.php @@ -0,0 +1,51 @@ + + */ +class Hooks extends AbstractApi +{ + public function all($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks'); + } + + public function show($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks/'.rawurlencode($id)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['name'], $params['config'])) { + throw new MissingArgumentException(array('name', 'config')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks', $params); + } + + public function update($username, $repository, $id, array $params) + { + if (!isset($params['name'], $params['config'])) { + throw new MissingArgumentException(array('name', 'config')); + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks/'.rawurlencode($id), $params); + } + + public function test($username, $repository, $id) + { + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks/'.rawurlencode($id).'/test'); + } + + public function remove($username, $repository, $id) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/hooks/'.rawurlencode($id)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Labels.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Labels.php new file mode 100644 index 000000000..e35b4ded7 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Labels.php @@ -0,0 +1,46 @@ + + */ +class Labels extends AbstractApi +{ + public function all($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels'); + } + + public function show($username, $repository, $label) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label)); + } + + public function create($username, $repository, array $params) + { + if (!isset($params['name'], $params['color'])) { + throw new MissingArgumentException(array('name', 'color')); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels', $params); + } + + public function update($username, $repository, $label, array $params) + { + if (!isset($params['name'], $params['color'])) { + throw new MissingArgumentException(array('name', 'color')); + } + + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label), $params); + } + + public function remove($username, $repository, $label) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/labels/'.rawurlencode($label)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Releases.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Releases.php new file mode 100644 index 000000000..652c1b3a6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Releases.php @@ -0,0 +1,98 @@ + + * @author Evgeniy Guseletov + */ +class Releases extends AbstractApi +{ + /** + * List releases in selected repository. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * + * @return array + */ + public function all($username, $repository) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases'); + } + + /** + * Get a release in selected repository. + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the release + * + * @return array + */ + public function show($username, $repository, $id) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id)); + } + + /** + * Create new release in selected repository. + * + * @param string $username + * @param string $repository + * @param array $params + * + * @throws MissingArgumentException + * + * @return array + */ + public function create($username, $repository, array $params) + { + if (!isset($params['tag_name'])) { + throw new MissingArgumentException('tag_name'); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases', $params); + } + + /** + * Edit release in selected repository. + * + * @param string $username + * @param string $repository + * @param int $id + * @param array $params + * + * @return array + */ + public function edit($username, $repository, $id, array $params) + { + return $this->patch('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id), $params); + } + + /** + * Delete a release in selected repository (Not thoroughly tested!). + * + * @param string $username the user who owns the repo + * @param string $repository the name of the repo + * @param int $id the id of the release + * + * @return array + */ + public function remove($username, $repository, $id) + { + return $this->delete('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id)); + } + + /** + * @return Assets + */ + public function assets() + { + return new Assets($this->client); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Statuses.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Statuses.php new file mode 100644 index 000000000..caab1e2b5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Repository/Statuses.php @@ -0,0 +1,62 @@ + + */ +class Statuses extends AbstractApi +{ + /** + * @link http://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-sha + * + * @param string $username + * @param string $repository + * @param string $sha + * + * @return array + */ + public function show($username, $repository, $sha) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha).'/statuses'); + } + + /** + * @link https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref + * + * @param string $username + * @param string $repository + * @param string $sha + * + * @return array + */ + public function combined($username, $repository, $sha) + { + return $this->get('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/commits/'.rawurlencode($sha).'/status'); + } + + /** + * @link http://developer.github.com/v3/repos/statuses/#create-a-status + * + * @param string $username + * @param string $repository + * @param string $sha + * @param array $params + * + * @throws MissingArgumentException + * + * @return array + */ + public function create($username, $repository, $sha, array $params = array()) + { + if (!isset($params['state'])) { + throw new MissingArgumentException('state'); + } + + return $this->post('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/statuses/'.rawurlencode($sha), $params); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Search.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Search.php new file mode 100644 index 000000000..bd9f79110 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/Search.php @@ -0,0 +1,83 @@ + + */ +class Search extends AbstractApi +{ + + /** + * Search repositories by filter (q). + * + * @link https://developer.github.com/v3/search/#search-repositories + * + * @param string $q the filter + * @param string $sort the sort field + * @param string $order asc/desc + * + * @return array list of repositories found + */ + public function repositories($q, $sort = 'updated', $order = 'desc') + { + return $this->get('search/repositories', array('q' => $q, 'sort' => $sort, 'order' => $order)); + } + + /** + * Search issues by filter (q). + * + * @link https://developer.github.com/v3/search/#search-issues + * + * @param string $q the filter + * @param string $sort the sort field + * @param string $order asc/desc + * + * @return array list of issues found + */ + public function issues($q, $sort = 'updated', $order = 'desc') + { + return $this->get('search/issues', array('q' => $q, 'sort' => $sort, 'order' => $order)); + } + + /** + * Search code by filter (q). + * + * @link https://developer.github.com/v3/search/#search-code + * + * @param string $q the filter + * @param string $sort the sort field + * @param string $order asc/desc + * + * @return array list of code found + */ + public function code($q, $sort = 'updated', $order = 'desc') + { + return $this->get('search/code', array('q' => $q, 'sort' => $sort, 'order' => $order)); + } + + /** + * Search users by filter (q). + * + * @link https://developer.github.com/v3/search/#search-users + * + * @param string $q the filter + * @param string $sort the sort field + * @param string $order asc/desc + * + * @return array list of users found + */ + public function users($q, $sort = 'updated', $order = 'desc') + { + return $this->get('search/users', array('q' => $q, 'sort' => $sort, 'order' => $order)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/User.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/User.php new file mode 100644 index 000000000..0cc707b0b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Api/User.php @@ -0,0 +1,208 @@ + + * @author Thibault Duplessis + */ +class User extends AbstractApi +{ + /** + * Search users by username. + * + * @link http://developer.github.com/v3/search/#search-users + * + * @param string $keyword the keyword to search + * + * @return array list of users found + */ + public function find($keyword) + { + return $this->get('legacy/user/search/'.rawurlencode($keyword)); + } + + /** + * Request all users. + * + * @link https://developer.github.com/v3/users/#get-all-users + * + * @param int|null $id ID of the last user that you've seen + * + * @return array list of users found + */ + public function all($id = null) + { + if (!is_int($id)) { + return $this->get('users'); + } + return $this->get('users?since=' . rawurldecode($id)); + } + + /** + * Get extended information about a user by its username. + * + * @link http://developer.github.com/v3/users/ + * + * @param string $username the username to show + * + * @return array informations about the user + */ + public function show($username) + { + return $this->get('users/'.rawurlencode($username)); + } + + /** + * Get extended information about a user by its username. + * + * @link https://developer.github.com/v3/orgs/ + * + * @param string $username the username to show + * + * @return array information about organizations that user belongs to + */ + public function organizations($username) + { + return $this->get('users/'.rawurlencode($username).'/orgs'); + } + + /** + * Request the users that a specific user is following. + * + * @link http://developer.github.com/v3/users/followers/ + * + * @param string $username the username + * + * @return array list of followed users + */ + public function following($username) + { + return $this->get('users/'.rawurlencode($username).'/following'); + } + + /** + * Request the users following a specific user. + * + * @link http://developer.github.com/v3/users/followers/ + * + * @param string $username the username + * + * @return array list of following users + */ + public function followers($username) + { + return $this->get('users/'.rawurlencode($username).'/followers'); + } + + /** + * Request the repository that a specific user is watching. + * + * @deprecated see subscriptions method + * + * @param string $username the username + * + * @return array list of watched repositories + */ + public function watched($username) + { + return $this->get('users/'.rawurlencode($username).'/watched'); + } + + /** + * Request starred repositories that a specific user has starred. + * + * @link http://developer.github.com/v3/activity/starring/ + * + * @param string $username the username + * @param int $page the page number of the paginated result set + * + * @return array list of starred repositories + */ + public function starred($username, $page = 1) + { + return $this->get('users/'.rawurlencode($username).'/starred', array( + 'page' => $page + )); + } + + /** + * Request the repository that a specific user is watching. + * + * @link http://developer.github.com/v3/activity/watching/ + * + * @param string $username the username + * + * @return array list of watched repositories + */ + public function subscriptions($username) + { + return $this->get('users/'.rawurlencode($username).'/subscriptions'); + } + + /** + * Get the repositories of a user. + * + * @link http://developer.github.com/v3/repos/ + * + * @param string $username the username + * @param string $type role in the repository + * @param string $sort sort by + * @param string $direction direction of sort, asc or desc + * + * @return array list of the user repositories + */ + public function repositories($username, $type = 'owner', $sort = 'full_name', $direction = 'asc') + { + return $this->get('users/'.rawurlencode($username).'/repos', array( + 'type' => $type, + 'sort' => $sort, + 'direction' => $direction + )); + } + + /** + * Get the public gists for a user. + * + * @link http://developer.github.com/v3/gists/ + * + * @param string $username the username + * + * @return array list of the user gists + */ + public function gists($username) + { + return $this->get('users/'.rawurlencode($username).'/gists'); + } + + /** + * Get the public keys for a user. + * + * @link http://developer.github.com/v3/users/keys/#list-public-keys-for-a-user + * + * @param string $username the username + * + * @return array list of the user public keys + */ + public function keys($username) + { + return $this->get('users/'.rawurlencode($username).'/keys'); + } + + /** + * List events performed by a user. + * + * @link http://developer.github.com/v3/activity/events/#list-public-events-performed-by-a-user + * + * @param string $username + * + * @return array + */ + public function publicEvents($username) + { + return $this->get('users/'.rawurlencode($username) . '/events/public'); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Client.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Client.php new file mode 100644 index 000000000..8182bf18b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Client.php @@ -0,0 +1,343 @@ + + * + * Website: http://github.com/KnpLabs/php-github-api + */ +class Client +{ + /** + * Constant for authentication method. Indicates the default, but deprecated + * login with username and token in URL. + */ + const AUTH_URL_TOKEN = 'url_token'; + + /** + * Constant for authentication method. Not indicates the new login, but allows + * usage of unauthenticated rate limited requests for given client_id + client_secret. + */ + const AUTH_URL_CLIENT_ID = 'url_client_id'; + + /** + * Constant for authentication method. Indicates the new favored login method + * with username and password via HTTP Authentication. + */ + const AUTH_HTTP_PASSWORD = 'http_password'; + + /** + * Constant for authentication method. Indicates the new login method with + * with username and token via HTTP Authentication. + */ + const AUTH_HTTP_TOKEN = 'http_token'; + + /** + * @var array + */ + private $options = array( + 'base_url' => 'https://api.github.com/', + + 'user_agent' => 'php-github-api (http://github.com/KnpLabs/php-github-api)', + 'timeout' => 10, + + 'api_limit' => 5000, + 'api_version' => 'beta', + + 'cache_dir' => null + ); + + /** + * The Buzz instance used to communicate with GitHub. + * + * @var HttpClient + */ + private $httpClient; + + /** + * Instantiate a new GitHub client. + * + * @param null|HttpClientInterface $httpClient Github http client + */ + public function __construct(HttpClientInterface $httpClient = null) + { + $this->httpClient = $httpClient; + } + + /** + * @param string $name + * + * @throws InvalidArgumentException + * + * @return ApiInterface + */ + public function api($name) + { + switch ($name) { + case 'me': + case 'current_user': + case 'currentUser': + $api = new Api\CurrentUser($this); + break; + + case 'deployment': + case 'deployments': + $api = new Api\Deployment($this); + break; + + case 'ent': + case 'enterprise': + $api = new Api\Enterprise($this); + break; + + case 'git': + case 'git_data': + case 'gitData': + $api = new Api\GitData($this); + break; + + case 'gist': + case 'gists': + $api = new Api\Gists($this); + break; + + case 'issue': + case 'issues': + $api = new Api\Issue($this); + break; + + case 'markdown': + $api = new Api\Markdown($this); + break; + + case 'notification': + case 'notifications': + $api = new Api\Notification($this); + break; + + case 'organization': + case 'organizations': + $api = new Api\Organization($this); + break; + + case 'pr': + case 'pullRequest': + case 'pull_request': + case 'pullRequests': + case 'pull_requests': + $api = new Api\PullRequest($this); + break; + + case 'repo': + case 'repos': + case 'repository': + case 'repositories': + $api = new Api\Repo($this); + break; + + case 'search': + $api = new Api\Search($this); + break; + + case 'team': + case 'teams': + $api = new Api\Organization\Teams($this); + break; + + case 'user': + case 'users': + $api = new Api\User($this); + break; + + case 'authorization': + case 'authorizations': + $api = new Api\Authorizations($this); + break; + + case 'meta': + $api = new Api\Meta($this); + break; + + default: + throw new InvalidArgumentException(sprintf('Undefined api instance called: "%s"', $name)); + } + + return $api; + } + + /** + * Authenticate a user for all next requests. + * + * @param string $tokenOrLogin GitHub private token/username/client ID + * @param null|string $password GitHub password/secret (optionally can contain $authMethod) + * @param null|string $authMethod One of the AUTH_* class constants + * + * @throws InvalidArgumentException If no authentication method was given + */ + public function authenticate($tokenOrLogin, $password = null, $authMethod = null) + { + if (null === $password && null === $authMethod) { + throw new InvalidArgumentException('You need to specify authentication method!'); + } + + if (null === $authMethod && in_array($password, array(self::AUTH_URL_TOKEN, self::AUTH_URL_CLIENT_ID, self::AUTH_HTTP_PASSWORD, self::AUTH_HTTP_TOKEN))) { + $authMethod = $password; + $password = null; + } + + if (null === $authMethod) { + $authMethod = self::AUTH_HTTP_PASSWORD; + } + + $this->getHttpClient()->authenticate($tokenOrLogin, $password, $authMethod); + } + + /** + * Sets the URL of your GitHub Enterprise instance. + * + * @param string $enterpriseUrl URL of the API in the form of http(s)://hostname + */ + public function setEnterpriseUrl($enterpriseUrl) + { + $baseUrl = (substr($enterpriseUrl, -1) == '/') ? substr($enterpriseUrl, 0, -1) : $enterpriseUrl; + $this->getHttpClient()->client->setBaseUrl($baseUrl . '/api/v3'); + } + + /** + * @return HttpClient + */ + public function getHttpClient() + { + if (null === $this->httpClient) { + $this->httpClient = new HttpClient($this->options); + } + + return $this->httpClient; + } + + /** + * @param HttpClientInterface $httpClient + */ + public function setHttpClient(HttpClientInterface $httpClient) + { + $this->httpClient = $httpClient; + } + + /** + * Clears used headers. + */ + public function clearHeaders() + { + $this->getHttpClient()->clearHeaders(); + } + + /** + * @param array $headers + */ + public function setHeaders(array $headers) + { + $this->getHttpClient()->setHeaders($headers); + } + + /** + * @param string $name + * + * @throws InvalidArgumentException + * + * @return mixed + */ + public function getOption($name) + { + if (!array_key_exists($name, $this->options)) { + throw new InvalidArgumentException(sprintf('Undefined option called: "%s"', $name)); + } + + return $this->options[$name]; + } + + /** + * @param string $name + * @param mixed $value + * + * @throws InvalidArgumentException + * @throws InvalidArgumentException + */ + public function setOption($name, $value) + { + if (!array_key_exists($name, $this->options)) { + throw new InvalidArgumentException(sprintf('Undefined option called: "%s"', $name)); + } + $supportedApiVersions = $this->getSupportedApiVersions(); + if ('api_version' == $name && !in_array($value, $supportedApiVersions)) { + throw new InvalidArgumentException(sprintf('Invalid API version ("%s"), valid are: %s', $name, implode(', ', $supportedApiVersions))); + } + + $this->options[$name] = $value; + } + + /** + * Returns an array of valid API versions supported by this client. + * + * @return array + */ + public function getSupportedApiVersions() + { + return array('v3', 'beta'); + } + + /** + * @param string $name + * + * @throws InvalidArgumentException + * + * @return ApiInterface + */ + public function __call($name, $args) + { + try { + return $this->api($name); + } catch (InvalidArgumentException $e) { + throw new BadMethodCallException(sprintf('Undefined method called: "%s"', $name)); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ApiLimitExceedException.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ApiLimitExceedException.php new file mode 100644 index 000000000..539243a68 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ApiLimitExceedException.php @@ -0,0 +1,16 @@ + + */ +class ApiLimitExceedException extends RuntimeException +{ + public function __construct($limit = 5000, $code = 0, $previous = null) + { + parent::__construct('You have reached GitHub hour limit! Actual limit is: '. $limit, $code, $previous); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/BadMethodCallException.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/BadMethodCallException.php new file mode 100644 index 000000000..83e05437b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/BadMethodCallException.php @@ -0,0 +1,12 @@ + + */ +class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ErrorException.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ErrorException.php new file mode 100644 index 000000000..61f61f36f --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ErrorException.php @@ -0,0 +1,12 @@ + + */ +class ErrorException extends \ErrorException implements ExceptionInterface +{ +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ExceptionInterface.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ExceptionInterface.php new file mode 100644 index 000000000..cba560542 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ExceptionInterface.php @@ -0,0 +1,7 @@ + + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/MissingArgumentException.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/MissingArgumentException.php new file mode 100644 index 000000000..4a7e372e9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/MissingArgumentException.php @@ -0,0 +1,20 @@ + + */ +class MissingArgumentException extends ErrorException +{ + public function __construct($required, $code = 0, $previous = null) + { + if (is_string($required)) { + $required = array($required); + } + + parent::__construct(sprintf('One or more of required ("%s") parameters is missing!', implode('", "', $required)), $code, $previous); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/RuntimeException.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/RuntimeException.php new file mode 100644 index 000000000..676cb9573 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/RuntimeException.php @@ -0,0 +1,12 @@ + + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/TwoFactorAuthenticationRequiredException.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/TwoFactorAuthenticationRequiredException.php new file mode 100644 index 000000000..6f93fe40b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/TwoFactorAuthenticationRequiredException.php @@ -0,0 +1,19 @@ +type = $type; + parent::__construct('Two factor authentication is enabled on this account', $code, $previous); + } + + public function getType() + { + return $this->type; + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ValidationFailedException.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ValidationFailedException.php new file mode 100644 index 000000000..e43bc43e0 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/Exception/ValidationFailedException.php @@ -0,0 +1,12 @@ + + */ +class ValidationFailedException extends ErrorException +{ +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Cache/CacheInterface.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Cache/CacheInterface.php new file mode 100644 index 000000000..77bb4f89e --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Cache/CacheInterface.php @@ -0,0 +1,51 @@ + + */ +interface CacheInterface +{ + /** + * @param string $id The id of the cached resource + * + * @return bool if present + */ + public function has($id); + + /** + * @param string $id The id of the cached resource + * + * @return null|int The modified since timestamp + */ + public function getModifiedSince($id); + + /** + * @param string $id The id of the cached resource + * + * @return null|string The ETag value + */ + public function getETag($id); + + /** + * @param string $id The id of the cached resource + * + * @throws \InvalidArgumentException If cache data don't exists + * + * @return Response The cached response object + */ + public function get($id); + + /** + * @param string $id The id of the cached resource + * @param Response $response The response to cache + * + * @throws \InvalidArgumentException If cache data cannot be saved + */ + public function set($id, Response $response); +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Cache/FilesystemCache.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Cache/FilesystemCache.php new file mode 100644 index 000000000..ff332b570 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Cache/FilesystemCache.php @@ -0,0 +1,85 @@ +path = $path; + } + + /** + * {@inheritdoc} + */ + public function get($id) + { + if (false !== $content = @file_get_contents($this->getPath($id))) { + return unserialize($content); + } + + throw new \InvalidArgumentException(sprintf('File "%s" not found', $this->getPath($id))); + } + + /** + * {@inheritdoc} + */ + public function set($id, Response $response) + { + if (!is_dir($this->path)) { + @mkdir($this->path, 0777, true); + } + + if (false === @file_put_contents($this->getPath($id), serialize($response))) { + throw new \InvalidArgumentException(sprintf('Cannot put content in file "%s"', $this->getPath($id))); + } + if (false === @file_put_contents($this->getPath($id).'.etag', $response->getHeader('ETag'))) { + throw new \InvalidArgumentException(sprintf('Cannot put content in file "%s"', $this->getPath($id).'.etag')); + } + } + + /** + * {@inheritdoc} + */ + public function has($id) + { + return file_exists($this->getPath($id)); + } + + /** + * {@inheritdoc} + */ + public function getModifiedSince($id) + { + if ($this->has($id)) { + return filemtime($this->getPath($id)); + } + } + + public function getETag($id) + { + if (file_exists($this->getPath($id).'.etag')) { + return file_get_contents($this->getPath($id).'.etag'); + } + } + + /** + * @param $id string + * + * @return string + */ + protected function getPath($id) + { + return sprintf('%s%s%s', rtrim($this->path, DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR, md5($id)); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Cache/GaufretteCache.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Cache/GaufretteCache.php new file mode 100644 index 000000000..b72a104c9 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Cache/GaufretteCache.php @@ -0,0 +1,71 @@ + + */ +class GaufretteCache implements CacheInterface +{ + /** + * @var Filesystem + */ + protected $filesystem; + + /** + * @param Filesystem $filesystem + */ + public function __construct(Filesystem $filesystem) + { + $this->filesystem = $filesystem; + } + + /** + * {@inheritdoc} + */ + public function get($id) + { + $content = $this->filesystem->read($id); + + return unserialize($content); + } + + /** + * {@inheritdoc} + */ + public function set($id, Response $response) + { + $this->filesystem->write($id, serialize($response), true); + $this->filesystem->write($id.'.etag', $response->getHeader('ETag'), true); + } + + /** + * {@inheritdoc} + */ + public function has($id) + { + $this->filesystem->has($id); + } + + /** + * {@inheritdoc} + */ + public function getModifiedSince($id) + { + if ($this->filesystem->has($id)) { + return $this->filesystem->mtime($id); + } + } + + public function getETag($id) + { + if ($this->filesystem->has($id)) { + return $this->filesystem->read($id.'.etag'); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/CachedHttpClient.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/CachedHttpClient.php new file mode 100644 index 000000000..f0e1368e6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/CachedHttpClient.php @@ -0,0 +1,108 @@ + + */ +class CachedHttpClient extends HttpClient +{ + /** + * @var CacheInterface + */ + protected $cache; + + /** + * Contains the lastResponse fetched from cache. + * + * @var Guzzle\Http\Message\Response + */ + private $lastCachedResponse; + + /** + * @return CacheInterface + */ + public function getCache() + { + if (null === $this->cache) { + $this->cache = new FilesystemCache($this->options['cache_dir'] ?: sys_get_temp_dir().DIRECTORY_SEPARATOR.'php-github-api-cache'); + } + + return $this->cache; + } + + /** + * @param $cache CacheInterface + */ + public function setCache(CacheInterface $cache) + { + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function request($path, $body = null, $httpMethod = 'GET', array $headers = array(), array $options = array()) + { + $response = parent::request($path, $body, $httpMethod, $headers, $options); + + if (304 == $response->getStatusCode()) { + $cacheResponse = $this->getCache()->get($path); + $this->lastCachedResponse = $cacheResponse; + + return $cacheResponse; + } + + $this->getCache()->set($path, $response); + + return $response; + } + + /** + * Create requests with If-Modified-Since headers. + * + * {@inheritdoc} + */ + protected function createRequest($httpMethod, $path, $body = null, array $headers = array(), array $options = array()) + { + $request = parent::createRequest($httpMethod, $path, $body, $headers, $options); + + if ($modifiedAt = $this->getCache()->getModifiedSince($path)) { + $modifiedAt = new \DateTime('@'.$modifiedAt); + $modifiedAt->setTimezone(new \DateTimeZone('GMT')); + + $request->addHeader( + 'If-Modified-Since', + sprintf('%s GMT', $modifiedAt->format('l, d-M-y H:i:s')) + ); + } + if ($etag = $this->getCache()->getETag($path)) { + $request->addHeader( + 'If-None-Match', + $etag + ); + } + + return $request; + } + + /** + * @return Guzzle\Http\Message\Response + */ + public function getLastResponse($force = false) + { + $lastResponse = parent::getLastResponse(); + if (304 != $lastResponse->getStatusCode()) { + $force = true; + } + + return ($force) ? $lastResponse : $this->lastCachedResponse; + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/HttpClient.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/HttpClient.php new file mode 100644 index 000000000..84b2b09bc --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/HttpClient.php @@ -0,0 +1,190 @@ + + */ +class HttpClient implements HttpClientInterface +{ + protected $options = array( + 'base_url' => 'https://api.github.com/', + + 'user_agent' => 'php-github-api (http://github.com/KnpLabs/php-github-api)', + 'timeout' => 10, + + 'api_limit' => 5000, + 'api_version' => 'v3', + + 'cache_dir' => null + ); + + protected $headers = array(); + + private $lastResponse; + private $lastRequest; + + /** + * @param array $options + * @param ClientInterface $client + */ + public function __construct(array $options = array(), ClientInterface $client = null) + { + $this->options = array_merge($this->options, $options); + $client = $client ?: new GuzzleClient($this->options['base_url'], $this->options); + $this->client = $client; + + $this->addListener('request.error', array(new ErrorListener($this->options), 'onRequestError')); + $this->clearHeaders(); + } + + /** + * {@inheritDoc} + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * {@inheritDoc} + */ + public function setHeaders(array $headers) + { + $this->headers = array_merge($this->headers, $headers); + } + + /** + * Clears used headers. + */ + public function clearHeaders() + { + $this->headers = array( + 'Accept' => sprintf('application/vnd.github.%s+json', $this->options['api_version']), + 'User-Agent' => sprintf('%s', $this->options['user_agent']), + ); + } + + public function addListener($eventName, $listener) + { + $this->client->getEventDispatcher()->addListener($eventName, $listener); + } + + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->client->addSubscriber($subscriber); + } + + /** + * {@inheritDoc} + */ + public function get($path, array $parameters = array(), array $headers = array()) + { + return $this->request($path, null, 'GET', $headers, array('query' => $parameters)); + } + + /** + * {@inheritDoc} + */ + public function post($path, $body = null, array $headers = array()) + { + return $this->request($path, $body, 'POST', $headers); + } + + /** + * {@inheritDoc} + */ + public function patch($path, $body = null, array $headers = array()) + { + return $this->request($path, $body, 'PATCH', $headers); + } + + /** + * {@inheritDoc} + */ + public function delete($path, $body = null, array $headers = array()) + { + return $this->request($path, $body, 'DELETE', $headers); + } + + /** + * {@inheritDoc} + */ + public function put($path, $body, array $headers = array()) + { + return $this->request($path, $body, 'PUT', $headers); + } + + /** + * {@inheritDoc} + */ + public function request($path, $body = null, $httpMethod = 'GET', array $headers = array(), array $options = array()) + { + $request = $this->createRequest($httpMethod, $path, $body, $headers, $options); + + try { + $response = $this->client->send($request); + } catch (\LogicException $e) { + throw new ErrorException($e->getMessage(), $e->getCode(), $e); + } catch (TwoFactorAuthenticationRequiredException $e) { + throw $e; + } catch (\RuntimeException $e) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + + $this->lastRequest = $request; + $this->lastResponse = $response; + + return $response; + } + + /** + * {@inheritDoc} + */ + public function authenticate($tokenOrLogin, $password = null, $method) + { + $this->addListener('request.before_send', array( + new AuthListener($tokenOrLogin, $password, $method), 'onRequestBeforeSend' + )); + } + + /** + * @return Request + */ + public function getLastRequest() + { + return $this->lastRequest; + } + + /** + * @return Response + */ + public function getLastResponse() + { + return $this->lastResponse; + } + + protected function createRequest($httpMethod, $path, $body = null, array $headers = array(), array $options = array()) + { + return $this->client->createRequest( + $httpMethod, + $path, + array_merge($this->headers, $headers), + $body, + $options + ); + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/HttpClientInterface.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/HttpClientInterface.php new file mode 100644 index 000000000..5ed0a9e34 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/HttpClientInterface.php @@ -0,0 +1,112 @@ + + */ +interface HttpClientInterface +{ + /** + * Send a GET request. + * + * @param string $path Request path + * @param array $parameters GET Parameters + * @param array $headers Reconfigure the request headers for this call only + * + * @return Response + */ + public function get($path, array $parameters = array(), array $headers = array()); + + /** + * Send a POST request. + * + * @param string $path Request path + * @param mixed $body Request body + * @param array $headers Reconfigure the request headers for this call only + * + * @return Response + */ + public function post($path, $body = null, array $headers = array()); + + /** + * Send a PATCH request. + * + * @param string $path Request path + * @param mixed $body Request body + * @param array $headers Reconfigure the request headers for this call only + * + * @internal param array $parameters Request body + * + * @return Response + */ + public function patch($path, $body = null, array $headers = array()); + + /** + * Send a PUT request. + * + * @param string $path Request path + * @param mixed $body Request body + * @param array $headers Reconfigure the request headers for this call only + * + * @return Response + */ + public function put($path, $body, array $headers = array()); + + /** + * Send a DELETE request. + * + * @param string $path Request path + * @param mixed $body Request body + * @param array $headers Reconfigure the request headers for this call only + * + * @return Response + */ + public function delete($path, $body = null, array $headers = array()); + + /** + * Send a request to the server, receive a response, + * decode the response and returns an associative array. + * + * @param string $path Request path + * @param mixed $body Request body + * @param string $httpMethod HTTP method to use + * @param array $headers Request headers + * + * @return Response + */ + public function request($path, $body, $httpMethod = 'GET', array $headers = array()); + + /** + * Change an option value. + * + * @param string $name The option name + * @param mixed $value The value + * + * @throws InvalidArgumentException + */ + public function setOption($name, $value); + + /** + * Set HTTP headers. + * + * @param array $headers + */ + public function setHeaders(array $headers); + + /** + * Authenticate a user for all next requests. + * + * @param string $tokenOrLogin GitHub private token/username/client ID + * @param null|string $password GitHub password/secret (optionally can contain $authMethod) + * @param null|string $authMethod One of the AUTH_* class constants + * + * @throws InvalidArgumentException If no authentication method was given + */ + public function authenticate($tokenOrLogin, $password, $authMethod); +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Listener/AuthListener.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Listener/AuthListener.php new file mode 100644 index 000000000..41ad42e46 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Listener/AuthListener.php @@ -0,0 +1,68 @@ +tokenOrLogin = $tokenOrLogin; + $this->password = $password; + $this->method = $method; + } + + public function onRequestBeforeSend(Event $event) + { + // Skip by default + if (null === $this->method) { + return; + } + + switch ($this->method) { + case Client::AUTH_HTTP_PASSWORD: + $event['request']->setHeader( + 'Authorization', + sprintf('Basic %s', base64_encode($this->tokenOrLogin . ':' . $this->password)) + ); + break; + + case Client::AUTH_HTTP_TOKEN: + $event['request']->setHeader('Authorization', sprintf('token %s', $this->tokenOrLogin)); + break; + + case Client::AUTH_URL_CLIENT_ID: + $url = $event['request']->getUrl(); + + $parameters = array( + 'client_id' => $this->tokenOrLogin, + 'client_secret' => $this->password, + ); + + $url .= (false === strpos($url, '?') ? '?' : '&'); + $url .= utf8_encode(http_build_query($parameters, '', '&')); + + $event['request']->setUrl($url); + break; + + case Client::AUTH_URL_TOKEN: + $url = $event['request']->getUrl(); + $url .= (false === strpos($url, '?') ? '?' : '&'); + $url .= utf8_encode(http_build_query(array('access_token' => $this->tokenOrLogin), '', '&')); + + $event['request']->setUrl($url); + break; + + default: + throw new RuntimeException(sprintf('%s not yet implemented', $this->method)); + break; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Listener/ErrorListener.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Listener/ErrorListener.php new file mode 100644 index 000000000..a945c45c6 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Listener/ErrorListener.php @@ -0,0 +1,94 @@ + + */ +class ErrorListener +{ + /** + * @var array + */ + private $options; + + /** + * @param array $options + */ + public function __construct(array $options) + { + $this->options = $options; + } + + /** + * {@inheritDoc} + */ + public function onRequestError(Event $event) + { + /** @var $request \Guzzle\Http\Message\Request */ + $request = $event['request']; + $response = $request->getResponse(); + + if ($response->isClientError() || $response->isServerError()) { + $remaining = (string) $response->getHeader('X-RateLimit-Remaining'); + + if (null != $remaining && 1 > $remaining && 'rate_limit' !== substr($request->getResource(), 1, 10)) { + throw new ApiLimitExceedException($this->options['api_limit']); + } + + if (401 === $response->getStatusCode()) { + if ($response->hasHeader('X-GitHub-OTP') && 0 === strpos((string) $response->getHeader('X-GitHub-OTP'), 'required;')) { + $type = substr((string) $response->getHeader('X-GitHub-OTP'), 9); + + throw new TwoFactorAuthenticationRequiredException($type); + } + } + + $content = ResponseMediator::getContent($response); + if (is_array($content) && isset($content['message'])) { + if (400 == $response->getStatusCode()) { + throw new ErrorException($content['message'], 400); + } elseif (422 == $response->getStatusCode() && isset($content['errors'])) { + $errors = array(); + foreach ($content['errors'] as $error) { + switch ($error['code']) { + case 'missing': + $errors[] = sprintf('The %s %s does not exist, for resource "%s"', $error['field'], $error['value'], $error['resource']); + break; + + case 'missing_field': + $errors[] = sprintf('Field "%s" is missing, for resource "%s"', $error['field'], $error['resource']); + break; + + case 'invalid': + $errors[] = sprintf('Field "%s" is invalid, for resource "%s"', $error['field'], $error['resource']); + break; + + case 'already_exists': + $errors[] = sprintf('Field "%s" already exists, for resource "%s"', $error['field'], $error['resource']); + break; + + default: + $errors[] = $error['message']; + break; + + } + } + + throw new ValidationFailedException('Validation Failed: ' . implode(', ', $errors), 422); + } + } + + throw new RuntimeException(isset($content['message']) ? $content['message'] : $content, $response->getStatusCode()); + }; + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Message/ResponseMediator.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Message/ResponseMediator.php new file mode 100644 index 000000000..72fe7c712 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/HttpClient/Message/ResponseMediator.php @@ -0,0 +1,50 @@ +getBody(true); + $content = json_decode($body, true); + + if (JSON_ERROR_NONE !== json_last_error()) { + return $body; + } + + return $content; + } + + public static function getPagination(Response $response) + { + $header = $response->getHeader('Link'); + + if (empty($header)) { + return null; + } + + $pagination = array(); + foreach (explode(',', $header) as $link) { + preg_match('/<(.*)>; rel="(.*)"/i', trim($link, ','), $match); + + if (3 === count($match)) { + $pagination[$match[2]] = $match[1]; + } + } + + return $pagination; + } + + public static function getApiLimit(Response $response) + { + $remainingCalls = $response->getHeader('X-RateLimit-Remaining'); + + if (null !== $remainingCalls && 1 > $remainingCalls) { + throw new ApiLimitExceedException($remainingCalls); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/ResultPager.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/ResultPager.php new file mode 100644 index 000000000..01c689a58 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/ResultPager.php @@ -0,0 +1,168 @@ + + * @author Mitchel Verschoof + */ +class ResultPager implements ResultPagerInterface +{ + /** + * The GitHub Client to use for pagination. + * + * @var \Github\Client + */ + protected $client; + + /** + * Comes from pagination headers in Github API results. + * + * @var array + */ + protected $pagination; + + /** + * The Github client to use for pagination. + * + * This must be the same instance that you got the Api instance from. + * + * Example code: + * + * $client = new \Github\Client(); + * $api = $client->api('someApi'); + * $pager = new \Github\ResultPager($client); + * + * @param \Github\Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * {@inheritdoc} + */ + public function getPagination() + { + return $this->pagination; + } + + /** + * {@inheritdoc} + */ + public function fetch(ApiInterface $api, $method, array $parameters = array()) + { + $result = call_user_func_array(array($api, $method), $parameters); + $this->postFetch(); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function fetchAll(ApiInterface $api, $method, array $parameters = array()) + { + // get the perPage from the api + $perPage = $api->getPerPage(); + + // set parameters per_page to GitHub max to minimize number of requests + $api->setPerPage(100); + + $result = array(); + $result = call_user_func_array(array($api, $method), $parameters); + $this->postFetch(); + + while ($this->hasNext()) { + $result = array_merge($result, $this->fetchNext()); + } + + // restore the perPage + $api->setPerPage($perPage); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function postFetch() + { + $this->pagination = ResponseMediator::getPagination($this->client->getHttpClient()->getLastResponse()); + } + + /** + * {@inheritdoc} + */ + public function hasNext() + { + return $this->has('next'); + } + + /** + * {@inheritdoc} + */ + public function fetchNext() + { + return $this->get('next'); + } + + /** + * {@inheritdoc} + */ + public function hasPrevious() + { + return $this->has('prev'); + } + + /** + * {@inheritdoc} + */ + public function fetchPrevious() + { + return $this->get('prev'); + } + + /** + * {@inheritdoc} + */ + public function fetchFirst() + { + return $this->get('first'); + } + + /** + * {@inheritdoc} + */ + public function fetchLast() + { + return $this->get('last'); + } + + /** + * {@inheritdoc} + */ + protected function has($key) + { + return !empty($this->pagination) && isset($this->pagination[$key]); + } + + /** + * {@inheritdoc} + */ + protected function get($key) + { + if ($this->has($key)) { + $result = $this->client->getHttpClient()->get($this->pagination[$key]); + $this->postFetch(); + + return ResponseMediator::getContent($result); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/ResultPagerInterface.php b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/ResultPagerInterface.php new file mode 100644 index 000000000..1130e8ec4 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/knplabs/github-api/lib/Github/ResultPagerInterface.php @@ -0,0 +1,90 @@ + + * @author Mitchel Verschoof + */ +interface ResultPagerInterface +{ + /** + * @return null|array pagination result of last request + */ + public function getPagination(); + + /** + * Fetch a single result (page) from an api call. + * + * @param ApiInterface $api the Api instance + * @param string $method the method name to call on the Api instance + * @param array $parameters the method parameters in an array + * + * @return array returns the result of the Api::$method() call + */ + public function fetch(ApiInterface $api, $method, array $parameters = array()); + + /** + * Fetch all results (pages) from an api call. + * + * Use with care - there is no maximum. + * + * @param ApiInterface $api the Api instance + * @param string $method the method name to call on the Api instance + * @param array $parameters the method parameters in an array + * + * @return array returns a merge of the results of the Api::$method() call + */ + public function fetchAll(ApiInterface $api, $method, array $parameters = array()); + + /** + * Method that performs the actual work to refresh the pagination property. + */ + public function postFetch(); + + /** + * Check to determine the availability of a next page. + * + * @return bool + */ + public function hasNext(); + + /** + * Check to determine the availability of a previous page. + * + * @return bool + */ + public function hasPrevious(); + + /** + * Fetch the next page. + * + * @return array + */ + public function fetchNext(); + + /** + * Fetch the previous page. + * + * @return array + */ + public function fetchPrevious(); + + /** + * Fetch the first page. + * + * @return array + */ + public function fetchFirst(); + + /** + * Fetch the last page. + * + * @return array + */ + public function fetchLast(); +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/.gitignore b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/.gitignore new file mode 100644 index 000000000..c49a5d8df --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/CHANGELOG.md b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/CHANGELOG.md new file mode 100644 index 000000000..bb42ee19c --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/CHANGELOG.md @@ -0,0 +1,23 @@ +CHANGELOG +========= + +2.5.0 +----- + + * added Debug\TraceableEventDispatcher (originally in HttpKernel) + * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface + * added RegisterListenersPass (originally in HttpKernel) + +2.1.0 +----- + + * added TraceableEventDispatcherInterface + * added ContainerAwareEventDispatcher + * added a reference to the EventDispatcher on the Event + * added a reference to the Event name on the event + * added fluid interface to the dispatch() method which now returns the Event + object + * added GenericEvent event class + * added the possibility for subscribers to subscribe several times for the + same event + * added ImmutableEventDispatcher diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php new file mode 100644 index 000000000..af0b6cef5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Lazily loads listeners and subscribers from the dependency injection + * container. + * + * @author Fabien Potencier + * @author Bernhard Schussek + * @author Jordan Alliot + */ +class ContainerAwareEventDispatcher extends EventDispatcher +{ + /** + * The container from where services are loaded. + * + * @var ContainerInterface + */ + private $container; + + /** + * The service IDs of the event listeners and subscribers. + * + * @var array + */ + private $listenerIds = array(); + + /** + * The services registered as listeners. + * + * @var array + */ + private $listeners = array(); + + /** + * Constructor. + * + * @param ContainerInterface $container A ContainerInterface instance + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Adds a service as event listener. + * + * @param string $eventName Event for which the listener is added + * @param array $callback The service ID of the listener service & the method + * name that has to be called + * @param int $priority The higher this value, the earlier an event listener + * will be triggered in the chain. + * Defaults to 0. + * + * @throws \InvalidArgumentException + */ + public function addListenerService($eventName, $callback, $priority = 0) + { + if (!is_array($callback) || 2 !== count($callback)) { + throw new \InvalidArgumentException('Expected an array("service", "method") argument'); + } + + $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); + } + + public function removeListener($eventName, $listener) + { + $this->lazyLoad($eventName); + + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $i => $args) { + list($serviceId, $method, $priority) = $args; + $key = $serviceId.'.'.$method; + if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { + unset($this->listeners[$eventName][$key]); + if (empty($this->listeners[$eventName])) { + unset($this->listeners[$eventName]); + } + unset($this->listenerIds[$eventName][$i]); + if (empty($this->listenerIds[$eventName])) { + unset($this->listenerIds[$eventName]); + } + } + } + } + + parent::removeListener($eventName, $listener); + } + + /** + * @see EventDispatcherInterface::hasListeners() + */ + public function hasListeners($eventName = null) + { + if (null === $eventName) { + return (bool) count($this->listenerIds) || (bool) count($this->listeners); + } + + if (isset($this->listenerIds[$eventName])) { + return true; + } + + return parent::hasListeners($eventName); + } + + /** + * @see EventDispatcherInterface::getListeners() + */ + public function getListeners($eventName = null) + { + if (null === $eventName) { + foreach (array_keys($this->listenerIds) as $serviceEventName) { + $this->lazyLoad($serviceEventName); + } + } else { + $this->lazyLoad($eventName); + } + + return parent::getListeners($eventName); + } + + /** + * Adds a service as event subscriber. + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name (which must implement EventSubscriberInterface) + */ + public function addSubscriberService($serviceId, $class) + { + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } elseif (is_string($params[0])) { + $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * {@inheritdoc} + * + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @throws \InvalidArgumentException if the service is not defined + */ + public function dispatch($eventName, Event $event = null) + { + $this->lazyLoad($eventName); + + return parent::dispatch($eventName, $event); + } + + public function getContainer() + { + return $this->container; + } + + /** + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + */ + protected function lazyLoad($eventName) + { + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $args) { + list($serviceId, $method, $priority) = $args; + $listener = $this->container->get($serviceId); + + $key = $serviceId.'.'.$method; + if (!isset($this->listeners[$eventName][$key])) { + $this->addListener($eventName, array($listener, $method), $priority); + } elseif ($listener !== $this->listeners[$eventName][$key]) { + parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method)); + $this->addListener($eventName, array($listener, $method), $priority); + } + + $this->listeners[$eventName][$key] = $listener; + } + } + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 000000000..2119b81b3 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,320 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Stopwatch\Stopwatch; +use Psr\Log\LoggerInterface; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements TraceableEventDispatcherInterface +{ + protected $logger; + protected $stopwatch; + + private $called; + private $dispatcher; + + /** + * Constructor. + * + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->called = array(); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + $this->preProcess($eventName); + $this->preDispatch($eventName, $event); + + $e = $this->stopwatch->start($eventName, 'section'); + + $this->dispatcher->dispatch($eventName, $event); + + if ($e->isStarted()) { + $e->stop(); + } + + $this->postDispatch($eventName, $event); + $this->postProcess($eventName); + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getCalledListeners() + { + $called = array(); + foreach ($this->called as $eventName => $listeners) { + foreach ($listeners as $listener) { + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + $called[$eventName.'.'.$info['pretty']] = $info; + } + } + + return $called; + } + + /** + * {@inheritdoc} + */ + public function getNotCalledListeners() + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $this->logger->info(sprintf('An exception was thrown while getting the uncalled listeners (%s)', $e->getMessage()), array('exception' => $e)); + } + + // unable to retrieve the uncalled listeners + return array(); + } + + $notCalled = array(); + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + $called = false; + if (isset($this->called[$eventName])) { + foreach ($this->called[$eventName] as $l) { + if ($l->getWrappedListener() === $listener) { + $called = true; + + break; + } + } + } + + if (!$called) { + $info = $this->getListenerInfo($listener, $eventName); + $notCalled[$eventName.'.'.$info['pretty']] = $info; + } + } + } + + return $notCalled; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return call_user_func_array(array($this->dispatcher, $method), $arguments); + } + + /** + * Called before dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function preDispatch($eventName, Event $event) + { + } + + /** + * Called after dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function postDispatch($eventName, Event $event) + { + } + + private function preProcess($eventName) + { + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $this->dispatcher->removeListener($eventName, $listener); + $info = $this->getListenerInfo($listener, $eventName); + $name = isset($info['class']) ? $info['class'] : $info['type']; + $this->dispatcher->addListener($eventName, new WrappedListener($listener, $name, $this->stopwatch, $this)); + } + } + + private function postProcess($eventName) + { + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. + continue; + } + // Unwrap listener + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener()); + + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty'])); + } + + if (!isset($this->called[$eventName])) { + $this->called[$eventName] = new \SplObjectStorage(); + } + + $this->called[$eventName]->attach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName)); + } + + if ($listener->stoppedPropagation()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName)); + } + + $skipped = true; + } + } + } + + /** + * Returns information about the listener + * + * @param object $listener The listener + * @param string $eventName The event name + * + * @return array Information about the listener + */ + private function getListenerInfo($listener, $eventName) + { + $info = array( + 'event' => $eventName, + ); + if ($listener instanceof \Closure) { + $info += array( + 'type' => 'Closure', + 'pretty' => 'closure', + ); + } elseif (is_string($listener)) { + try { + $r = new \ReflectionFunction($listener); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Function', + 'function' => $listener, + 'file' => $file, + 'line' => $line, + 'pretty' => $listener, + ); + } elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) { + if (!is_array($listener)) { + $listener = array($listener, '__invoke'); + } + $class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0]; + try { + $r = new \ReflectionMethod($class, $listener[1]); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Method', + 'class' => $class, + 'method' => $listener[1], + 'file' => $file, + 'line' => $line, + 'pretty' => $class.'::'.$listener[1], + ); + } + + return $info; + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcherInterface.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcherInterface.php new file mode 100644 index 000000000..5483e8150 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcherInterface.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\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Fabien Potencier + */ +interface TraceableEventDispatcherInterface extends EventDispatcherInterface +{ + /** + * Gets the called listeners. + * + * @return array An array of called listeners + */ + public function getCalledListeners(); + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + */ + public function getNotCalledListeners(); +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/WrappedListener.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/WrappedListener.php new file mode 100644 index 000000000..e16627d6a --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/WrappedListener.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Fabien Potencier + */ +class WrappedListener +{ + private $listener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + + public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->name = $name; + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->called = false; + $this->stoppedPropagation = false; + } + + public function getWrappedListener() + { + return $this->listener; + } + + public function wasCalled() + { + return $this->called; + } + + public function stoppedPropagation() + { + return $this->stoppedPropagation; + } + + public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) + { + $this->called = true; + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 000000000..7e74a37aa --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + /** + * @var string + */ + protected $dispatcherService; + + /** + * @var string + */ + protected $listenerTag; + + /** + * @var string + */ + protected $subscriberTag; + + /** + * Constructor. + * + * @param string $dispatcherService Service name of the event dispatcher in processed container + * @param string $listenerTag Tag name used for listener + * @param string $subscriberTag Tag name used for subscribers + */ + public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber') + { + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + return; + } + + $definition = $container->findDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id)); + } + + foreach ($events as $event) { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) { + throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback(array( + '/(?<=\b)[a-z]/i', + '/[^a-z0-9]/i', + ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + } + + $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + } + } + + foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id)); + } + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getParameterBag()->resolveValue($def->getClass()); + + $refClass = new \ReflectionClass($class); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + if (!$refClass->implementsInterface($interface)) { + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Event.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Event.php new file mode 100644 index 000000000..dc39b05d5 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Event.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * + * @api + */ +class Event +{ + /** + * @var bool Whether no further event listeners should be triggered + */ + private $propagationStopped = false; + + /** + * @var EventDispatcher Dispatcher that dispatched this event + */ + private $dispatcher; + + /** + * @var string This event's name + */ + private $name; + + /** + * Returns whether further event listeners should be triggered. + * + * @see Event::stopPropagation() + * + * @return bool Whether propagation was already stopped for this event. + * + * @api + */ + public function isPropagationStopped() + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + * + * @api + */ + public function stopPropagation() + { + $this->propagationStopped = true; + } + + /** + * Stores the EventDispatcher that dispatches this Event. + * + * @param EventDispatcherInterface $dispatcher + * + * @deprecated Deprecated in 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call. + * + * @api + */ + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Returns the EventDispatcher that dispatches this Event. + * + * @return EventDispatcherInterface + * + * @deprecated Deprecated in 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call. + * + * @api + */ + public function getDispatcher() + { + return $this->dispatcher; + } + + /** + * Gets the event's name. + * + * @return string + * + * @deprecated Deprecated in 2.4, to be removed in 3.0. The event name is passed to the listener call. + * + * @api + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the event's name property. + * + * @param string $name The event name. + * + * @deprecated Deprecated in 2.4, to be removed in 3.0. The event name is passed to the listener call. + * + * @api + */ + public function setName($name) + { + $this->name = $name; + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcher.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcher.php new file mode 100644 index 000000000..3b032fb08 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcher.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Jordan Alliot + * + * @api + */ +class EventDispatcher implements EventDispatcherInterface +{ + private $listeners = array(); + private $sorted = array(); + + /** + * @see EventDispatcherInterface::dispatch() + * + * @api + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + $event->setDispatcher($this); + $event->setName($eventName); + + if (!isset($this->listeners[$eventName])) { + return $event; + } + + $this->doDispatch($this->getListeners($eventName), $eventName, $event); + + return $event; + } + + /** + * @see EventDispatcherInterface::getListeners() + */ + public function getListeners($eventName = null) + { + if (null !== $eventName) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach (array_keys($this->listeners) as $eventName) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return array_filter($this->sorted); + } + + /** + * @see EventDispatcherInterface::hasListeners() + */ + public function hasListeners($eventName = null) + { + return (bool) count($this->getListeners($eventName)); + } + + /** + * @see EventDispatcherInterface::addListener() + * + * @api + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName]); + } + + /** + * @see EventDispatcherInterface::removeListener() + */ + public function removeListener($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]); + } + } + } + + /** + * @see EventDispatcherInterface::addSubscriber() + * + * @api + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->addListener($eventName, array($subscriber, $params)); + } elseif (is_string($params[0])) { + $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * @see EventDispatcherInterface::removeSubscriber() + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_array($params) && is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, array($subscriber, $listener[0])); + } + } else { + $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0])); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param callable[] $listeners The event listeners. + * @param string $eventName The name of the event to dispatch. + * @param Event $event The event object to pass to the event handlers/listeners. + */ + protected function doDispatch($listeners, $eventName, Event $event) + { + foreach ($listeners as $listener) { + call_user_func($listener, $event, $eventName, $this); + if ($event->isPropagationStopped()) { + break; + } + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + * + * @param string $eventName The name of the event. + */ + private function sortListeners($eventName) + { + $this->sorted[$eventName] = array(); + + if (isset($this->listeners[$eventName])) { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]); + } + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcherInterface.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcherInterface.php new file mode 100644 index 000000000..efb7c5bec --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcherInterface.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Bernhard Schussek + * + * @api + */ +interface EventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + * @param Event $event The event to pass to the event handlers/listeners. + * If not supplied, an empty Event instance is created. + * + * @return Event + * + * @api + */ + public function dispatch($eventName, Event $event = null); + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $listener The listener + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + * + * @api + */ + public function addListener($eventName, $listener, $priority = 0); + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events he is + * interested in and added as a listener for these events. + * + * @param EventSubscriberInterface $subscriber The subscriber. + * + * @api + */ + public function addSubscriber(EventSubscriberInterface $subscriber); + + /** + * Removes an event listener from the specified events. + * + * @param string $eventName The event to remove a listener from + * @param callable $listener The listener to remove + */ + public function removeListener($eventName, $listener); + + /** + * Removes an event subscriber. + * + * @param EventSubscriberInterface $subscriber The subscriber + */ + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners. + * + * @param string $eventName The name of the event + * + * @return array The event listeners for the specified event, or all event listeners by event name + */ + public function getListeners($eventName = null); + + /** + * Checks whether an event has any registered listeners. + * + * @param string $eventName The name of the event + * + * @return bool true if the specified event has any listeners, false otherwise + */ + public function hasListeners($eventName = null); +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventSubscriberInterface.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventSubscriberInterface.php new file mode 100644 index 000000000..ff7e305cd --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventSubscriberInterface.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * An EventSubscriber knows himself what events he is interested in. + * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * + * @api + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array The event names to listen to + * + * @api + */ + public static function getSubscribedEvents(); +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/GenericEvent.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/GenericEvent.php new file mode 100644 index 000000000..a8955ca42 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/GenericEvent.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + /** + * Event subject. + * + * @var mixed usually object or callable + */ + protected $subject; + + /** + * Array of arguments. + * + * @var array + */ + protected $arguments; + + /** + * Encapsulate an event with $subject and $args. + * + * @param mixed $subject The subject of the event, usually an object. + * @param array $arguments Arguments to store in the event. + */ + public function __construct($subject = null, array $arguments = array()) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed $subject The observer subject. + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @param string $key Key. + * + * @throws \InvalidArgumentException If key is not found. + * + * @return mixed Contents of array key. + */ + public function getArgument($key) + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('%s not found in %s', $key, $this->getName())); + } + + /** + * Add argument to event. + * + * @param string $key Argument name. + * @param mixed $value Value. + * + * @return GenericEvent + */ + public function setArgument($key, $value) + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Set args property. + * + * @param array $args Arguments. + * + * @return GenericEvent + */ + public function setArguments(array $args = array()) + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + * + * @param string $key Key of arguments array. + * + * @return bool + */ + public function hasArgument($key) + { + return array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key. + * + * @throws \InvalidArgumentException If key does not exist in $this->args. + * + * @return mixed + */ + public function offsetGet($key) + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set. + * @param mixed $value Value. + */ + public function offsetSet($key, $value) + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key. + */ + public function offsetUnset($key) + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key. + * + * @return bool + */ + public function offsetExists($key) + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php new file mode 100644 index 000000000..7ef9ece75 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.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\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + /** + * The proxied dispatcher. + * + * @var EventDispatcherInterface + */ + private $dispatcher; + + /** + * Creates an unmodifiable proxy for an event dispatcher. + * + * @param EventDispatcherInterface $dispatcher The proxied event dispatcher. + */ + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + return $this->dispatcher->dispatch($eventName, $event); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/LICENSE b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/LICENSE new file mode 100644 index 000000000..43028bc60 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/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/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/README.md b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/README.md new file mode 100644 index 000000000..8031f4dd3 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/README.md @@ -0,0 +1,27 @@ +EventDispatcher Component +========================= + +The Symfony EventDispatcher component implements the Mediator pattern in a +simple and effective way to make your projects truly extensible. + +```php +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\Event; + +$dispatcher = new EventDispatcher(); + +$dispatcher->addListener('event_name', function (Event $event) { + // ... +}); + +$dispatcher->dispatch('event_name'); +``` + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/EventDispatcher/ + $ composer install + $ phpunit diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/AbstractEventDispatcherTest.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/AbstractEventDispatcherTest.php new file mode 100644 index 000000000..b9e419496 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/AbstractEventDispatcherTest.php @@ -0,0 +1,369 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +abstract class AbstractEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + /* Some pseudo events */ + const preFoo = 'pre.foo'; + const postFoo = 'post.foo'; + const preBar = 'pre.bar'; + const postBar = 'post.bar'; + + /** + * @var EventDispatcher + */ + private $dispatcher; + + private $listener; + + protected function setUp() + { + $this->dispatcher = $this->createEventDispatcher(); + $this->listener = new TestEventListener(); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->listener = null; + } + + abstract protected function createEventDispatcher(); + + public function testInitialState() + { + $this->assertEquals(array(), $this->dispatcher->getListeners()); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddListener() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo)); + $this->assertCount(2, $this->dispatcher->getListeners()); + } + + public function testGetListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener1->name = '1'; + $listener2->name = '2'; + $listener3->name = '3'; + + $this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10); + $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10); + $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo')); + + $expected = array( + array($listener2, 'preFoo'), + array($listener3, 'preFoo'), + array($listener1, 'preFoo'), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo')); + } + + public function testGetAllListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener4 = new TestEventListener(); + $listener5 = new TestEventListener(); + $listener6 = new TestEventListener(); + + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->addListener('post.foo', $listener4, -10); + $this->dispatcher->addListener('post.foo', $listener5); + $this->dispatcher->addListener('post.foo', $listener6, 10); + + $expected = array( + 'pre.foo' => array($listener3, $listener2, $listener1), + 'post.foo' => array($listener6, $listener5, $listener4), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners()); + } + + public function testDispatch() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->dispatcher->dispatch(self::preFoo); + $this->assertTrue($this->listener->preFooInvoked); + $this->assertFalse($this->listener->postFooInvoked); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent')); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo)); + $event = new Event(); + $return = $this->dispatcher->dispatch(self::preFoo, $event); + $this->assertEquals('pre.foo', $event->getName()); + $this->assertSame($event, $return); + } + + public function testDispatchForClosure() + { + $invoked = 0; + $listener = function () use (&$invoked) { + $invoked++; + }; + $this->dispatcher->addListener('pre.foo', $listener); + $this->dispatcher->addListener('post.foo', $listener); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(1, $invoked); + } + + public function testStopEventPropagation() + { + $otherListener = new TestEventListener(); + + // postFoo() stops the propagation, so only one listener should + // be executed + // Manually set priority to enforce $this->listener to be called first + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10); + $this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo')); + $this->dispatcher->dispatch(self::postFoo); + $this->assertTrue($this->listener->postFooInvoked); + $this->assertFalse($otherListener->postFooInvoked); + } + + public function testDispatchByPriority() + { + $invoked = array(); + $listener1 = function () use (&$invoked) { + $invoked[] = '1'; + }; + $listener2 = function () use (&$invoked) { + $invoked[] = '2'; + }; + $listener3 = function () use (&$invoked) { + $invoked[] = '3'; + }; + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(array('3', '2', '1'), $invoked); + } + + public function testRemoveListener() + { + $this->dispatcher->addListener('pre.bar', $this->listener); + $this->assertTrue($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('pre.bar', $this->listener); + $this->assertFalse($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('notExists', $this->listener); + } + + public function testAddSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]); + } + + public function testAddSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertEquals('preFoo2', $listeners[0][1]); + } + + public function testRemoveSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testRemoveSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + public function testRemoveSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + public function testEventReceivesTheDispatcherInstance() + { + $dispatcher = null; + $this->dispatcher->addListener('test', function ($event) use (&$dispatcher) { + $dispatcher = $event->getDispatcher(); + }); + $this->dispatcher->dispatch('test'); + $this->assertSame($this->dispatcher, $dispatcher); + } + + public function testEventReceivesTheDispatcherInstanceAsArgument() + { + $listener = new TestWithDispatcher(); + $this->dispatcher->addListener('test', array($listener, 'foo')); + $this->assertNull($listener->name); + $this->assertNull($listener->dispatcher); + $this->dispatcher->dispatch('test'); + $this->assertEquals('test', $listener->name); + $this->assertSame($this->dispatcher, $listener->dispatcher); + } + + /** + * @see https://bugs.php.net/bug.php?id=62976 + * + * This bug affects: + * - The PHP 5.3 branch for versions < 5.3.18 + * - The PHP 5.4 branch for versions < 5.4.8 + * - The PHP 5.5 branch is not affected + */ + public function testWorkaroundForPhpBug62976() + { + $dispatcher = $this->createEventDispatcher(); + $dispatcher->addListener('bug.62976', new CallableClass()); + $dispatcher->removeListener('bug.62976', function () {}); + $this->assertTrue($dispatcher->hasListeners('bug.62976')); + } + + public function testHasListenersWhenAddedCallbackListenerIsRemoved() + { + $listener = function () {}; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertFalse($this->dispatcher->hasListeners()); + } + + public function testGetListenersWhenAddedCallbackListenerIsRemoved() + { + $listener = function () {}; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertSame(array(), $this->dispatcher->getListeners()); + } + + public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled() + { + $this->assertFalse($this->dispatcher->hasListeners('foo')); + $this->assertFalse($this->dispatcher->hasListeners()); + } +} + +class CallableClass +{ + public function __invoke() + { + } +} + +class TestEventListener +{ + public $preFooInvoked = false; + public $postFooInvoked = false; + + /* Listener methods */ + + public function preFoo(Event $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(Event $e) + { + $this->postFooInvoked = true; + + $e->stopPropagation(); + } +} + +class TestWithDispatcher +{ + public $name; + public $dispatcher; + + public function foo(Event $e, $name, $dispatcher) + { + $this->name = $name; + $this->dispatcher = $dispatcher; + } +} + +class TestEventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo'); + } +} + +class TestEventSubscriberWithPriorities implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'pre.foo' => array('preFoo', 10), + 'post.foo' => array('postFoo'), + ); + } +} + +class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => array( + array('preFoo1'), + array('preFoo2', 10), + )); + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php new file mode 100644 index 000000000..6f2fbcb9d --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Scope; +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + $container = new Container(); + + return new ContainerAwareEventDispatcher($container); + } + + public function testAddAListenerService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testAddASubscriberService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.subscriber', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testPreventDuplicateListenerService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10); + + $dispatcher->dispatch('onEvent', $event); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testTriggerAListenerServiceOutOfScope() + { + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $scope = new Scope('scope'); + $container = new Container(); + $container->addScope($scope); + $container->enterScope('scope'); + + $container->set('service.listener', $service, 'scope'); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $container->leaveScope('scope'); + $dispatcher->dispatch('onEvent'); + } + + public function testReEnteringAScope() + { + $event = new Event(); + + $service1 = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service1 + ->expects($this->exactly(2)) + ->method('onEvent') + ->with($event) + ; + + $scope = new Scope('scope'); + $container = new Container(); + $container->addScope($scope); + $container->enterScope('scope'); + + $container->set('service.listener', $service1, 'scope'); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + $dispatcher->dispatch('onEvent', $event); + + $service2 = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service2 + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container->enterScope('scope'); + $container->set('service.listener', $service2, 'scope'); + + $dispatcher->dispatch('onEvent', $event); + + $container->leaveScope('scope'); + + $dispatcher->dispatch('onEvent'); + } + + public function testHasListenersOnLazyLoad() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $event->setDispatcher($dispatcher); + $event->setName('onEvent'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $this->assertTrue($dispatcher->hasListeners()); + + if ($dispatcher->hasListeners('onEvent')) { + $dispatcher->dispatch('onEvent'); + } + } + + public function testGetListenersOnLazyLoad() + { + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $listeners = $dispatcher->getListeners(); + + $this->assertTrue(isset($listeners['onEvent'])); + + $this->assertCount(1, $dispatcher->getListeners('onEvent')); + } + + public function testRemoveAfterDispatch() + { + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', new Event()); + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } + + public function testRemoveBeforeDispatch() + { + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } +} + +class Service +{ + public function onEvent(Event $e) + { + } +} + +class SubscriberService implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'onEvent' => array('onEvent'), + ); + } + + public function onEvent(Event $e) + { + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/Debug/TraceableEventDispatcherTest.php new file mode 100644 index 000000000..68b952365 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\Debug; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Stopwatch\Stopwatch; + +class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + public function testAddRemoveListener() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () {; }); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame($listener, $listeners[0]); + + $tdispatcher->removeListener('foo', $listener); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + public function testGetListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () {; }); + $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo')); + } + + public function testHasListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $this->assertFalse($dispatcher->hasListeners('foo')); + $this->assertFalse($tdispatcher->hasListeners('foo')); + + $tdispatcher->addListener('foo', $listener = function () {; }); + $this->assertTrue($dispatcher->hasListeners('foo')); + $this->assertTrue($tdispatcher->hasListeners('foo')); + } + + public function testAddRemoveSubscriber() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $subscriber = new EventSubscriber(); + + $tdispatcher->addSubscriber($subscriber); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame(array($subscriber, 'call'), $listeners[0]); + + $tdispatcher->removeSubscriber($subscriber); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + public function testGetCalledListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', $listener = function () {; }); + + $this->assertEquals(array(), $tdispatcher->getCalledListeners()); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure')), $tdispatcher->getNotCalledListeners()); + + $tdispatcher->dispatch('foo'); + + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure')), $tdispatcher->getCalledListeners()); + $this->assertEquals(array(), $tdispatcher->getNotCalledListeners()); + } + + public function testGetCalledListenersNested() + { + $tdispatcher = null; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) { + $tdispatcher = $dispatcher; + $dispatcher->dispatch('bar'); + }); + $dispatcher->addListener('bar', function (Event $event) {}); + $dispatcher->dispatch('foo'); + $this->assertSame($dispatcher, $tdispatcher); + $this->assertCount(2, $dispatcher->getCalledListeners()); + } + + public function testLogger() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function () {; }); + $tdispatcher->addListener('foo', $listener2 = function () {; }); + + $logger->expects($this->at(0))->method('debug')->with("Notified event \"foo\" to listener \"closure\"."); + $logger->expects($this->at(1))->method('debug')->with("Notified event \"foo\" to listener \"closure\"."); + + $tdispatcher->dispatch('foo'); + } + + public function testLoggerWithStoppedEvent() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); }); + $tdispatcher->addListener('foo', $listener2 = function () {; }); + + $logger->expects($this->at(0))->method('debug')->with("Notified event \"foo\" to listener \"closure\"."); + $logger->expects($this->at(1))->method('debug')->with("Listener \"closure\" stopped propagation of the event \"foo\"."); + $logger->expects($this->at(2))->method('debug')->with("Listener \"closure\" was not called for event \"foo\"."); + + $tdispatcher->dispatch('foo'); + } + + public function testDispatchCallListeners() + { + $called = array(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', $listener1 = function () use (&$called) { $called[] = 'foo1'; }); + $tdispatcher->addListener('foo', $listener2 = function () use (&$called) { $called[] = 'foo2'; }); + + $tdispatcher->dispatch('foo'); + + $this->assertEquals(array('foo1', 'foo2'), $called); + } + + public function testDispatchNested() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $loop = 1; + $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) { + ++$loop; + if (2 == $loop) { + $dispatcher->dispatch('foo'); + } + }); + + $dispatcher->dispatch('foo'); + } + + public function testDispatchReusedEventNested() + { + $nestedCall = false; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) { + $dispatcher->dispatch('bar', $e); + }); + $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) { + $nestedCall = true; + }); + + $this->assertFalse($nestedCall); + $dispatcher->dispatch('foo'); + $this->assertTrue($nestedCall); + } +} + +class EventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('foo' => 'call'); + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php new file mode 100644 index 000000000..0fdd6372b --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; + +class RegisterListenersPassTest extends \PHPUnit_Framework_TestCase +{ + /** + * Tests that event subscribers not implementing EventSubscriberInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testEventSubscriberWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('isPublic') + ->will($this->returnValue(true)); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('stdClass')); + + $builder = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('hasDefinition', 'findTaggedServiceIds', 'getDefinition') + ); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + public function testValidEventSubscriber() + { + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('isPublic') + ->will($this->returnValue(true)); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')); + + $builder = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition') + ); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $builder->expects($this->atLeastOnce()) + ->method('findDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must be public as event listeners are lazy-loaded. + */ + public function testPrivateEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must be public as event subscribers are lazy-loaded. + */ + public function testPrivateEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must not be abstract as event listeners are lazy-loaded. + */ + public function testAbstractEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must not be abstract as event subscribers are lazy-loaded. + */ + public function testAbstractEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + public function testEventSubscriberResolvableClassName() + { + $container = new ContainerBuilder(); + + $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expected_calls = array( + array( + 'addSubscriberService', + array( + 'foo', + 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService', + ), + ), + ); + $this->assertSame($expected_calls, $definition->getMethodCalls()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class" + */ + public function testEventSubscriberUnresolvableClassName() + { + $container = new ContainerBuilder(); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } +} + +class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php new file mode 100644 index 000000000..5faa5c8be --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\EventDispatcher; + +class EventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + return new EventDispatcher(); + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventTest.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventTest.php new file mode 100644 index 000000000..4bd269722 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcher; + +/** + * Test class for Event. + */ +class EventTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\EventDispatcher\Event + */ + protected $event; + + /** + * @var \Symfony\Component\EventDispatcher\EventDispatcher + */ + protected $dispatcher; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + $this->event = new Event(); + $this->dispatcher = new EventDispatcher(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + $this->event = null; + $this->dispatcher = null; + } + + public function testIsPropagationStopped() + { + $this->assertFalse($this->event->isPropagationStopped()); + } + + public function testStopPropagationAndIsPropagationStopped() + { + $this->event->stopPropagation(); + $this->assertTrue($this->event->isPropagationStopped()); + } + + /** + * @group legacy + */ + public function testLegacySetDispatcher() + { + $this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED); + $this->event->setDispatcher($this->dispatcher); + $this->assertSame($this->dispatcher, $this->event->getDispatcher()); + } + + /** + * @group legacy + */ + public function testLegacyGetDispatcher() + { + $this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED); + $this->assertNull($this->event->getDispatcher()); + } + + /** + * @group legacy + */ + public function testLegacyGetName() + { + $this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED); + $this->assertNull($this->event->getName()); + } + + /** + * @group legacy + */ + public function testLegacySetName() + { + $this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED); + $this->event->setName('foo'); + $this->assertEquals('foo', $this->event->getName()); + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php new file mode 100644 index 000000000..aebd82dab --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\GenericEvent; + +/** + * Test class for Event. + */ +class GenericEventTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var GenericEvent + */ + private $event; + + private $subject; + + /** + * Prepares the environment before running a test. + */ + protected function setUp() + { + parent::setUp(); + + $this->subject = new \stdClass(); + $this->event = new GenericEvent($this->subject, array('name' => 'Event')); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->subject = null; + $this->event = null; + + parent::tearDown(); + } + + public function testConstruct() + { + $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event'))); + } + + /** + * Tests Event->getArgs(). + */ + public function testGetArguments() + { + // test getting all + $this->assertSame(array('name' => 'Event'), $this->event->getArguments()); + } + + public function testSetArguments() + { + $result = $this->event->setArguments(array('foo' => 'bar')); + $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event); + $this->assertSame($this->event, $result); + } + + public function testSetArgument() + { + $result = $this->event->setArgument('foo2', 'bar2'); + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + $this->assertEquals($this->event, $result); + } + + public function testGetArgument() + { + // test getting key + $this->assertEquals('Event', $this->event->getArgument('name')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetArgException() + { + $this->event->getArgument('nameNotExist'); + } + + public function testOffsetGet() + { + // test getting key + $this->assertEquals('Event', $this->event['name']); + + // test getting invalid arg + $this->setExpectedException('InvalidArgumentException'); + $this->assertFalse($this->event['nameNotExist']); + } + + public function testOffsetSet() + { + $this->event['foo2'] = 'bar2'; + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + } + + public function testOffsetUnset() + { + unset($this->event['name']); + $this->assertAttributeSame(array(), 'arguments', $this->event); + } + + public function testOffsetIsset() + { + $this->assertTrue(isset($this->event['name'])); + $this->assertFalse(isset($this->event['nameNotExist'])); + } + + public function testHasArgument() + { + $this->assertTrue($this->event->hasArgument('name')); + $this->assertFalse($this->event->hasArgument('nameNotExist')); + } + + public function testGetSubject() + { + $this->assertSame($this->subject, $this->event->getSubject()); + } + + public function testHasIterator() + { + $data = array(); + foreach ($this->event as $key => $value) { + $data[$key] = $value; + } + $this->assertEquals(array('name' => 'Event'), $data); + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php new file mode 100644 index 000000000..80a7e43be --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\ImmutableEventDispatcher; + +/** + * @author Bernhard Schussek + */ +class ImmutableEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $innerDispatcher; + + /** + * @var ImmutableEventDispatcher + */ + private $dispatcher; + + protected function setUp() + { + $this->innerDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher); + } + + public function testDispatchDelegates() + { + $event = new Event(); + + $this->innerDispatcher->expects($this->once()) + ->method('dispatch') + ->with('event', $event) + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->dispatch('event', $event)); + } + + public function testGetListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('getListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->getListeners('event')); + } + + public function testHasListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('hasListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->hasListeners('event')); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddListenerDisallowed() + { + $this->dispatcher->addListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddSubscriberDisallowed() + { + $subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveListenerDisallowed() + { + $this->dispatcher->removeListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveSubscriberDisallowed() + { + $subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $this->dispatcher->removeSubscriber($subscriber); + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/composer.json b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/composer.json new file mode 100644 index 000000000..a516d4670 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/composer.json @@ -0,0 +1,43 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Symfony EventDispatcher Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7", + "symfony/dependency-injection": "~2.6", + "symfony/expression-language": "~2.6", + "symfony/config": "~2.0,>=2.0.5", + "symfony/stopwatch": "~2.3", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\EventDispatcher\\": "" } + }, + "target-dir": "Symfony/Component/EventDispatcher", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + } +} diff --git a/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/phpunit.xml.dist b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/phpunit.xml.dist new file mode 100644 index 000000000..b14fde575 --- /dev/null +++ b/modules/devshop/devshop_github/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/modules/devshop/devshop_hosting.drush.inc b/modules/devshop/devshop_hosting.drush.inc new file mode 100644 index 000000000..b292252a9 --- /dev/null +++ b/modules/devshop/devshop_hosting.drush.inc @@ -0,0 +1,59 @@ + 'Trigger deletion of a platform. This is mainly used by the devshop upgrade task to delete the old hostmaster platform.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, + 'arguments' => array( + 'path' => 'The root path of the platform', + ), + ); + return $items; +} + + +/** + * Command function for platform-delete. + */ +function drush_devshop_hosting_platform_delete($platform_path = NULL) { + if (empty($platform_path)) { + return drush_set_error(DRUSH_APPLICATION_ERROR, 'You must specify a platform path.'); + } + drush_log('[DEVSHOP] Looking up platform at ' . $platform_path, 'ok'); + + $nid = db_query('SELECT nid FROM {hosting_platform} WHERE publish_path = :publish_path AND status = :status' , array( + ':publish_path' => $platform_path, + ':status' => HOSTING_PLATFORM_ENABLED, + ))->fetchField(); + + if (empty($nid)) { + return drush_set_error(DRUSH_APPLICATION_ERROR, 'No platform with that path was found.'); + } + else { + drush_log('[DEVSHOP] Found platform ' . $nid, 'ok'); + + // Look for existing enabled sites. + $site_count = db_select('hosting_site') + ->condition('platform', $nid) + ->condition('status', HOSTING_SITE_ENABLED) + ->fields('nid') + ->countQuery() + ->execute() + ->fetchField(); + + if (!empty($site_count)) { + return drush_set_error(DRUSH_APPLICATION_ERROR, "The platform at path $platform_path still has sites present. Please delete them first."); + } + + if (drush_confirm(dt("Schedule the platform at $platform_path for deletion?"))){ + return hosting_add_task($nid, 'delete'); + } + } +} diff --git a/modules/devshop/devshop_hosting.info b/modules/devshop/devshop_hosting.info new file mode 100644 index 000000000..bc94b4c4f --- /dev/null +++ b/modules/devshop/devshop_hosting.info @@ -0,0 +1,6 @@ +name = DevShop Hosting +description = Provides the streamlined DevShop user interface and experience. NOTE: This module alters some of of the UI of Aegir Hostmaster. +core = 7.x +package = DevShop + +dependencies[] = hosting diff --git a/modules/devshop/devshop_hosting.install b/modules/devshop/devshop_hosting.install new file mode 100644 index 000000000..7f6f40f3d --- /dev/null +++ b/modules/devshop/devshop_hosting.install @@ -0,0 +1,130 @@ +devshop.')); + + db_update('system') + ->fields(array( + 'weight' => 11, + )) + ->condition('name', "devshop_hosting") + ->execute(); +} + +/** + * Implements hook_requirements(). + * @param $phase + * @return array + */ +function devshop_hosting_requirements($phase) { + $requirements = array(); + + $devshop_version = file_get_contents(drupal_get_path('profile', 'devmaster') . '/VERSION.txt'); + $t = get_t(); + + // Report DevShop version + if ($phase == 'runtime') { + $requirements['devshop'] = array( + 'title' => $t('DevShop Version'), + 'value' => $devshop_version, + 'severity' => REQUIREMENT_OK, + ); + } + + return $requirements; +} + +/** + * Set weight of this module higher than views. + */ +function devshop_hosting_update_7001() { + $ret = array(); + $ret[] = update_sql('UPDATE {system} SET weight = 11 WHERE name = "devshop_hosting"'); + return $ret; +} + +/** + * Disable aegir's "Welcome" page. + */ +function devshop_hosting_update_7002() { + variable_set('hosting_welcome_page', 0); +} + +/** + * Enable devshop_github module. + */ +function devshop_hosting_update_7003() { + module_enable(array('devshop_hosting')); +} + +/** + * Enable devshop_dothooks module. + */ +function devshop_hosting_update_7004() { + module_enable(array('devshop_dothooks')); +} + +/** + * Save 'aegir_hostmaster_site_nid' variable for devshop. + */ +function devshop_hosting_update_7005() { + $nid = db_query('SELECT site.nid + FROM hosting_site site + JOIN hosting_package_instance pkgi + ON pkgi.rid = site.nid + JOIN hosting_package pkg + ON pkg.nid = pkgi.package_id + WHERE pkg.short_name = \'devmaster\'')->fetchField(); + variable_set('aegir_hostmaster_site_nid', $nid); +} + +/** + * Enable devshop_stats module. + * + * Removed now that we had a "successful failure". https://travis-ci.org/opendevshop/devmaster/jobs/189200584#L2682 + * +function devshop_hosting_update_7006() { + module_enable(array('devshop_stats')); +} + */ + +/** + * Disable and uninstall distro_update module. + */ +function devshop_hosting_update_7006() { + module_disable(array('distro_update')); + drupal_uninstall_modules(array('distro_update')); +} + +/** + * Set the "Force removal of deleted sites, platforms, and servers" setting. + */ +function devshop_hosting_update_7007() { + variable_set('hosting_delete_force', 1); +} + +/** + * Enable Aegir Download, Commit, Update, and Config modules. + */ +function devshop_hosting_update_7009() { + module_enable(array('aegir_download', 'aegir_commit', 'aegir_update', 'aegir_config', 'aegir_features')); +} + +/** + * Enable Hosting Git Tag module. + */ +function devshop_hosting_update_7010() { + module_enable(array('hosting_git_tag')); +} \ No newline at end of file diff --git a/modules/devshop/devshop_hosting.module b/modules/devshop/devshop_hosting.module new file mode 100644 index 000000000..ba467401d --- /dev/null +++ b/modules/devshop/devshop_hosting.module @@ -0,0 +1,428 @@ +$welcome +

    $note

    + + + $link +
    + $url +
    + +HTML; + + } +} + +/** + * Implements hook_init(). + * + * Adds CSS and sets the logo. + */ +function devshop_hosting_init() { + drupal_add_css(drupal_get_path('module', 'devshop_hosting') . '/devshop.css'); + $path = drupal_get_path('module', 'devshop_hosting') . '/icon.png'; + + drupal_add_html_head(''); + + +} + +/** + * Implements hook_menu(). + * + * Defines a new menu callback: login page for new users, redirect to projects + * for authenticated users. + */ +function devshop_hosting_menu() { + $items['devshop'] = array( + 'title' => 'Welcome to DevShop', + 'description' => '', + 'page callback' => 'devshop_hosting_home', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + $items['admin/reports/devshop_sites'] = array( + 'title' => 'DevShop Environments', + 'description' => "List all active sites and their IP addresses.", + 'page callback' => 'devshop_hosting_sites_status', + 'weight' => -60, + 'access arguments' => array('administer site configuration'), + ); + $items['devshop/login/reset/%'] = array( + 'title' => 'Reset Login', + 'description' => "List all active sites and their IP addresses.", + 'page callback' => 'devshop_hosting_login_reset', + 'page arguments' => array(3), + 'weight' => -60, + 'access arguments' => array('create login-reset task'), + ); + return $items; +} + +/** + * Menu callback for path "devshop" + * + * Provides a login page or redirects to proejcts + */ +function devshop_hosting_home() { + if (user_is_logged_in()) { + drupal_goto(variable_get('devshop_frontpage', 'projects')); + } + else { + $output = '

    ' . t('Please log in.') . '

    '; + return drupal_get_form('user_login'); + } +} + +/** + * Implements hook_menu_alter(). + * + * Streamlines login and servers pages. + */ +function devshop_hosting_menu_alter(&$items) { + + // Commented out for the upgrade. Remove when 1.0.0 is released. +// $items['user/password']['type'] = MENU_CALLBACK; +// unset($items['hosting/sites']); +// unset($items['hosting/platforms']); +// unset($items['hosting/clients']); +// +// $items['hosting/servers/add'] = $items['node/add/server']; +// $items['hosting/servers/add']['title'] = t('Add new Server'); +// $items['hosting/servers/add']['type'] = MENU_LOCAL_TASK; +// $items['hosting/servers/add']['page arguments'] = array('server'); +// +// $items['hosting/servers/list'] = array( +// 'title' => t('All Servers'), +// 'weight' => -1, +// 'type' => MENU_DEFAULT_LOCAL_TASK, +// ); + + + $items['hosting/sites']['type'] = MENU_LOCAL_TASK; + $items['hosting/platforms']['type'] = MENU_LOCAL_TASK; + $items['hosting/clients']['type'] = MENU_LOCAL_TASK; + + $items['node/%node/goto_site']['page callback'] = 'devshop_hosting_site_goto'; + +} + +/** + * Override for hosting_site_goto(). + * @param $node + */ +function devshop_hosting_site_goto($node) { + $cid = "hosting:site:" . $node->nid . ":login_link"; + $cache = cache_get($cid); + if (user_access('create login-reset task') && !is_null($cache) && (REQUEST_TIME < $cache->data['expire'])) { + $theurl = $cache->data['link']; + cache_clear_all($cid, 'cache'); + } + else { + $theurl = devshop_hosting_site_url($node); + } + + drupal_goto($theurl); + exit(); +} + +/** + * Override for _hosting_site_url(). + * @param $node + * + * Allows us to check for project live domain alias. + */ +function devshop_hosting_site_url($node) { + $schema = 'http'; + $port = null; + + if (isset($node->project_nid)) { + $project = node_load($node->project_nid); + $url = "{$node->environment}.{$project->base_url}"; + } + else { + $url = strtolower(trim($node->title)); + } + + $platform = node_load($node->platform); + $server = node_load($platform->web_server); + + + if ($server->services['http']->has_port()) { + $port = $server->services['http']->port; + if ($port == 80) { + $port = null; + } + } + + /** + * This is part of the ssl feature, but is better to implement here. + */ + if (isset($node->ssl_enabled) && ($node->ssl_enabled == 2)) { + // this is a bit of a magic number, because we cant rely on the constant being available yet. + // 2 == Only SSL is enabled. + $schema = 'https'; + + if ($server->services['http']->has_port()) { + $port = $server->services['http']->ssl_port; + if ($port == 443) { + $port = null; + } + } + } + + if (is_numeric($port)) { + return "{$schema}://{$url}:{$port}"; + } + + return "{$schema}://{$url}"; +} + +/** + * Implements hook_form_alter() for user_login(). + * Provides some UI enhancements. + */ +function devshop_hosting_form_user_login_alter(&$form) { + $form['pass']['#description'] = l(t('Forgot your Password?'), 'user/password'); + + // Add user register link + if (user_register_access()) { + $form['submit']['#suffix'] = t('or') . ' ' . l(t('Create an Account'), 'user/register'); + } +} + +/** + * Implements hook_form_alter() for user_login_block(). + * Hides the login block form on "devshop" page. + */ +function devshop_hosting_form_user_login_block_alter(&$form) { + if (arg(0) == 'devshop') { + $form = array(); + } +} + +/** + * Page callback for devshop site status. + * @return string + */ +function devshop_hosting_sites_status() { + + $query = db_query("SELECT nid FROM {node} WHERE type = :type AND status = :status", array(':type' => 'project', ':status' => 1)); + while ($result = db_fetch_object($query)) { + $node = node_load($result->nid); + + foreach ($node->project->environments as $environment) { + if ($environment->site_status == HOSTING_SITE_ENABLED && count($environment->ip_addresses)) { + $ip = array_shift($environment->ip_addresses); + $items[] = "$ip $environment->uri"; + } + } + } + return theme('item_list', array('items' => $items, 'title' => t('Site Hosts'))); +} + +/** + * Special callback that waits for the login task to become available. + */ +function devshop_hosting_login_reset($site_nid) { + + $site = node_load($site_nid); + + // Set time limit to 3 minutes. + drupal_set_time_limit(180); + + // Create new reset-login task, if needed + $cache = cache_get("hosting:site:" . $site_nid . ":login_link"); + if (!($cache && (REQUEST_TIME < $cache->data['expire']))) { + $task = hosting_add_task($site_nid, 'login-reset'); + drupal_get_messages(); + } + else { + $tasks = hosting_get_tasks('rid', $site_nid, 'login-reset'); + } + + // Wait for it to complete + while ($task->task_status == HOSTING_TASK_QUEUED || $task->task_status == HOSTING_TASK_PROCESSING) { + sleep(1); + $cache = cache_get("hosting:site:" . $site_nid . ":login_link"); + if ($cache && (REQUEST_TIME < $cache->data['expire'])) { + break; + } + } + + // Output link to login. + $title = t('Log in'); + $close = t('Close'); + $login_text = t('Log in to !site', array('!site' => $site->environment->uri)); + $url = url("node/{$site_nid}/goto_site"); + + print << + + + + +HTML; + +} + +/** + * Replacement for hosting_get_tasks() that will load tasks for the platform and the site, as well as clone targets. + * @param $environment + * The environment object. + * + * @param null $type + * The task type you wish to retrieve. + * + * @param null $key + * + * If $key is specified, the return array will be re-ordered, with the key of + * the array being set to $task_node->$key. + * + * For example, if you want all tasks for an environment, sorted by date, use + * 'vid' for the $key parameter: + * + * $tasks = devshop_get_tasks($environment, NULL, 'vid'); + * + * @return array + * + * By default, an array is returned with Task types as the keys. Each element + * in the array is another array, keyed by VID. + * + */ +function devshop_get_tasks($environment, $type = NULL, $key = NULL, $limit = 20) { + + // Load tasks from hosting_task table for both site and platform. + $args = array(); + if ($environment->site) { + if ($type) { + $where = "(t.rid = :site OR t.rid = :platform) AND t.task_type = :type"; + $args = array( + ':site' => $environment->site, + ':platform' => $environment->platform, + ':type' => $type + ); + } + else { + $where = "(t.rid = :site OR t.rid = :platform)"; + $args = array( + ':site' => $environment->site, + ':platform' => $environment->platform, + ); + } + } + else { + if ($type) { + $where = "(t.rid = :platform AND t.task_type = :type)"; + $args = array( + ':platform' => $environment->platform, + ':type' => $type + ); + } + else { + $where = "(t.rid = :platform)"; + $args = array( + ':platform' => $environment->platform, + ); + } + } + + // Ensure limit is safe. + if (!is_int($limit) || $limit <= 0) { + $limit = 20; + } + $results = db_query(" + SELECT t.nid, t.task_type + FROM {node} tn + INNER JOIN {hosting_task} t ON tn.nid = t.nid + WHERE + $where + ORDER BY nid DESC + LIMIT $limit + ", + $args + ); + $tasks = array(); + foreach ($results as $result) { + $tasks[$result->task_type][$result->nid] = node_load($result->nid); + } + + // Load "Clone" tasks + if ($type == NULL || $type == 'clone') { + $results = db_query( + " + SELECT ta.nid + FROM {hosting_task_arguments} ta + INNER JOIN {hosting_task} t ON ta.nid = t.nid + WHERE + name = :name AND value = :platform + LIMIT 1 + ", + array( + ':platform' => $environment->platform, + ':name' => 'target_platform', + ) + ); + foreach ($results as $result) { + $task_node = node_load($result->nid); + $tasks[$task_node->task_type][$task_node->nid] = $task_node; + } + } + + // If key is requested... + if ($key) { + foreach ($tasks as $task_type => $task_list) { + foreach ($task_list as $task) { + $new_task_list[$task->{$key}] = $task; + } + } + if ($key == 'nid') { + krsort($new_task_list); + } + else { + ksort($new_task_list); + } + return $new_task_list; + } + else { + ksort($tasks); + return $tasks; + } +} diff --git a/modules/devshop/devshop_permissions/devshop_permissions.features.user_permission.inc b/modules/devshop/devshop_permissions/devshop_permissions.features.user_permission.inc new file mode 100644 index 000000000..95e9199b1 --- /dev/null +++ b/modules/devshop/devshop_permissions/devshop_permissions.features.user_permission.inc @@ -0,0 +1,749 @@ + 'access content', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: access disabled sites + $permissions['access disabled sites'] = array( + 'name' => 'access disabled sites', + 'roles' => array( + '0' => 'administrator', + '1' => 'anonymous user', + '2' => 'authenticated user', + ), + ); + + // Exported permission: access filemanager + $permissions['access filemanager'] = array( + 'name' => 'access filemanager', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: access hosting logs + $permissions['access hosting logs'] = array( + 'name' => 'access hosting logs', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: access hosting wizard + $permissions['access hosting wizard'] = array( + 'name' => 'access hosting wizard', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: access project logs + $permissions['access project logs'] = array( + 'name' => 'access project logs', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: access task logs + $permissions['access task logs'] = array( + 'name' => 'access task logs', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer SSH public keys + $permissions['administer SSH public keys'] = array( + 'name' => 'administer SSH public keys', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: administer clients + $permissions['administer clients'] = array( + 'name' => 'administer clients', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: administer hosting + $permissions['administer hosting'] = array( + 'name' => 'administer hosting', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer hosting aliases + $permissions['administer hosting aliases'] = array( + 'name' => 'administer hosting aliases', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer hosting backup queue + $permissions['administer hosting backup queue'] = array( + 'name' => 'administer hosting backup queue', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer hosting features + $permissions['administer hosting features'] = array( + 'name' => 'administer hosting features', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer hosting queues + $permissions['administer hosting queues'] = array( + 'name' => 'administer hosting queues', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer hosting settings + $permissions['administer hosting settings'] = array( + 'name' => 'administer hosting settings', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer hosting site backup manager + $permissions['administer hosting site backup manager'] = array( + 'name' => 'administer hosting site backup manager', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer platforms + $permissions['administer platforms'] = array( + 'name' => 'administer platforms', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer projects + $permissions['administer projects'] = array( + 'name' => 'administer projects', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer servers + $permissions['administer servers'] = array( + 'name' => 'administer servers', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: administer sites + $permissions['administer sites'] = array( + 'name' => 'administer sites', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: administer tasks + $permissions['administer tasks'] = array( + 'name' => 'administer tasks', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: cancel own tasks + $permissions['cancel own tasks'] = array( + 'name' => 'cancel own tasks', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: configure devshop pull + $permissions['configure devshop pull'] = array( + 'name' => 'configure devshop pull', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create backup schedules + $permissions['create backup schedules'] = array( + 'name' => 'create backup schedules', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create backup task + $permissions['create backup task'] = array( + 'name' => 'create backup task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create backup-delete task + $permissions['create backup-delete task'] = array( + 'name' => 'create backup-delete task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create client + $permissions['create client'] = array( + 'name' => 'create client', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: create clone task + $permissions['create clone task'] = array( + 'name' => 'create clone task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create delete task + $permissions['create delete task'] = array( + 'name' => 'create delete task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create devshop-delete task + $permissions['create devshop-delete task'] = array( + 'name' => 'create devshop-delete task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create devshop-deploy task + $permissions['create devshop-deploy task'] = array( + 'name' => 'create devshop-deploy task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create disable task + $permissions['create disable task'] = array( + 'name' => 'create disable task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create download task + $permissions['create download task'] = array( + 'name' => 'create download task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create enable task + $permissions['create enable task'] = array( + 'name' => 'create enable task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create flush_cache task + $permissions['create flush_cache task'] = array( + 'name' => 'create flush_cache task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create flush_drush_cache task + $permissions['create flush_drush_cache task'] = array( + 'name' => 'create flush_drush_cache task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create lock task + $permissions['create lock task'] = array( + 'name' => 'create lock task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create login-reset task + $permissions['create login-reset task'] = array( + 'name' => 'create login-reset task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create migrate task + $permissions['create migrate task'] = array( + 'name' => 'create migrate task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create package + $permissions['create package'] = array( + 'name' => 'create package', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: create platform + $permissions['create platform'] = array( + 'name' => 'create platform', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create project + $permissions['create project'] = array( + 'name' => 'create project', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create rebuild_registry task + $permissions['create rebuild_registry task'] = array( + 'name' => 'create rebuild_registry task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create restore task + $permissions['create restore task'] = array( + 'name' => 'create restore task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create run_cron task + $permissions['create run_cron task'] = array( + 'name' => 'create run_cron task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create server + $permissions['create server'] = array( + 'name' => 'create server', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: create site + $permissions['create site'] = array( + 'name' => 'create site', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create site aliases + $permissions['create site aliases'] = array( + 'name' => 'create site aliases', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create sites on locked platforms + $permissions['create sites on locked platforms'] = array( + 'name' => 'create sites on locked platforms', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create sync task + $permissions['create sync task'] = array( + 'name' => 'create sync task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create unlock task + $permissions['create unlock task'] = array( + 'name' => 'create unlock task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create update task + $permissions['create update task'] = array( + 'name' => 'create update task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: create verify task + $permissions['create verify task'] = array( + 'name' => 'create verify task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: delete own client + $permissions['delete own client'] = array( + 'name' => 'delete own client', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: delete package + $permissions['delete package'] = array( + 'name' => 'delete package', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: delete platform + $permissions['delete platform'] = array( + 'name' => 'delete platform', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: delete projects + $permissions['delete projects'] = array( + 'name' => 'delete projects', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: delete server + $permissions['delete server'] = array( + 'name' => 'delete server', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: delete site + $permissions['delete site'] = array( + 'name' => 'delete site', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: edit client uname + $permissions['edit client uname'] = array( + 'name' => 'edit client uname', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: edit client users + $permissions['edit client users'] = array( + 'name' => 'edit client users', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: edit own client + $permissions['edit own client'] = array( + 'name' => 'edit own client', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: edit package + $permissions['edit package'] = array( + 'name' => 'edit package', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: edit platform + $permissions['edit platform'] = array( + 'name' => 'edit platform', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: edit project + $permissions['edit project'] = array( + 'name' => 'edit project', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: edit server + $permissions['edit server'] = array( + 'name' => 'edit server', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: edit site + $permissions['edit site'] = array( + 'name' => 'edit site', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: manage any SSH public keys + $permissions['manage any SSH public keys'] = array( + 'name' => 'manage any SSH public keys', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: manage own SSH public keys + $permissions['manage own SSH public keys'] = array( + 'name' => 'manage own SSH public keys', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: retry failed tasks + $permissions['retry failed tasks'] = array( + 'name' => 'retry failed tasks', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: update status of tasks + $permissions['update status of tasks'] = array( + 'name' => 'update status of tasks', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view any SSH public keys + $permissions['view any SSH public keys'] = array( + 'name' => 'view any SSH public keys', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: view client + $permissions['view client'] = array( + 'name' => 'view client', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: view git commit logs + $permissions['view git commit logs'] = array( + 'name' => 'view git commit logs', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view locked platforms + $permissions['view locked platforms'] = array( + 'name' => 'view locked platforms', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view own SSH public keys + $permissions['view own SSH public keys'] = array( + 'name' => 'view own SSH public keys', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view own tasks + $permissions['view own tasks'] = array( + 'name' => 'view own tasks', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view package + $permissions['view package'] = array( + 'name' => 'view package', + 'roles' => array( + '0' => 'administrator', + ), + ); + + // Exported permission: view platform + $permissions['view platform'] = array( + 'name' => 'view platform', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view project + $permissions['view project'] = array( + 'name' => 'view project', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view projects + $permissions['view projects'] = array( + 'name' => 'view projects', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view server + $permissions['view server'] = array( + 'name' => 'view server', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view site + $permissions['view site'] = array( + 'name' => 'view site', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + // Exported permission: view task + $permissions['view task'] = array( + 'name' => 'view task', + 'roles' => array( + '0' => 'administrator', + '1' => 'authenticated user', + ), + ); + + return $permissions; +} diff --git a/modules/devshop/devshop_permissions/devshop_permissions.info b/modules/devshop/devshop_permissions/devshop_permissions.info new file mode 100644 index 000000000..bf72bb6ac --- /dev/null +++ b/modules/devshop/devshop_permissions/devshop_permissions.info @@ -0,0 +1,90 @@ +name = "DevShop Permissions" +description = "Default user permissions for devshop." +core = 7.x +package = "Features" +dependencies[] = "devshop_projects" +dependencies[] = "features" +features[user_permission][] = "access content" +features[user_permission][] = "access disabled sites" +features[user_permission][] = "access filemanager" +features[user_permission][] = "access hosting logs" +features[user_permission][] = "access hosting wizard" +features[user_permission][] = "access project logs" +features[user_permission][] = "access task logs" +features[user_permission][] = "administer SSH public keys" +features[user_permission][] = "administer clients" +features[user_permission][] = "administer hosting" +features[user_permission][] = "administer hosting aliases" +features[user_permission][] = "administer hosting backup queue" +features[user_permission][] = "administer hosting features" +features[user_permission][] = "administer hosting queues" +features[user_permission][] = "administer hosting settings" +features[user_permission][] = "administer hosting site backup manager" +features[user_permission][] = "administer platforms" +features[user_permission][] = "administer projects" +features[user_permission][] = "administer servers" +features[user_permission][] = "administer sites" +features[user_permission][] = "administer tasks" +features[user_permission][] = "cancel own tasks" +features[user_permission][] = "configure devshop pull" +features[user_permission][] = "create backup schedules" +features[user_permission][] = "create backup task" +features[user_permission][] = "create backup-delete task" +features[user_permission][] = "create client" +features[user_permission][] = "create clone task" +features[user_permission][] = "create delete task" +features[user_permission][] = "create devshop-delete task" +features[user_permission][] = "create devshop-deploy task" +features[user_permission][] = "create disable task" +features[user_permission][] = "create download task" +features[user_permission][] = "create enable task" +features[user_permission][] = "create flush_cache task" +features[user_permission][] = "create flush_drush_cache task" +features[user_permission][] = "create lock task" +features[user_permission][] = "create login-reset task" +features[user_permission][] = "create migrate task" +features[user_permission][] = "create package" +features[user_permission][] = "create platform" +features[user_permission][] = "create project" +features[user_permission][] = "create rebuild_registry task" +features[user_permission][] = "create restore task" +features[user_permission][] = "create run_cron task" +features[user_permission][] = "create server" +features[user_permission][] = "create site" +features[user_permission][] = "create site aliases" +features[user_permission][] = "create sites on locked platforms" +features[user_permission][] = "create sync task" +features[user_permission][] = "create unlock task" +features[user_permission][] = "create update task" +features[user_permission][] = "create verify task" +features[user_permission][] = "delete own client" +features[user_permission][] = "delete package" +features[user_permission][] = "delete platform" +features[user_permission][] = "delete projects" +features[user_permission][] = "delete server" +features[user_permission][] = "delete site" +features[user_permission][] = "edit client uname" +features[user_permission][] = "edit client users" +features[user_permission][] = "edit own client" +features[user_permission][] = "edit package" +features[user_permission][] = "edit platform" +features[user_permission][] = "edit project" +features[user_permission][] = "edit server" +features[user_permission][] = "edit site" +features[user_permission][] = "manage any SSH public keys" +features[user_permission][] = "manage own SSH public keys" +features[user_permission][] = "retry failed tasks" +features[user_permission][] = "update status of tasks" +features[user_permission][] = "view any SSH public keys" +features[user_permission][] = "view client" +features[user_permission][] = "view git commit logs" +features[user_permission][] = "view locked platforms" +features[user_permission][] = "view own SSH public keys" +features[user_permission][] = "view own tasks" +features[user_permission][] = "view package" +features[user_permission][] = "view platform" +features[user_permission][] = "view project" +features[user_permission][] = "view projects" +features[user_permission][] = "view server" +features[user_permission][] = "view site" +features[user_permission][] = "view task" diff --git a/modules/devshop/devshop_permissions/devshop_permissions.module b/modules/devshop/devshop_permissions/devshop_permissions.module new file mode 100644 index 000000000..5f0757208 --- /dev/null +++ b/modules/devshop/devshop_permissions/devshop_permissions.module @@ -0,0 +1,3 @@ +tagline = t('Hosted by DevShop'); +} \ No newline at end of file diff --git a/modules/devshop/devshop_projects/devshop_projects.drush.inc b/modules/devshop/devshop_projects/devshop_projects.drush.inc new file mode 100644 index 000000000..1bae652d0 --- /dev/null +++ b/modules/devshop/devshop_projects/devshop_projects.drush.inc @@ -0,0 +1,587 @@ +ref; + if (($ref->type == 'site' || $ref->type == 'platform') && isset($ref->environment)) { + $ref->environment->last_task = $task->nid; + + // @TODO: Look for environment_target on clone task, so we can update that environment's last task as well. + + // Prevent "Verify" and "reset password" tasks from cluttering up the environment status indicator. + // If last task is a successful verify or reset password, load the task before that as the "last task". + if (($task->task_type == 'verify' || $task->task_type == 'login-reset') && $status == HOSTING_TASK_SUCCESS) { + + // If this object is a platform, lookup the latest tasks from the site node. + $tasks_list = hosting_get_tasks('rid', $ref->nid); + + // The first item in $tasks should be this task. + // If so, use the next task as the "last_task", because verify and reset password tasks are not important. + foreach ($tasks_list as &$task_list_item) { + // If the first task is this task, we want to skip it. + if ($task_list_item->nid == $task->nid && $task_list_item->vid == $task->vid) { + continue; + } + // If the next task is also a login-reset or verify, skip it. + if ($task_list_item->task_type == 'verify' || $task_list_item->task_type == 'login-reset') { + continue; + } + + // Grab the first task that isn't this one or another skippable task + $ref->environment->last_task = $task_list_item->nid; + + // Stop the loop through the tasks. + break; + } + } + + devshop_environment_save_last_task($ref->environment); + } +} + +/** + * Implements drush_HOOK_pre_COMMAND() + * + * This runs for each task during the command + * drush @hostmaster hosting-tasks + * + * NOTE: This ONLY runs when being called from a hostmaster task. + * This hook should ONLY be used to pass options from a hostmaster task form to + * the $task object, or if you don't need this functionality from the command + * line. + */ +function drush_devshop_projects_pre_hosting_task() +{ + + $task =& drush_get_context('HOSTING_TASK'); + + // For all tasks + if ($task->ref->type == 'site' || $task->ref->type == 'platform') { + $drush_aliases = new Provision_Config_ProjectAliases($task->ref->project->name, $task->ref->project); + } + elseif ($task->ref->type == 'project') { + $drush_aliases = new Provision_Config_ProjectAliases(d($task->ref->hosting_name)->name, d($task->ref->hosting_name)->project); + } + + // Only write drush aliases if project name was found. + if (!empty($drush_aliases->data['name'])) { + $drush_aliases->write(); + drush_log(dt('Drush aliases written for %name project.', [ + '%name' => $drush_aliases->data['name'], + ]), 'devshop_log'); + } + + + // Passing options for Download task. This should go in aegir_download.drush.inc but I can't get that hook working right now. + if ($task->task_type == 'download') { + drush_log('[AEGIR DEVSHOP_PROJECTS] Download package enabled...', 'ok'); + + $task->options['packages'] = $task->task_args['packages']; + $task->options['commit'] = $task->task_args['update']; + $task->options['message'] = $task->task_args['message']; + $task->options['update'] = $task->task_args['update']; + } + + // Verify Platform + // Here is where we hook in and clone the site + // @TODO: This can be removed once hosting_git migration is complete. +// if ($task->ref->type == 'platform' && $task->task_type == 'verify' && !empty($task->ref->project->git_url)) { +// drush_devshop_platform_verify(); +// } + + // Pull + if ($task->ref->type == 'project' && $task->task_type == 'devshop-pull') { + $task->args['environments'] = $task->task_args['environments']; + $task->options['update'] = $task->task_args['update']; + $task->options['revert'] = !empty($task->task_args['revert']); + $task->options['cache'] = $task->task_args['cache']; + $task->options['force'] = FALSE; + } + + // Deploy + if ($task->ref->type == 'site' && $task->task_type == 'devshop-deploy') { + + // The git_ref is the only argument + $task->args['git_ref'] = $task->task_args['git_ref']; + + // Just load in all of the task_args as options(they come from the task form.) + foreach ($task->task_args as $arg => $value) { + $task->options[$arg] = $value; + } + } + + // Run tests + if ($task->ref->type == 'site' && $task->task_type == 'test') { + + $task->options['tests-to-run'] = $task->task_args['tests_to_run']; + $task->options['test-type'] = $task->task_args['test_type']; + } + + // On every task triggered by devshop, set this option. + $task->options['is-devshop'] = TRUE; +} + +/** + * Implementation of hook_post_hosting_TASK_TYPE_task() + * for Verify tasks. + * + * Provides the "Fork environment" feature: Checks out a new branch and pushes it. + */ +function devshop_projects_post_hosting_verify_task($task, $data) { + if ($task->ref->type == 'platform') { + + // Fork Environment: + // If this is the first verification, and branch needs to be created (for "fork environment") + $platform = $task->ref; + if (!$platform->verified && !empty($platform->environment->settings->branch_to_fork)) { + devshop_process("git checkout -b {$platform->environment->git_ref}", $platform->repo_path, dt('Creating a new branch.')); + + // Push the branch + devshop_process("git push -u origin {$platform->environment->git_ref}", $platform->repo_path, dt('Pushing new branch.')); + } +// // Run 'composer install' if configured to do so. +// if ($platform->environment->settings->deploy['composer'] || $platform->project->settings->deploy['default_hooks']['composer']) { +// +// if (file_exists($platform->git['repo_path'] . '/composer.json')) { +// $cwd = $platform->git['repo_path']; +// } +// elseif (file_exists($platform->publish_path . '/composer.json')) { +// $cwd = $platform->publish_path; +// } +// else { +// // If composer.json was not found in the drupal root or repo root, do not run. +// drush_log(dt('composer.json not found, even though "composer install" hook is enabled.'), 'warning'); +// return; +// } +//// +//// devshop_process("composer install", $cwd, dt('composer.json detected, running composer install in !folder', array( +//// '!folder' => $cwd, +//// ))); +// +// devshop_process("git status", $platform->git['repo_path'], dt('Running git status')); +// } + } +} + +// @TODO: Remove this function for hosting_git migration. +/** + * Pre hosting task hook for "verify platform" task. + * - Clones the repository on first run, checks out the selected after that. + * + * Not a hook! Called from drush_devshop_projects_pre_hosting_task + */ +//function drush_devshop_platform_verify() { + +// drush_log(dt("[DEVSHOP] Hello. Preparing aegir platform codebase...")); +// + // Verify Platform +// $task = &drush_get_context('HOSTING_TASK'); + +// $platform = $task->ref; + + // If for some reason we still do not have an environment... bail. +// if (empty($platform->environment)) { +// return drush_set_error('DEVSHOP_ERROR', dt('Unable to determine environment. Please contact your administrator.')); +// } + +// $root = $platform->publish_path; +// $git_ref = $platform->environment->git_ref; +// +// $git_remote = $platform->project->git_url; +// $drupal_path = $platform->project->drupal_path; +// +// Remove drupal_path to clone. +// if ($drupal_path) { +// $root = str_replace($drupal_path, '', $root); +// } + + // Check if the platform code exists. If it doesn't, clone it. +// if (!is_dir($root)) { +// +// // Clone the repo. +// devshop_drush_process("git clone --recursive $git_remote $root", NULL, dt('Cloning git repository')); + + // Skip errors about submodule + + + // If there is "site_to_clone", checkout that site's git ref. + // This is done for Pull Request Environments. When a PR comes in, we want + // to clone the site to an identical platform first. This will always succeed. + // Then, we trigger a deployment to the new branch. + // + // This ensures that initial pull request environments run the same "deploy" + // hooks. It means we don't have to hack into the "clone" task to run deploy + // hooks anymore. +// if ($platform->environment->settings->site_to_clone) { +// $site = node_load($platform->environment->settings->site_to_clone); +// $git_ref = $site->environment->git_ref; +// } + + // If there is a git ref, check it out. + // We don't do this on git clone, because it could be a tag. + // @TODO: Won't the platform ALWAYS have a git_branch? +// if ($git_ref) { +// devshop_drush_process("git checkout $git_ref", $root, dt('Checking out specified version')); +// } +// } +// +// // If this is the first verification, and branch needs to be created (for "fork environment") +// if (!$platform->verified && !empty($platform->environment->settings->branch_to_fork)) { +// devshop_drush_process("git checkout -b {$platform->environment->git_ref}", $root, dt('Creating a new branch.')); +// +// // Push the branch +// devshop_drush_process("git push -u origin {$platform->environment->git_ref}", $root, dt('Pushing new branch.')); +// } +// +// // Detect a bad path_to_drupal +// if (!file_exists($platform->environment->root . '/index.php')) { +// return drush_set_error('DEVSHOP_ERROR', dt($platform->environment->root . '/index.php file not found. Check "Path to Drupal" in Project Settings.')); +// } +// +// // When verifying the platform for the first time, run 'composer install' if configured to do so. +// if ($platform->verified && $platform->environment->settings->deploy['composer'] || $platform->project->settings->deploy['default_hooks']['composer']) { +// +// if (file_exists($platform->environment->repo_root . '/composer.json')) { +// $cwd = $platform->environment->repo_root; +// } +// elseif (file_exists($root . '/composer.json')) { +// $cwd = $root; +// } +// else { +// // If composer.json was not found in the drupal root or repo root, do not run. +// drush_log(dt('composer.json not found, even though "composer install" hook is enabled.'), 'warning'); +// return; +// } +// +// devshop_drush_process("composer install", $cwd, dt('composer.json detected, running composer install in !folder', array( +// '!folder' => $cwd, +// ))); +// +// devshop_drush_process("git status", $platform->environment->repo_root, dt('Running git status')); +// } +//} + +/** + * Implements hook_post_hosting_TASK_TYPE_task() for delete tasks(). + */ +function devshop_projects_post_hosting_delete_task($task, $data) { + + // When a project is deleted... + if ($task->ref->type == 'project') { + // Queue site deletion for each environment. + $project = $task->ref->project; + foreach ($project->environments as $environment) { + // If site exists, trigger deletion. + if ($environment->site) { + hosting_add_task($environment->site, 'delete'); + } + // If platform exists, trigger deletion. + elseif ($environment->platform) { + hosting_add_task($environment->platform, 'delete', array('force' => 1)); + } + } + + // @TODO: Should probably add our own status column + // Unpublish the project node. + $task->ref->status = 0; + $task->ref->no_verify = TRUE; + node_save($task->ref); + } + + // When a site is deleted (if it is in a project, delete the platform it is on. + // @TODO: Check for another site on this platform? + if ($task->ref->type == 'site' && !empty($task->ref->project)) { + // We trigger platform deletion here. + hosting_add_task($task->ref->platform, 'delete'); + } + + // When a platform is deleted (if it is in a project), delete the environment record. + if ($task->ref->type == 'platform' && !empty($task->ref->project)) { + db_delete('hosting_devshop_project_environment') + ->condition('project_nid', $task->ref->project->nid) + ->condition('platform', $task->ref->platform) + ->execute(); + + // If drupal root is not repo root, delete folder at code_path. + $repo_path = str_replace($task->ref->project->drupal_path, '', $task->ref->publish_path); + if (file_exists($repo_path) ) { + _provision_recursive_delete($repo_path); + } + } + + // When a platform is deleted, if it is the last in the project, + // and the project has been unpublished, delete the directory. + if ($task->ref->type == 'platform' && !empty($task->ref->project)) { + if ($task->ref->project->status == 0 && count($task->ref->project->environments) == 1) { + // Delete the project folder. + exec("rm -rf {$project->code_path}"); + } + } +} + +/** + * Implements hook_post_hosting_TASK_TYPE_task() for install. + */ +function devshop_projects_post_hosting_install_task($task, $data) { + + // Queue up a verification of the site. + hosting_add_task($task->ref->nid, 'verify'); + +} + + /** + * Implements hook_post_hosting_TASK_TYPE_task() for devshop deploy tasks(). + */ +function devshop_projects_post_hosting_devshop_deploy_task($task, $data) { + + // Save the deployed git ref to the environment record. + // Doing this post deploy ensures it was actually checked out. + drush_log('[DEVSHOP] Environment Deployed. Saving new git ref to project settings.'); + + $git_ref = trim(str_replace('refs/heads/', '', shell_exec("cd {$task->ref->environment->repo_path}; git describe --tags --exact-match || git symbolic-ref -q HEAD")));; + + $args = array( + ':git' => $git_ref, + ':site' => $task->ref->environment->site, + ); + + if (db_query('UPDATE {hosting_git} SET git_ref = :git WHERE nid = :site', $args)) { + drush_log("[DEVSHOP] Git reference {$git_ref} saved to {$task->ref->environment->name} environment record."); + } + else { + return drush_set_error('[DEVSHOP] Environment update failed! Unable to save git ref to hosting_devshop_project_environment table.'); + } + + // Queue up a verification of the platform. + hosting_add_task($task->ref->environment->platform, 'verify'); + + // Queue up a verification of the site. + hosting_add_task($task->ref->nid, 'verify'); +} + +// @TODO: Remove this function. This is not needed now that we create site nodes at environment create time. +///** +// * Implements hook_post_hosting_TASK_TYPE_task(). +// * +// * Acts after the successful verification of platforms. +// * This is where the sites get automatically created. +// */ +//function devshop_projects_post_hosting_verify_task($task, $data) { +// + // If this is a new platform created by a project, we will create a site here. +// if ($task->ref->type == 'platform') { +// drush_log('[DevShop] Codebase Clone Complete... Preparing next steps.', 'devshop_log'); + +// // If this platform isn't in a project, bail. +// $platform = $task->ref; +// if (!isset($platform->project)) { +// $project = $task->ref->project; +// +// // @TODO: On first verify, "environment" can be missing. Not sure why, yet. +// if (empty($platform->environment) && isset($project->environments[$task->project->environment])) { +// $platform->environment = $project->environments[$task->project->environment]; +// } +// +// drush_log('[DEVSHOP] No project found for this platform.', 'notice'); +// return; +// } +// +// // If the project doesn't have an install profile chosen yet, bail. +// if (empty($platform->project->install_profile)) { +// drush_log('[DEVSHOP] No install profile found for this project. Not doing anything.', 'ok'); +// return; +// } +// +// // If the platform has a site already, trigger verification, then bail. +// if (!empty($platform->environment->site)){ +// drush_log('[DEVSHOP] Platform already has a site.', 'notice'); +// // hosting_add_task($platform->environment->site, 'verify'); +// return; +// } +// else { +// +// // Check for site to clone +// if (!empty($platform->environment->settings->site_to_clone)) { +// $site = node_load($platform->environment->settings->site_to_clone); +// $args['target_platform'] = $platform->nid; +// +// // Ensure new URI doesn't contain underscores or other invalid characters. +// $args['new_uri'] = str_replace('_', '-', $platform->environment->name) . '.' . $platform->project->base_url; +// +// // Use the default database server for the project. +// $args['new_db_server'] = $site->db_server; +// $args['environment_target'] = $platform->environment->name; +// drush_log('[DEVSHOP] Cloning environment: ' . $platform->environment->name); +// +// // Queue the Cloning of the site. +// // @see devshop_projects_post_hosting_import_task(). +// +// // If, for some reason, we are still missing names, abort. +// if (empty($site->environment->name) || empty($platform->environment->name)) { +// return drush_set_error('DEVSHOP_ERROR', 'Source or target environment not found. Contact your systems administrator.'); +// } +// +// $task = hosting_add_task($platform->environment->settings->site_to_clone, 'clone', $args); +// +// $link = l(t('View Logs'), "node/{$task->nid}"); +// drush_log("[DevShop] Environment '{$site->environment->name}' is scheduled to be cloned to '{$platform->environment->name}'. $link", 'devshop_log'); +// } +// else { +// +// // Determine what DB server to use +// if (isset($platform->environment->settings->db_server)) { +// $db_server = $platform->environment->settings->db_server; +// } +// else { +// $db_server = $platform->project->settings->default_environment['db_server']; +// } +// +// if (empty($platform->environment->name)) { +// return drush_set_error('DEVSHOP_ERROR', 'Missing environment name. Unable to create new site.'); +// } +// else { +// $new_site = devshop_projects_create_site($platform->project, $platform, $platform->environment->name, $db_server); +// drush_log("[DevShop] Environment '{$platform->environment->name}' is scheduled to be installed. Please wait...", 'devshop_log'); +// } +// } +// } +// } +//} + +///** +// * Implements hook_post_hosting_TASK_TYPE_task(). +// */ +//function devshop_projects_post_hosting_install_task($task, $data) { +// +// // Now that environment data is stored in project data we need to verify projects a lot. +// if ($task->ref->type == 'site' && isset($task->ref->project->nid)) { +// hosting_add_task($task->ref->project->nid, 'verify'); +// } +//} + +// @TODO: Remove this. We no longer use Clone task. +///** +// * Implements hook_post_hosting_TASK_TYPE_task(). +// */ +//function devshop_projects_post_hosting_clone_task($task, $data) { +// +// // Now that environment data is stored in project data we need to verify projects a lot. +// if ($task->ref->type == 'site' && isset($task->ref->project->nid)) { +// hosting_add_task($task->ref->project->nid, 'verify'); +// } +//} + +///** +// * Implements hook_post_hosting_TASK_TYPE_task(). +// * +// * Ensures that cloned sites that are on project platforms inherit the project. +// */ +//function devshop_projects_post_hosting_import_task($task, $data) { +// +// // Now that environment data is stored in project data we need to verify projects a lot. +// if ($task->ref->type == 'site' && isset($task->ref->project->nid)) { +// hosting_add_task($task->ref->project->nid, 'verify'); +// } +// +// // If this is a new platform created by a project, we will create a site here. +// if ($task->ref->type == 'site') { +// $site = $task->ref; +// $platform = node_load($site->platform); +// +// if (isset($platform->environment) && !empty($platform->environment->name)) { +// +// // Save environment record. +// drush_log('[DevShop Environment] Saving environment data...', 'devshop_command'); +// db_update('hosting_devshop_project_environment') +// ->fields(array( +// 'site' => $site->nid, +// )) +// ->condition('project_nid', $platform->project->nid) +// ->condition('name', $platform->environment->name) +// ->execute(); +// drush_log("Saved {$platform->environment->name} in project {$platform->project->name}.", 'devshop_ok'); +// +// // Save aliases if needed. +// if ($platform->project->settings->live['environment_aliases'] && !empty($platform->project->settings->live['live_domain'])) { +// $domain = $platform->environment->name . '.' . $platform->project->settings->live['live_domain']; +// +// $site->aliases[] = $domain; +// drush_log('[DevShop Environment] Saving environment data', 'devshop_command'); +// $site->no_verify = TRUE; +// node_save($site); +// +// } +// +// // If we have been cloned from another environment, run a deploy task. +// if (!empty($platform->environment->settings->clone_source)) { +// +// // Trigger a verify of the site, so the alias has project information. +// hosting_add_task($site->nid, 'verify'); +// +// // Trigger a deploy using the project's default hooks. +// $args = $platform->project->settings->deploy['default_hooks']; +// $args['git_ref'] = $platform->environment->git_ref; +// +// // Handle a Pull Request. If there is one, we want to deploy to the PR branch. +// if (!empty($platform->environment->github_pull_request->pull_request_object->head->ref)) { +// $args['git_ref'] = $platform->environment->github_pull_request->pull_request_object->head->ref; +// } +// +// hosting_add_task($site->nid, 'devshop-deploy', $args); +// +// } else { +// // Trigger a verify of the site, so the alias has project information. +// hosting_add_task($site->nid, 'verify'); +// } +// } +// } +//} +// +///** +// * Utility for execute git commands. +// */ +//function _devshop_projects_git_execute($commands, $root = '') { +// // Execute +// $return = ''; +// if (is_string($commands)) { +// $commands = array($commands); +// } +// foreach ($commands as $command) { +// if (!empty($command)) { +// drush_log("[DEVSHOP] Running Command in $root: $command"); +// +// // @TODO: Create a d()->server->shell_cd_and_exec() function +// // server->shell_exec() uses escapeshellcmd(), so we cannot cd_and_exec! +// // So instead, this is the code from d()->server->shell_exec +// // d()->server->shell_exec($cmd); +// if (provision_is_local_host(d()->server->remote_host)) { +// +// // Run in $root if it exists. +// if (file_exists($root)) { +// drush_shell_cd_and_exec($root, escapeshellcmd($command)); +// } +// else { +// drush_shell_exec(escapeshellcmd($command)); +// } +// } +// else { +// drush_shell_cd_and_exec($root, 'ssh ' . drush_get_option('ssh-options', '-o PasswordAuthentication=no') . ' %s %s', $this->script_user . '@' . d()->server->remote_host, escapeshellcmd($command)); +// } +// +// $output = drush_shell_exec_output(); +// drush_log('Shell Output: ' . implode("\n", $output), 'ok'); +// $return .= implode("\n", $output); +// } +// } +// return $return; +//} diff --git a/modules/devshop/devshop_projects/devshop_projects.info b/modules/devshop/devshop_projects/devshop_projects.info new file mode 100644 index 000000000..38f30d85a --- /dev/null +++ b/modules/devshop/devshop_projects/devshop_projects.info @@ -0,0 +1,29 @@ +name = DevShop Projects +description = A DevShop module to sites/platforms together into projects. +core = 7.x +package = DevShop + +files[] = inc/create/create.inc +files[] = inc/create/step-1.inc +files[] = inc/create/step-2.inc +files[] = inc/create/step-3.inc +files[] = inc/create/step-4.inc +files[] = inc/admin.inc +files[] = inc/create-wizard.inc +files[] = inc/forms.inc +files[] = inc/logs.inc +files[] = inc/nodes.inc +files[] = inc/queue.inc +files[] = inc/tasks-ajax.inc +files[] = inc/ui.inc +files[] = tasks/commit.inc +files[] = tasks/create.inc +files[] = tasks/deploy.inc +files[] = tasks/pull.inc +files[] = tasks/tasks.inc +dependencies[] = hosting +dependencies[] = hosting_alias +dependencies[] = hosting_package +dependencies[] = hosting_git +dependencies[] = hosting_git_pull +dependencies[] = ctools diff --git a/modules/devshop/devshop_projects/devshop_projects.install b/modules/devshop/devshop_projects/devshop_projects.install new file mode 100644 index 000000000..896c59ae0 --- /dev/null +++ b/modules/devshop/devshop_projects/devshop_projects.install @@ -0,0 +1,478 @@ + 7300, + ); + return $dependencies; +} + +/** + * Implements hook_install() + */ +function devshop_projects_install() { + + // Push devshop_projects's system weight to 1. + db_update('system') + ->fields(array( + 'weight' => 1 + )) + ->condition('name', 'devshop_projects') + ->execute(); +} + +/** + * Implements hook_schema(). + */ +function devshop_projects_schema() { + $schema['hosting_devshop_project'] = array( + 'fields' => array( + 'nid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Project/Node ID.', + ), + 'git_url' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'code_path' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'drupal_path' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'base_url' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'install_profile' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'settings' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + 'description' => 'A serialized array of settings for this project.', + ), + ), + 'primary key' => array('nid'), + ); + + // Table for tracking environments + $schema['hosting_devshop_project_environment'] = array( + 'fields' => array( + 'project_nid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => "The project's Node ID.", + ), + 'name' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => 64, + 'default' => '', + 'description' => 'Environment name.', + ), + 'site' => array( + 'type' => 'int', + 'not null' => FALSE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'The node ID of the site for this environment.', + ), + 'platform' => array( + 'type' => 'int', + 'not null' => FALSE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'The node ID of the platform for this environment.', + ), + 'settings' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + 'description' => 'A serialized array of settings for this environment.', + ), + 'last_task' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + 'description' => 'The NID of the last task that should be displayed on an environment.', + ), + ), + 'indexes' => array( + 'project_environment' => array('project_nid', 'name'), + ), + ); + return $schema; +} + +/** + * Add the "data" column to projects. + */ +function devshop_projects_update_1() { + $ret = array(); + $ret[] = update_sql("ALTER TABLE {hosting_devshop_project} " . + "ADD COLUMN data longtext NOT NULL default ''"); + return $ret; +} + +/* + * Update 2: Delete rows in the hosting_devshop_project_objects table + * that the node pointed to by object nid no longer exists. + */ +function devshop_projects_update_2() { + $ret = array(); + $query = db_query("SELECT object_nid " . + "FROM {hosting_devshop_project_object}"); + + while($proj = db_fetch_object($query)) { + $count = db_result(db_query("SELECT COUNT(*) FROM {node} " . + "WHERE nid = %d", $proj->object_nid)); + if ($count != 1) { + $ret[] = update_sql('DELETE FROM {hosting_devshop_project_object} ' . + 'WHERE object_nid = %d', $proj->object_nid); + } + } + + return $ret; +} + +/** + * Adds git_branch column to hosting_devshop_project_objects table. + */ +function devshop_projects_update_3() { + $ret = array(); + db_add_field($ret, 'hosting_devshop_project_object', 'git_branch', array('type' => 'varchar', 'length' => 16, 'not null' => FALSE)); + return $ret; +} + +/** + * Changes env_type to environment in {hosting_devshop_project_object}. + */ +function devshop_projects_update_4() { + $ret = array(); + $schema = devshop_projects_schema(); + $spec = $schema['hosting_devshop_project_object']['fields']['environment']; + db_change_field($ret, 'hosting_devshop_project_object', 'env_type', 'environment', $spec); + return $ret; +} + +/** + * Makes "git_branch" field larger. + */ +function devshop_projects_update_5() { + $ret = array(); + $ret[] = update_sql("ALTER TABLE {hosting_devshop_project_object} CHANGE git_branch git_branch VARCHAR(128) NULL"); + return $ret; +} + +/** + * Adds drupal_path column to hosting_devshop_project table. + */ +function devshop_projects_update_6() { + $ret = array(); + db_add_field($ret, 'hosting_devshop_project', 'drupal_path', array('type' => 'text', 'size' => 'big', 'not null' => FALSE)); + return $ret; +} + +/** + * Adds drupal_path column to hosting_devshop_project table. + */ +function devshop_projects_update_7() { + $ret = array(); + db_add_field($ret, 'hosting_devshop_project_object', 'drupal_path', array('type' => 'text', 'size' => 'big', 'not null' => FALSE)); + return $ret; +} + +/** + * Makes "environment" field larger. + */ +function devshop_projects_update_8() { + $ret = array(); + $ret[] = update_sql("ALTER TABLE {hosting_devshop_project_object} CHANGE environment environment VARCHAR(64) NOT NULL"); + + return $ret; +} + +/** + * Add clone_nid column to hosting_devshop_project_object. + */ +function devshop_projects_update_9() { + $ret = array(); + $ret[] = db_add_field($ret, 'hosting_devshop_project_object', 'clone_nid', array('type' => 'int', 'not null' => FALSE, 'default' => 0, 'unsigned' => TRUE)); + + return $ret; +} + +/** + * 6.x-1.9-beta4 -^ + * + * 6.x-1.9-rc1 -v (FUTURE RELEASE) + */ + +/** + * Remove live_domain from hosting_devshop_project schema. + */ +function devshop_projects_update_6000() { + $ret = array(); + if (db_column_exists('hosting_devshop_project', 'live_domain')){ + $ret[] = db_drop_field($ret, 'hosting_devshop_project', 'live_domain'); + } + return $ret; +} + +/** + * Create new hosting_devshop_environment table. + */ +function devshop_projects_update_6001() { + // Create only the new table + // Clone of drupal_install_schema('devshop_projects'); + $module = 'devshop_projects'; + $name = 'hosting_devshop_project_environment'; + + $schema = drupal_get_schema_unprocessed($module); + _drupal_initialize_schema($module, $schema); + + $ret = array(); + db_create_table($ret, $name, $schema['hosting_devshop_project_environment']); + return $ret; +} + +/** + * Enable hosting_alias module to allow custom domains on sites. + */ +function devshop_projects_update_6002() { + module_enable(array('hosting_alias')); + return array(); +} + +/** + * Remove legacy `hosting_devshop_project_object` table. + */ +function devshop_projects_update_6003(){ + $ret = array(); + db_drop_table($ret, 'hosting_devshop_project_object'); + return $ret; +} + +/** + * Change "data" column to "settings" column. + */ +function devshop_projects_update_6004(){ + $ret = array(); + db_change_column($ret, 'hosting_devshop_project', 'data', 'settings', 'text', array( + 'not null' => FALSE, + 'size' => 'big', + 'description' => 'A serialized array of settings for this project.', + )); + return $ret; +} + +/** + * Turn on the new Boots theme. + * Uncomment once the theme is ready. +function devshop_projects_update_600X() { + variable_set('theme_default','boots'); + return array(); +} + */ + +/** + * Add "last_task" column for environments. + */ +function devshop_projects_update_6008(){ + $schema = devshop_projects_schema(); + $ret = array(); + $ret[] = db_add_field( + $ret, + 'hosting_devshop_project_environment', + 'last_task', $schema['hosting_devshop_project_environment']['fields']['last_task'] + ); + return $ret; +} + +/** + * Update all environments with their last_task(); + */ +function devshop_projects_update_6009 () { + + // Lookup all environments + $query = db_query('SELECT * FROM {hosting_devshop_project_environment}'); + while ($result = db_fetch_object($query)) { + $site = node_load($result->site); + $result = db_fetch_object(db_query("SELECT t.nid, t.vid FROM hosting_task t INNER JOIN node n ON n.vid = t.vid WHERE rid = %d GROUP BY t.nid ORDER BY vid DESC", $site->nid)); + + $task = node_load($result->nid); + $site->environment->last_task = array( + 'status' => $task->task_status, + 'type' => $task->task_type, + 'nid' => $task->nid, + ); + devshop_environment_save_last_task($site->environment); + } +} + + +/** + * Reset all environment's "last task" to the NID. + */ +function devshop_projects_update_6010 () { + + // Lookup all environments + $query = db_query('SELECT * FROM {hosting_devshop_project_environment}'); + while ($result = db_fetch_object($query)) { + $site = node_load($result->site); + $result = db_fetch_object(db_query("SELECT t.nid, t.vid FROM hosting_task t INNER JOIN node n ON n.vid = t.vid WHERE rid = %d GROUP BY t.nid ORDER BY vid DESC", $site->nid)); + $site->environment->last_task = $result->nid; + devshop_environment_save_last_task($site->environment); + } +} + +/** + * Reset all environment's "last task" to the NID, again. + */ +function devshop_projects_update_6011 () { + devshop_reset_last_tasks(); +} + +/** + * Remove the menu form from the project node form. + */ +function devshop_projects_update_7000 () { + variable_set('menu_options_project', array()); +} + +/** + * Remove the menu form from the site node form. + */ +function devshop_projects_update_7001 () { + variable_set('menu_options_site', array()); +} + +// @TODO: This breaks upgrades. Figure out why and in include it in an upcoming release. +///** +// * Enable devshop_testing. +// */ +//function devshop_projects_update_7002 () { +// module_enable(array( +// 'devshop_testing', +// )); +//} + +/** + * Fully resetting project and site node menu settings. + */ +function devshop_projects_update_7003 () { + variable_set('menu_options_site', ''); + variable_set('menu_options_project', ''); +} + +/** + * Enable Hosting Git and Hosting Git Checkout. + */ +function devshop_projects_update_7004 () { + module_enable(array( + 'hosting_git', + 'hosting_git_pull', + )); +} + +/** + * Save git_ref data to hosting_git table and drop the devshop environment's git_ref field. + */ +function devshop_projects_update_7005 () { + $environments = db_query('SELECT * FROM {hosting_devshop_project_environment} e LEFT JOIN {hosting_devshop_project} p ON p.nid = e.project_nid'); + foreach ($environments as $environment) { + if ($environment->platform) { + db_merge('hosting_git') + ->key(array('nid' => $environment->platform)) + ->fields(array( + 'repo_url' => $environment->git_url, + 'git_ref' => $environment->git_ref, + )) + ->execute(); + drupal_set_message(t("Updated Platform $environment->platform with $environment->git_url and $environment->git_ref")); + } + else { + drupal_set_message(t("No Platform found for environment $environment->name for site $environment->site."), 'warning'); + } + } + + if (db_drop_field('hosting_devshop_project_environment', 'git_ref')) { + drupal_set_message(t("Git ref information has been migrated and the field has been removed.")); + } + else { + drupal_set_message(t("Unable to remove git_ref field from hosting_devshop_project_environment table."), 'error'); + } +} + +/** + * Reduce the character limit for bootstrap to turn form element descriptions into tooltips. + */ +function devshop_projects_update_7007 () { + // Get the settings + $settings = variable_get('theme_boots_settings', array()); + + // Set the variable + $settings['bootstrap_forms_smart_descriptions_limit'] = 40; + + // Save our settings + variable_set('theme_boots_settings', $settings); +} + +/** + * Set weight of devshop_projects module to 1 so it loads alters after hosting_git. + */ +function devshop_projects_update_7008 () { + + // Push devshop_projects's system weight to 1. + db_update('system') + ->fields(array( + 'weight' => 1 + )) + ->condition('name', 'devshop_projects') + ->execute(); + +} + +/** + * Update hosting contexts for project node to ensure they all begin with "project_". + */ +function devshop_projects_update_7009 () { + $query = "UPDATE hosting_context c JOIN node n ON n.nid=c.nid SET c.name = CONCAT('project_', name) WHERE n.type='project' AND c.name NOT LIKE 'project_'"; + if (!db_query($query)) { + drupal_set_message(t('Something went wrong when running a query to update hosting_context table to ensure all projects are prefixed with "project_":') . $query); + } +} \ No newline at end of file diff --git a/modules/devshop/devshop_projects/devshop_projects.module b/modules/devshop/devshop_projects/devshop_projects.module new file mode 100644 index 000000000..ac9ab1d3c --- /dev/null +++ b/modules/devshop/devshop_projects/devshop_projects.module @@ -0,0 +1,903 @@ +step)) { + + // Make sure we don't show this message twice. + $message = t('You have an unfinished project. You should !link.', array( + '!link' => l(t('finish creating your project'), 'projects/add'), + )); + + $show_message = TRUE; + $messages = drupal_get_messages('status'); + + if (isset($messages['status'])) { + foreach ($messages['status'] as $m) { + if (strpos($m, $message) !== FALSE) { + $show_message = FALSE; + } + else { + drupal_set_message($m); + } + } + } + + if ($show_message) { + drupal_set_message($message); + } + } + } + + // If on the projects page, or a project or site node... + if (arg(0) == 'node' && is_numeric(arg(1))) { + $node = node_load(arg(1)); + if ($node->type == 'project' || $node->type == 'site' || $node->type == 'task') { + drupal_add_js(drupal_get_path('module', 'devshop_projects') . '/inc/task-ajax.js'); + + if ($node->type == 'project') { + drupal_add_js(array('devshopProject' => arg(1)), 'setting'); + } + if ($node->type == 'task') { + drupal_add_js(array('devshopTask' => arg(1)), 'setting'); + } + } + } + // If on the projects list page... + else { + drupal_add_js(drupal_get_path('module', 'devshop_projects') . '/inc/task-ajax.js'); + } +} + +/** + * Implements hook_permission(). + * + * Since we have a node type, "create project content permission is + * automatically added by Drupal + */ +function devshop_projects_permission() { + return array( + 'view projects' => array( + 'title' => t('view projects'), + 'description' => t('Access the projects page.'), + ), + 'create devshop-delete task' => array( + 'title' => t('create devshop-delete task'), + 'description' => t('Create "devshop-delete" task.'), + ), + 'create devshop-deploy task' => array( + 'title' => t('create devshop-deploy task'), + 'description' => t('Create "devshop-deploy" task.'), + ), + 'create project' => array( + 'title' => t('create project'), + 'description' => t('Create new DevShop projects.'), + ), + 'view project' => array( + 'title' => t('view project'), + 'description' => t('Access DevShop project dashboards.'), + ), + 'edit project' => array( + 'title' => t('edit project'), + 'description' => t('Edit DevShop projects.'), + ), + 'delete projects' => array( + 'title' => t('delete projects'), + 'description' => t('Delete DevShop projects.'), + ), + 'administer projects' => array( + 'title' => t('administer projects'), + 'description' => t('Administer DevShop projects.'), + ), + 'change site domain name' => array( + 'title' => t("Change a site's primary domain name."), + 'description' => t('Alter the main domain name the site responds to.'), + ), + ); +} + +/** + * Implements hook_devshop_environment_menu(). + * + * Defines the list of tasks that appear under the gear icon. + */ +function devshop_environment_menu_first_items($environment, $tasks) { + $site = null; + $platform = null; + $items = array(); + $aegir_items = array(); + + global $user; + + // Show for every site, even disabled ones. + if ($environment->site) { + $site = node_load($environment->site); + if (node_access('update', $site)) { + $items[] = l(' '.t('Environment Settings'), + "node/{$environment->site}/edit", + array( + 'html' => TRUE, + )); + $items[] = '
    '; + + } + + if (user_access('create git-tag task') && $environment->platform) { + $items[] = l(' '.t('Create a tag'), + "hosting_confirm/{$environment->platform}/platform_git-tag", + array( + 'html' => TRUE, + )); + $items[] = '
    '; + } + + if (user_access('change site domain names') && user_access('create migrate task') ) { + $href = "hosting_confirm/{$environment->site}/site_migrate"; + $items[] = l(' '.t('Change Domain Name'), + $href, array( + 'query' => array( + 'rename' => 1, + 'token' => drupal_get_token($user->uid), + ), + 'html' => TRUE, + )); + } + if (node_access('view', $site)) { + $aegir_items[] = l(' '.t('Aegir Site'), + "node/{$environment->site}", + array( + 'html' => TRUE, + )); + } + } + + // If platform exists + if ($environment->platform) { + $platform = node_load($environment->platform); + if (node_access('view', $platform)) { + $aegir_items[] = l(' '.t('Aegir Platform'), + "node/{$environment->platform}", + array( + 'query' => array( + 'rename' => 1, + 'token' => drupal_get_token($user->uid), + ), + 'html' => TRUE, + )); + } + } + + // Load test task, if it exists + if ($tasks['test']) { + $href = "hosting_confirm/{$environment->site}/site_test"; + $url = url($href, array( + 'query' => array( + 'token' => drupal_get_token($user->uid), + ) + )); + $title = $tasks['test']['title']; + if (isset($tasks['test']['icon'])) { + $icon = ''; + } + else { + $icon = ''; + } + $items[] = << + $icon $title + +HTML; + $items[] = '
    '; + } + + // Add aegir items to items + if (!empty($aegir_items)) { + $items = array_merge($items, $aegir_items); + + if (!empty($actions)) { + $items[] = '
    '; + } + } + + // Site specific actions. + if ($environment->site && $environment->site_status == HOSTING_SITE_ENABLED) { + if (node_access('create', 'site')) { + $items[] = l(' '.t('Clone Environment'), + "node/add/site/{$environment->project_name}/clone/{$environment->name}", + array( + 'html' => TRUE, + ) + ); + +// Removing "Fork Environment" because it's a decent amount of work to re-do now +// now that the site node form is being used. It was confusing to users , and we will +// have a new beta shortly after this one anyway. +// @TODO: Un-deprecate Fork Environment. +// $items[] = l(' '.t('Fork Environment'), +// "node/add/site/{$environment->project_name}/fork/{$environment->name}", +// array( +// 'html' => TRUE, +// ) +// ); + } + + // Add disable or delete task based on hosting variable. + if (!variable_get('hosting_require_disable_before_delete', TRUE)) { + $items[] = 'disable'; + $items[] = 'delete'; + } else { + + if ($environment->site_status == HOSTING_SITE_DISABLED) { + $items[] = 'enable'; + $items[] = 'delete'; + } elseif (empty($environment->settings->locked)) { + $items[] = 'disable'; + } + } + + $items[] = '
    '; + + // Add export and import config buttons to Drupal 8 sites. + if (module_exists('aegir_config') && strpos($environment->version, '8') === 0) { + $items[] = 'config_export'; + $items[] = 'config_import'; + $items[] = '
    '; + } + elseif (module_exists('aegir_features') && strpos($environment->version, '7') === 0) { + $items[] = 'features_update'; + $items[] = 'features_revert_all'; + $items[] = '
    '; + } + } + elseif ($environment->site && $environment->site_status == HOSTING_SITE_DISABLED) { + $items[] = 'enable'; + $items[] = 'delete'; + } + + return $items; +} + +/** + * Implements hook_node_access(). + */ +function devshop_projects_node_access($node, $op, $account) { + switch ($op) { + case 'create': + return user_access('create project', $account); + break; + case 'update': + return user_access('edit project', $account); + break; + case 'delete': + return user_access('delete projects', $account); + break; + case 'view': + return user_access('view project', $account); + break; + } +} + +/** + * Implements hook_menu(). + */ +function devshop_projects_menu() { + //Settings page. + $items['admin/devshop'] = array( + 'title' => 'DevShop Settings', + 'description' => 'Default values for use in creation project', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('devshop_projects_settings_form'), + 'access arguments' => array('administer projects'), + 'file' => 'admin.inc', + 'file path' => drupal_get_path('module', 'devshop_projects') . '/inc', + 'type' => MENU_NORMAL_ITEM, + ); + $items['admin/devshop/settings'] = array( + 'title' => t('DevShop Settings'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + + $items['projects'] = array( + 'title' => t('Projects'), + 'description' => 'Display a list of all projects', + 'page callback' => 'devshop_projects_projects_page', + 'access arguments' => array('view projects'), + 'menu_name' => 'main-menu', + 'type' => MENU_NORMAL_ITEM, + 'weight' => 1, + ); + $items['projects/list'] = array( + 'title' => t('All Projects'), + 'description' => '', + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items['projects/add'] = array( + 'title' => 'Start New Project', + 'type' => MENU_LOCAL_TASK, + 'title' => t('Start a new Project'), + 'title callback' => 'check_plain', + 'page callback' => 'devshop_projects_add', + 'page arguments' => array(2), + 'access arguments' => array('create project'), + 'description' => 'Start a new Drupal website project.', + 'file' => 'create.inc', + 'file path' => drupal_get_path('module', 'devshop_projects') . '/inc/create', + ); + + // Ajax endpoint for reloads + $items['projects/add/status'] = array( + 'page callback' => 'devshop_projects_add_status', + 'access callback' => 'node_access', + 'access arguments' => array('create', 'project'), + 'file' => 'create.inc', + 'file path' => drupal_get_path('module', 'devshop_projects') . '/inc/create', + ); + + // hosting tasks ajax pages. + $tasks = hosting_available_tasks('project') ; + if (!empty($tasks)) { + foreach (hosting_available_tasks('project') as $task => $info) { + $path = 'hosting_confirm/%/project_' . $task; + $items[$path] = array( + 'title' => $info['title'], + 'description' => $info['description'], + 'page callback' => 'devshop_projects_hosting_task_confirm_form_page', + 'page arguments' => array(1, $task), + 'access callback' => 'hosting_task_menu_access_csrf', + 'access arguments' => array(1, $task), + 'type' => MENU_CALLBACK, + ); + $items[$path] = array_merge($items[$path], $info); + } + } + + // Drush aliases download. + $items['node/%node/aliases'] = array( + 'title' => 'Drush Aliases', + 'description' => '', + 'page callback' => 'devshop_project_drush_aliases_page', + 'page arguments' => array(1), + 'access callback' => 'node_access', + 'access arguments' => array('update', 1), + 'weight' => 1, + 'file' => 'inc/drush.inc', + 'type' => MENU_CALLBACK, + ); + +// // Add Environment page +// $items['project/%/add-environment'] = array( +// 'title' => 'Add Environment', +// 'type' => MENU_CALLBACK, +// 'page callback' => 'devshop_projects_environment_add_page', +// 'page arguments' => array(1), +// 'access callback' => 'devshop_projects_environment_add_access', +// 'access arguments' => array(1), +// ); + + // Ajax endpoint for reloads + $items['devshop/tasks'] = array( + 'page callback' => 'devshop_projects_tasks_status_json', + 'page arguments' => array(2), + 'description' => '', + 'access arguments' => array('access task logs'), + 'file' => 'tasks-ajax.inc', + 'file path' => drupal_get_path('module', 'devshop_projects') . '/inc', + ); + + return $items; +} + +/** + * Get the bootstrap class from hosting task status. + */ +function devshop_task_status_class($status) { + $codes = array( + HOSTING_TASK_SUCCESS => 'success', + HOSTING_TASK_QUEUED => 'queued', + HOSTING_TASK_ERROR => 'danger', + HOSTING_TASK_PROCESSING => 'processing', + HOSTING_TASK_WARNING => 'warning', + ); + return $codes[$status]; +} + +/** + * Get the fontawesome icon from hosting task status. + */ +function devshop_task_status_icon($status) { + $icons = array( + HOSTING_TASK_SUCCESS => 'check', + HOSTING_TASK_QUEUED => 'cog', + HOSTING_TASK_ERROR => 'exclamation-circle', + HOSTING_TASK_PROCESSING => 'cog fa-spin', + HOSTING_TASK_WARNING => 'warning', + ); + return $icons[$status]; +} + +function devshop_project_logs_access() { + return TRUE; +} + +/** + * Implements hook_block_info_alter() + * + * Disables Aegir's queue and "supporting aegir" block. + * + * @param $blocks + * @param $theme + * @param $code_blocks + */ +function devshop_projects_block_info_alter(&$blocks, $theme, $code_blocks) { + $blocks['hosting']['hosting_queues']['status'] = 0; + $blocks['hosting']['hosting_queues_summary']['status'] = 0; + $blocks['hosting']['hosting_supporting_aegir']['status'] = 0; +} + +/** + * Implements hook_block_info(). + */ +function devshop_projects_block_info() { + $blocks['project_nav'] = array( + 'info' => t('DevShop Project Nav'), + 'weight' => '-10', + 'cache' => DRUPAL_CACHE_GLOBAL, + 'status' => 1, + 'region' => 'header', + ); + $blocks['project_create'] = array( + 'info' => t('DevShop Create Project'), + 'weight' => '-10', + 'cache' => DRUPAL_CACHE_PER_USER, + 'status' => 1, + 'region' => 'sidebar_first', + ); + return $blocks; +} + +/** + * Implements hook_block_view(). + */ +function devshop_projects_block_view($delta) { + if ($delta == 'project_nav' && ( + arg(0) == 'node' || arg(0) == 'hosting_confirm' + )) { + + if (is_numeric(arg(1))) { + $node = node_load(arg(1)); + } + elseif (arg(1) == 'add') { + $node = devshop_projects_load_by_name(arg(3)); + } + + if (node_access('view', $node) && ($node->type == 'project' || !empty($node->project))) { + + // If viewed node is project, use that. + if (is_numeric($node->project)) { + if ($project_node = node_load($node->project)) { + $project = $project_node->project; + } + } + elseif (!empty($node->project)) { + $project = $node->project; + } + else { + return ''; + } + + $block['subject'] = ''; + $block['content'] = theme('devshop_project_nav', array('project' => $project)); + } + } + elseif ($delta == 'project_create') { + if (arg(0) == 'projects' && arg(1) == 'add') { + ctools_include('object-cache'); + $project = ctools_object_cache_get('project', 'devshop_project'); + $block['subject'] = ''; + $block['content'] = theme('devshop_project_add_status', array('project' => $project)); + } + } + if (isset($block)) { + return $block; + } +} + +/** + * Page Callback for projects/add + */ +function devshop_projects_add($step = NULL) { + if ($step == NULL) { + // Check to see if this project is still in the wizard + ctools_include('object-cache'); + $project_wizard_cache = ctools_object_cache_get('project', 'devshop_project'); + if (!empty($project_wizard_cache->step)) { + drupal_goto('projects/add/' . $project_wizard_cache->step); + } + } + return devshop_projects_create_wizard($step); +} + +/** + * Replacement for hosting_task_confirm_form() + * + * @TODO: Remove once http://drupal.org/node/1861898 is committed. + */ +function devshop_projects_hosting_task_confirm_form_page($nid, $task) { + $node = node_load($nid); + return drupal_get_form('hosting_task_confirm_form', $node, $task); +} + +/** + * Implements hook_menu_alter(). + * + * Replaces node/add/project with a ctools wizard. + */ +function devshop_projects_menu_alter(&$items) { + $items['node/add/project']['page callback'] = 'devshop_projects_create_wizard'; + $items['node/add/project']['page arguments'] = array(3); + $items['node/add/project']['file'] = 'create-wizard.inc'; + $items['node/add/project']['file path'] = drupal_get_path('module', 'devshop_projects') . '/inc'; + + // Make project node pages more user-friendly. + $items['node/%node/view']['title callback'] = 'devshop_hosting_project_tab_title'; + $items['node/%node/view']['title arguments'] = array('View', 1); + + $items['node/%node/edit']['title callback'] = 'devshop_hosting_project_tab_title'; + $items['node/%node/edit']['title arguments'] = array('Edit', 1); + + $items['node/%node/edit']['access callback'] = 'devshop_hosting_project_tab_access'; + + + + $items['node/%node/view']['weight'] = -10; + $items['node/%node/edit']['weight'] = -9; + + // Hosting now hides all but migrate and clone tasks. + $path = sprintf("hosting_confirm/%hosting_%s_wildcard/site_devshop-deploy", 'deploy', 'deploy', 'devshop-deploy'); + $items[$path]['access callback'] = 'devshop_hosting_task_menu_access_csrf'; + + // Redirect hosting/task/% to the node + $items['hosting/task/%'] = array( + 'page callback' => 'drupal_goto_nid', + 'page arguments' => array(2), + 'access callback' => TRUE, + ); +} + +/** + * Helper to redirect to a node page. + * @param $nid + */ +function drupal_goto_nid($nid) { + drupal_goto("node/{$nid}"); +} + +/** + * Access callback helper for hosting task menu items. + * A copy of hosting_task_menu_access() except we allow devshop-deploy tasks. + * @see hosting_task_menu_access() + */ +function devshop_hosting_task_menu_access_csrf($node, $task) { + global $user; + + $interactive_tasks = array('migrate', 'clone', 'devshop-deploy'); + + // To prevent CSRF attacks, a unique token based upon user is used. Deny + // access if the token is missing or invalid. We only do this on + // non-interactive tasks. + if (!in_array($task, $interactive_tasks) && (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $user->uid))) { + return FALSE; + } + // Call the main menu access handler. + return hosting_task_menu_access($node, $task); +} + +/** + * Tab title replacer + */ +function devshop_hosting_project_tab_title($default, $node) { + if ($default == 'View' && $node->type == 'project') { + return t('Project Dashboard'); + } + if ($default == 'Edit' && $node->type == 'project') { + return t('Project Settings'); + } + if ($default == 'View' && $node->type == 'site' && isset($node->project)) { + return t('Environment Dashboard'); + } + if ($default == 'Edit' && $node->type == 'site' && isset($node->project)) { + return t('Environment Settings'); + } + if ($node->type == 'task') { + if ($default == 'View') { + $tasks = hosting_available_tasks('site'); + $label = drupal_ucfirst($tasks[$node->task_type]['title']); + return t('Task: !type', array('!type' => $label)); + } + } + + // Otherwise, just return the page text + return t($default); +} + +/** + * replacement for node_access + */ +function devshop_hosting_project_tab_access($type, $node) { + if ($node->type == 'task') { + return FALSE; + } + return node_access($type, $node); +} + +/** + * Access Callback for Aegir Tasks + * + * This function defines access to the various aegir tasks. + * + * @arg $node object + * The node object is running the task. (Site, Platform, Server) + * + * @arg $task string + * The type of the task that is running. + * + * @see hosting_task_menu_access() + * + * @TODO: This NEVER runs for verify! Only for devshop-xyz tasks. + * project verify task is defined in devshop_projects_hosting_tasks() in + * inc/tasks.inc, and has this function as it's access callback. But this + * function seems to never run. + */ +function devshop_hosting_task_menu_access($node, $task) { + // If we are passed the nid by mistake + if (!isset($node->nid)) { + $node = node_load($node); + } + + if ($node->type != 'project' && $task != 'delete' && !$node->status) { + return FALSE; + } + + if (user_access("create " . $task . " task")) { + return TRUE; + } +} + + + +/** + * Implements hook_hosting_drush_aliases_name(). + * + * See http://drupal.org/project/hosting_drush_aliases + */ +function devshop_projects_hosting_drush_aliases_name($node) { + if (isset($node->project_name)) { + return $node->project_name . "." . $node->project_environment; + } +} + +/** + * Helper function to create a site in a project. + * Used by Wizard & "Create Platform" > Post Verify + */ +function devshop_projects_create_site($project, $platform_node, $environment_name, $db_server = NULL) { + global $user; + + // Create the site nodes + $node = new stdClass(); + $node->type = 'site'; + $node->status = 1; + $node->uid = $user->uid; + $node->title = devshop_environment_url($project, $environment_name); + + // Aegir properties + // @TODO: better client & DB support + $node->client = HOSTING_DEFAULT_CLIENT; + $servers = hosting_get_servers('db'); + $server = $db_server ? $db_server : key($servers); + $node->db_server = $server; + + $node->platform = $platform_node->nid; + + // Lookup this platforms install profile + $node->profile = db_query('SELECT nid FROM {hosting_package} WHERE short_name = :short_name', array(':short_name' => $project->install_profile))->fetchField(); + + // @TODO: Improve site language handling? + $node->site_language = !empty($user->language) ? $user->language : 'en'; + + // Subdomain alias, if configured. + if ($project->settings->live['environment_aliases'] && !empty($project->settings->live['live_domain'])) { + $node->aliases = array($environment_name . '.' . $project->settings->live['live_domain']); + } + + // Save the site node + if ($node = node_submit($node)) { + node_save($node); + } + + // Lookup install task to use it for last task. + $tasks = hosting_get_tasks('rid', $node->nid); + + // Update environment with our site + db_update('hosting_devshop_project_environment') + ->fields(array( + 'site' => $node->nid, + 'last_task' => current($tasks)->nid, + )) + ->condition('project_nid', $project->nid) + ->condition('name', $environment_name) + ->execute(); + + return $node; +} + +/** + * Helper to get select #options for git ref. + */ +function devshop_projects_git_ref_options($project, $current_ref = '') { + + // Build branch options + if (is_array($project->settings->git['branches']) && !empty($project->settings->git['branches'])) { + $options = array( + 'Branches' => array_combine($project->settings->git['branches'], $project->settings->git['branches']), + ); + + // If there are tags... + if (is_array($project->settings->git['tags']) && !empty($project->settings->git['tags'])) { + $options['Tags'] = array_combine($project->settings->git['tags'], $project->settings->git['tags']); + } + } + + // If there are none... + if (!isset($options)) { + $options = array(t('No branches or tags! Re-validate the project.')); + } + + // Set "current" label. + if (isset($options['Branches'][$current_ref])) { + $options['Branches'][$current_ref] .= ' (' . t('current') . ')'; + } + elseif (isset($options['Tags'][$current_ref])) { + $options['Tags'][$current_ref] .= ' (' . t('current') . ')'; + } + + + return $options; +} + +/** + * Check if a site has features diff enabled. + */ +function _devshop_projects_site_has_module($nid, $module) { + if (is_object($nid) && isset($nid->nid)) { + $nid = $nid->nid; + } + + $query = db_select('hosting_package_instance', 'i') + ->fields('i', array('status')); + $query->leftJoin('hosting_package', 'p', 'i.package_id = p.nid'); + + $query->condition('p.short_name', $module); + $query->condition('rid', $nid); + + $result = $query->execute()->fetchField(); + return $result; +} + +/** + * Check if a site has features diff enabled. + */ +function _devshop_projects_project_has_module($node, $module) { + $environment = key($node->project->environments); + + if (isset($node->project->environments[$environment]->site)) { + return _devshop_projects_site_has_module($node->project->environments[$environment]->site, $module); + } +} + +/** + * Reset all environment's "last task" to the NID. + */ +function devshop_reset_last_tasks () { + + // Lookup all environments + $results = db_query('SELECT * FROM {hosting_devshop_project_environment}'); + foreach ($results as $result) { + $site = node_load($result->site); + $nid = db_query("SELECT t.nid FROM hosting_task t INNER JOIN node n ON n.nid = t.nid WHERE rid = :site", array( + ':site' => $site->nid + ))->fetchField(); + + $site->environment->last_task = $nid; + devshop_environment_save_last_task($site->environment); + } +} + +/** + * Helper to get a project node by name. + */ +function devshop_load_project_by_name($name) { + $nid = db_select('node', 'n') + ->fields('n', array('nid')) + ->condition('type', 'project') + ->condition('title', $name) + ->condition('status', 1) + ->execute() + ->fetchField() + ; + $node = node_load($nid); + return $node; +} + +/** + * Generate an environment's URL from project and environment name using the domain name pattern. + * + * @param $project + * @param $environment_name + * @return string + */ +function devshop_environment_url($project, $environment_name) { + + // Generate field prefix and suffix using domain name pattern. + if (variable_get('devshop_projects_allow_custom_base_url')) { + $pattern = $project->base_url; + } + else { + $pattern = variable_get('devshop_project_environment_url_pattern', '@project.@environment.@hostname'); + } + $domain = strtr($pattern, array( + '@environment' => $environment_name, + '@project' => $project->name, + '@hostname' => $_SERVER['SERVER_NAME'], + )); + return $domain; +} + +/** + * Retrieve a project node by name. + * + * @param $name + * @return bool|mixed + */ +function devshop_projects_load_by_name($name) { + $nid = db_select('node', 'n') + ->fields('n', array('nid')) + ->condition('title', $name) + ->condition('type', 'project') + ->condition('status', 1) + ->execute() + ->fetchField(); + + return node_load($nid); +} \ No newline at end of file diff --git a/modules/devshop/devshop_projects/drush/Provision/Config/ProjectAliases.php b/modules/devshop/devshop_projects/drush/Provision/Config/ProjectAliases.php new file mode 100644 index 000000000..555c244b9 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/Provision/Config/ProjectAliases.php @@ -0,0 +1,31 @@ +data = array( + 'name' => strtr($context, array('@project_' => '')), + 'project' => $project, + ); + } + + function filename() { + return drush_server_home() . '/.drush/project_aliases/' . $this->data['name'] . '.aliases.drushrc.php'; + } +} diff --git a/modules/devshop/devshop_projects/drush/Provision/Config/project_aliases.tpl.php b/modules/devshop/devshop_projects/drush/Provision/Config/project_aliases.tpl.php new file mode 100644 index 000000000..19202aae2 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/Provision/Config/project_aliases.tpl.php @@ -0,0 +1,39 @@ +environments as $name => $environment) { + + if ($environment->site_status != 1) { + continue; + } + // Tell drush to inherit from the provision site alias record. + $alias = array( + 'parent' => '@' . $environment->uri + ); + + // If web server is not server master, add "remote host and user. + if (d($environment->drush_alias)->platform->web_server->name != '@server_master') { + $alias['remote-host'] = d($environment->drush_alias)->platform->web_server->remote_host; + $alias['remote-user'] = d($environment->drush_alias)->platform->web_server->script_user; + } + + $export = var_export($alias, TRUE); + ?> + +$aliases[''] = ; + + settings->aliases as $name => $remote_alias) { + $export = var_export($remote_alias, TRUE); + ?> + +$aliases[''] = ; + + diff --git a/modules/devshop/devshop_projects/drush/Provision/Context/project.php b/modules/devshop/devshop_projects/drush/Provision/Context/project.php new file mode 100644 index 000000000..abd16a6f1 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/Provision/Context/project.php @@ -0,0 +1,46 @@ +project['environments'][$name])) { + return (object) $this->project['environments'][$name]; + } + else { + return drush_set_error('DEVSHOP_PROJECT_ERROR', dt('Environment %name not found.', array( + '%name' => $name, + ))); + } + } + + static function option_documentation() { + return array( + '--project_name' => 'Project: The codename for this project.', + '--project' => 'Project: JSON encoded data about the project.', + + //@TODO: Document this! +// '--code_path' => 'Project: The path to the project codebases. (NOT the Drupal root)', +// '--drupal_path' => 'Project: The path to the drupal root.', +// '--git_url' => 'Project: The Git URL for this project.', +// '--git_branches' => 'Project: The available Git branches in the remote repository for this project.', +// '--git_tags' => 'Project: The available Git tags in the remote repository for this project.', +// '--base_url' => 'Project: the base URL that the dev/test/live subdomains will be attached to.', +// '--server' => 'Project: The server hosting this project. (Default is @server_master)', +// '--install_profile' => 'Project: The desired installation profile for all sites.', + ); + } + + function init_project() { + $this->setProperty('project_name'); + $this->setProperty('project'); + } + + function verify() { + $this->type_invoke('verify'); + drush_log('[DEVSHOP] verify()', 'ok'); + } +} diff --git a/modules/devshop/devshop_projects/drush/Provision/Service/Process.php b/modules/devshop/devshop_projects/drush/Provision/Service/Process.php new file mode 100644 index 000000000..9a9654881 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/Provision/Service/Process.php @@ -0,0 +1,26 @@ +server->remote_host)) { + return devshop_process($command, $cwd, $label, $env, $log_output); + } + else { + return devshop_process('ssh ' . drush_get_option('ssh-options', '-o PasswordAuthentication=no') . ' ' . $this->server->script_user . '@' . $this->server->remote_host . ' ' . $command, $cwd, $label, $env); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/Provision/Service/project.php b/modules/devshop/devshop_projects/drush/Provision/Service/project.php new file mode 100644 index 000000000..b20d70be7 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/Provision/Service/project.php @@ -0,0 +1,26 @@ +setProperty('environment'); + $context->setProperty('environment_settings'); + $context->setProperty('project'); + } + + /** + * Add environment, project, and git ref to platform aliases. + */ + static function subscribe_platform($context) { + $context->setProperty('environment'); + $context->setProperty('project'); + } + +} diff --git a/modules/devshop/devshop_projects/drush/acquia/README.txt b/modules/devshop/devshop_projects/drush/acquia/README.txt new file mode 100644 index 000000000..be1b71e65 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/acquia/README.txt @@ -0,0 +1,9 @@ +Acquia Cloud Hooks Integration +============================== + +DevShop now supports firing Acquia Cloud Hooks when deploying to environments. + +The code in acquia.drush.inc will detect an acquia repo and trigger your hooks +to run. + +Currently, only the `post-code-update` hook is supported. More to come. \ No newline at end of file diff --git a/modules/devshop/devshop_projects/drush/acquia/acquia.drush.inc b/modules/devshop/devshop_projects/drush/acquia/acquia.drush.inc new file mode 100644 index 000000000..59346a401 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/acquia/acquia.drush.inc @@ -0,0 +1,76 @@ +type == 'site'){ + + $project_name = d()->project; + $project = (object) d("@project_{$project_name}")->project; + $environment = (object) $project->environments[d()->environment]; + + // Respect environment settings. + if ($environment->settings['deploy']['acquia_hooks'] != 1) { + return; + } + + // If project has no path to drupal, we know it's not acquia. + if ($project->drupal_path != 'docroot') { + drush_log('[DEVSHOP] Acquia Cloud skipped.', 'ok'); + return; + } + + // Detect hook files. + $cloud_hooks_path = $environment->repo_path . '/hooks'; + if (file_exists($cloud_hooks_path)) { + drush_log('[DEVSHOP] Acquia Cloud Hooks detected...', 'ok'); + } + + // Collect scripts to run. + // Common scripts. + $files = scandir($cloud_hooks_path . '/common/post-code-update'); + if (empty($files)) $files = array(); + $scripts = array_diff($files, array('..', '.')); + foreach ($scripts as &$script) { + $script = realpath($cloud_hooks_path . '/common/post-code-update/' . $script); + } + + // Environment scripts: Post Code Update + if (file_exists($cloud_hooks_path . '/' . $environment->name)) { + $files = scandir($cloud_hooks_path . '/' . $environment->name . '/post-code-update'); + if (empty($files)) $files = array(); + $scripts += array_diff($files, array('..', '.')); + foreach ($scripts as &$script) { + $script = realpath($cloud_hooks_path . '/' . $environment->name . '/post-code-update/' . $script); + } + } + + // @TODO: Post Code Deploy & Post DB Copy + + drush_log(implode("\n", $scripts), 'ok'); + + // Run Scripts + // Usage: post-code-deploy site target-env source-branch deployed-tag repo-url repo-type + foreach ($scripts as $file) { + drush_log('[DEVSHOP] Running Acquia Cloud Hook: ' . $file, 'ok'); + + if (drush_shell_exec("sh $file {$project_name} {$environment->name} old_branch {$branch} repo_url repo_type devshop $environment->url") !== 0) { + $output = drush_shell_exec_output(); + drush_log(implode("\n", $output), 'ok'); + } + else { + return drush_set_error(DRUSH_FRAMEWORK_ERROR, 'The last cloud hook returned a non-zero exit code. Remaining hooks skipped.'); + } + } + } +} \ No newline at end of file diff --git a/modules/devshop/devshop_projects/drush/acquia/example.slack.sh b/modules/devshop/devshop_projects/drush/acquia/example.slack.sh new file mode 100644 index 000000000..3c4f848d1 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/acquia/example.slack.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# +# Altered from https://github.com/acquia/cloud-hooks/tree/master/samples/slack +# Requires a /var/aegir/slack_settings file that contains: +# +# SLACK_WEBHOOK_URL=https://example.slack.com/services/hooks/incoming-webhook?token=TOKEN +# +# Cloud Hook: post-code-deploy +# +# The post-code-deploy hook is run whenever you use the Workflow page to +# deploy new code to an environment, either via drag-drop or by selecting +# an existing branch or tag from the Code drop-down list. See +# ../README.md for details. +# +# Usage: post-code-deploy site target-env source-branch deployed-tag repo-url +# repo-type + +site="$1" +target_env="$2" +source_branch="$3" +deployed_tag="$4" +repo_url="$5" +repo_type="$6" +source="$7" +site_url="$8" + +# Load the Slack webhook URL (which is not stored in this repo). +. $HOME/slack_settings + +if [ $source = 'devshop' ]; then + source="DevShop" + image="https://www.drupal.org/files/project-images/devshop-icon.png" +else + source="Acquia Cloud" + image="https://pbs.twimg.com/profile_images/1901642489/cloud_icon_150.png" +fi + +# Post deployment notice to Slack +curl -X POST --data-urlencode "payload={\"channel\": \"#dev-engageny\", \"username\": \"$source\", \"text\": \"Git branch \`$deployed_tag\` updated on *$target_env*: $site_url.\", \"icon_url\": \"$image\"}" $SLACK_WEBHOOK_URL diff --git a/modules/devshop/devshop_projects/drush/aegir_extras/aegir_commit.drush.inc b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_commit.drush.inc new file mode 100644 index 000000000..74f22f4cf --- /dev/null +++ b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_commit.drush.inc @@ -0,0 +1,118 @@ + 'Commits code to git.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + 'options' => array( + 'message' => 'The commit message to use.', + 'push' => 'Push the code after committing.', + 'files' => 'A list of files, if desired.', + 'name' => 'The name of the user to commit as.', + 'mail' => 'The email of the user to commit as.', + ), + ); + return $items; +} + +/** + * Implements the provision-fua command. + */ +function drush_aegir_commit_provision_commit() { + + if (d()->type == 'site') { + $git_url = d()->platform->repo_url; + $git_path = d()->platform->repo_path; + + $message = trim(drush_get_option('message', '')); + $files = json_decode(drush_get_option('files', '[]')); + $push = drush_get_option('push', FALSE); + $name = drush_get_option('name', FALSE); + $mail = drush_get_option('mail', FALSE); + + // Git Add + try { + $wrapper = new GitWrapper(); + $wrapper->streamOutput(); + + $git = $wrapper->workingCopy($git_path); + $status = $git->getStatus(); + + if (empty($status)) { + drush_log('[Git Status] Working Directory is clean. Nothing to commit.', 'devshop_log'); + } + else { + devshop_process('git status', $git_path, dt('Git Status')); + $cmds = array(); + + // If files option was detected + if ($files) { + $files_list = ''; + + foreach ($files as $file) { + $file = $git_path . '/' . $file; + if (file_exists($file)) { + $files_list .= ' ' . $file; + } + } + + // If files lists is not empty, add "git add" command to queue. + if (!empty($files_list)) { + $cmds[] = "git add {$files_list}"; + } + } + else { + $cmds[] = 'git add -A'; + } + + foreach ($cmds as $cmd) { + devshop_process($cmd, $git_path, 'Adding files to local repository...'); + } + + devshop_process('git status', $git_path, dt('Git Status')); + + $hostmaster_uri = d('hostmaster')->uri; + $message = escapeshellarg("$message +-------------- +Committed by OpenDevShop via http://$hostmaster_uri"); + + devshop_process("git commit -m {$message}", $git_path, 'Committing changes to local repository...', array( + 'GIT_AUTHOR_NAME' => $name, + 'GIT_AUTHOR_EMAIL' => $mail, + 'GIT_COMMITTER_NAME' => $name, + 'GIT_COMMITTER_EMAIL' => $mail, + )); + + devshop_process('git status', $git_path, dt('Git Status')); + } + + // Push the result, if desired. + if ($push) { + + // Do a little pre-check to ensure we don't hang at http password. + $git_url = parse_url($git_url); + if (($git_url['scheme'] == 'http' || $git_url['scheme'] == 'https') && empty($git_url['pass'])) { + drush_log('http git url detected, with no password. Unable to push to this repository.', 'warning'); + return; + } + else { + devshop_process("git push", $git_path, 'Pushing code to remote repository...'); + devshop_process('git status', $git_path, dt('Git Status')); + } + } + } + catch (GitException $e) { + drush_log('Git Error', 'devshop_command'); + drush_log("Unable to load git repository at path " . $git_path, 'devshop_error'); + drush_set_error($e->getMessage()); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/aegir_extras/aegir_download.drush.inc b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_download.drush.inc new file mode 100644 index 000000000..2965e3984 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_download.drush.inc @@ -0,0 +1,130 @@ + 'Downloads drupal modules and themes, and optionally commits them to git.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + 'options' => array( + 'packages' => 'This list of modules and themes to download.', + 'commit' => 'Commit the downloaded code to git.', + 'message' => 'The message to use when committing.', + 'update' => 'Run update.php after the download.', + 'test' => 'Queue a test run after the download.', + ), + 'arguments' => array( + ), + 'examples' => array( + 'drush @env.project.domain.com provision-download views ctools --update --commit --message="the usuals"' => 'Downloads views & ctools, commits them to the repo with a message, and run drush updb.', + ), + 'aliases' => array('pdl'), + ); + return $items; +} + +/** + * Implements the provision-git-pull command. + */ +function drush_aegir_download_provision_download() { + $packages = drush_get_option('packages'); + + if (is_array($packages)) { + $packages = implode($packages); + } + + // No modules, no more. + if (empty($packages)) { + return drush_set_error('No packages defined.'); + } + + // Run `drush dl $modules` + $target = d()->name; + $cmd = "drush $target dl $packages --yes --strict=0"; + + // Pass through options to the drush dl command. + if (drush_get_option('commit', FALSE)) { + $cmd .= ' --commit '; + } + if (drush_get_option('message', '')) { + $message = drush_get_option('message', ''); + $cmd .= " --message='$message' "; + } + if (drush_get_option('update', FALSE)) { + $cmd .= " --update"; + } + + drush_log($cmd, 'devshop_command'); + + $process = new Process($cmd); + $process->setTimeout(null); + $exit_code = $process->run( + function ($type, $buffer) { + if (Process::ERR === $type) { + drush_log($buffer, 'devshop_ok'); + } else { + drush_log($buffer, 'devshop_ok'); + } + } + ); + + // check exit code + if ($exit_code === 0) { + drush_log("Aegir Download: OK", 'ok'); + } else { + drush_log("Aegir Download Failed!", 'error'); + drush_log(dt('Downloading modules failed. Check the logs and try again.'), 'devshop_error'); + return; + } +} + +/** + * Implements hook_drush_pm_post_download() + * + * Runs after a project has been downloaded. + * + * This is needed for devshop because we want to commit the new module we just + * downloaded, and only this hook knows about the path. + * + * @param $drupal_project + * @param $release + */ +function aegir_download_drush_pm_post_download($drupal_project, $release) { + + $project_name = d()->project; + $project = (object) d("@project_{$project_name}")->project; + $environment = (object) $project->environments[d()->environment]; + + if (d()->environment && drush_get_option('commit', FALSE)) { + drush_log(dt('[DEVSHOP] Committing new module...'), 'ok'); + + $message = drush_get_option('message', dt('Committed by DevShop: ') . $drupal_project['name']); + + $wrapper = new GitWrapper(); + $wrapper->streamOutput(); + + try { + $git = $wrapper->workingCopy($environment->repo_path); + + $download_path = $drupal_project['full_project_path']; + $git + ->add($download_path) + ->commit($message) + ->push(); + } + catch (GitException $e) { + return drush_set_error('DRUSH_ERROR', "Git Exception: " . $e->getMessage()); + } + } + else { + drush_log(dt('Skipping Commit. Make sure you commit or delete this module at some point.'), 'notice'); + } +} diff --git a/modules/devshop/devshop_projects/drush/aegir_extras/aegir_features.drush.inc b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_features.drush.inc new file mode 100644 index 000000000..332cb991e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_features.drush.inc @@ -0,0 +1,60 @@ + 'Runs drush features-update-all.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + 'options' => array( + 'features' => 'The list of features to update.', + 'revert' => 'Whether or not to revert the features afterwards.', + ), + ); + return $items; +} + +/** + * Implements the provision-fua command. + */ +function drush_aegir_features_provision_features_update() { + + if (d()->type == 'site') { + + $project_name = d()->project; + $project = (object) d("@project_{$project_name}")->project; + $environment = (object) $project->environments[d()->environment]; + + $features = trim(drush_get_option('features', '')); + $revert = drush_get_option('revert', FALSE); + + // If features were specified... + if (!empty($features)) { + devshop_process("drush {$environment->drush_alias} features-update {$features} -y", NULL, dt('Update Features')); + } + else { + devshop_process("drush {$environment->drush_alias} features-update-all -y", NULL, dt('Update All Features')); + } + + // Show git status + devshop_process("git status", $environment->repo_path, NULL, dt('Checking git status')); + + // Revert force revert the features if the user asked. + if ($revert) { + if (!empty($features)) { + devshop_process("drush {$environment->drush_alias} fr {$features} --force -y", NULL, dt('Revert Features')); + } + else { + devshop_process("drush {$environment->drush_alias} fra --force -y", NULL, dt('Revert Features')); + } + } + } +} diff --git a/modules/devshop/devshop_projects/drush/composer.json b/modules/devshop/devshop_projects/drush/composer.json new file mode 100644 index 000000000..be849d2d6 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "symfony/process": "^2.7", + "cpliakas/git-wrapper": "~1.0", + "symfony/class-loader": "^3.0" + } +} diff --git a/modules/devshop/devshop_projects/drush/composer.lock b/modules/devshop/devshop_projects/drush/composer.lock new file mode 100644 index 000000000..a06c03025 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/composer.lock @@ -0,0 +1,239 @@ +{ + "_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": "99d65f6a12dd5b91c0eaf59137155905", + "content-hash": "90ec948563783f502e22b7dc6254cf67", + "packages": [ + { + "name": "cpliakas/git-wrapper", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/cpliakas/git-wrapper.git", + "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cpliakas/git-wrapper/zipball/5b89e9c5b4598c110e2d93d1245b4e26df3855a5", + "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/event-dispatcher": "~2.3|~3.0", + "symfony/process": "~2.3|~3.0" + }, + "require-dev": { + "pdepend/pdepend": "~1.0", + "phploc/phploc": "~2.0", + "phpmd/phpmd": "~1.0", + "phpunit/phpunit": "~3.0", + "psr/log": "~1.0", + "scrutinizer/ocular": "~1.0", + "sebastian/phpcpd": "~2.0", + "symfony/filesystem": "~2.0" + }, + "suggest": { + "monolog/monolog": "Enables logging of executed git commands" + }, + "type": "library", + "autoload": { + "psr-0": { + "GitWrapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Pliakas", + "email": "opensource@chrispliakas.com" + } + ], + "description": "A PHP wrapper around the Git command line utility.", + "homepage": "https://github.com/cpliakas/git-wrapper", + "keywords": [ + "git" + ], + "time": "2015-11-11 15:45:43" + }, + { + "name": "symfony/class-loader", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/92e7cf1af2bc1695daabb4ac972db169606e9030", + "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ClassLoader Component", + "homepage": "https://symfony.com", + "time": "2016-02-03 09:33:23" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf", + "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2015-10-30 23:35:59" + }, + { + "name": "symfony/process", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/1b988a88e3551102f3c2d9e1d47a18c3a78d6312", + "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2015-11-30 12:35:10" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/modules/devshop/devshop_projects/drush/contexts.inc b/modules/devshop/devshop_projects/drush/contexts.inc new file mode 100644 index 000000000..5227e37fa --- /dev/null +++ b/modules/devshop/devshop_projects/drush/contexts.inc @@ -0,0 +1,144 @@ +ref->project->git_url); + + // If something went wrong connecting to the git repo, don't wipe out our branches. + if (!empty($branches['branches'])) { + $task->ref->project->settings->git['branches'] = $branches['branches']; + $task->ref->project->settings->git['tags'] = $branches['tags']; + $task->ref->project->settings->git['refs'] = $branches['refs']; + + // Save the project node now that we have branches and tags. + // Don't verify again, this is the verification process. + $task->ref->no_verify = TRUE; + node_save($task->ref); + } + + // Save project object to drush alias (aegir context). + if (isset($task->ref->project)) { + $task->context_options['server'] = '@server_master'; + $task->context_options['project_name'] = $task->ref->title; + $task->context_options['project'] = $task->ref->project; + } +} + +/** + * Implements hook_hosting_site_context_options(). + * + * Runs on verify task. Passes data to the drush alias. + * Save environment name, project name, and git ref to site aliases. + */ +function devshop_projects_hosting_site_context_options(&$task) { + + if (isset($task->ref->environment)) { + $task->context_options['environment'] = $task->ref->environment->name; + $task->context_options['environment_settings'] = $task->ref->environment->settings; + $task->context_options['project'] = $task->ref->project->name; + $task->context_options['git_ref'] = $task->ref->environment->git_ref; + } + + // If install task is set to force-reinstall, pass to drush options. + if ($task->task_type == 'install' && $task->task_args['force-reinstall']) { + $task->options['force-reinstall'] = 1; + } + +} + +/** + * Implements hook_hosting_site_context_options(). + * + * Runs on verify task. Passes data to the drush alias. + * Save environment name, project name, and git ref to site aliases. + */ +function devshop_projects_hosting_platform_context_options(&$task) { + + if (isset($task->ref->environment)) { + $task->context_options['environment'] = $task->ref->environment->name; + $task->context_options['project'] = $task->ref->project->name; + $task->context_options['git_ref'] = $task->ref->environment->git_ref; + } +} + +/** + * Implements hook_drush_context_import(). + * + * This allows project nodes to be created from contexts (aliases) + */ +function devshop_projects_drush_context_import($context, &$node) { + if ($context->type == 'Project') { + $node->title = $context->project_name; + $node->type = 'project'; + $node->project = $context->project; + } +} + +/** + * Helpfer for getting branches and tags from a git URL + */ +function getBranchesAndTags($git_url = NULL) { + if (is_null($git_url)) { + $git_url = drush_get_option('git_url'); + } + $command = "git ls-remote {$git_url}"; + $output = devshop_process($command); + $lines = explode("\n", $output); + + // Build tag and branch list + $branches = array(); + $tags = array(); + $refs = array(); + + foreach ($lines as $line_string) { + + // "annotated" tags come with an extra row and these characters at the end. + // See http://stackoverflow.com/questions/15472107/when-listing-git-ls-remote-why-theres-after-the-tag-name + if (substr($line_string, -3, 3) == '^{}') { + continue; + } + + // Example remote line: + // 9fc5727c0823d8d3300ba5aae3328d5998033e45 refs/heads/master + // 9fc5727c0823d8d3300ba5aae3328d5998033e45 refs/tags/1.0 + $line = trim(substr($line_string, 40)); + if (empty($line) || $line == 'HEAD') { + continue; + } + + // If branch + if (strpos($line, 'refs/heads/') === 0) { + $git_ref = str_replace('refs/heads/', '', $line); + $branches[] = $git_ref; + $refs[$git_ref] = 'branch'; + } + // else if tag + elseif (strpos($line, 'refs/tags/') === 0) { + $git_ref = str_replace('refs/tags/', '', $line); + $tags[] = $git_ref; + $refs[$git_ref] = 'tag'; + } + // If not a tag or a head, continue. + // @TODO: Should we store alternative types? GitHub Pull Requests use this. + else { + continue; + } + } + drush_log(dt('Found !count branches: !list', array( + '!count' => count($branches), + '!list' => implode(', ', $branches), + )), 'devshop_log'); + drush_log(dt('Found !count tags: !list', array( + '!count' => count($tags), + '!list' => implode(', ', $tags), + )), 'devshop_log'); + + return array('branches' => $branches, 'tags' => $tags, 'refs' => $refs); +} diff --git a/modules/devshop/devshop_projects/drush/deploy.devshop.provision.inc b/modules/devshop/devshop_projects/drush/deploy.devshop.provision.inc new file mode 100644 index 000000000..fb104304b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/deploy.devshop.provision.inc @@ -0,0 +1,149 @@ +type == 'site') { + if (empty(d()->environment)) { + return drush_set_error('DEVSHOP_FRAMEWORK_ERROR', 'This site is not a part of a project. You cannot use this command.'); + } + } + else { + return drush_set_error('DEVSHOP_FRAMEWORK_ERROR', 'provision-devshop-deploy must be run on a site context.'); + } + + // Verify that the branch or tag exists + if (empty($git_ref)) { + return drush_set_error('DEVSHOP_FRAMEWORK_ERROR', 'You must specify a valid branch or tag.'); + } + + $project_alias = '@project_' . d()->project; + $project = (object) d($project_alias)->project; + + if (!isset($project->settings['git']['refs'][$git_ref])) { + $drush_command = "drush $project_alias provision-verify"; + return drush_set_error('DEVSHOP_FRAMEWORK_ERROR', "Branch or tag '$git_ref' not found. Try running '$drush_command' to fetch new remote branches or tags."); + } +} + +/** + * Implements the provision-devshop-deploy command. + */ +function drush_devshop_provision_provision_devshop_deploy($git_ref = '') +{ + $project_name = d()->project; + $project = (object) d("@project_{$project_name}")->project; + $environment = (object) $project->environments[d()->environment]; + $desired_ref_type = $project->settings['git']['refs'][$git_ref]; + + drush_log('[Current Working Directory]' . d()->platform->repo_path, 'devshop_log'); + + // Stash any changes? Not sure if we should do this anymore... + // $git->command('stash'); + + // Fetch + devshop_process("git fetch --all", d()->platform->repo_path, dt('DevShop Deploy')); + + // Checkout the chosen ref + $git_checkout_output = devshop_process("git checkout {$git_ref}", d()->platform->repo_path, dt('DevShop Deploy')); + $git_checkout_output_lines = explode("\n", $git_checkout_output); + + // Run Git Pull, if on a branch. + if ($desired_ref_type == 'branch') { + $git_pull_output = devshop_process("git pull", d()->platform->repo_path, dt('DevShop Deploy')); + } + + // Run a submodule update and init. + devshop_process("git submodule update --init --recursive --force ", d()->platform->repo_path, dt('DevShop Deploy')); + + devshop_process("git status", d()->platform->repo_path, dt('DevShop Deploy')); +} + +/** + * Post provision-devshop-deploy + */ +function drush_devshop_provision_post_provision_devshop_deploy($git_ref = '') { + + // Get post deploy options + $revert = drush_get_option('revert'); + $update = drush_get_option('update'); + $cache = drush_get_option('cache'); + $composer = drush_get_option('composer', TRUE); + + $project_name = d()->project; + $project = (object) d("@project_{$project_name}")->project; + $environment = (object) $project->environments[d()->environment]; + $desired_ref_type = $project->settings['git']['refs'][$git_ref]; + + // Ensure drush_alias exists. Not sure why it was missing for me. + if (empty($environment->drush_alias)) { + $environment->drush_alias = d()->name; + } + + $commands = array(); + + drush_log("[{$project_name}] {$environment->name}: " . dt('Running deploy hooks.'), 'notice'); + + // Built in Hooks + if ($composer) { + if (file_exists(d()->platform->repo_path . '/composer.json')) { + $commands[] = "composer install"; + } + elseif (file_exists($environment->root . '/composer.json')) { + $commands[] = "cd {$environment->root} && composer install"; + } + } + else { + drush_log(dt('[DEVSHOP] Skipped running composer install...'), 'ok'); + } + + // Built in Hooks + if ($update) { + $commands[] = "drush {$environment->drush_alias} updatedb --yes"; + } + else { + drush_log(dt('[DEVSHOP] Skipped updating database...'), 'ok'); + } + + // Clear the whole cache, unless option is false + if ($cache) { + if (drush_drupal_major_version(d()->root) == 8) { + $commands[] = "drush {$environment->drush_alias} cache-rebuild"; + } + else { + $commands[] = "drush {$environment->drush_alias} cache-clear all"; + } + } + else { + drush_log(dt('[DEVSHOP] Skipped clearing all caches...'), 'ok'); + } + + // Revert All Features, unless option is false + if ($revert) { + $commands[] = "drush {$environment->drush_alias} features-revert-all --yes"; + } + else { + drush_log(dt('[DEVSHOP] Skipped reverting all features...'), 'ok'); + } + + foreach ($commands as $command) { + $output = devshop_process($command, d()->platform->repo_path, 'DevShop Deploy Hook'); + + // Detect common errors and help the user. + // Composer install with incorrect PHP version. + if (strpos($command, 'composer install') !== FALSE && strpos($output, 'your PHP version') !== FALSE && strpos($output, 'does not satisfy that requirement') !== FALSE) { + drush_log(dt('Composer indicated that it could not install packages because the site codebase requires a higher version of PHP than what is running on the server.'), 'devshop_log'); + drush_log(dt('You can either upgrade your version of PHP on this server or set the maximum PHP version in your composer.json file. See !link for instructions for how to modify your composer.json file, then make sure to run `composer update` on your codebase to pin your packages to a lower PHP version.', [ + '!link' => 'https://getcomposer.org/doc/06-config.md#platform', + ]), 'devshop_log'); + } + + } +} + diff --git a/modules/devshop/devshop_projects/drush/deploy_hooks_example/devshop.drush.inc b/modules/devshop/devshop_projects/drush/deploy_hooks_example/devshop.drush.inc new file mode 100644 index 000000000..bb6fceb5e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/deploy_hooks_example/devshop.drush.inc @@ -0,0 +1,39 @@ +type == 'site'){ + + $project_name = d()->project; + $project = (object) d("@project_{$project_name}")->project; + $environment = (object) $project->environments[d()->environment]; + + drush_log("[CUSTOM] Successfully checked out $branch to environment $environment->name in project $project_name.", 'ok'); + +// provision_backend_invoke(d()->name, 'status'); + + } +} diff --git a/modules/devshop/devshop_projects/drush/devshop-dev.make b/modules/devshop/devshop_projects/drush/devshop-dev.make new file mode 100644 index 000000000..b9808342b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/devshop-dev.make @@ -0,0 +1,19 @@ +; +; This makefile is used by the DevShop standalone installer to build devmaster. +; + +core = 6.x +api = 2 + +projects[drupal][type] = "core" + +; DEVELOPMENT MODE: +; When in development, use this: +projects[devmaster][type] = "profile" +projects[devmaster][download][type] = "git" +projects[devmaster][download][url] = "git@git.drupal.org:project/devmaster.git" +projects[devmaster][download][branch] = "6.x-1.x" + +; RELEASE: +; When releasing, lock in the devmaster version. +;projects[devmaster][version] = "6.x-1.0" \ No newline at end of file diff --git a/modules/devshop/devshop_projects/drush/devshop_provision.drush.inc b/modules/devshop/devshop_projects/drush/devshop_provision.drush.inc new file mode 100644 index 000000000..a7b32753f --- /dev/null +++ b/modules/devshop/devshop_projects/drush/devshop_provision.drush.inc @@ -0,0 +1,596 @@ + 'Deploys a tag or branch to an environment and (optionally) run update.php, clear cache, and revert features.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + 'options' => array( + 'update' => 'Run update.php after code pull.', + 'revert' => 'Revert all features after code pull.', + 'cache' => 'Clear all caches after code pull.', + 'reset' => 'Runs "git reset --hard" before pulling.', + 'force' => "Runs always update,revert and cache options, even if files don't change.", + 'test' => 'Queue a test run after the deploy. (Only works from Hostmaster)', + ), + 'arguments' => array( + 'git_ref' => 'The git branch or tag to deploy.', + ), + 'examples' => array( + 'drush @env.project.domain.com provision-devshop-deploy master --cache --update' => 'Triggers a git checkout & pull of branch master to the dev environment, clearing caches and running updates.', + ), + 'aliases' => array('deploy'), + ); + $items['provision-test'] = array( + 'description' => 'Run a set of tests.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + 'options' => array( + 'tests-to-run' => 'The list of tests to run, separated by comma.', + 'test-type' => 'The type of tests to run. simpletest, behat', + 'behat-folder-path' => 'The path to this sites behat tests.', + 'behat-bin-path' => 'The path to the behat executable within behat-folder-path', + 'output-path' => 'The path to a folder to store results in. Will be created if it doesn\'t exist', + ), + 'aliases' => array('test'), + ); + return $items; +} + +/** + * Function for checking if this is a project and we have a repo. + * + * Used in pre drush command hooks + */ +function devshop_provision_pre_flight($platform_name = NULL){ + if (d()->type != 'Project'){ + return drush_set_error(DEVSHOP_FRAMEWORK_ERROR, 'All provision-devshop-* commands must be run on a project alias.'); + } +} + +/** + * Append PHP code to Drupal's settings.php file. + * + * To use templating, return an include statement for the template. + * + * @param $uri + * URI for the site. + * @param $data + * Associative array of data from provisionConfig_drupal_settings::data. + * + * @return + * Lines to add to the site's settings.php file. + * + * @see provisionConfig_drupal_settings + */ +function devshop_provision_provision_drupal_config($uri, $data, $config = NULL) { + + $environment = d()->environment; + $project = d()->project; + + return << NULL, + 'Process' => 'Process', + ); +} + +/** + * Implements hook_provision_apache_vhost_config() + * + * Adds "devshop_project" and "devshop_environment" server variables. + * + * @param $uri + * URI for the site. + * @param $data + * Associative array of data from Provision_Config_Apache_Site::data. + * + * @return + * Lines to add to the configuration file. + * + * @see Provision_Config_Apache_Site + * provision_logs_provision_apache_vhost_config + */ +function devshop_provision_provision_apache_vhost_config($uri, $data) { + + $environment = d()->environment; + $project = d()->project; + + if (empty($environment) || empty($project)) { + return; + } + + return <<environment; + $project = d()->project; + + if (empty($environment) || empty($project)) { + return; + } + + return <<project) && empty(d()->environment)) { + drush_log('[DEVSHOP] New environment detected. Rebuilding Registry.', 'ok'); + drush_invoke('registry-rebuild'); + } +} + +/** + * Run a Symfony Process saving to drush logs. + */ +function devshop_process($command, $cwd = null, $label = 'Process', $env = array(), $log_output = TRUE, $error_message = 'Process Failed') { + drush_log("[$label] $command", 'devshop_command'); + + // Merge in env vars, inheriting the CLI's + if (is_array($env)) { + $env = array_merge($_SERVER, $env); + } + else { + $env = $_SERVER; + } + + // Make sure colors always come through + $env['TERM'] = 'xterm'; + + $process = new Process(escapeshellcmd($command), $cwd, $env); + $process->setTimeout(NULL); + if ($log_output) { + $exit_code = $process->run(function ($type, $buffer) { + if (drush_get_option('is-devshop', FALSE)) { + drush_log($buffer, 'devshop_info'); + } + else { + echo $buffer; + } + +// if (Process::ERR === $type) { +// drush_log($buffer, 'devshop_info'); +// } else { +// drush_log($buffer, 'devshop_info'); +// } + }); + } + else { + $exit_code = $process->run(); + } + + // check exit code + if ($exit_code === 0) { + drush_log('', 'devshop_ok'); + return $process->getOutput(); + } + else { + drush_log('', 'devshop_error'); + drush_set_error('DEVSHOP_PROCESS_ERROR', dt($error_message)); + return $process->getErrorOutput(); + } +} + +/** + * Find the username of the current running procses + * + * This will return the username of the current running user (as seen + * from posix_geteuid()) and should be used instead of + * get_current_user() (which looks at the file owner instead). + * + * @see get_current_user() + * @see posix_geteuid() + * + * @return + * String. The username. + */ +function devshop_current_user() { + return devshop_posix_username(posix_geteuid()); +} + +/** + * Check whether a user is a member of a group. + * + * @param user + * username or user id of user. + * @param group + * groupname or group id of group. + * + * @return + * Boolean. True if user does belong to group, + * and FALSE if the user does not belong to the group, or either the user or group do not exist. + */ +function devshop_user_in_group($user, $group) { + // TODO: make these singletons with static variables for caching. + $user = devshop_posix_username($user); + $group = devshop_posix_groupname($group); + if ($user && $group) { + $info = posix_getgrnam($group); + if (in_array($user, $info['members'])) { + return TRUE; + } + } + return FALSE; +} + +/** + * Return the valid system username for $user. + * + * @return + * Returns the username if found, otherwise returns FALSE + */ +function devshop_posix_username($user) { + // TODO: make these singletons with static variables for caching. + // we do this both ways, so that the function returns NULL if no such user was found. + if (is_numeric($user)) { + $info = posix_getpwuid($user); + $user = $info['name']; + } + else { + $info = posix_getpwnam($user); + $user = $info['name']; + } + return $user; +} + +/** + * Return the valid system groupname for $group. + * + * @return + * Returns the groupname if found, otherwise returns FALSE + */ +function devshop_posix_groupname($group) { + // TODO: make these singletons with static variables for caching. + // we do this both ways, so that the function returns NULL if no such user was found. + if (is_numeric($group)) { + $info = posix_getgrgid($group); + $group = $info['name']; + } + else { + $info = posix_getgrnam($group); + $group = $info['name']; + } + return $group; +} + +/** + * Generate a random alphanumeric password. + * + * This is a copy of Drupal core's user_password() function. We keep it + * here in case we need this and don't have a bootstrapped Drupal + * around. + * + * @see user_password() + */ +function devshop_password($length = 10) { + // This variable contains the list of allowable characters for the + // password. Note that the number 0 and the letter 'O' have been + // removed to avoid confusion between the two. The same is true + // of 'I', 1, and 'l'. + $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'; + + // Zero-based count of characters in the allowable list: + $len = strlen($allowable_characters) - 1; + + // Declare the password as a blank string. + $pass = ''; + + // Loop the number of times specified by $length. + for ($i = 0; $i < $length; $i++) { + + // Each iteration, pick a random character from the + // allowable string and append it to the password: + $pass .= $allowable_characters[mt_rand(0, $len)]; + } + + return $pass; +} + +function devshop_default_web_group() { + $info = posix_getgrgid(posix_getgid()); + $common_groups = array( + 'www-data', + 'apache', + 'nginx', + 'www', + '_www', + 'webservd', + 'httpd', + 'nogroup', + 'nobody', + $info['name']); + + foreach ($common_groups as $group) { + if (devshop_posix_groupname($group)) { + return $group; + break; + } + } + return NULL; +} + +/** + * return the FQDN of the machine or provided host + * + * this replicates hostname -f, which is not portable + */ +function devshop_fqdn($host = NULL) { + if (is_null($host)) { + $host = php_uname('n'); + } + return strtolower(gethostbyaddr(gethostbyname($host))); +} + +// +//if (!function_exists('provision_autoload_register_prefix')) { +// /** +// * Register a PECL style prefix with the provision autoloader. +// * +// * @param string $prefix +// * The class prefix to register. +// * @param string $dir +// * The directory to search for the classes in. +// * @param bool $prepend +// * If the directory should be searched first for the classes in the given +// * prefix, set this to TRUE, otherwise, the default, FALSE, is fine. +// */ +// function provision_autoload_register_prefix($prefix, $dir, $prepend = FALSE) { +// +// // Get any current directories set for this prefix. +// $current_prefixes = provision_autoload()->getPrefixes(); +// if (isset($current_prefixes[$prefix])) { +// $dirs = $current_prefixes[$prefix]; +// } +// else { +// $dirs = array(); +// } +// +// // Now add the new one. +// if ($prepend) { +// array_unshift($dirs, $dir); +// } +// else { +// array_push($dirs, $dir); +// } +// +// // Set the prefixes. +// provision_autoload()->addPrefix($prefix, $dirs); +// } +//} +// +//if (!function_exists('provision_autoload')) { +// /** +// * Return an instance of the provision autoloader. +// * +// * This will instiatate an instance if it needs to. +// */ +// function provision_autoload() { +// static $instance = NULL; +// +// if (is_null($instance)) { +// $instance = new ClassLoader(); +// // Activate the autoloader. +// $instance->register(); +// } +// +// return $instance; +// } +//} + +/** + * Implementation of drush_hook_provision_pre_COMMAND() + * Calls drush_devshop_provision_pre_provision_verify() + */ +function drush_devshop_provision_pre_provision_deploy() { + drush_devshop_provision_pre_provision_verify(); +} + +/** + * Implementation of drush_hook_provision_pre_COMMAND() + * for Verify tasks: Writes easier to use drush aliases for each project. + */ +function drush_devshop_provision_pre_provision_verify() { + if (d()->type === 'project' || (!empty(d()->project) && d()->type === 'site' || d()->type === 'platform')) { + + } +} + +/** + * Implementation of drush_hook_post_provision_install() + * + * This function handles all of the alternative environment install methods. + */ +function drush_devshop_provision_post_provision_install() { + + // Install Method: Clone + // Sync from a drush alias. + drush_log('Installing environment with method: ' . d()->install_method , 'devshop_log'); + if (d()->install_method == 'clone') { + drush_log(dt('Environment Clone initiated...'), 'devshop_log'); + $clone_source = d()->environment_settings['install_method']['clone_source']; + + // If "clone_source" is set to "_other", use the drush alias in "clone_source_drush". + if ($clone_source == '_other') { + $source = d()->environment_settings['install_method']['clone_source_drush']; + } + elseif (!empty($clone_source)) { + $source = d()->environment_settings['install_method']['clone_source']; + } + else { + return drush_set_error('DEVSHOP_ERROR', dt('Install_method"clone_source" was not set.')); + } + + $destination = d()->name; + $command = "drush provision-sync $source $destination --database --files --update-uri --disable-rollback-backup --updatedb --cache-clear --registry-rebuild"; + devshop_process($command, NULL, 'Cloning site from ' . d($source)->uri); + devshop_process('drush cc drush', NULL, 'Clearing drush caches'); +// devshop_drush_process_cache_clear($destination); + } + + // Install Method: import + elseif (d()->install_method == 'import') { + + + // If import source is a mysql URL: + $path_or_url = d()->environment_settings['install_method']['import']; + $url = parse_url($path_or_url); + if ($url['scheme'] == 'mysql') { + + // Dump the file + $temp_file_name = tempnam('/tmp', 'devshop_remote_db.sql.'); + $db_name = ltrim($url['path'], '/'); + $command = "mysqldump -u{$url['user']} -p{$url['pass']} -h{$url['host']} {$db_name} --result-file={$temp_file_name}"; + devshop_process($command, NULL, dt('Saving database file...')); + + $file_to_import = $temp_file_name; + } + + // If file exists but is not readable, alert the user. + elseif (file_exists($path_or_url) && !is_readable($path_or_url)){ + return drush_set_error('DEVSHOP_IMPORT_ERROR', dt('Unable to import SQL file @path: not readable.')); + } + + // If file exists and is readable, set it as the file to import. + elseif (file_exists($path_or_url) && is_readable($path_or_url)){ + $file_to_import = $path_or_url; + } + + // Import the file + $alias = d()->name; + $command = "drush {$alias} sqlq 'SOURCE {$file_to_import}'"; + devshop_process($command, NULL, dt('Importing database file...')); + devshop_process('drush cc drush', NULL, 'Clearing drush caches'); + devshop_drush_process_cache_clear($alias); + devshop_process("drush {$alias} sqlq 'SHOW TABLES'", NULL, dt('Show Tables')); + } +} + +/** + * Runs drush cache-clear or drush cache-rebuild on the chosen alias. + * + * @param $alias + * A drush alias. + */ +function devshop_drush_process_cache_clear($alias) { + + // Clear all caches. + if (drush_drupal_major_version(d()->root) == 8) { + devshop_process("drush {$alias} cache-rebuild", NULL, dt('Clear all Caches')); + } + else { + devshop_process("drush {$alias} cache-clear all", NULL, dt('Clear all Caches')); + } +} \ No newline at end of file diff --git a/modules/devshop/devshop_projects/drush/install.devshop.inc b/modules/devshop/devshop_projects/drush/install.devshop.inc new file mode 100644 index 000000000..e1e09b454 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/install.devshop.inc @@ -0,0 +1,289 @@ + join("\n", drush_shell_exec_output())))); + } +} + +function drush_provision_devshop_install_validate($site = NULL) { + // set defaults for this whole script + // those are settings that are not prompted to the user but still overridable + drush_set_default('version', provision_version()); + drush_set_default('profile', 'devmaster'); + + // Get values needed to set other defaults + $version = drush_get_option('version'); + $aegir_root = drush_set_default('aegir_root', drush_server_home()); + $profile = drush_get_option('profile'); + + drush_set_default('root', $aegir_root . '/' . $profile . '-' . $version); + drush_set_default('r', drush_get_option('root')); + drush_set_default('script_user', devshop_current_user()); + drush_set_default('web_group', _provision_default_web_group()); + drush_set_default('http_service_type', 'apache'); + drush_set_default('http_port', '80'); + drush_set_default('aegir_db_user', 'root'); + drush_set_default('aegir_db_port', '3306'); + drush_set_default('client_name', 'admin'); + $aegir_db_user = drush_get_option('aegir_db_user'); + + // Generate "makefile" message only if there is one set. + $root = drush_get_option(array('r', 'root')); + if (is_dir($root) && !drush_get_option('makefile', FALSE)) { + // Don't assume we know the makefile used to build an existing platform + $makefile_msg = ''; + } + else { + drush_set_default('makefile', dirname(__FILE__) . '/devshop-dev.make'); + $makefile_msg = dt("Aegir makefile: !makefile\n", array('!makefile' => drush_get_option('makefile'))); + } + + // Generate "profile" message only if there is one set. + if (!drush_get_option('profile', FALSE)) { + $profile_msg = ''; + } + else { + $profile_msg = dt("Aegir install profile: !profile\n", array('!profile' => $profile)); + } + + drush_print("Aegir $version automated install script"); + drush_print("=============================================================================="); + + if (!$site || !drush_get_option('aegir_host', NULL) || !drush_get_option('aegir_db_pass', NULL) || filter_var(drush_get_option('client_email'), FILTER_VALIDATE_EMAIL)) { + drush_print("Some settings have not been provided and will now be prompted. +Don't worry: you will get to review those settings after the final install"); + } + // now we prompt the user for settings if not provided or not sane + if (!$site) { + $site = drush_prompt(dt("Aegir frontend URL"), get_fqdn()); + } + drush_set_option('site', $site); + + drush_set_default('aegir_host', get_fqdn()); + drush_set_default('aegir_db_host', 'localhost'); + + if (is_null(drush_get_option('aegir_db_pass', NULL))) { + // XXX: may not be portable everywhere? + system('stty -echo'); + drush_set_option('aegir_db_pass', drush_prompt(dt('MySQL privileged user ("!root") password', array('!root' => $aegir_db_user)))); + system('stty echo'); + print "\n"; // add a newline since the user's didn't print + } + + if (drush_get_option('aegir_host') == 'localhost') { + $default_email = 'webmaster@example.com'; + } else { + $default_email = 'webmaster@' . drush_get_option('aegir_host'); + } + drush_set_default('client_email', $default_email); + while (!filter_var(drush_get_option('client_email'), FILTER_VALIDATE_EMAIL) && !drush_get_context('DRUSH_AFFIRMATIVE')) { + drush_set_option('client_email', drush_prompt(dt("Admin user e-mail"), $default_email)); + } + + drush_print(dt(' +This script will operate the following changes in your system: + +1. Create server-level configuration directories +2. Create the Hostmaster frontend platform +3. Install the frontend site +4. Setup the dispatcher (a user cron job) + +We are making the following assumptions: + * you have read and are following the install instructions at: + http://community.aegirproject.org/installing + * the FQDN of this machine is valid and resolves + * you are executing this script as your "aegir" user + +The following settings will be used: + Aegir frontend URL: !site + Master server FQDN: !fqdn + Aegir root: !home + Aegir user: !user + Web group: !web + Web server: !web_server + Web server port: !web_server_port + Aegir DB host: !db_host + Aegir DB user: !db_user + Aegir DB password: !db_pass + Aegir DB port: !db_port + Aegir version: !version + Aegir platform path: !root + Admin email: !email + !makefile !profile', + array( + '!site' => $site, + '!fqdn' => drush_get_option('aegir_host'), + '!home' => drush_get_option('aegir_root'), + '!user' => drush_get_option('script_user'), + '!web' => drush_get_option('web_group'), + '!web_server' => drush_get_option('http_service_type'), + '!web_server_port' => drush_get_option('http_port'), + '!db_host' => drush_get_option('aegir_db_host'), + '!db_user' => drush_get_option('aegir_db_user'), + '!db_pass' => is_null(drush_get_option('aegir_db_pass', NULL, 'process')) ? '' : '', + '!db_port' => drush_get_option('aegir_db_port'), + '!version' => drush_get_option('version'), + '!root' => $root, + '!makefile' => $makefile_msg, + '!profile' => $profile_msg, + '!email' => drush_get_option('client_email'), + ))); + + if (!drush_confirm(dt('Do you really want to proceed with the install'))) { + return drush_set_error('PROVISION_CANCEL_INSTALL', dt('Installation aborted')); + } + + return TRUE; +} + +/** + * Drush command to install hostmaster. + */ +function drush_provision_devshop_install($site = NULL) { + $version = drush_get_option('version'); + $site = drush_get_option('site', get_fqdn()); + $aegir_root = drush_get_option('aegir_root'); + $platform = drush_get_option(array('r', 'root')); + + $aegir_http_host = drush_get_option('aegir_host'); + $aegir_http_port = drush_get_option('http_port'); + $aegir_db_user = drush_get_option('aegir_db_user'); + $aegir_db_pass = drush_get_option('aegir_db_pass'); + $aegir_db_port = drush_get_option('aegir_db_port'); + $aegir_db_host = drush_get_option('aegir_db_host'); + + $server = '@server_master'; + $master_context = array( + 'context_type' => 'server', + // files + 'remote_host' => $aegir_http_host, + 'aegir_root' => $aegir_root, + 'script_user' => drush_get_option('script_user'), + // apache or nginx or.. + 'http_service_type' => drush_get_option('http_service_type'), + 'http_port' => $aegir_http_port, + 'web_group' => drush_get_option('web_group'), + 'master_url' => "http://" . $site, + 'db_port' => $aegir_db_port, + ); + + $master_db = sprintf("mysql://%s:%s@%s:%s", urlencode($aegir_db_user), urlencode($aegir_db_pass), $aegir_db_host, $aegir_db_port); + if ($aegir_http_host == $aegir_db_host) { + $master_context['db_service_type'] = 'mysql'; + $master_context['master_db'] = $master_db; + $dbserver = $server; + } else { + $dbserver = '@server_' . $aegir_db_host; + $dbserver_context = array( + 'remote_host' => $aegir_db_host, + 'context_type' => 'server', + 'db_service_type' => 'mysql', + 'master_db' => $master_db, + 'db_port' => $aegir_db_port, + ); + drush_invoke_process('@none', "provision-save", array($dbserver), $dbserver_context); + provision_backend_invoke($dbserver, 'provision-verify'); + } + drush_invoke_process('@none', "provision-save", array($server), $master_context); + provision_backend_invoke($server, 'provision-verify'); + + // exit if an error has occured. + if (drush_get_error()) { + return false; + } + + if (drush_get_option('backend-only')) { + return; + } + + $platform_name = '@platform_hostmaster'; + drush_invoke_process('@none', "provision-save", array($platform_name), array( + 'context_type' => 'platform', + 'server' => $server, + 'web_server' => $server, + 'root' => $platform, + 'makefile' => drush_get_option('makefile'), + )); + // propagate working-copy args downward + $options = array(); + if (drush_get_option('working-copy')) { + $options['working-copy'] = 1; + } + provision_backend_invoke($platform_name, 'provision-verify', array(), $options); + + // exit if an error has occured. + if (drush_get_error()) { + return false; + } + + drush_set_default('profile', 'devmaster'); + $profile = drush_get_option('profile'); + + $site_name = '@hostmaster'; + drush_invoke_process('@none', "provision-save", array($site_name), array( + 'context_type' => 'site', + 'platform' => $platform_name, + 'db_server' => $dbserver, + 'uri' => $site, + 'client_name' => drush_get_option('client_name'), + 'profile' => $profile, + )); + $data = provision_backend_invoke($site_name, 'provision-install', array(), array('client_email' => drush_get_option('client_email'))); + provision_backend_invoke($site_name, 'provision-verify'); + + // exit if an error has occured. + if (drush_get_error()) { + return false; + } + + + drush_print(dt("Initializing the hosting system")); + drush_invoke_process('@none', 'cache-clear', array('drush')); + provision_backend_invoke($site_name, 'hosting-setup'); + + drush_print(""); + drush_print("=============================================================================="); + drush_print(""); + drush_print(""); + drush_print(dt("Congratulations, Aegir has now been installed.")); + drush_print(""); + drush_print(dt("You should now log in to the Aegir frontend by opening the following link in your web browser:")); + drush_print(""); + drush_print($data['context']['login_link']); + drush_print(""); + drush_print(""); + drush_print("=============================================================================="); + drush_print(""); +} + +/** + * Implements drush_hook_post_hostmaster_install(). + */ +function drush_provision_post_devshop_install() { + $backend_only = drush_get_option('backend-only'); + if (empty($backend_only)) { + drush_invoke_process('@hostmaster', 'cache-clear', array('drush')); + } +} diff --git a/modules/devshop/devshop_projects/drush/pull.devshop.provision.inc b/modules/devshop/devshop_projects/drush/pull.devshop.provision.inc new file mode 100644 index 000000000..2d2e7fdc4 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/pull.devshop.provision.inc @@ -0,0 +1,123 @@ +project_name; + $platform_alias = '@' . implode('_', array('platform', $project_name, $platform_name)); + $platform = d($platform_alias); + + $site_alias = '@' . $platform_name . '.' . d()->project['base_url']; + $site = d($site_alias); + + // Find repo path + $repo_path = $platform->root; + + // Ensure it's a git repo. + provision_git_is_repo($repo_path); + + // Determine revert setting from project settings (or if set as option, always) + $reset = drush_get_option('reset') ? drush_get_option('reset') : FALSE; + + // If reset is true, do a git reset --hard first. + if ($reset) { + drush_shell_cd_and_exec($repo_path, 'git reset --hard'); + drush_log(dt('[DEVSHOP] Git repository reset.'), 'ok'); + } + + // Pull latest version of site + // Execute git pull --rebase + if (drush_shell_cd_and_exec($repo_path, 'git pull --rebase')) { + drush_log(dt('[DEVSHOP] Git repository pulled.', array('!path' => $repo_path)), 'ok'); + $output = drush_shell_exec_output(); + drush_log(implode("\n", drush_shell_exec_output()), 'ok'); + } + else { + drush_set_error('DRUSH_PROVISION_GIT_PULL_FAILED', dt("Git pull failed in !path.\nThe specific errors are below:\n!errors", array('!path' => $repo_path, '!errors' => implode("\n", drush_shell_exec_output())))); + continue; + } + + // If no new files were detected... and force is false then skip out. + if (count($output) == 1 && !$force) { + drush_log('[DEVSHOP] No changes detected. Nothing else needs to be done', 'ok'); + continue; + } + + // Verify the platform. + provision_backend_invoke($platform_alias, 'provision-verify'); + + // update db, unless option is false. + if ($update){ + drush_log(dt('[DEVSHOP] Updating database...'), 'ok'); + provision_backend_invoke($site_alias, 'updb'); + } + else { + drush_log(dt('[DEVSHOP] Skipped updating database...'), 'ok'); + } + + // Revert All Features, unless option is false + if ($revert){ + drush_log(dt('[DEVSHOP] Reverting all features...'), 'ok'); + provision_backend_invoke($site_alias, 'features-revert-all'); + } + else { + drush_log(dt('[DEVSHOP] Skipped reverting all features...'), 'ok'); + } + + // Clear the whole cache, unless option is false + // Seriously, lets do this twice. Go Drupal! + if ($cache){ + drush_log(dt('[DEVSHOP] Clearing all caches...'), 'ok'); + provision_backend_invoke($site_alias, 'cc all'); + provision_backend_invoke($site_alias, 'cc all'); + } + else { + drush_log(dt('[DEVSHOP] Skipped clearing all caches...'), 'ok'); + } + } +} + diff --git a/modules/devshop/devshop_projects/drush/test.devshop.provision.inc b/modules/devshop/devshop_projects/drush/test.devshop.provision.inc new file mode 100644 index 000000000..e69de29bb diff --git a/modules/devshop/devshop_projects/drush/test.provision.inc b/modules/devshop/devshop_projects/drush/test.provision.inc new file mode 100644 index 000000000..84713906e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/test.provision.inc @@ -0,0 +1,184 @@ +project; + $project = (object) d("@project_{$project_name}")->project; + $environment = (object) $project->environments[d()->environment]; + + drush_log(dt('Provision DevShop Run Tests started...'), 'status'); + + // Get tests to run + if (!empty($project->settings['testing']['test_type'])) { + $type = $project->settings['testing']['test_type']; + } + else { + $type = drush_get_option('test-type', NULL); + } + + if ($project->settings['testing']['tests_to_run'] === NULL) { + $tests = array(); + } + else { + $tests = array_filter($project->settings['testing']['tests_to_run']); + } + $tests_to_run = drush_get_option('tests-to-run', $tests); + + // Run Simpletest + if ($type == 'simpletest') { + drush_log(dt("Running $type tests $tests_to_run"), 'ok'); + provision_backend_invoke('@self', 'en simpletest'); + provision_backend_invoke('@self', 'test-run', array($tests_to_run)); + } + elseif ($type == 'behat') { + // Get paths from options or site context. + $repo_path = d()->platform->repo_path? d()->platform->repo_path: d()->platform->root; + $behat_folder_path = drush_get_option('behat-folder-path', $project->settings['testing']['behat_folder_path']); + + // If no repo at that path, error. + if (!file_exists($repo_path)){ + return drush_set_error('DEVSHOP_MISSING_FILE', dt('repo_path does not exist.')); + } + // If no file at behat bin path, error. + $tests_folder = $repo_path . '/' .$behat_folder_path; + $yml_path = $repo_path . '/' .$behat_folder_path . '/behat.yml'; + if (!file_exists($yml_path)){ + + $message = << Testing. + 2. Copy the files from https://github.com/opendevshop/devmaster/tree/1.x/modules/devshop/devshop_testing/tests_example + 3. Write more tests! + +TXT; + + + drush_log(dt($message, array('!path' => $behat_folder_path)), 'error'); + return drush_set_error('DEVSHOP_MISSING_FILE', dt('Your project is not yet ready to run Behat tests. Please follow the instructions and try again.')); + } + + // Prepare path and command + $full_behat_folder_path = $repo_path . '/' . $behat_folder_path; + $full_behat_bin_path = $repo_path . '/' . $behat_folder_path . '/bin/behat'; + + // Load the behat.yml from behat_folder_path. + if (file_exists($full_behat_folder_path . '/behat.yml')) { + $behat_yml = file_get_contents($full_behat_folder_path . '/behat.yml'); + } + elseif (file_exists($full_behat_folder_path . '/config/behat.yml')) { + $behat_yml = file_get_contents($full_behat_folder_path . '/config/behat.yml'); + } + else { + return drush_set_error('DEVSHOP_MISSING_FILE', dt('behat.yml file not found in behat_folder_path: ') . $behat_folder_path); + } + + // Run composer install. + devshop_process('composer install', $full_behat_folder_path, 'DevShop Testing'); + + // Write custom behat.yml to temporary folder. + $environment_name = $environment->name; + $alias = d()->name; + $root = d()->root; + + $username = d()->http_basic_auth_username; + $password = d()->http_basic_auth_password; + $uri = d()->uri; + $url = d()->ssl_enabled? + "https://$uri": + "http://$uri"; + + if (!empty($username)) { + $url = d()->ssl_enabled? + "https://$username:$password@$uri": + "http://$username:$password@$uri"; + } + + $behat_yml = << $behat_yml_file, + )), 'ok'); + + // Run behat tests for each feature. + $no_errors = TRUE; + $test_result = ''; + + // Fill an empty item if empty so we run all the tests. + if (empty($tests_to_run)) { + $tests_to_run[] = ''; + } + + // Foreach test to run... + $i = 0; + foreach ($tests_to_run as $feature) { + $i++; + + // Check for path. + if (substr($feature, 0, 1) !== '/') { + $feature = '/'.$feature; + } + $feature_path = empty($feature) ? '' : "features{$feature}"; + + // Create Command + // The extra profile is used so we can dynamically set the URL and drush alias of the behat.yml. + // the "colors" option is to force it to output ANSI colors. "format-settings" "expand: true" is so + // Behat will output all of the steps when using "Scenario Outlines". + $cmd = "$full_behat_bin_path $feature_path --config $behat_yml_file --profile devshop --colors --strict --format-settings='{\"expand\": true}'"; + + // Run behat command + devshop_process($cmd, $full_behat_folder_path, 'DevShop Testing', array(), TRUE, 'Test Run Failed'); + } + } +} + diff --git a/modules/devshop/devshop_projects/drush/vendor/autoload.php b/modules/devshop/devshop_projects/drush/vendor/autoload.php new file mode 100644 index 000000000..dd11eed60 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + private $classMapAuthoritative = false; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/LICENSE b/modules/devshop/devshop_projects/drush/vendor/composer/LICENSE new file mode 100644 index 000000000..c8d57af8b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 2015 Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/autoload_classmap.php b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_classmap.php new file mode 100644 index 000000000..7a91153b0 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/cpliakas/git-wrapper/src'), +); diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/autoload_psr4.php b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_psr4.php new file mode 100644 index 000000000..a9a7dea0a --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_psr4.php @@ -0,0 +1,12 @@ + array($vendorDir . '/symfony/process'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\ClassLoader\\' => array($vendorDir . '/symfony/class-loader'), +); diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/autoload_real.php b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_real.php new file mode 100644 index 000000000..f8acedade --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_real.php @@ -0,0 +1,45 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + return $loader; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/installed.json b/modules/devshop/devshop_projects/drush/vendor/composer/installed.json new file mode 100644 index 000000000..f21f15b0d --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/composer/installed.json @@ -0,0 +1,230 @@ +[ + { + "name": "symfony/process", + "version": "v2.8.0", + "version_normalized": "2.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/1b988a88e3551102f3c2d9e1d47a18c3a78d6312", + "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "time": "2015-11-30 12:35:10", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf", + "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2015-10-30 23:35:59", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com" + }, + { + "name": "cpliakas/git-wrapper", + "version": "1.6.1", + "version_normalized": "1.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/cpliakas/git-wrapper.git", + "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cpliakas/git-wrapper/zipball/5b89e9c5b4598c110e2d93d1245b4e26df3855a5", + "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/event-dispatcher": "~2.3|~3.0", + "symfony/process": "~2.3|~3.0" + }, + "require-dev": { + "pdepend/pdepend": "~1.0", + "phploc/phploc": "~2.0", + "phpmd/phpmd": "~1.0", + "phpunit/phpunit": "~3.0", + "psr/log": "~1.0", + "scrutinizer/ocular": "~1.0", + "sebastian/phpcpd": "~2.0", + "symfony/filesystem": "~2.0" + }, + "suggest": { + "monolog/monolog": "Enables logging of executed git commands" + }, + "time": "2015-11-11 15:45:43", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "GitWrapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Pliakas", + "email": "opensource@chrispliakas.com" + } + ], + "description": "A PHP wrapper around the Git command line utility.", + "homepage": "https://github.com/cpliakas/git-wrapper", + "keywords": [ + "git" + ] + }, + { + "name": "symfony/class-loader", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/92e7cf1af2bc1695daabb4ac972db169606e9030", + "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "time": "2016-02-03 09:33:23", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ClassLoader Component", + "homepage": "https://symfony.com" + } +] diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.editorconfig b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.editorconfig new file mode 100644 index 000000000..153cf3ef5 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.editorconfig @@ -0,0 +1,10 @@ +; top-most EditorConfig file +root = true + +; Unix-style newlines +[*] +end_of_line = LF + +[*.php] +indent_style = space +indent_size = 4 diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.gitignore b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.gitignore new file mode 100644 index 000000000..a13670229 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.gitignore @@ -0,0 +1,4 @@ +build/ +composer.lock +nbproject +vendor/ diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.scrutinizer.yml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.scrutinizer.yml new file mode 100644 index 000000000..543119899 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.scrutinizer.yml @@ -0,0 +1,22 @@ +checks: + php: + code_rating: true + duplication: true + +filter: + paths: + - src/* + +tools: + php_sim: true + php_pdepend: true + php_analyzer: true + php_mess_detector: + config: + ruleset: ./phpmd.xml + php_cs_fixer: + config: { level: psr2 } + external_code_coverage: + runs: 4 + timeout: 600 + diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.travis.yml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.travis.yml new file mode 100644 index 000000000..47a99c84e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.travis.yml @@ -0,0 +1,14 @@ +language: php + +php: + - "5.6" + - "5.5" + - "5.4" + - "5.3" + +before_script: + - composer install --prefer-dist --dev + +after_script: + - vendor/bin/ocular code-coverage:upload --format=php-clover build/logs/clover.xml + diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/LICENSE b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/LICENSE new file mode 100644 index 000000000..5ef03e0a1 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Acquia, Inc. + +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/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/README.md b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/README.md new file mode 100644 index 000000000..71904b66f --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/README.md @@ -0,0 +1,186 @@ +# Overview + +[![Build Status](https://travis-ci.org/cpliakas/git-wrapper.svg?branch=master)](https://travis-ci.org/cpliakas/git-wrapper) +[![Code Coverage](https://scrutinizer-ci.com/g/cpliakas/git-wrapper/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/cpliakas/git-wrapper/?branch=master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/cpliakas/git-wrapper/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/cpliakas/git-wrapper/?branch=master) +[![Total Downloads](https://img.shields.io/packagist/dt/cpliakas/git-wrapper.svg)](https://packagist.org/packages/cpliakas/git-wrapper) +[![Latest Stable Version](https://img.shields.io/packagist/v/cpliakas/git-wrapper.svg)](https://packagist.org/packages/cpliakas/git-wrapper) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/cpliakas/git-wrapper/master/LICENSE) + +This library is a PHP wrapper around the Git command line tool. + +Its purpose is to provide a readable API that abstracts some of the challenges +of executing Git commands from within a PHP process. Specifically, this library +builds upon the Symfony framework's Process component to execute the Git command +in a way that works across platforms and uses the best-in-breed techniques +available to PHP. This library also provides an SSH wrapper script and API +method for developers to easily specify a private key other than one of the +defaults by using the technique in [this thread on StackOverflow](http://stackoverflow.com/a/3500308/870667). +Finally, various commands are expected to be executed in the directory +containing the working copy. Although this a fairly simple challenge to +overcome, the library handles this transparently so the developer doesn't have +to think about it. + +## Usage + +```php +use GitWrapper\GitWrapper; + +// Initialize the library. If the path to the Git binary is not passed as +// the first argument when instantiating GitWrapper, it is auto-discovered. +require_once 'vendor/autoload.php'; +$wrapper = new GitWrapper(); + +// Optionally specify a private key other than one of the defaults. +$wrapper->setPrivateKey('/path/to/private/key'); + +// Clone a repo into `/path/to/working/copy`, get a working copy object. +$git = $wrapper->clone('git://github.com/cpliakas/git-wrapper.git', '/path/to/working/copy'); + +// Create a file in the working copy. +touch('/path/to/working/copy/text.txt'); + +// Add it, commit it, and push the change. +$git + ->add('test.txt') + ->commit('Added the test.txt file as per the examples.') + ->push(); + +// Render the output. +print $git->getOutput(); + +// Stream output of subsequent Git commands in real time to STDOUT and STDERR. +$wrapper->streamOutput(); + +// Execute an arbitrary git command. +// The following is synonymous with `git config -l` +$wrapper->git('config -l'); +``` + +All command methods adhere to the following paradigm: + +```php +$git->command($arg1, $arg2, ..., $options); +``` + +Replace `command` with the Git command being executed, e.g. `checkout`, `push`, +etc. The `$arg*` parameters are a variable number of arguments as they would be +passed to the Git command line tool. `$options` is an optional array of command +line options in the following format: + +```php +$options = array( + 'verbose' => true, // Passes the "--verbose" flag. + 't' => 'my-branch', // Passes the "-t my-branch" option. +); +``` + +#### Logging + +Use the logger listener with [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +compatible loggers such as [Monolog](https://github.com/Seldaek/monolog) to log +commands that are executed. + +```php + +use GitWrapper\Event\GitLoggerListener; +use Monolog\Logger; +use Monolog\Handler\StreamHandler; + +// Log to a file named "git.log" +$log = new Logger('git'); +$log->pushHandler(new StreamHandler('git.log', Logger::DEBUG)); + +// Instantiate the listener, add the logger to it, and register it. +$listener = new GitLoggerListener($log); +$wrapper->addLoggerListener($listener); + +$git = $wrapper->clone('git://github.com/cpliakas/git-wrapper.git', '/path/to/working/copy'); + +// The "git.log" file now has info about the command that was executed above. + +``` + +## Installation + +Git Wrapper can be installed with [Composer](http://getcomposer.org) by adding +the library as a dependency to your composer.json file. + +```json +{ + "require": { + "cpliakas/git-wrapper": "~1.0" + } +} +``` + +Please refer to [Composer's documentation](https://github.com/composer/composer/blob/master/doc/00-intro.md#introduction) +for installation and usage instructions. + +## Gotchas + +There are a few "gotchas" that are out of scope for this library to solve but +might prevent a successful implementation of running Git via PHP. The following +is an incomplete list of challenges that are often encountered when executing +Git from PHP. + +### Missing HOME Environment Variable + +Sometimes the `HOME` environment variable is not set in the Git process that is +spawned by PHP. This will cause many Git operations to fail. It is advisable to +set the `HOME` environment variable to a path outside of the document root that +the web server has write access to. Note that this environment variable is only +set for the process running Git and NOT the PHP process that is spawns it. + +```php +$wrapper->setEnvVar('HOME', '/path/to/a/private/writable/dir'); +``` + +It is important that the storage is persistent as the ~/.gitconfig file will be +written to this location. See the following "gotcha" for why this is important. + +### Missing Identity And Configurations + +Many repositories require that a name and email address are specified. This data +is set by running `git config [name] [value]` on the command line, and the +configurations are usually stored in the `~/.gitconfig file`. When executing Git +via PHP, however, the process might have a different home directory than the +user who normally runs git via the command line. Therefore no identity is sent +to the repository, and it will likely throw an error. + +```php +// Set configuration options globally. +$wrapper->git('config --global user.name "User name"'); +$wrapper->git('config --global user.email user@example.com'); + +// Set configuration options per repository. +$git + ->config('user.name', 'User name') + ->config('user.email', 'user@example.com'); +``` + +### Commits To Repositories With No Changes + +Running `git commit` on a repository with no changes returns no output but exits +with a status of 1. Therefore the library will throw a `GitException` since it +correctly detected an error. It is advisable to check whether a working copy has +any changes prior to running the commit operation in order to prevent unwanted +exceptions. + +```php +if ($git->hasChanges()) { + $git->commit('Committed the changes.'); +} +``` + +### Permissions Of The GIT_SSH Wrapper Script + +On checkout, the bin/git-ssh-wrapper.sh script should be executable. If it is +not, git commands with fail if a non-default private key is specified. + + $> chmod 0755 ./bin/git-ssh-wrapper.sh + +## For Developers + +Refer to [PHP Project Starter's documentation](https://github.com/cpliakas/php-project-starter#using-apache-ant) +for the Apache Ant targets supported by this project. diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/bin/git-ssh-wrapper.sh b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/bin/git-ssh-wrapper.sh new file mode 100755 index 000000000..7782562eb --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/bin/git-ssh-wrapper.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +ssh -i $GIT_SSH_KEY -p $GIT_SSH_PORT -o StrictHostKeyChecking=no -o IdentitiesOnly=yes "$@" diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/build.xml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/build.xml new file mode 100644 index 000000000..2e67de86a --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/build.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/composer.json b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/composer.json new file mode 100644 index 000000000..09508032a --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/composer.json @@ -0,0 +1,37 @@ +{ + "name": "cpliakas/git-wrapper", + "type": "library", + "description": "A PHP wrapper around the Git command line utility.", + "keywords": ["git"], + "homepage": "https://github.com/cpliakas/git-wrapper", + "license": "MIT", + "authors": [ + { + "name": "Chris Pliakas", + "email": "opensource@chrispliakas.com" + } + ], + "require": { + "php": ">=5.3.0", + "symfony/process": "~2.3|~3.0", + "symfony/event-dispatcher": "~2.3|~3.0" + }, + "require-dev": { + "pdepend/pdepend": "~1.0", + "phploc/phploc": "~2.0", + "phpmd/phpmd": "~1.0", + "phpunit/phpunit": "~3.0", + "psr/log": "~1.0", + "scrutinizer/ocular": "~1.0", + "sebastian/phpcpd": "~2.0", + "symfony/filesystem": "~2.0" + }, + "suggest": { + "monolog/monolog": "Enables logging of executed git commands" + }, + "autoload": { + "psr-0": { + "GitWrapper": "src/" + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpmd.xml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpmd.xml new file mode 100644 index 000000000..ac63555ed --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpmd.xml @@ -0,0 +1,21 @@ + + + + PHP Project Starter Rulset: Adopted From Jenkins for Symfony 2 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpunit.xml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpunit.xml new file mode 100644 index 000000000..a030f91eb --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpunit.xml @@ -0,0 +1,30 @@ + + + + + + test + + + + + + src + + + + + + + + + + diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvent.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvent.php new file mode 100644 index 000000000..5c61c2306 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvent.php @@ -0,0 +1,82 @@ +wrapper = $wrapper; + $this->process = $process; + $this->command = $command; + } + + /** + * Gets the GitWrapper object that likely instantiated this class. + * + * @return \GitWrapper\GitWrapper + */ + public function getWrapper() + { + return $this->wrapper; + } + + /** + * Gets the Process object being run. + * + * @return \Symfony\Component\Process\Process + */ + public function getProcess() + { + return $this->process; + } + + /** + * Gets the GitCommand object being executed. + * + * @return \GitWrapper\GitCommand + */ + public function getCommand() + { + return $this->command; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvents.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvents.php new file mode 100644 index 000000000..6e477784c --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvents.php @@ -0,0 +1,53 @@ + LogLevel::INFO, + GitEvents::GIT_OUTPUT => LogLevel::DEBUG, + GitEvents::GIT_SUCCESS => LogLevel::INFO, + GitEvents::GIT_ERROR => LogLevel::ERROR, + GitEvents::GIT_BYPASS => LogLevel::INFO, + ); + + /** + * @param \Psr\Log\LoggerInterface $logger + */ + public function __construct(LoggerInterface $logger) + { + $this->setLogger($logger); + } + + /** + * {@inheritDoc} + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * @return \Psr\Log\LoggerInterface + */ + public function getLogger() + { + return $this->logger; + } + + /** + * Sets the log level mapping for an event. + * + * @param string $eventName + * @param string|false $logLevel + * + * @return \GitWrapper\Event\GitLoggerListener + */ + public function setLogLevelMapping($eventName, $logLevel) + { + $this->logLevelMappings[$eventName] = $logLevel; + return $this; + } + + /** + * Returns the log level mapping for an event. + * + * @param string $eventName + * + * @return string + * + * @throws \DomainException + */ + public function getLogLevelMapping($eventName) + { + if (!isset($this->logLevelMappings[$eventName])) { + throw new \DomainException('Unknown event: ' . $eventName); + } + + return $this->logLevelMappings[$eventName]; + } + + /** + * {@inheritDoc} + */ + public static function getSubscribedEvents() + { + return array( + GitEvents::GIT_PREPARE => array('onPrepare', 0), + GitEvents::GIT_OUTPUT => array('handleOutput', 0), + GitEvents::GIT_SUCCESS => array('onSuccess', 0), + GitEvents::GIT_ERROR => array('onError', 0), + GitEvents::GIT_BYPASS => array('onBypass', 0), + ); + } + + /** + * Adds a logg message using the level defined in the mappings. + * + * @param \GitWrapper\Event\GitEvent $event + * @param string $message + * @param array $context + * + * @throws \DomainException + */ + public function log(GitEvent $event, $message, array $context = array()) + { + $method = $this->getLogLevelMapping($event->getName()); + if ($method !== false) { + $context += array('command' => $event->getProcess()->getCommandLine()); + $this->logger->$method($message, $context); + } + } + + public function onPrepare(GitEvent $event) + { + $this->log($event, 'Git command preparing to run'); + } + + public function handleOutput(GitOutputEvent $event) + { + $context = array('error' => $event->isError() ? true : false); + $this->log($event, $event->getBuffer(), $context); + } + + public function onSuccess(GitEvent $event) + { + $this->log($event, 'Git command successfully run'); + } + + public function onError(GitEvent $event) + { + $this->log($event, 'Error running Git command'); + } + + public function onBypass(GitEvent $event) + { + $this->log($event, 'Git command bypassed'); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputEvent.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputEvent.php new file mode 100644 index 000000000..d1eaa84a9 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputEvent.php @@ -0,0 +1,64 @@ +type = $type; + $this->buffer = $buffer; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + */ + public function getBuffer() + { + return $this->buffer; + } + + /** + * Tests wheter the buffer was captured from STDERR. + */ + public function isError() + { + return (Process::ERR == $this->type); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputListenerInterface.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputListenerInterface.php new file mode 100644 index 000000000..f8c85c1ce --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputListenerInterface.php @@ -0,0 +1,11 @@ +isError() ? STDERR : STDOUT; + fputs($handler, $event->getBuffer()); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitBranches.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitBranches.php new file mode 100644 index 000000000..9b27d5b3e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitBranches.php @@ -0,0 +1,105 @@ +git = clone $git; + $output = (string) $git->branch(array('a' => true)); + } + + /** + * Fetches the branches via the `git branch` command. + * + * @param boolean $onlyRemote + * Whether to fetch only remote branches, defaults to false which returns + * all branches. + * + * @return array + */ + public function fetchBranches($onlyRemote = false) + { + $this->git->clearOutput(); + $options = ($onlyRemote) ? array('r' => true) : array('a' => true); + $output = (string) $this->git->branch($options); + $branches = preg_split("/\r\n|\n|\r/", rtrim($output)); + return array_map(array($this, 'trimBranch'), $branches); + } + + /** + * Strips unwanted characters from the branch. + * + * @param string $branch + * The raw branch returned in the output of the Git command. + * + * @return string + * The processed branch name. + */ + public function trimBranch($branch) + { + return ltrim($branch, ' *'); + } + + /** + * Implements \IteratorAggregate::getIterator(). + */ + public function getIterator() + { + $branches = $this->all(); + return new \ArrayIterator($branches); + } + + /** + * Returns all branches. + * + * @return array + */ + public function all() + { + return $this->fetchBranches(); + } + + /** + * Returns only remote branches. + * + * @return array + */ + public function remote() + { + return $this->fetchBranches(true); + } + + /** + * Returns currently active branch (HEAD) of the working copy. + * + * @return string + */ + public function head() + { + return trim((string) $this->git->run(array('rev-parse --abbrev-ref HEAD'))); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitCommand.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitCommand.php new file mode 100644 index 000000000..05719e607 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitCommand.php @@ -0,0 +1,298 @@ +command = array_shift($args); + + // If the last element is an array, set it as the options. + $options = end($args); + if (is_array($options)) { + $this->setOptions($options); + array_pop($args); + } + + // Pass all other method arguments as the Git command arguments. + foreach ($args as $arg) { + $this->addArgument($arg); + } + } + } + + /** + * Constructs a GitCommand object. + * + * Accepts a variable number of arguments to model the arguments passed to + * the Git command line utility. If the last argument is an array, it is + * passed as the command options. + * + * @param string $command + * The Git command being run, e.g. "clone", "commit", etc. + * @param string ... + * Zero or more arguments passed to the Git command. + * @param array $options + * An optional array of arguments to pass to the command. + * + * @return \GitWrapper\GitCommand + */ + public static function getInstance() + { + $args = func_get_args(); + return new static($args); + } + + /** + * Returns Git command being run, e.g. "clone", "commit", etc. + * + * @return string + */ + public function getCommand() + { + return $this->command; + } + + /** + * Sets the path to the directory containing the working copy. + * + * @param string $directory + * The path to the directory containing the working copy. + * + * @return \GitWrapper\GitCommand + */ + public function setDirectory($directory) + { + $this->directory = $directory; + return $this; + } + + /** + * Gets the path to the directory containing the working copy. + * + * @return string|null + * The path, null if no path is set. + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * A boolean flagging whether to skip running the command. + * + * @param boolean $bypass + * Whether to bypass execution of the command. The parameter defaults to + * true for code readability, however the default behavior of this class + * is to run the command. + * + * @return \GitWrapper\GitCommand + */ + public function bypass($bypass = true) + { + $this->bypass = (bool) $bypass; + return $this; + } + + /** + * Returns true if the Git command should be run. + * + * The return value is the boolean opposite $this->bypass. Although this + * seems complex, it makes the code more readable when checking whether the + * command should be run or not. + * + * @return boolean + * If true, the command should be run. + */ + public function notBypassed() + { + return !$this->bypass; + } + + /** + * Builds the command line options for use in the Git command. + * + * @return string + */ + public function buildOptions() + { + $options = array(); + foreach ($this->options as $option => $values) { + foreach ((array) $values as $value) { + $prefix = (strlen($option) != 1) ? '--' : '-'; + $rendered = $prefix . $option; + if ($value !== true) { + $rendered .= ('--' == $prefix) ? '=' : ' '; + $rendered .= ProcessUtils::escapeArgument($value); + } + $options[] = $rendered; + } + } + return join(' ', $options); + } + + /** + * Sets a command line option. + * + * Option names are passed as-is to the command line, whereas the values are + * escaped using \Symfony\Component\Process\ProcessUtils. + * + * @param string $option + * The option name, e.g. "branch", "q". + * @param string|true $value + * The option's value, pass true if the options is a flag. + * + * @reutrn \GitWrapper\GitCommand + */ + public function setOption($option, $value) + { + $this->options[$option] = $value; + return $this; + } + + /** + * Sets multiple command line options. + * + * @param array $options + * An associative array of command line options. + * + * @reutrn \GitWrapper\GitCommand + */ + public function setOptions(array $options) + { + foreach ($options as $option => $value) { + $this->setOption($option, $value); + } + return $this; + } + + /** + * Sets a command line flag. + * + * @param string $flag + * The flag name, e.g. "q", "a". + * + * @reutrn \GitWrapper\GitCommand + * + * @see \GitWrapper\GitCommand::setOption() + */ + public function setFlag($option) + { + return $this->setOption($option, true); + } + + /** + * Gets a command line option. + * + * @param string $option + * The option name, e.g. "branch", "q". + * @param mixed $default + * Value that is returned if the option is not set, defaults to null. + * + * @return mixed + */ + public function getOption($option, $default = null) + { + return (isset($this->options[$option])) ? $this->options[$option] : $default; + } + + /** + * Unsets a command line option. + * + * @param string $option + * The option name, e.g. "branch", "q". + * + * @return \GitWrapper\GitCommand + */ + public function unsetOption($option) + { + unset($this->options[$option]); + return $this; + } + + /** + * Adds a command line argument passed to the Git command. + * + * @param string $arg + * The argument, e.g. the repo URL, directory, etc. + * + * @return \GitWrapper\GitCommand + */ + public function addArgument($arg) + { + $this->args[] = $arg; + return $this; + } + + /** + * Renders the arguments and options for the Git command. + * + * @return string + * + * @see GitCommand::getCommand() + * @see GitCommand::buildOptions() + */ + public function getCommandLine() + { + $command = array( + $this->getCommand(), + $this->buildOptions(), + join(' ', array_map(array('\Symfony\Component\Process\ProcessUtils', 'escapeArgument'), $this->args)), + ); + return join(' ', array_filter($command)); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitException.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitException.php new file mode 100644 index 000000000..2557aa387 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitException.php @@ -0,0 +1,8 @@ +git = $git; + $this->command = $command; + + // Build the command line options, flags, and arguments. + $binary = ProcessUtils::escapeArgument($git->getGitBinary()); + $commandLine = rtrim($binary . ' ' . $command->getCommandLine()); + + // Resolve the working directory of the Git process. Use the directory + // in the command object if it exists. + if (null === $cwd) { + if (null !== $directory = $command->getDirectory()) { + if (!$cwd = realpath($directory)) { + throw new GitException('Path to working directory could not be resolved: ' . $directory); + } + } + } + + // Finalize the environment variables, an empty array is converted + // to null which enherits the environment of the PHP process. + $env = $git->getEnvVars(); + if (!$env) { + $env = null; + } + + parent::__construct($commandLine, $cwd, $env, null, $git->getTimeout(), $git->getProcOptions()); + } + + /** + * {@inheritdoc} + */ + public function run($callback = null) + { + $event = new Event\GitEvent($this->git, $this, $this->command); + $dispatcher = $this->git->getDispatcher(); + + try { + + // Throw the "git.command.prepare" event prior to executing. + $dispatcher->dispatch(Event\GitEvents::GIT_PREPARE, $event); + + // Execute command if it is not flagged to be bypassed and throw the + // "git.command.success" event, otherwise do not execute the comamnd + // and throw the "git.command.bypass" event. + if ($this->command->notBypassed()) { + parent::run($callback); + + if ($this->isSuccessful()) { + $dispatcher->dispatch(Event\GitEvents::GIT_SUCCESS, $event); + } else { + $output = $this->getErrorOutput(); + + if(trim($output) == '') { + $output = $this->getOutput(); + } + + throw new \RuntimeException($output); + } + } else { + $dispatcher->dispatch(Event\GitEvents::GIT_BYPASS, $event); + } + + } catch (\RuntimeException $e) { + $dispatcher->dispatch(Event\GitEvents::GIT_ERROR, $event); + throw new GitException($e->getMessage()); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWorkingCopy.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWorkingCopy.php new file mode 100644 index 000000000..519a2d4ab --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWorkingCopy.php @@ -0,0 +1,1119 @@ +wrapper = $wrapper; + $this->directory = $directory; + } + + /** + * Returns the GitWrapper object that likely instantiated this class. + * + * @return \GitWrapper\GitWrapper + */ + public function getWrapper() + { + return $this->wrapper; + } + + /** + * Gets the path to the directory containing the working copy. + * + * @return string + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * Gets the output captured by the last run Git commnd(s). + * + * @return string + */ + public function getOutput() + { + $output = $this->output; + $this->output = ''; + return $output; + } + + /** + * Clears the stored output captured by the last run Git command(s). + * + * @return \GitWrapper\GitWorkingCopy + */ + public function clearOutput() + { + $this->output = ''; + return $this; + } + + /** + * Manually sets the cloned flag. + * + * @param boolean $cloned + * Whether the repository is cloned into the directory or not. + * + * @return \GitWrapper\GitWorkingCopy + */ + public function setCloned($cloned) + { + $this->cloned = (bool) $cloned; + return $this; + } + + /** + * Checks whether a repository has already been cloned to this directory. + * + * If the flag is not set, test if it looks like we're at a git directory. + * + * @return boolean + */ + public function isCloned() + { + if (!isset($this->cloned)) { + $gitDir = $this->directory; + if (is_dir($gitDir . '/.git')) { + $gitDir .= '/.git'; + }; + $this->cloned = (is_dir($gitDir . '/objects') && is_dir($gitDir . '/refs') && is_file($gitDir . '/HEAD')); + } + return $this->cloned; + } + + /** + * Runs a Git command and captures the output. + * + * @param array $args + * The arguments passed to the command method. + * @param boolean $setDirectory + * Set the working directory, defaults to true. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + * + * @see GitWrapper::run() + */ + public function run($args, $setDirectory = true) + { + $command = call_user_func_array(array('GitWrapper\GitCommand', 'getInstance'), $args); + if ($setDirectory) { + $command->setDirectory($this->directory); + } + $this->output .= $this->wrapper->run($command); + return $this; + } + + /** + * @defgroup command_helpers Git Command Helpers + * + * Helper methods that wrap common Git commands. + * + * @{ + */ + + /** + * Returns the output of a `git status -s` command. + * + * @return string + * + * @throws \GitWrapper\GitException + */ + public function getStatus() + { + return $this->wrapper->git('status -s', $this->directory); + } + + /** + * Returns true if there are changes to commit. + * + * @return bool + * + * @throws \GitWrapper\GitException + */ + public function hasChanges() + { + $output = $this->getStatus(); + return !empty($output); + } + + /** + * Returns whether HEAD has a remote tracking branch. + * + * @return bool + */ + public function isTracking() + { + try { + $this->run(array('rev-parse @{u}')); + } catch (GitException $e) { + return false; + } + return true; + } + + /** + * Returns whether HEAD is up-to-date with its remote tracking branch. + * + * @return bool + * + * @throws \GitWrapper\GitException + * Thrown when HEAD does not have a remote tracking branch. + */ + public function isUpToDate() + { + if (!$this->isTracking()) { + throw new GitException('Error: HEAD does not have a remote tracking branch. Cannot check if it is up-to-date.'); + } + $this->clearOutput(); + $merge_base = (string) $this->run(array('merge-base @ @{u}')); + $remote_sha = (string) $this->run(array('rev-parse @{u}')); + return $merge_base === $remote_sha; + } + + /** + * Returns whether HEAD is ahead of its remote tracking branch. + * + * If this returns true it means that commits are present locally which have + * not yet been pushed to the remote. + * + * @return bool + * + * @throws \GitWrapper\GitException + * Thrown when HEAD does not have a remote tracking branch. + */ + public function isAhead() + { + if (!$this->isTracking()) { + throw new GitException('Error: HEAD does not have a remote tracking branch. Cannot check if it is ahead.'); + } + $this->clearOutput(); + $merge_base = (string) $this->run(array('merge-base @ @{u}')); + $local_sha = (string) $this->run(array('rev-parse @')); + $remote_sha = (string) $this->run(array('rev-parse @{u}')); + return $merge_base === $remote_sha && $local_sha !== $remote_sha; + } + + /** + * Returns whether HEAD is behind its remote tracking branch. + * + * If this returns true it means that a pull is needed to bring the branch + * up-to-date with the remote. + * + * @return bool + * + * @throws \GitWrapper\GitException + * Thrown when HEAD does not have a remote tracking branch. + */ + public function isBehind() + { + if (!$this->isTracking()) { + throw new GitException('Error: HEAD does not have a remote tracking branch. Cannot check if it is behind.'); + } + $this->clearOutput(); + $merge_base = (string) $this->run(array('merge-base @ @{u}')); + $local_sha = (string) $this->run(array('rev-parse @')); + $remote_sha = (string) $this->run(array('rev-parse @{u}')); + return $merge_base === $local_sha && $local_sha !== $remote_sha; + } + + /** + * Returns whether HEAD needs to be merged with its remote tracking branch. + * + * If this returns true it means that HEAD has diverged from its remote + * tracking branch; new commits are present locally as well as on the + * remote. + * + * @return bool + * true if HEAD needs to be merged with the remote, false otherwise. + * + * @throws \GitWrapper\GitException + * Thrown when HEAD does not have a remote tracking branch. + */ + public function needsMerge() + { + if (!$this->isTracking()) { + throw new GitException('Error: HEAD does not have a remote tracking branch. Cannot check if it is behind.'); + } + $this->clearOutput(); + $merge_base = (string) $this->run(array('merge-base @ @{u}')); + $local_sha = (string) $this->run(array('rev-parse @')); + $remote_sha = (string) $this->run(array('rev-parse @{u}')); + return $merge_base !== $local_sha && $merge_base !== $remote_sha; + } + + /** + * Returns a GitBranches object containing information on the repository's + * branches. + * + * @return GitBranches + */ + public function getBranches() + { + return new GitBranches($this); + } + + /** + * Helper method that pushes a tag to a repository. + * + * This is synonymous with `git push origin tag v1.2.3`. + * + * @param string $tag + * The tag being pushed. + * @param string $repository + * The destination of the push operation, which is either a URL or name of + * the remote. Defaults to "origin". + * @param array $options + * (optional) An associative array of command line options. + * + * @see GitWorkingCopy::push() + */ + public function pushTag($tag, $repository = 'origin', array $options = array()) + { + return $this->push($repository, 'tag', $tag, $options); + } + + /** + * Helper method that pushes all tags to a repository. + * + * This is synonymous with `git push --tags origin`. + * + * @param string $repository + * The destination of the push operation, which is either a URL or name of + * the remote. Defaults to "origin". + * @param array $options + * (optional) An associative array of command line options. + * + * @see GitWorkingCopy::push() + */ + public function pushTags($repository = 'origin', array $options = array()) + { + $options['tags'] = true; + return $this->push($repository, $options); + } + + /** + * Fetches all remotes. + * + * This is synonymous with `git fetch --all`. + * + * @param array $options + * (optional) An associative array of command line options. + * + * @see GitWorkingCopy::fetch() + */ + public function fetchAll(array $options = array()) + { + $options['all'] = true; + return $this->fetch($options); + } + + /** + * Create a new branch and check it out. + * + * This is synonymous with `git checkout -b`. + * + * @param string $branch + * The new branch being created. + * + * @see GitWorkingCopy::checkout() + */ + public function checkoutNewBranch($branch, array $options = array()) + { + $options['b'] = true; + return $this->checkout($branch, $options); + } + + /** + * @} End of "defgroup command_helpers". + */ + + /** + * @defgroup commands Git Commands + * + * All methods in this group correspond with Git commands, for example + * "git add", "git commit", "git push", etc. + * + * @{ + */ + + /** + * Executes a `git add` command. + * + * Add file contents to the index. + * + * @code + * $git->add('some/file.txt'); + * @endcode + * + * @param string $filepattern + * Files to add content from. Fileglobs (e.g. *.c) can be given to add + * all matching files. Also a leading directory name (e.g. dir to add + * dir/file1 and dir/file2) can be given to add all files in the + * directory, recursively. + * @param array $options + * An optional array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function add($filepattern, array $options = array()) + { + $args = array( + 'add', + $filepattern, + $options, + ); + return $this->run($args); + } + + /** + * Executes a `git apply` command. + * + * Apply a patch to files and/or to the index + * + * @code + * $git->apply('the/file/to/read/the/patch/from'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return GitWorkingCopy + * + * @throws GitException + */ + public function apply() + { + $args = func_get_args(); + array_unshift($args, 'apply'); + return $this->run($args); + } + + /** + * Executes a `git bisect` command. + * + * Find by binary search the change that introduced a bug. + * + * @code + * $git->bisect('good', '2.6.13-rc2'); + * $git->bisect('view', array('stat' => true)); + * @endcode + * + * @param string $sub_command + * The subcommand passed to `git bisect`. + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function bisect($sub_command) + { + $args = func_get_args(); + $args[0] = 'bisect ' . ProcessUtils::escapeArgument($sub_command); + return $this->run($args); + } + + /** + * Executes a `git branch` command. + * + * List, create, or delete branches. + * + * @code + * $git->branch('my2.6.14', 'v2.6.14'); + * $git->branch('origin/html', 'origin/man', array('d' => true, 'r' => 'origin/todo')); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function branch() + { + $args = func_get_args(); + array_unshift($args, 'branch'); + return $this->run($args); + } + + /** + * Executes a `git checkout` command. + * + * Checkout a branch or paths to the working tree. + * + * @code + * $git->checkout('new-branch', array('b' => true)); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function checkout() + { + $args = func_get_args(); + array_unshift($args, 'checkout'); + return $this->run($args); + } + + /** + * Executes a `git clone` command. + * + * Clone a repository into a new directory. Use GitWorkingCopy::clone() + * instead for more readable code. + * + * @code + * $git->clone('git://github.com/cpliakas/git-wrapper.git'); + * @endcode + * + * @param string $repository + * The Git URL of the repository being cloned. + * @param array $options + * (optional) An associative array of command line options. + * + * @param string $repository + * The URL of the repository being cloned. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function cloneRepository($repository, $options = array()) + { + $args = array( + 'clone', + $repository, + $this->directory, + $options, + ); + return $this->run($args, false); + } + + /** + * Executes a `git commit` command. + * + * Record changes to the repository. If only one argument is passed, it is + * assumed to be the commit message. Therefore `$git->commit('Message');` + * yields a `git commit -am "Message"` command. + * + * @code + * $git->commit('My commit message'); + * $git->commit('Makefile', array('m' => 'My commit message')); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function commit() + { + $args = func_get_args(); + if (isset($args[0]) && is_string($args[0]) && !isset($args[1])) { + $args[0] = array( + 'm' => $args[0], + 'a' => true, + ); + } + array_unshift($args, 'commit'); + return $this->run($args); + } + + /** + * Executes a `git config` command. + * + * Get and set repository options. + * + * @code + * $git->config('user.email', 'opensource@chrispliakas.com'); + * $git->config('user.name', 'Chris Pliakas'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function config() + { + $args = func_get_args(); + array_unshift($args, 'config'); + return $this->run($args); + } + + /** + * Executes a `git diff` command. + * + * Show changes between commits, commit and working tree, etc. + * + * @code + * $git->diff(); + * $git->diff('topic', 'master'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function diff() + { + $args = func_get_args(); + array_unshift($args, 'diff'); + return $this->run($args); + } + + /** + * Executes a `git fetch` command. + * + * Download objects and refs from another repository. + * + * @code + * $git->fetch('origin'); + * $git->fetch(array('all' => true)); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function fetch() + { + $args = func_get_args(); + array_unshift($args, 'fetch'); + return $this->run($args); + } + + /** + * Executes a `git grep` command. + * + * Print lines matching a pattern. + * + * @code + * $git->grep('time_t', '--', '*.[ch]'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function grep() + { + $args = func_get_args(); + array_unshift($args, 'grep'); + return $this->run($args); + } + + /** + * Executes a `git init` command. + * + * Create an empty git repository or reinitialize an existing one. + * + * @code + * $git->init(array('bare' => true)); + * @endcode + * + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function init(array $options = array()) + { + $args = array( + 'init', + $this->directory, + $options, + ); + return $this->run($args, false); + } + + /** + * Executes a `git log` command. + * + * Show commit logs. + * + * @code + * $git->log(array('no-merges' => true)); + * $git->log('v2.6.12..', 'include/scsi', 'drivers/scsi'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function log() + { + $args = func_get_args(); + array_unshift($args, 'log'); + return $this->run($args); + } + + /** + * Executes a `git merge` command. + * + * Join two or more development histories together. + * + * @code + * $git->merge('fixes', 'enhancements'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function merge() + { + $args = func_get_args(); + array_unshift($args, 'merge'); + return $this->run($args); + } + + /** + * Executes a `git mv` command. + * + * Move or rename a file, a directory, or a symlink. + * + * @code + * $git->mv('orig.txt', 'dest.txt'); + * @endcode + * + * @param string $source + * The file / directory being moved. + * @param string $destination + * The target file / directory that the source is being move to. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function mv($source, $destination, array $options = array()) + { + $args = array( + 'mv', + $source, + $destination, + $options, + ); + return $this->run($args); + } + + /** + * Executes a `git pull` command. + * + * Fetch from and merge with another repository or a local branch. + * + * @code + * $git->pull('upstream', 'master'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function pull() + { + $args = func_get_args(); + array_unshift($args, 'pull'); + return $this->run($args); + } + + /** + * Executes a `git push` command. + * + * Update remote refs along with associated objects. + * + * @code + * $git->push('upstream', 'master'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function push() + { + $args = func_get_args(); + array_unshift($args, 'push'); + return $this->run($args); + } + + /** + * Executes a `git rebase` command. + * + * Forward-port local commits to the updated upstream head. + * + * @code + * $git->rebase('subsystem@{1}', array('onto' => 'subsystem')); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function rebase() + { + $args = func_get_args(); + array_unshift($args, 'rebase'); + return $this->run($args); + } + + /** + * Executes a `git remote` command. + * + * Manage the set of repositories ("remotes") whose branches you track. + * + * @code + * $git->remote('add', 'upstream', 'git://github.com/cpliakas/git-wrapper.git'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function remote() + { + $args = func_get_args(); + array_unshift($args, 'remote'); + return $this->run($args); + } + + /** + * Executes a `git reset` command. + * + * Reset current HEAD to the specified state. + * + * @code + * $git->reset(array('hard' => true)); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function reset() + { + $args = func_get_args(); + array_unshift($args, 'reset'); + return $this->run($args); + } + + /** + * Executes a `git rm` command. + * + * Remove files from the working tree and from the index. + * + * @code + * $git->rm('oldfile.txt'); + * @endcode + * + * @param string $filepattern + * Files to remove from version control. Fileglobs (e.g. *.c) can be + * given to add all matching files. Also a leading directory name (e.g. + * dir to add dir/file1 and dir/file2) can be given to add all files in + * the directory, recursively. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function rm($filepattern, array $options = array()) + { + $args = array( + 'rm', + $filepattern, + $options, + ); + return $this->run($args); + } + + /** + * Executes a `git show` command. + * + * Show various types of objects. + * + * @code + * $git->show('v1.0.0'); + * @endcode + * + * @param string $object + * The names of objects to show. For a more complete list of ways to spell + * object names, see "SPECIFYING REVISIONS" section in gitrevisions(7). + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function show($object, array $options = array()) + { + $args = array('show', $object, $options); + return $this->run($args); + } + + /** + * Executes a `git status` command. + * + * Show the working tree status. + * + * @code + * $git->status(array('s' => true)); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function status() + { + $args = func_get_args(); + array_unshift($args, 'status'); + return $this->run($args); + } + + /** + * Executes a `git tag` command. + * + * Create, list, delete or verify a tag object signed with GPG. + + * @code + * $git->tag('v1.0.0'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function tag() + { + $args = func_get_args(); + array_unshift($args, 'tag'); + return $this->run($args); + } + + /** + * Executes a `git clean` command. + * + * Remove untracked files from the working tree + * + * @code + * $git->clean('-d', '-f'); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function clean() + { + $args = func_get_args(); + array_unshift($args, 'clean'); + return $this->run($args); + } + + /** + * Executes a `git archive` command. + * + * Create an archive of files from a named tree + * + * @code + * $git->archive('HEAD', array('o' => '/path/to/archive')); + * @endcode + * + * @param string ... + * (optional) Additional command line arguments. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + */ + public function archive() + { + $args = func_get_args(); + array_unshift($args, 'archive'); + return $this->run($args); + } + + /** + * @} End of "defgroup command". + */ + + /** + * Hackish, allows us to use "clone" as a method name. + * + * $throws \BadMethodCallException + * @throws \GitWrapper\GitException + */ + public function __call($method, $args) + { + if ('clone' == $method) { + return call_user_func_array(array($this, 'cloneRepository'), $args); + } else { + $class = get_called_class(); + $message = "Call to undefined method $class::$method()"; + throw new \BadMethodCallException($message); + } + } + + /** + * Gets the output captured by the last run Git commnd(s). + * + * @return string + * + * @see GitWorkingCopy::getOutput() + */ + public function __toString() + { + return $this->getOutput(); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWrapper.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWrapper.php new file mode 100644 index 000000000..a14b25440 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWrapper.php @@ -0,0 +1,555 @@ +find('git'); + if (!$gitBinary) { + throw new GitException('Unable to find the Git executable.'); + } + // @codeCoverageIgnoreEnd + } + + $this->setGitBinary($gitBinary); + } + + /** + * Gets the dispatcher used by this library to dispatch events. + * + * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + public function getDispatcher() + { + if (!isset($this->dispatcher)) { + $this->dispatcher = new EventDispatcher(); + } + return $this->dispatcher; + } + + /** + * Sets the dispatcher used by this library to dispatch events. + * + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher + * The Symfony event dispatcher object. + * + * @return \GitWrapper\GitWrapper + */ + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + return $this; + } + + /** + * Sets the path to the Git binary. + * + * @param string $gitBinary + * Path to the Git binary. + * + * @return \GitWrapper\GitWrapper + */ + public function setGitBinary($gitBinary) + { + $this->gitBinary = $gitBinary; + return $this; + } + + /** + * Returns the path to the Git binary. + * + * @return string + */ + public function getGitBinary() + { + return $this->gitBinary; + } + + /** + * Sets an environment variable that is defined only in the scope of the Git + * command. + * + * @param string $var + * The name of the environment variable, e.g. "HOME", "GIT_SSH". + * @param mixed $default + * The value of the environment variable is not set, defaults to null. + * + * @return \GitWrapper\GitWrapper + */ + public function setEnvVar($var, $value) + { + $this->env[$var] = $value; + return $this; + } + + /** + * Unsets an environment variable that is defined only in the scope of the + * Git command. + * + * @param string $var + * The name of the environment variable, e.g. "HOME", "GIT_SSH". + * + * @return \GitWrapper\GitWrapper + */ + public function unsetEnvVar($var) + { + unset($this->env[$var]); + return $this; + } + + /** + * Returns an environment variable that is defined only in the scope of the + * Git command. + * + * @param string $var + * The name of the environment variable, e.g. "HOME", "GIT_SSH". + * @param mixed $default + * The value returned if the environment variable is not set, defaults to + * null. + * + * @return mixed + */ + public function getEnvVar($var, $default = null) + { + return isset($this->env[$var]) ? $this->env[$var] : $default; + } + + /** + * Returns the associative array of environment variables that are defined + * only in the scope of the Git command. + * + * @return array + */ + public function getEnvVars() + { + return $this->env; + } + + /** + * Sets the timeout of the Git command. + * + * @param int $timeout + * The timeout in seconds. + * + * @return \GitWrapper\GitWrapper + */ + public function setTimeout($timeout) + { + $this->timeout = (int) $timeout; + return $this; + } + + /** + * Gets the timeout of the Git command. + * + * @return int + * The timeout in seconds. + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Sets the options passed to proc_open() when executing the Git command. + * + * @param array $timeout + * The options passed to proc_open(). + * + * @return \GitWrapper\GitWrapper + */ + public function setProcOptions(array $options) + { + $this->procOptions = $options; + return $this; + } + + /** + * Gets the options passed to proc_open() when executing the Git command. + * + * @return array + */ + public function getProcOptions() + { + return $this->procOptions; + } + + /** + * Set an alternate private key used to connect to the repository. + * + * This method sets the GIT_SSH environment variable to use the wrapper + * script included with this library. It also sets the custom GIT_SSH_KEY + * and GIT_SSH_PORT environment variables that are used by the script. + * + * @param string $privateKey + * Path to the private key. + * @param int $port + * Port that the SSH server being connected to listens on, defaults to 22. + * @param string|null $wrapper + * Path the the GIT_SSH wrapper script, defaults to null which uses the + * script included with this library. + * + * @return \GitWrapper\GitWrapper + * + * @throws \GitWrapper\GitException + * Thrown when any of the paths cannot be resolved. + */ + public function setPrivateKey($privateKey, $port = 22, $wrapper = null) + { + if (null === $wrapper) { + $wrapper = __DIR__ . '/../../bin/git-ssh-wrapper.sh'; + } + if (!$wrapperPath = realpath($wrapper)) { + throw new GitException('Path to GIT_SSH wrapper script could not be resolved: ' . $wrapper); + } + if (!$privateKeyPath = realpath($privateKey)) { + throw new GitException('Path private key could not be resolved: ' . $privateKey); + } + + return $this + ->setEnvVar('GIT_SSH', $wrapperPath) + ->setEnvVar('GIT_SSH_KEY', $privateKeyPath) + ->setEnvVar('GIT_SSH_PORT', (int) $port) + ; + } + + /** + * Unsets the private key by removing the appropriate environment variables. + * + * @return \GitWrapper\GitWrapper + */ + public function unsetPrivateKey() + { + return $this + ->unsetEnvVar('GIT_SSH') + ->unsetEnvVar('GIT_SSH_KEY') + ->unsetEnvVar('GIT_SSH_PORT') + ; + } + + /** + * Adds output listener. + * + * @param \GitWrapper\Event\GitOutputListenerInterface $listener + * + * @return \GitWrapper\GitWrapper + */ + public function addOutputListener(Event\GitOutputListenerInterface $listener) + { + $this + ->getDispatcher() + ->addListener(Event\GitEvents::GIT_OUTPUT, array($listener, 'handleOutput')) + ; + return $this; + } + + /** + * Adds logger listener listener. + * + * @param Event\GitLoggerListener $listener + * + * @return GitWrapper + */ + public function addLoggerListener(Event\GitLoggerListener $listener) + { + $this + ->getDispatcher() + ->addSubscriber($listener) + ; + return $this; + } + + /** + * Removes an output listener. + * + * @param \GitWrapper\Event\GitOutputListenerInterface $listener + * + * @return \GitWrapper\GitWrapper + */ + public function removeOutputListener(Event\GitOutputListenerInterface $listener) + { + $this + ->getDispatcher() + ->removeListener(Event\GitEvents::GIT_OUTPUT, array($listener, 'handleOutput')) + ; + return $this; + } + + /** + * Set whether or not to stream real-time output to STDOUT and STDERR. + * + * @param boolean $streamOutput + * + * @return \GitWrapper\GitWrapper + */ + public function streamOutput($streamOutput = true) + { + if ($streamOutput && !isset($this->streamListener)) { + $this->streamListener = new Event\GitOutputStreamListener(); + $this->addOutputListener($this->streamListener); + } + + if (!$streamOutput && isset($this->streamListener)) { + $this->removeOutputListener($this->streamListener); + unset($this->streamListener); + } + + return $this; + } + + /** + * Returns an object that interacts with a working copy. + * + * @param string $directory + * Path to the directory containing the working copy. + * + * @return GitWorkingCopy + */ + public function workingCopy($directory) + { + return new GitWorkingCopy($this, $directory); + } + + /** + * Returns the version of the installed Git client. + * + * @return string + * + * @throws \GitWrapper\GitException + */ + public function version() + { + return $this->git('--version'); + } + + /** + * Parses name of the repository from the path. + * + * For example, passing the "git@github.com:cpliakas/git-wrapper.git" + * repository would return "git-wrapper". + * + * @param string $repository + * The repository URL. + * + * @return string + */ + public static function parseRepositoryName($repository) + { + $scheme = parse_url($repository, PHP_URL_SCHEME); + + if (null === $scheme) { + $parts = explode('/', $repository); + $path = end($parts); + } else { + $strpos = strpos($repository, ':'); + $path = substr($repository, $strpos + 1); + } + + return basename($path, '.git'); + } + + /** + * Executes a `git init` command. + * + * Create an empty git repository or reinitialize an existing one. + * + * @param string $directory + * The directory being initialized. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + * + * @see GitWorkingCopy::cloneRepository() + * + * @ingroup commands + */ + public function init($directory, array $options = array()) + { + $git = $this->workingCopy($directory); + $git->init($options); + $git->setCloned(true); + return $git; + } + + /** + * Executes a `git clone` command and returns a working copy object. + * + * Clone a repository into a new directory. Use GitWorkingCopy::clone() + * instead for more readable code. + * + * @param string $repository + * The Git URL of the repository being cloned. + * @param string $directory + * The directory that the repository will be cloned into. If null is + * passed, the directory will automatically be generated from the URL via + * the GitWrapper::parseRepositoryName() method. + * @param array $options + * (optional) An associative array of command line options. + * + * @return \GitWrapper\GitWorkingCopy + * + * @throws \GitWrapper\GitException + * + * @see GitWorkingCopy::cloneRepository() + * + * @ingroup commands + */ + public function cloneRepository($repository, $directory = null, array $options = array()) + { + if (null === $directory) { + $directory = self::parseRepositoryName($repository); + } + $git = $this->workingCopy($directory); + $git->clone($repository, $options); + $git->setCloned(true); + return $git; + } + + /** + * Runs an arbitrary Git command. + * + * The command is simply a raw command line entry for everything after the + * Git binary. For example, a `git config -l` command would be passed as + * `config -l` via the first argument of this method. + * + * Note that no events are thrown by this method. + * + * @param string $commandLine + * The raw command containing the Git options and arguments. The Git + * binary should not be in the command, for example `git config -l` would + * translate to "config -l". + * @param string|null $cwd + * The working directory of the Git process. Defaults to null which uses + * the current working directory of the PHP process. + * + * @return string + * The STDOUT returned by the Git command. + * + * @throws \GitWrapper\GitException + * + * @see GitWrapper::run() + */ + public function git($commandLine, $cwd = null) + { + $command = GitCommand::getInstance($commandLine); + $command->setDirectory($cwd); + return $this->run($command); + } + + /** + * Runs a Git command. + * + * @param \GitWrapper\GitCommand $command + * The Git command being executed. + * @param string|null $cwd + * Explicitly specify the working directory of the Git process. Defaults + * to null which automatically sets the working directory based on the + * command being executed relative to the working copy. + * + * @return string + * The STDOUT returned by the Git command. + * + * @throws \GitWrapper\GitException + * + * @see Process + */ + public function run(GitCommand $command, $cwd = null) + { + $wrapper = $this; + $process = new GitProcess($this, $command, $cwd); + $process->run(function ($type, $buffer) use ($wrapper, $process, $command) { + $event = new Event\GitOutputEvent($wrapper, $process, $command, $type, $buffer); + $wrapper->getDispatcher()->dispatch(Event\GitEvents::GIT_OUTPUT, $event); + }); + return $command->notBypassed() ? $process->getOutput() : ''; + } + + /** + * Hackish, allows us to use "clone" as a method name. + * + * $throws \BadMethodCallException + * @throws \GitWrapper\GitException + */ + public function __call($method, $args) + { + if ('clone' == $method) { + return call_user_func_array(array($this, 'cloneRepository'), $args); + } else { + $class = get_called_class(); + $message = "Call to undefined method $class::$method()"; + throw new \BadMethodCallException($message); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestBypassListener.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestBypassListener.php new file mode 100644 index 000000000..22e7f20fd --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestBypassListener.php @@ -0,0 +1,13 @@ +getCommand()->bypass(); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestDispatcher.php new file mode 100644 index 000000000..57c2e6ba8 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestDispatcher.php @@ -0,0 +1,7 @@ +methods); + } + + /** + * @return \GitWrapper\Event\GitEvent + */ + public function getEvent() + { + return $this->event; + } + + public function onPrepare(GitEvent $event) + { + $this->methods[] = 'onPrepare'; + $this->event = $event; + } + + public function onSuccess(GitEvent $event) + { + $this->methods[] = 'onSuccess'; + } + + public function onError(GitEvent $event) + { + $this->methods[] = 'onError'; + } + + public function onBypass(GitEvent $event) + { + $this->methods[] = 'onBypass'; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestOutputListener.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestOutputListener.php new file mode 100644 index 000000000..be67b536f --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestOutputListener.php @@ -0,0 +1,27 @@ +event; + } + + public function handleOutput(GitOutputEvent $event) + { + $this->event = $event; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitCommandTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitCommandTest.php new file mode 100644 index 000000000..0decf63f0 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitCommandTest.php @@ -0,0 +1,55 @@ +randomString(); + $argument = $this->randomString(); + $flag = $this->randomString(); + $optionName = $this->randomString(); + $optionValue = $this->randomString(); + + $git = GitCommand::getInstance($command) + ->addArgument($argument) + ->setFlag($flag) + ->setOption($optionName, $optionValue); + + $expected = "$command --$flag --$optionName='$optionValue' '$argument'"; + $commandLine = $git->getCommandLine(); + + $this->assertEquals($expected, $commandLine); + } + + public function testOption() + { + $optionName = $this->randomString(); + $optionValue = $this->randomString(); + + $git = GitCommand::getInstance() + ->setOption($optionName, $optionValue); + + $this->assertEquals($optionValue, $git->getOption($optionName)); + + $git->unsetOption($optionName); + $this->assertNull($git->getOption($optionName)); + } + + /** + * @see https://github.com/cpliakas/git-wrapper/issues/50 + */ + public function testMultiOption() + { + $git = GitCommand::getInstance('test-command') + ->setOption('test-arg', array(true, true)); + + $expected = 'test-command --test-arg --test-arg'; + $commandLine = $git->getCommandLine(); + + $this->assertEquals($expected, $commandLine); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitListenerTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitListenerTest.php new file mode 100644 index 000000000..50ea6a3fe --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitListenerTest.php @@ -0,0 +1,58 @@ +addListener(); + $this->wrapper->version(); + + $this->assertTrue($listener->methodCalled('onPrepare')); + $this->assertTrue($listener->methodCalled('onSuccess')); + $this->assertFalse($listener->methodCalled('onError')); + $this->assertFalse($listener->methodCalled('onBypass')); + } + + public function testListenerError() + { + $listener = $this->addListener(); + $this->runBadCommand(true); + + $this->assertTrue($listener->methodCalled('onPrepare')); + $this->assertFalse($listener->methodCalled('onSuccess')); + $this->assertTrue($listener->methodCalled('onError')); + $this->assertFalse($listener->methodCalled('onBypass')); + } + + public function testGitBypass() + { + $this->addBypassListener(); + $listener = $this->addListener(); + + $output = $this->wrapper->version(); + + $this->assertTrue($listener->methodCalled('onPrepare')); + $this->assertFalse($listener->methodCalled('onSuccess')); + $this->assertFalse($listener->methodCalled('onError')); + $this->assertTrue($listener->methodCalled('onBypass')); + + $this->assertEmpty($output); + } + + public function testEvent() + { + $process = new Process(''); + $command = GitCommand::getInstance(); + $event = new GitEvent($this->wrapper, $process, $command); + + $this->assertEquals($this->wrapper, $event->getWrapper()); + $this->assertEquals($process, $event->getProcess()); + $this->assertEquals($command, $event->getCommand()); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitLoggerListenerTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitLoggerListenerTest.php new file mode 100644 index 000000000..0a0674b2c --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitLoggerListenerTest.php @@ -0,0 +1,89 @@ +assertEquals($log, $listener->getLogger()); + } + + public function testSetLogLevelMapping() + { + $listener = new GitLoggerListener(new NullLogger()); + $listener->setLogLevelMapping('test.event', 'test-level'); + $this->assertEquals('test-level', $listener->getLogLevelMapping('test.event')); + } + + /** + * @expectedException \DomainException + */ + public function testGetInvalidLogLevelMapping() + { + $listener = new GitLoggerListener(new NullLogger()); + $listener->getLogLevelMapping('bad.event'); + } + + public function testRegisterLogger() + { + $logger = new TestLogger(); + $this->wrapper->addLoggerListener(new GitLoggerListener($logger)); + $git = $this->wrapper->init(self::REPO_DIR, array('bare' => true)); + + $this->assertEquals('Git command preparing to run', $logger->messages[0]); + $this->assertEquals('Initialized empty Git repository in ' . realpath(self::REPO_DIR) . "/\n", $logger->messages[1]); + $this->assertEquals('Git command successfully run', $logger->messages[2]); + + $this->assertArrayHasKey('command', $logger->contexts[0]); + $this->assertArrayHasKey('command', $logger->contexts[1]); + $this->assertArrayHasKey('error', $logger->contexts[1]); + $this->assertArrayHasKey('command', $logger->contexts[2]); + + $this->assertEquals(LogLevel::INFO, $logger->levels[0]); + $this->assertEquals(LogLevel::DEBUG, $logger->levels[1]); + $this->assertEquals(LogLevel::INFO, $logger->levels[2]); + + try { + $logger->clearMessages(); + $git->commit('fatal: This operation must be run in a work tree'); + } catch (\Exception $e) { + // Nothing to do, this is expected. + } + + $this->assertEquals('Error running Git command', $logger->messages[2]); + $this->assertArrayHasKey('command', $logger->contexts[2]); + $this->assertEquals(LogLevel::ERROR, $logger->levels[2]); + } + + public function testLogBypassedCommand() + { + $logger = new TestLogger(); + $this->wrapper->addLoggerListener(new GitLoggerListener($logger)); + + $command = GitCommand::getInstance('status', array('s' => true)); + $command->bypass(); + + $this->wrapper->run($command); + + $this->assertEquals('Git command bypassed', $logger->messages[1]); + $this->assertArrayHasKey('command', $logger->contexts[1]); + $this->assertEquals(LogLevel::INFO, $logger->levels[1]); + } + + public function tearDown() + { + parent::tearDown(); + + if (is_dir(self::REPO_DIR)) { + $this->filesystem->remove(self::REPO_DIR); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWorkingCopyTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWorkingCopyTest.php new file mode 100644 index 000000000..7e9d86218 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWorkingCopyTest.php @@ -0,0 +1,558 @@ +wrapper->init(self::REPO_DIR, array('bare' => true)); + + // Clone the local repository. + $directory = 'build/test/wc_init'; + $git = $this->wrapper->clone('file://' . realpath(self::REPO_DIR), $directory); + $git->config('user.email', self::CONFIG_EMAIL); + $git->config('user.name', self::CONFIG_NAME); + + // Create the initial structure. + file_put_contents($directory . '/change.me', "unchanged\n"); + $this->filesystem->touch($directory . '/move.me'); + $this->filesystem->mkdir($directory . '/a.directory', 0755); + $this->filesystem->touch($directory . '/a.directory/remove.me'); + + // Initial commit. + $git + ->add('*') + ->commit('Initial commit.') + ->push('origin', 'master', array('u' => true)) + ; + + // Create a branch, add a file. + $branch = 'test-branch'; + file_put_contents($directory . '/branch.txt', "$branch\n"); + $git + ->checkoutNewBranch($branch) + ->add('branch.txt') + ->commit('Committed testing branch.') + ->push('origin', $branch, array('u' => true)) + ; + + // Create a tag of the branch. + $git + ->tag('test-tag') + ->pushTags() + ; + + $this->filesystem->remove($directory); + } + + /** + * Removes the local repository. + */ + public function tearDown() + { + parent::tearDown(); + + $this->filesystem->remove(self::REPO_DIR); + + if (is_dir(self::WORKING_DIR)) { + $this->filesystem->remove(self::WORKING_DIR); + } + } + + /** + * Clones the local repo and returns an initialized GitWorkingCopy object. + * + * @param string $directory + * The directory that the repository is being cloned to, defaults to + * "test/wc". + * + * @return \GitWrapper\GitWorkingCopy + */ + public function getWorkingCopy($directory = self::WORKING_DIR) + { + $git = $this->wrapper->workingCopy($directory); + $git + ->cloneRepository('file://' . realpath(self::REPO_DIR)) + ->config('user.email', self::CONFIG_EMAIL) + ->config('user.name', self::CONFIG_NAME) + ->clearOutput() + ; + return $git; + } + + /** + * @expectedException \BadMethodCallException + */ + public function testCallError() + { + $git = $this->getWorkingCopy(); + $git->badMethod(); + } + + public function testIsCloned() + { + $git = $this->getWorkingCopy(); + $this->assertTrue($git->isCloned()); + } + + public function testGetOutput() + { + $git = $this->getWorkingCopy(); + + // Test getting output of a simple status command. + $output = (string) $git->status(); + $this->assertTrue(strpos($output, 'nothing to commit') !== false); + + // Getting output should clear the buffer. + $cleared = (string) $git; + $this->assertEmpty($cleared); + } + + public function testClearOutput() + { + $git = $this->getWorkingCopy(); + + // Put stuff in the output buffer. + $git->status(); + + $git->clearOutput(); + $output = $git->getOutput(); + $this->assertEmpty($output); + } + + public function testHasChanges() + { + $git = $this->getWorkingCopy(); + $this->assertFalse($git->hasChanges()); + + file_put_contents(self::WORKING_DIR . '/change.me', "changed\n"); + $this->assertTrue($git->hasChanges()); + } + + public function testGetBranches() + { + $git = $this->getWorkingCopy(); + $branches = $git->getBranches(); + + $this->assertTrue($branches instanceof \GitWrapper\GitBranches); + + // Dumb count checks. Is there a better way to do this? + $allBranches = 0; + foreach ($branches as $branch) { + $allBranches++; + } + $this->assertEquals($allBranches, 4); + + $remoteBranches = $branches->remote(); + $this->assertEquals(count($remoteBranches), 3); + } + + public function testFetchAll() + { + $git = $this->getWorkingCopy(); + + $output = rtrim((string) $git->fetchAll()); + + $this->assertEquals('Fetching origin', $output); + } + + public function testGitAdd() + { + $git = $this->getWorkingCopy(); + $this->filesystem->touch(self::WORKING_DIR . '/add.me'); + + $git->add('add.me'); + + $match = (bool) preg_match('@A\\s+add\\.me@s', $git->getStatus()); + $this->assertTrue($match); + } + + public function testGitApply() + { + $git = $this->getWorkingCopy(); + + $patch = <<apply('patch.txt'); + $this->assertRegExp('@\?\?\\s+FileCreatedByPatch\\.txt@s', $git->getStatus()); + $this->assertEquals("contents\n", file_get_contents(self::WORKING_DIR . '/FileCreatedByPatch.txt')); + } + + public function testGitRm() + { + $git = $this->getWorkingCopy(); + $git->rm('a.directory/remove.me'); + $this->assertFalse(is_file(self::WORKING_DIR . '/a.directory/remove.me')); + } + + public function testGitMv() + { + $git = $this->getWorkingCopy(); + $git->mv('move.me', 'moved'); + + $this->assertFalse(is_file(self::WORKING_DIR . '/move.me')); + $this->assertTrue(is_file(self::WORKING_DIR . '/moved')); + } + + public function testGitBranch() + { + $branchName = $this->randomString(); + + // Create the branch. + $git = $this->getWorkingCopy(); + $git->branch($branchName); + + // Get list of local branches. + $branches = (string) $git->branch(); + + // Check that our branch is there. + $this->assertTrue(strpos($branches, $branchName) !== false); + } + + public function testGitLog() + { + $git = $this->getWorkingCopy(); + $output = (string) $git->log(); + return $this->assertTrue(strpos($output, 'Initial commit.') !== false); + } + + public function testGitConfig() + { + $git = $this->getWorkingCopy(); + $email = rtrim((string) $git->config('user.email')); + $this->assertEquals('opensource@chrispliakas.com', $email); + } + + public function testGitTag() + { + $tag = $this->randomString(); + + $git = $this->getWorkingCopy(); + $git + ->tag($tag) + ->pushTag($tag) + ; + + $tags = (string) $git->tag(); + $this->assertTrue(strpos($tags, $tag) !== false); + } + + public function testGitClean() + { + $git = $this->getWorkingCopy(); + + file_put_contents(self::WORKING_DIR . '/untracked.file', "untracked\n"); + + $result = $git + ->clean('-d', '-f') + ; + + $this->assertSame($git, $result); + $this->assertFileNotExists(self::WORKING_DIR . '/untracked.file'); + } + + public function testGitReset() + { + $git = $this->getWorkingCopy(); + file_put_contents(self::WORKING_DIR . '/change.me', "changed\n"); + + $this->assertTrue($git->hasChanges()); + $git->reset(array('hard' => true)); + $this->assertFalse($git->hasChanges()); + } + + public function testGitStatus() + { + $git = $this->getWorkingCopy(); + file_put_contents(self::WORKING_DIR . '/change.me', "changed\n"); + $output = (string) $git->status(array('s' => true)); + $this->assertEquals(" M change.me\n", $output); + } + + public function testGitPull() + { + $git = $this->getWorkingCopy(); + $output = (string) $git->pull(); + $this->assertEquals("Already up-to-date.\n", $output); + } + + public function testGitArchive() + { + $archiveName = uniqid().'.tar'; + $archivePath = '/tmp/'.$archiveName; + $git = $this->getWorkingCopy(); + $output = (string) $git->archive('HEAD', array('o' => $archivePath)); + $this->assertEquals("", $output); + $this->assertFileExists($archivePath); + } + + /** + * This tests an odd case where sometimes even though a command fails and an exception is thrown + * the result of Process::getErrorOutput() is empty because the output is sent to STDOUT instead of STDERR. So + * there's a code path in GitProcess::run() to check the output from Process::getErrorOutput() and if it's empty use + * the result from Process::getOutput() instead + */ + public function testGitPullErrorWithEmptyErrorOutput() + { + $git = $this->getWorkingCopy(); + + try { + $git->commit('Nothing to commit so generates an error / not error'); + } catch(GitException $exception) { + $errorOutput = $exception->getMessage(); + } + + $this->assertTrue(strpos($errorOutput, "Your branch is up-to-date with 'origin/master'.") !== false); + } + + public function testGitDiff() + { + $git = $this->getWorkingCopy(); + file_put_contents(self::WORKING_DIR . '/change.me', "changed\n"); + $output = (string) $git->diff(); + $this->assertTrue(strpos($output, 'diff --git a/change.me b/change.me') === 0); + } + + public function testGitGrep() + { + $git = $this->getWorkingCopy(); + $output = (string) $git->grep('changed', '--', '*.me'); + $this->assertTrue(strpos($output, 'change.me') === 0); + } + + public function testGitShow() + { + $git = $this->getWorkingCopy(); + $output = (string) $git->show('test-tag'); + $this->assertTrue(strpos($output, 'commit ') === 0); + } + + public function testGitBisect() + { + $git = $this->getWorkingCopy(); + $output = (string) $git->bisect('help'); + $this->assertTrue(stripos($output, 'usage: git bisect') === 0); + } + + public function testGitRemote() + { + $git = $this->getWorkingCopy(); + $output = (string) $git->remote(); + $this->assertEquals(rtrim($output), 'origin'); + } + + public function testRebase() + { + $git = $this->getWorkingCopy(); + $git + ->checkout('test-branch') + ->clearOutput() + ; + + $output = (string) $git->rebase('test-branch', 'master'); + $this->assertTrue(strpos($output, 'First, rewinding head') === 0); + } + + public function testMerge() + { + $git = $this->getWorkingCopy(); + $git + ->checkout('test-branch') + ->checkout('master') + ->clearOutput() + ; + + $output = (string) $git->merge('test-branch'); + $this->assertTrue(strpos($output, 'Updating ') === 0); + } + + public function testOutputListener() + { + $git = $this->getWorkingCopy(); + + $listener = new Event\TestOutputListener(); + $git->getWrapper()->addOutputListener($listener); + + $git->status(); + $event = $listener->getLastEvent(); + + $expectedType = Process::OUT; + $this->assertEquals($expectedType, $event->getType()); + + $this->assertTrue(stripos($event->getBuffer(), 'nothing to commit') !== false); + } + + public function testLiveOutput() + { + $git = $this->getWorkingCopy(); + + // Capture output written to STDOUT and use echo so we can suppress and + // capture it using normal output buffering. + stream_filter_register('suppress', '\GitWrapper\Test\StreamSuppressFilter'); + $stdoutSuppress = stream_filter_append(STDOUT, 'suppress'); + + $git->getWrapper()->streamOutput(true); + ob_start(); + $git->status(); + $contents = ob_get_contents(); + ob_end_clean(); + + $this->assertTrue(stripos($contents, 'nothing to commit') !== false); + + $git->clearOutput(); + $git->getWrapper()->streamOutput(false); + ob_start(); + $git->status(); + $empty = ob_get_contents(); + ob_end_clean(); + + $this->assertEmpty($empty); + + stream_filter_remove($stdoutSuppress); + } + + public function testCommitWithAuthor() + { + $git = $this->getWorkingCopy(); + file_put_contents(self::WORKING_DIR . '/commit.txt', "created\n"); + + $this->assertTrue($git->hasChanges()); + + $git + ->add('commit.txt') + ->commit(array( + 'm' => 'Committed testing branch.', + 'a' => true, + 'author' => 'test ' + )) + ; + + $output = (string) $git->log(); + $this->assertContains('Committed testing branch', $output); + $this->assertContains('Author: test ', $output); + } + + public function testIsTracking() + { + $git = $this->getWorkingCopy(); + + // The master branch is a remote tracking branch. + $this->assertTrue($git->isTracking()); + + // Create a new branch without pushing it, so it does not have a remote. + $git->checkoutNewBranch('non-tracking-branch'); + $this->assertFalse($git->isTracking()); + } + + public function testIsUpToDate() + { + $git = $this->getWorkingCopy(); + + // The default test branch is up-to-date with its remote. + $git->checkout('test-branch'); + $this->assertTrue($git->isUpToDate()); + + // If we create a new commit, we are still up-to-date. + file_put_contents(self::WORKING_DIR . '/commit.txt', "created\n"); + $git + ->add('commit.txt') + ->commit(array( + 'm' => '1 commit ahead. Still up-to-date.', + 'a' => true, + )) + ; + $this->assertTrue($git->isUpToDate()); + + // Reset the branch to its first commit, so that it is 1 commit behind. + $git->reset( + 'HEAD~2', + array('hard' => true) + ); + + $this->assertFalse($git->isUpToDate()); + } + + public function testIsAhead() + { + $git = $this->getWorkingCopy(); + + // The default master branch is not ahead of the remote. + $this->assertFalse($git->isAhead()); + + // Create a new commit, so that the branch is 1 commit ahead. + file_put_contents(self::WORKING_DIR . '/commit.txt', "created\n"); + $git + ->add('commit.txt') + ->commit(array('m' => '1 commit ahead.')) + ; + + $this->assertTrue($git->isAhead()); + } + + public function testIsBehind() + { + $git = $this->getWorkingCopy(); + + // The default test branch is not behind the remote. + $git->checkout('test-branch'); + $this->assertFalse($git->isBehind()); + + // Reset the branch to its parent commit, so that it is 1 commit behind. + $git->reset( + 'HEAD^', + array('hard' => true) + ); + + $this->assertTrue($git->isBehind()); + } + + public function testNeedsMerge() + { + $git = $this->getWorkingCopy(); + + // The default test branch does not need to be merged with the remote. + $git->checkout('test-branch'); + $this->assertFalse($git->needsMerge()); + + // Reset the branch to its parent commit, so that it is 1 commit behind. + // This does not require the branches to be merged. + $git->reset( + 'HEAD^', + array('hard' => true) + ); + $this->assertFalse($git->needsMerge()); + + // Create a new commit, so that the branch is also 1 commit ahead. Now a + // merge is needed. + file_put_contents(self::WORKING_DIR . '/commit.txt', "created\n"); + $git + ->add('commit.txt') + ->commit(array('m' => '1 commit ahead.')) + ; + $this->assertTrue($git->needsMerge()); + + // Merge the remote, so that we are no longer behind, but only ahead. A + // merge should then no longer be needed. + $git->merge('@{u}'); + $this->assertFalse($git->needsMerge()); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTest.php new file mode 100644 index 000000000..f21ce54ec --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTest.php @@ -0,0 +1,199 @@ +wrapper->setGitBinary($binary); + $this->assertEquals($binary, $this->wrapper->getGitBinary()); + } + + public function testSetDispatcher() + { + $dispatcher = new TestDispatcher(); + $this->wrapper->setDispatcher($dispatcher); + $this->assertEquals($dispatcher, $this->wrapper->getDispatcher()); + } + + public function testSetTimeout() + { + $timeout = mt_rand(1, 60); + $this->wrapper->setTimeout($timeout); + $this->assertEquals($timeout, $this->wrapper->getTimeout()); + } + + public function testEnvVar() + { + $var = $this->randomString(); + $value = $this->randomString(); + + $this->wrapper->setEnvVar($var, $value); + $this->assertEquals($value, $this->wrapper->getEnvVar($var)); + + $envvars = $this->wrapper->getEnvVars(); + $this->assertEquals($value, $envvars[$var]); + + $this->wrapper->unsetEnvVar($var); + $this->assertNull($this->wrapper->getEnvVar($var)); + } + + public function testEnvVarDefault() + { + $var = $this->randomString(); + $default = $this->randomString(); + $this->assertEquals($default, $this->wrapper->getEnvVar($var, $default)); + } + + public function testProcOptions() + { + $value = (bool) mt_rand(0, 1); + $options = array('suppress_errors' => $value); + $this->wrapper->setProcOptions($options); + $this->assertEquals($options, $this->wrapper->getProcOptions()); + } + + public function testGitVersion() + { + $version = $this->wrapper->version(); + $this->assertGitVersion($version); + } + + public function testSetPrivateKey() + { + $key = './test/id_rsa'; + $keyExpected = realpath($key); + $sshWrapperExpected = realpath(__DIR__ . '/../../../bin/git-ssh-wrapper.sh'); + + $this->wrapper->setPrivateKey($key); + $this->assertEquals($keyExpected, $this->wrapper->getEnvVar('GIT_SSH_KEY')); + $this->assertEquals(22, $this->wrapper->getEnvVar('GIT_SSH_PORT')); + $this->assertEquals($sshWrapperExpected, $this->wrapper->getEnvVar('GIT_SSH')); + } + + public function testSetPrivateKeyPort() + { + $port = mt_rand(1024, 10000); + $this->wrapper->setPrivateKey('./test/id_rsa', $port); + $this->assertEquals($port, $this->wrapper->getEnvVar('GIT_SSH_PORT')); + } + + public function testSetPrivateKeyWrapper() + { + $sshWrapper = './test/dummy-wrapper.sh'; + $sshWrapperExpected = realpath($sshWrapper); + $this->wrapper->setPrivateKey('./test/id_rsa', 22, $sshWrapper); + $this->assertEquals($sshWrapperExpected, $this->wrapper->getEnvVar('GIT_SSH')); + } + + /** + * @expectedException \GitWrapper\GitException + */ + public function testSetPrivateKeyError() + { + $badKey = './test/id_rsa_bad'; + $this->wrapper->setPrivateKey($badKey); + } + + /** + * @expectedException \GitWrapper\GitException + */ + public function testSetPrivateKeyWrapperError() + { + $badWrapper = './test/dummy-wrapper-bad.sh'; + $this->wrapper->setPrivateKey('./test/id_rsa', 22, $badWrapper); + } + + public function testUnsetPrivateKey() + { + // Set and unset the private key. + $key = './test/id_rsa'; + $sshWrapper = './test/dummy-wrapper.sh'; + $this->wrapper->setPrivateKey($key, 22, $sshWrapper); + $this->wrapper->unsetPrivateKey(); + + $this->assertNull($this->wrapper->getEnvVar('GIT_SSH_KEY')); + $this->assertNull($this->wrapper->getEnvVar('GIT_SSH_PORT')); + $this->assertNull($this->wrapper->getEnvVar('GIT_SSH')); + } + + public function testGitCommand() + { + $version = $this->wrapper->git('--version'); + $this->assertGitVersion($version); + } + + /** + * @expectedException \GitWrapper\GitException + */ + public function testGitCommandError() + { + $this->runBadCommand(); + } + + public function testGitRun() + { + $command = GitCommand::getInstance(); + $command->setFlag('version'); + $command->setDirectory('./test'); // Directory just has to exist. + $version = $this->wrapper->run($command); + $this->assertGitVersion($version); + } + + /** + * @expectedException \GitWrapper\GitException + */ + public function testGitRunDirectoryError() + { + $command = GitCommand::getInstance(); + $command->setFlag('version'); + $command->setDirectory('/some/bad/directory'); + $this->wrapper->run($command); + } + + public function testWrapperExecutable() + { + $sshWrapper = realpath(__DIR__ . '/../../../bin/git-ssh-wrapper.sh'); + $this->assertTrue(is_executable($sshWrapper)); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testCallError() + { + $this->wrapper->badMethod(); + } + + public function testWorkingCopy() + { + $directory = './' . $this->randomString(); + $git = $this->wrapper->workingCopy($directory); + + $this->assertTrue($git instanceof GitWorkingCopy); + $this->assertEquals($directory, $git->getDirectory()); + $this->assertEquals($this->wrapper, $git->getWrapper()); + } + + public function testParseRepositoryName() + { + $nameGit = GitWrapper::parseRepositoryName('git@github.com:cpliakas/git-wrapper.git'); + $this->assertEquals($nameGit, 'git-wrapper'); + + $nameHttps = GitWrapper::parseRepositoryName('https://github.com/cpliakas/git-wrapper.git'); + $this->assertEquals($nameHttps, 'git-wrapper'); + } + + public function testCloneWothoutDirectory() + { + $this->addBypassListener(); + $this->wrapper->clone('file:///' . $this->randomString()); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTestCase.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTestCase.php new file mode 100644 index 000000000..e567de67b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTestCase.php @@ -0,0 +1,119 @@ +filesystem = new Filesystem(); + $this->wrapper = new GitWrapper(); + } + + /** + * Generates a random string. + * + * @param type $length + * The string length, defaults to 8 characters. + * + * @return string + * + * @see http://api.drupal.org/api/drupal/modules%21simpletest%21drupal_web_test_case.php/function/DrupalTestCase%3A%3ArandomName/7 + */ + public function randomString($length = 8) + { + $values = array_merge(range(65, 90), range(97, 122), range(48, 57)); + $max = count($values) - 1; + $str = chr(mt_rand(97, 122)); + for ($i = 1; $i < $length; $i++) { + $str .= chr($values[mt_rand(0, $max)]); + } + return $str; + } + + /** + * Adds the test listener for all events, returns the listener. + * + * @return \GitWrapper\Test\Event\TestListener + */ + public function addListener() + { + $dispatcher = $this->wrapper->getDispatcher(); + $listener = new TestListener(); + + $dispatcher->addListener(GitEvents::GIT_PREPARE, array($listener, 'onPrepare')); + $dispatcher->addListener(GitEvents::GIT_SUCCESS, array($listener, 'onSuccess')); + $dispatcher->addListener(GitEvents::GIT_ERROR, array($listener, 'onError')); + $dispatcher->addListener(GitEvents::GIT_BYPASS, array($listener, 'onBypass')); + + return $listener; + } + + /** + * Adds the bypass listener so that Git commands are not run. + * + * @return \GitWrapper\Test\Event\TestBypassListener + */ + public function addBypassListener() + { + $listener = new TestBypassListener(); + $dispatcher = $this->wrapper->getDispatcher(); + $dispatcher->addListener(GitEvents::GIT_PREPARE, array($listener, 'onPrepare'), -5); + return $listener; + } + + /** + * Asserts a correct Git version string was returned. + * + * @param type $version + * The version returned by the `git --version` command. + */ + public function assertGitVersion($version) + { + $match = preg_match('/^git version [.0-9]+/', $version); + $this->assertNotEmpty($match); + } + + /** + * Executes a bad command. + * + * @param bool $catchException + * Whether to catch the exception to continue script execution, defaults + * to false. + */ + public function runBadCommand($catchException = false) + { + try { + $this->wrapper->git('a-bad-command'); + } catch (GitException $e) { + if (!$catchException) { + throw $e; + } + } + } +} \ No newline at end of file diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/StreamSuppressFilter.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/StreamSuppressFilter.php new file mode 100644 index 000000000..c8ef3def5 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/StreamSuppressFilter.php @@ -0,0 +1,21 @@ +data; + $bucket->data = ''; + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + } + return PSFS_PASS_ON; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/TestLogger.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/TestLogger.php new file mode 100644 index 000000000..32785d0db --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/TestLogger.php @@ -0,0 +1,28 @@ +messages[] = $message; + $this->levels[] = $level; + $this->contexts[] = $context; + } + + public function clearMessages() + { + $this->messages = $this->levels = $this->contexts = array(); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/bootstrap.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/bootstrap.php new file mode 100644 index 000000000..d2d2fd7a8 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/bootstrap.php @@ -0,0 +1,11 @@ +add('GitWrapper\Test', 'test'); +$loader->register(); diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/dummy-wrapper.sh b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/dummy-wrapper.sh new file mode 100644 index 000000000..e69de29bb diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/id_rsa b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/id_rsa new file mode 100644 index 000000000..e69de29bb diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/.gitignore b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/.gitignore new file mode 100644 index 000000000..c49a5d8df --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ApcClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ApcClassLoader.php new file mode 100644 index 000000000..843f2cc83 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ApcClassLoader.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +/** + * ApcClassLoader implements a wrapping autoloader cached in APC for PHP 5.3. + * + * It expects an object implementing a findFile method to find the file. This + * allows using it as a wrapper around the other loaders of the component (the + * ClassLoader for instance) but also around any other autoloaders following + * this convention (the Composer one for instance). + * + * // with a Symfony autoloader + * use Symfony\Component\ClassLoader\ClassLoader; + * + * $loader = new ClassLoader(); + * $loader->addPrefix('Symfony\Component', __DIR__.'/component'); + * $loader->addPrefix('Symfony', __DIR__.'/framework'); + * + * // or with a Composer autoloader + * use Composer\Autoload\ClassLoader; + * + * $loader = new ClassLoader(); + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * $cachedLoader = new ApcClassLoader('my_prefix', $loader); + * + * // activate the cached autoloader + * $cachedLoader->register(); + * + * // eventually deactivate the non-cached loader if it was registered previously + * // to be sure to use the cached one. + * $loader->unregister(); + * + * @author Fabien Potencier + * @author Kris Wallsmith + */ +class ApcClassLoader +{ + private $prefix; + + /** + * A class loader object that implements the findFile() method. + * + * @var object + */ + protected $decorated; + + /** + * Constructor. + * + * @param string $prefix The APC namespace prefix to use. + * @param object $decorated A class loader object that implements the findFile() method. + * + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function __construct($prefix, $decorated) + { + if (!function_exists('apcu_fetch')) { + throw new \RuntimeException('Unable to use ApcClassLoader as APC is not installed.'); + } + + if (!method_exists($decorated, 'findFile')) { + throw new \InvalidArgumentException('The class finder must implement a "findFile" method.'); + } + + $this->prefix = $prefix; + $this->decorated = $decorated; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + + return true; + } + } + + /** + * Finds a file by class name while caching lookups to APC. + * + * @param string $class A class name to resolve to file + * + * @return string|null + */ + public function findFile($class) + { + if (false === $file = apcu_fetch($this->prefix.$class)) { + apcu_store($this->prefix.$class, $file = $this->decorated->findFile($class)); + } + + return $file; + } + + /** + * Passes through all unknown calls onto the decorated object. + */ + public function __call($method, $args) + { + return call_user_func_array(array($this->decorated, $method), $args); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/CHANGELOG.md b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/CHANGELOG.md new file mode 100644 index 000000000..64ef8d9c9 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/CHANGELOG.md @@ -0,0 +1,36 @@ +CHANGELOG +========= + +3.0.0 +----- + + * The DebugClassLoader class has been removed + * The DebugUniversalClassLoader class has been removed + * The UniversalClassLoader class has been removed + * The ApcUniversalClassLoader class has been removed + +2.4.0 +----- + + * deprecated the UniversalClassLoader in favor of the ClassLoader class instead + * deprecated the ApcUniversalClassLoader in favor of the ApcClassLoader class instead + * deprecated the DebugUniversalClassLoader in favor of the DebugClassLoader class from the Debug component + * deprecated the DebugClassLoader as it has been moved to the Debug component instead + +2.3.0 +----- + + * added a WinCacheClassLoader for WinCache + +2.1.0 +----- + + * added a DebugClassLoader able to wrap any autoloader providing a findFile + method + * added a new ApcClassLoader and XcacheClassLoader using composition to wrap + other loaders + * added a new ClassLoader which does not distinguish between namespaced and + pear-like classes (as the PEAR convention is a subset of PSR-0) and + supports using Composer's namespace maps + * added a class map generator + * added support for loading globally-installed PEAR packages diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassCollectionLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassCollectionLoader.php new file mode 100644 index 000000000..19d450b93 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassCollectionLoader.php @@ -0,0 +1,374 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +/** + * ClassCollectionLoader. + * + * @author Fabien Potencier + */ +class ClassCollectionLoader +{ + private static $loaded; + private static $seen; + private static $useTokenizer = true; + + /** + * Loads a list of classes and caches them in one big file. + * + * @param array $classes An array of classes to load + * @param string $cacheDir A cache directory + * @param string $name The cache name prefix + * @param bool $autoReload Whether to flush the cache when the cache is stale or not + * @param bool $adaptive Whether to remove already declared classes or not + * @param string $extension File extension of the resulting file + * + * @throws \InvalidArgumentException When class can't be loaded + */ + public static function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php') + { + // each $name can only be loaded once per PHP process + if (isset(self::$loaded[$name])) { + return; + } + + self::$loaded[$name] = true; + + $declared = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); + + if ($adaptive) { + // don't include already declared classes + $classes = array_diff($classes, $declared); + + // the cache is different depending on which classes are already declared + $name = $name.'-'.substr(hash('sha256', implode('|', $classes)), 0, 5); + } + + $classes = array_unique($classes); + + $cache = $cacheDir.'/'.$name.$extension; + + // auto-reload + $reload = false; + if ($autoReload) { + $metadata = $cache.'.meta'; + if (!is_file($metadata) || !is_file($cache)) { + $reload = true; + } else { + $time = filemtime($cache); + $meta = unserialize(file_get_contents($metadata)); + + sort($meta[1]); + sort($classes); + + if ($meta[1] != $classes) { + $reload = true; + } else { + foreach ($meta[0] as $resource) { + if (!is_file($resource) || filemtime($resource) > $time) { + $reload = true; + + break; + } + } + } + } + } + + if (!$reload && is_file($cache)) { + require_once $cache; + + return; + } + + $files = array(); + $content = ''; + foreach (self::getOrderedClasses($classes) as $class) { + if (in_array($class->getName(), $declared)) { + continue; + } + + $files[] = $class->getFileName(); + + $c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($class->getFileName())); + + // fakes namespace declaration for global code + if (!$class->inNamespace()) { + $c = "\nnamespace\n{\n".$c."\n}\n"; + } + + $c = self::fixNamespaceDeclarations('= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + unset($tokens, $rawChunk); + gc_mem_caches(); + } + + return $output; + } + + /** + * This method is only useful for testing. + */ + public static function enableTokenizer($bool) + { + self::$useTokenizer = (bool) $bool; + } + + /** + * Strips leading & trailing ws, multiple EOL, multiple ws. + * + * @param string $code Original PHP code + * + * @return string compressed code + */ + private static function compressCode($code) + { + return preg_replace( + array('/^\s+/m', '/\s+$/m', '/([\n\r]+ *[\n\r]+)+/', '/[ \t]+/'), + array('', '', "\n", ' '), + $code + ); + } + + /** + * Writes a cache file. + * + * @param string $file Filename + * @param string $content Temporary file content + * + * @throws \RuntimeException when a cache file cannot be written + */ + private static function writeCacheFile($file, $content) + { + $tmpFile = tempnam(dirname($file), basename($file)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { + @chmod($file, 0666 & ~umask()); + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + } + + /** + * Gets an ordered array of passed classes including all their dependencies. + * + * @param array $classes + * + * @return \ReflectionClass[] An array of sorted \ReflectionClass instances (dependencies added if needed) + * + * @throws \InvalidArgumentException When a class can't be loaded + */ + private static function getOrderedClasses(array $classes) + { + $map = array(); + self::$seen = array(); + foreach ($classes as $class) { + try { + $reflectionClass = new \ReflectionClass($class); + } catch (\ReflectionException $e) { + throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class)); + } + + $map = array_merge($map, self::getClassHierarchy($reflectionClass)); + } + + return $map; + } + + private static function getClassHierarchy(\ReflectionClass $class) + { + if (isset(self::$seen[$class->getName()])) { + return array(); + } + + self::$seen[$class->getName()] = true; + + $classes = array($class); + $parent = $class; + while (($parent = $parent->getParentClass()) && $parent->isUserDefined() && !isset(self::$seen[$parent->getName()])) { + self::$seen[$parent->getName()] = true; + + array_unshift($classes, $parent); + } + + $traits = array(); + + foreach ($classes as $c) { + foreach (self::resolveDependencies(self::computeTraitDeps($c), $c) as $trait) { + if ($trait !== $c) { + $traits[] = $trait; + } + } + } + + return array_merge(self::getInterfaces($class), $traits, $classes); + } + + private static function getInterfaces(\ReflectionClass $class) + { + $classes = array(); + + foreach ($class->getInterfaces() as $interface) { + $classes = array_merge($classes, self::getInterfaces($interface)); + } + + if ($class->isUserDefined() && $class->isInterface() && !isset(self::$seen[$class->getName()])) { + self::$seen[$class->getName()] = true; + + $classes[] = $class; + } + + return $classes; + } + + private static function computeTraitDeps(\ReflectionClass $class) + { + $traits = $class->getTraits(); + $deps = array($class->getName() => $traits); + while ($trait = array_pop($traits)) { + if ($trait->isUserDefined() && !isset(self::$seen[$trait->getName()])) { + self::$seen[$trait->getName()] = true; + $traitDeps = $trait->getTraits(); + $deps[$trait->getName()] = $traitDeps; + $traits = array_merge($traits, $traitDeps); + } + } + + return $deps; + } + + /** + * Dependencies resolution. + * + * This function does not check for circular dependencies as it should never + * occur with PHP traits. + * + * @param array $tree The dependency tree + * @param \ReflectionClass $node The node + * @param \ArrayObject $resolved An array of already resolved dependencies + * @param \ArrayObject $unresolved An array of dependencies to be resolved + * + * @return \ArrayObject The dependencies for the given node + * + * @throws \RuntimeException if a circular dependency is detected + */ + private static function resolveDependencies(array $tree, $node, \ArrayObject $resolved = null, \ArrayObject $unresolved = null) + { + if (null === $resolved) { + $resolved = new \ArrayObject(); + } + if (null === $unresolved) { + $unresolved = new \ArrayObject(); + } + $nodeName = $node->getName(); + + if (isset($tree[$nodeName])) { + $unresolved[$nodeName] = $node; + foreach ($tree[$nodeName] as $dependency) { + if (!$resolved->offsetExists($dependency->getName())) { + self::resolveDependencies($tree, $dependency, $resolved, $unresolved); + } + } + $resolved[$nodeName] = $node; + unset($unresolved[$nodeName]); + } + + return $resolved; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassLoader.php new file mode 100644 index 000000000..a506dc094 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassLoader.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +/** + * ClassLoader implements an PSR-0 class loader. + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new ClassLoader(); + * + * // register classes with namespaces + * $loader->addPrefix('Symfony\Component', __DIR__.'/component'); + * $loader->addPrefix('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (e.g. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class ClassLoader +{ + private $prefixes = array(); + private $fallbackDirs = array(); + private $useIncludePath = false; + + /** + * Returns prefixes. + * + * @return array + */ + public function getPrefixes() + { + return $this->prefixes; + } + + /** + * Returns fallback directories. + * + * @return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirs; + } + + /** + * Adds prefixes. + * + * @param array $prefixes Prefixes to add + */ + public function addPrefixes(array $prefixes) + { + foreach ($prefixes as $prefix => $path) { + $this->addPrefix($prefix, $path); + } + } + + /** + * Registers a set of classes. + * + * @param string $prefix The classes prefix + * @param array|string $paths The location(s) of the classes + */ + public function addPrefix($prefix, $paths) + { + if (!$prefix) { + foreach ((array) $paths as $path) { + $this->fallbackDirs[] = $path; + } + + return; + } + if (isset($this->prefixes[$prefix])) { + if (is_array($paths)) { + $this->prefixes[$prefix] = array_unique(array_merge( + $this->prefixes[$prefix], + $paths + )); + } elseif (!in_array($paths, $this->prefixes[$prefix])) { + $this->prefixes[$prefix][] = $paths; + } + } else { + $this->prefixes[$prefix] = array_unique((array) $paths); + } + } + + /** + * Turns on searching the include for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = (bool) $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|null The path, if found + */ + public function findFile($class) + { + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)).DIRECTORY_SEPARATOR; + $className = substr($class, $pos + 1); + } else { + // PEAR-like class name + $classPath = null; + $className = $class; + } + + $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className).'.php'; + + foreach ($this->prefixes as $prefix => $dirs) { + if ($class === strstr($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) { + return $dir.DIRECTORY_SEPARATOR.$classPath; + } + } + } + } + + foreach ($this->fallbackDirs as $dir) { + if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) { + return $dir.DIRECTORY_SEPARATOR.$classPath; + } + } + + if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) { + return $file; + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassMapGenerator.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassMapGenerator.php new file mode 100644 index 000000000..17e95c0bc --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassMapGenerator.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\ClassLoader; + +/** + * ClassMapGenerator. + * + * @author Gyula Sallai + */ +class ClassMapGenerator +{ + /** + * Generate a class map file. + * + * @param array|string $dirs Directories or a single path to search in + * @param string $file The name of the class map file + */ + public static function dump($dirs, $file) + { + $dirs = (array) $dirs; + $maps = array(); + + foreach ($dirs as $dir) { + $maps = array_merge($maps, static::createMap($dir)); + } + + file_put_contents($file, sprintf('isFile()) { + continue; + } + + $path = $file->getRealPath(); + + if (pathinfo($path, PATHINFO_EXTENSION) !== 'php') { + continue; + } + + $classes = self::findClasses($path); + + if (PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + gc_mem_caches(); + } + + foreach ($classes as $class) { + $map[$class] = $path; + } + } + + return $map; + } + + /** + * Extract the classes in the given file. + * + * @param string $path The file to check + * + * @return array The found classes + */ + private static function findClasses($path) + { + $contents = file_get_contents($path); + $tokens = token_get_all($contents); + + $classes = array(); + + $namespace = ''; + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + + if (!isset($token[1])) { + continue; + } + + $class = ''; + + switch ($token[0]) { + case T_NAMESPACE: + $namespace = ''; + // If there is a namespace, extract it + while (isset($tokens[++$i][1])) { + if (in_array($tokens[$i][0], array(T_STRING, T_NS_SEPARATOR))) { + $namespace .= $tokens[$i][1]; + } + } + $namespace .= '\\'; + break; + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + // Skip usage of ::class constant + $isClassConstant = false; + for ($j = $i - 1; $j > 0; --$j) { + if (!isset($tokens[$j][1])) { + break; + } + + if (T_DOUBLE_COLON === $tokens[$j][0]) { + $isClassConstant = true; + break; + } elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) { + break; + } + } + + if ($isClassConstant) { + break; + } + + // Find the classname + while (isset($tokens[++$i][1])) { + $t = $tokens[$i]; + if (T_STRING === $t[0]) { + $class .= $t[1]; + } elseif ('' !== $class && T_WHITESPACE === $t[0]) { + break; + } + } + + $classes[] = ltrim($namespace.$class, '\\'); + break; + default: + break; + } + } + + return $classes; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/LICENSE b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/LICENSE new file mode 100644 index 000000000..12a74531e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 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/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/MapClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/MapClassLoader.php new file mode 100644 index 000000000..1d8bcc2a0 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/MapClassLoader.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\ClassLoader; + +/** + * A class loader that uses a mapping file to look up paths. + * + * @author Fabien Potencier + */ +class MapClassLoader +{ + private $map = array(); + + /** + * Constructor. + * + * @param array $map A map where keys are classes and values the absolute file path + */ + public function __construct(array $map) + { + $this->map = $map; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + */ + public function loadClass($class) + { + if (isset($this->map[$class])) { + require $this->map[$class]; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|null The path, if found + */ + public function findFile($class) + { + if (isset($this->map[$class])) { + return $this->map[$class]; + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Psr4ClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Psr4ClassLoader.php new file mode 100644 index 000000000..84647b758 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Psr4ClassLoader.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\ClassLoader; + +/** + * A PSR-4 compatible class loader. + * + * See http://www.php-fig.org/psr/psr-4/ + * + * @author Alexander M. Turek + */ +class Psr4ClassLoader +{ + /** + * @var array + */ + private $prefixes = array(); + + /** + * @param string $prefix + * @param string $baseDir + */ + public function addPrefix($prefix, $baseDir) + { + $prefix = trim($prefix, '\\').'\\'; + $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + $this->prefixes[] = array($prefix, $baseDir); + } + + /** + * @param string $class + * + * @return string|null + */ + public function findFile($class) + { + $class = ltrim($class, '\\'); + + foreach ($this->prefixes as list($currentPrefix, $currentBaseDir)) { + if (0 === strpos($class, $currentPrefix)) { + $classWithoutPrefix = substr($class, strlen($currentPrefix)); + $file = $currentBaseDir.str_replace('\\', DIRECTORY_SEPARATOR, $classWithoutPrefix).'.php'; + if (file_exists($file)) { + return $file; + } + } + } + } + + /** + * @param string $class + * + * @return bool + */ + public function loadClass($class) + { + $file = $this->findFile($class); + if (null !== $file) { + require $file; + + return true; + } + + return false; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Removes this instance from the registered autoloaders. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/README.md b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/README.md new file mode 100644 index 000000000..45093c5df --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/README.md @@ -0,0 +1,85 @@ +ClassLoader Component +===================== + +ClassLoader loads your project classes automatically if they follow some +standard PHP conventions. + +The ClassLoader object is able to autoload classes that implement the PSR-0 +standard or the PEAR naming convention. + +First, register the autoloader: + +```php +require_once __DIR__.'/src/Symfony/Component/ClassLoader/ClassLoader.php'; + +use Symfony\Component\ClassLoader\ClassLoader; + +$loader = new ClassLoader(); +$loader->register(); +``` + +Then, register some namespaces with the `addPrefix()` method: + +```php +$loader->addPrefix('Symfony', __DIR__.'/src'); +$loader->addPrefix('Monolog', __DIR__.'/vendor/monolog/src'); +``` + +The `addPrefix()` method takes a namespace prefix and a path where to look for +the classes as arguments. + +You can also register a sub-namespaces: + +```php +$loader->addPrefix('Doctrine\\Common', __DIR__.'/vendor/doctrine-common/lib'); +``` + +The order of registration is significant and the first registered namespace +takes precedence over later registered one. + +You can also register more than one path for a given namespace: + +```php +$loader->addPrefix('Symfony', array(__DIR__.'/src', __DIR__.'/symfony/src')); +``` + +Alternatively, you can use the `addPrefixes()` method to register more +than one namespace at once: + +```php +$loader->addPrefixes(array( + 'Symfony' => array(__DIR__.'/src', __DIR__.'/symfony/src'), + 'Doctrine\\Common' => __DIR__.'/vendor/doctrine-common/lib', + 'Doctrine' => __DIR__.'/vendor/doctrine/lib', + 'Monolog' => __DIR__.'/vendor/monolog/src', +)); +``` + +For better performance, you can use the APC class loader: + +```php +require_once __DIR__.'/src/Symfony/Component/ClassLoader/ClassLoader.php'; +require_once __DIR__.'/src/Symfony/Component/ClassLoader/ApcClassLoader.php'; + +use Symfony\Component\ClassLoader\ClassLoader; +use Symfony\Component\ClassLoader\ApcClassLoader; + +$loader = new ClassLoader(); +$loader->addPrefix('Symfony', __DIR__.'/src'); + +$loader = new ApcClassLoader('apc.prefix.', $loader); +$loader->register(); +``` + +Furthermore, the component provides tools to aggregate classes into a single +file, which is especially useful to improve performance on servers that do not +provide byte caches. + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/ClassLoader/ + $ composer install + $ phpunit diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ApcClassLoaderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ApcClassLoaderTest.php new file mode 100644 index 000000000..e96ba9540 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ApcClassLoaderTest.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use Symfony\Component\ClassLoader\ApcClassLoader; +use Symfony\Component\ClassLoader\ClassLoader; + +class ApcClassLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!(ini_get('apc.enabled') && ini_get('apc.enable_cli'))) { + $this->markTestSkipped('The apc extension is not enabled.'); + } else { + apcu_clear_cache(); + } + } + + protected function tearDown() + { + if (ini_get('apc.enabled') && ini_get('apc.enable_cli')) { + apcu_clear_cache(); + } + } + + public function testConstructor() + { + $loader = new ClassLoader(); + $loader->addPrefix('Apc\Namespaced', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + + $loader = new ApcClassLoader('test.prefix.', $loader); + + $this->assertEquals($loader->findFile('\Apc\Namespaced\FooBar'), apcu_fetch('test.prefix.\Apc\Namespaced\FooBar'), '__construct() takes a prefix as its first argument'); + } + + /** + * @dataProvider getLoadClassTests + */ + public function testLoadClass($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Apc\Namespaced', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Apc_Pearlike_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + + $loader = new ApcClassLoader('test.prefix.', $loader); + $loader->loadClass($testClassName); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassTests() + { + return array( + array('\\Apc\\Namespaced\\Foo', 'Apc\\Namespaced\\Foo', '->loadClass() loads Apc\Namespaced\Foo class'), + array('Apc_Pearlike_Foo', 'Apc_Pearlike_Foo', '->loadClass() loads Apc_Pearlike_Foo class'), + ); + } + + /** + * @dataProvider getLoadClassFromFallbackTests + */ + public function testLoadClassFromFallback($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Apc\Namespaced', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Apc_Pearlike_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('', array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/fallback')); + + $loader = new ApcClassLoader('test.prefix.fallback', $loader); + $loader->loadClass($testClassName); + + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassFromFallbackTests() + { + return array( + array('\\Apc\\Namespaced\\Baz', 'Apc\\Namespaced\\Baz', '->loadClass() loads Apc\Namespaced\Baz class'), + array('Apc_Pearlike_Baz', 'Apc_Pearlike_Baz', '->loadClass() loads Apc_Pearlike_Baz class'), + array('\\Apc\\Namespaced\\FooBar', 'Apc\\Namespaced\\FooBar', '->loadClass() loads Apc\Namespaced\Baz class from fallback dir'), + array('Apc_Pearlike_FooBar', 'Apc_Pearlike_FooBar', '->loadClass() loads Apc_Pearlike_Baz class from fallback dir'), + ); + } + + /** + * @dataProvider getLoadClassNamespaceCollisionTests + */ + public function testLoadClassNamespaceCollision($namespaces, $className, $message) + { + $loader = new ClassLoader(); + $loader->addPrefixes($namespaces); + + $loader = new ApcClassLoader('test.prefix.collision.', $loader); + $loader->loadClass($className); + + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassNamespaceCollisionTests() + { + return array( + array( + array( + 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha', + 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta', + ), + 'Apc\NamespaceCollision\A\Foo', + '->loadClass() loads NamespaceCollision\A\Foo from alpha.', + ), + array( + array( + 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta', + 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha', + ), + 'Apc\NamespaceCollision\A\Bar', + '->loadClass() loads NamespaceCollision\A\Bar from alpha.', + ), + array( + array( + 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha', + 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta', + ), + 'Apc\NamespaceCollision\A\B\Foo', + '->loadClass() loads NamespaceCollision\A\B\Foo from beta.', + ), + array( + array( + 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta', + 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha', + ), + 'Apc\NamespaceCollision\A\B\Bar', + '->loadClass() loads NamespaceCollision\A\B\Bar from beta.', + ), + ); + } + + /** + * @dataProvider getLoadClassPrefixCollisionTests + */ + public function testLoadClassPrefixCollision($prefixes, $className, $message) + { + $loader = new ClassLoader(); + $loader->addPrefixes($prefixes); + + $loader = new ApcClassLoader('test.prefix.collision.', $loader); + $loader->loadClass($className); + + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassPrefixCollisionTests() + { + return array( + array( + array( + 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc', + 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc', + ), + 'ApcPrefixCollision_A_Foo', + '->loadClass() loads ApcPrefixCollision_A_Foo from alpha.', + ), + array( + array( + 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc', + 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc', + ), + 'ApcPrefixCollision_A_Bar', + '->loadClass() loads ApcPrefixCollision_A_Bar from alpha.', + ), + array( + array( + 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc', + 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc', + ), + 'ApcPrefixCollision_A_B_Foo', + '->loadClass() loads ApcPrefixCollision_A_B_Foo from beta.', + ), + array( + array( + 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc', + 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc', + ), + 'ApcPrefixCollision_A_B_Bar', + '->loadClass() loads ApcPrefixCollision_A_B_Bar from beta.', + ), + ); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassCollectionLoaderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassCollectionLoaderTest.php new file mode 100644 index 000000000..906e74941 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassCollectionLoaderTest.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use Symfony\Component\ClassLoader\ClassCollectionLoader; + +require_once __DIR__.'/Fixtures/ClassesWithParents/GInterface.php'; +require_once __DIR__.'/Fixtures/ClassesWithParents/CInterface.php'; +require_once __DIR__.'/Fixtures/ClassesWithParents/B.php'; +require_once __DIR__.'/Fixtures/ClassesWithParents/A.php'; + +class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase +{ + public function testTraitDependencies() + { + require_once __DIR__.'/Fixtures/deps/traits.php'; + + $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader'); + $m = $r->getMethod('getOrderedClasses'); + $m->setAccessible(true); + + $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', array('CTFoo')); + + $this->assertEquals( + array('TD', 'TC', 'TB', 'TA', 'TZ', 'CTFoo'), + array_map(function ($class) { return $class->getName(); }, $ordered) + ); + + $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', array('CTBar')); + + $this->assertEquals( + array('TD', 'TZ', 'TC', 'TB', 'TA', 'CTBar'), + array_map(function ($class) { return $class->getName(); }, $ordered) + ); + } + + /** + * @dataProvider getDifferentOrders + */ + public function testClassReordering(array $classes) + { + $expected = array( + 'ClassesWithParents\\GInterface', + 'ClassesWithParents\\CInterface', + 'ClassesWithParents\\B', + 'ClassesWithParents\\A', + ); + + $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader'); + $m = $r->getMethod('getOrderedClasses'); + $m->setAccessible(true); + + $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes); + + $this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered)); + } + + public function getDifferentOrders() + { + return array( + array(array( + 'ClassesWithParents\\A', + 'ClassesWithParents\\CInterface', + 'ClassesWithParents\\GInterface', + 'ClassesWithParents\\B', + )), + array(array( + 'ClassesWithParents\\B', + 'ClassesWithParents\\A', + 'ClassesWithParents\\CInterface', + )), + array(array( + 'ClassesWithParents\\CInterface', + 'ClassesWithParents\\B', + 'ClassesWithParents\\A', + )), + array(array( + 'ClassesWithParents\\A', + )), + ); + } + + /** + * @dataProvider getDifferentOrdersForTraits + */ + public function testClassWithTraitsReordering(array $classes) + { + require_once __DIR__.'/Fixtures/ClassesWithParents/ATrait.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/BTrait.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/CTrait.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/D.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/E.php'; + + $expected = array( + 'ClassesWithParents\\GInterface', + 'ClassesWithParents\\CInterface', + 'ClassesWithParents\\ATrait', + 'ClassesWithParents\\BTrait', + 'ClassesWithParents\\CTrait', + 'ClassesWithParents\\B', + 'ClassesWithParents\\A', + 'ClassesWithParents\\D', + 'ClassesWithParents\\E', + ); + + $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader'); + $m = $r->getMethod('getOrderedClasses'); + $m->setAccessible(true); + + $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes); + + $this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered)); + } + + public function getDifferentOrdersForTraits() + { + return array( + array(array( + 'ClassesWithParents\\E', + 'ClassesWithParents\\ATrait', + )), + array(array( + 'ClassesWithParents\\E', + )), + ); + } + + public function testFixClassWithTraitsOrdering() + { + require_once __DIR__.'/Fixtures/ClassesWithParents/CTrait.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/F.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/G.php'; + + $classes = array( + 'ClassesWithParents\\F', + 'ClassesWithParents\\G', + ); + + $expected = array( + 'ClassesWithParents\\CTrait', + 'ClassesWithParents\\F', + 'ClassesWithParents\\G', + ); + + $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader'); + $m = $r->getMethod('getOrderedClasses'); + $m->setAccessible(true); + + $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes); + + $this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered)); + } + + /** + * @dataProvider getFixNamespaceDeclarationsData + */ + public function testFixNamespaceDeclarations($source, $expected) + { + $this->assertEquals('assertEquals('assertEquals(<<<'EOF' +namespace Namespaced +{ +class WithComments +{ +public static $loaded = true; +} +$string ='string should not be modified {$string}'; +$heredoc = (<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use Symfony\Component\ClassLoader\ClassLoader; + +class ClassLoaderTest extends \PHPUnit_Framework_TestCase +{ + public function testGetPrefixes() + { + $loader = new ClassLoader(); + $loader->addPrefix('Foo', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Bar', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Bas', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $prefixes = $loader->getPrefixes(); + $this->assertArrayHasKey('Foo', $prefixes); + $this->assertArrayNotHasKey('Foo1', $prefixes); + $this->assertArrayHasKey('Bar', $prefixes); + $this->assertArrayHasKey('Bas', $prefixes); + } + + public function testGetFallbackDirs() + { + $loader = new ClassLoader(); + $loader->addPrefix(null, __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix(null, __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $fallback_dirs = $loader->getFallbackDirs(); + $this->assertCount(2, $fallback_dirs); + } + + /** + * @dataProvider getLoadClassTests + */ + public function testLoadClass($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->loadClass($testClassName); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassTests() + { + return array( + array('\\Namespaced2\\Foo', 'Namespaced2\\Foo', '->loadClass() loads Namespaced2\Foo class'), + array('\\Pearlike2_Foo', 'Pearlike2_Foo', '->loadClass() loads Pearlike2_Foo class'), + ); + } + + /** + * @dataProvider getLoadNonexistentClassTests + */ + public function testLoadNonexistentClass($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->loadClass($testClassName); + $this->assertFalse(class_exists($className), $message); + } + + public function getLoadNonexistentClassTests() + { + return array( + array('\\Pearlike3_Bar', '\\Pearlike3_Bar', '->loadClass() loads non existing Pearlike3_Bar class with a leading slash'), + ); + } + + public function testAddPrefixSingle() + { + $loader = new ClassLoader(); + $loader->addPrefix('Foo', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Foo', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $prefixes = $loader->getPrefixes(); + $this->assertArrayHasKey('Foo', $prefixes); + $this->assertCount(1, $prefixes['Foo']); + } + + public function testAddPrefixesSingle() + { + $loader = new ClassLoader(); + $loader->addPrefixes(array('Foo' => array('foo', 'foo'))); + $loader->addPrefixes(array('Foo' => array('foo'))); + $prefixes = $loader->getPrefixes(); + $this->assertArrayHasKey('Foo', $prefixes); + $this->assertCount(1, $prefixes['Foo'], print_r($prefixes, true)); + } + + public function testAddPrefixMulti() + { + $loader = new ClassLoader(); + $loader->addPrefix('Foo', 'foo'); + $loader->addPrefix('Foo', 'bar'); + $prefixes = $loader->getPrefixes(); + $this->assertArrayHasKey('Foo', $prefixes); + $this->assertCount(2, $prefixes['Foo']); + $this->assertContains('foo', $prefixes['Foo']); + $this->assertContains('bar', $prefixes['Foo']); + } + + public function testUseIncludePath() + { + $loader = new ClassLoader(); + $this->assertFalse($loader->getUseIncludePath()); + + $this->assertNull($loader->findFile('Foo')); + + $includePath = get_include_path(); + + $loader->setUseIncludePath(true); + $this->assertTrue($loader->getUseIncludePath()); + + set_include_path(__DIR__.'/Fixtures/includepath'.PATH_SEPARATOR.$includePath); + + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'includepath'.DIRECTORY_SEPARATOR.'Foo.php', $loader->findFile('Foo')); + + set_include_path($includePath); + } + + /** + * @dataProvider getLoadClassFromFallbackTests + */ + public function testLoadClassFromFallback($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('', array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures/fallback')); + $loader->loadClass($testClassName); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassFromFallbackTests() + { + return array( + array('\\Namespaced2\\Baz', 'Namespaced2\\Baz', '->loadClass() loads Namespaced2\Baz class'), + array('\\Pearlike2_Baz', 'Pearlike2_Baz', '->loadClass() loads Pearlike2_Baz class'), + array('\\Namespaced2\\FooBar', 'Namespaced2\\FooBar', '->loadClass() loads Namespaced2\Baz class from fallback dir'), + array('\\Pearlike2_FooBar', 'Pearlike2_FooBar', '->loadClass() loads Pearlike2_Baz class from fallback dir'), + ); + } + + /** + * @dataProvider getLoadClassNamespaceCollisionTests + */ + public function testLoadClassNamespaceCollision($namespaces, $className, $message) + { + $loader = new ClassLoader(); + $loader->addPrefixes($namespaces); + + $loader->loadClass($className); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassNamespaceCollisionTests() + { + return array( + array( + array( + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'NamespaceCollision\C\Foo', + '->loadClass() loads NamespaceCollision\C\Foo from alpha.', + ), + array( + array( + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'NamespaceCollision\C\Bar', + '->loadClass() loads NamespaceCollision\C\Bar from alpha.', + ), + array( + array( + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'NamespaceCollision\C\B\Foo', + '->loadClass() loads NamespaceCollision\C\B\Foo from beta.', + ), + array( + array( + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'NamespaceCollision\C\B\Bar', + '->loadClass() loads NamespaceCollision\C\B\Bar from beta.', + ), + array( + array( + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'PrefixCollision_C_Foo', + '->loadClass() loads PrefixCollision_C_Foo from alpha.', + ), + array( + array( + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'PrefixCollision_C_Bar', + '->loadClass() loads PrefixCollision_C_Bar from alpha.', + ), + array( + array( + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'PrefixCollision_C_B_Foo', + '->loadClass() loads PrefixCollision_C_B_Foo from beta.', + ), + array( + array( + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'PrefixCollision_C_B_Bar', + '->loadClass() loads PrefixCollision_C_B_Bar from beta.', + ), + ); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassMapGeneratorTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassMapGeneratorTest.php new file mode 100644 index 000000000..6aed6efa3 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassMapGeneratorTest.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use Symfony\Component\ClassLoader\ClassMapGenerator; + +class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var string|null + */ + private $workspace = null; + + public function prepare_workspace() + { + $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); + } + + /** + * @param string $file + */ + private function clean($file) + { + if (is_dir($file) && !is_link($file)) { + $dir = new \FilesystemIterator($file); + foreach ($dir as $childFile) { + $this->clean($childFile); + } + + rmdir($file); + } else { + unlink($file); + } + } + + /** + * @dataProvider getTestCreateMapTests + */ + public function testDump($directory) + { + $this->prepare_workspace(); + + $file = $this->workspace.'/file'; + + $generator = new ClassMapGenerator(); + $generator->dump($directory, $file); + $this->assertFileExists($file); + + $this->clean($this->workspace); + } + + /** + * @dataProvider getTestCreateMapTests + */ + public function testCreateMap($directory, $expected) + { + $this->assertEqualsNormalized($expected, ClassMapGenerator::createMap($directory)); + } + + public function getTestCreateMapTests() + { + $data = array( + array(__DIR__.'/Fixtures/Namespaced', array( + 'Namespaced\\Bar' => realpath(__DIR__).'/Fixtures/Namespaced/Bar.php', + 'Namespaced\\Foo' => realpath(__DIR__).'/Fixtures/Namespaced/Foo.php', + 'Namespaced\\Baz' => realpath(__DIR__).'/Fixtures/Namespaced/Baz.php', + 'Namespaced\\WithComments' => realpath(__DIR__).'/Fixtures/Namespaced/WithComments.php', + ), + ), + array(__DIR__.'/Fixtures/beta/NamespaceCollision', array( + 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php', + 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php', + 'NamespaceCollision\\C\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Bar.php', + 'NamespaceCollision\\C\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Foo.php', + )), + array(__DIR__.'/Fixtures/Pearlike', array( + 'Pearlike_Foo' => realpath(__DIR__).'/Fixtures/Pearlike/Foo.php', + 'Pearlike_Bar' => realpath(__DIR__).'/Fixtures/Pearlike/Bar.php', + 'Pearlike_Baz' => realpath(__DIR__).'/Fixtures/Pearlike/Baz.php', + 'Pearlike_WithComments' => realpath(__DIR__).'/Fixtures/Pearlike/WithComments.php', + )), + array(__DIR__.'/Fixtures/classmap', array( + 'Foo\\Bar\\A' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php', + 'Foo\\Bar\\B' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php', + 'A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Alpha\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Alpha\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Beta\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Beta\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'ClassMap\\SomeInterface' => realpath(__DIR__).'/Fixtures/classmap/SomeInterface.php', + 'ClassMap\\SomeParent' => realpath(__DIR__).'/Fixtures/classmap/SomeParent.php', + 'ClassMap\\SomeClass' => realpath(__DIR__).'/Fixtures/classmap/SomeClass.php', + )), + array(__DIR__.'/Fixtures/php5.4', array( + 'TFoo' => __DIR__.'/Fixtures/php5.4/traits.php', + 'CFoo' => __DIR__.'/Fixtures/php5.4/traits.php', + 'Foo\\TBar' => __DIR__.'/Fixtures/php5.4/traits.php', + 'Foo\\IBar' => __DIR__.'/Fixtures/php5.4/traits.php', + 'Foo\\TFooBar' => __DIR__.'/Fixtures/php5.4/traits.php', + 'Foo\\CBar' => __DIR__.'/Fixtures/php5.4/traits.php', + )), + array(__DIR__.'/Fixtures/php5.5', array( + 'ClassCons\\Foo' => __DIR__.'/Fixtures/php5.5/class_cons.php', + )), + ); + + return $data; + } + + public function testCreateMapFinderSupport() + { + $finder = new \Symfony\Component\Finder\Finder(); + $finder->files()->in(__DIR__.'/Fixtures/beta/NamespaceCollision'); + + $this->assertEqualsNormalized(array( + 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php', + 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php', + 'NamespaceCollision\\C\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Bar.php', + 'NamespaceCollision\\C\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Foo.php', + ), ClassMapGenerator::createMap($finder)); + } + + protected function assertEqualsNormalized($expected, $actual, $message = null) + { + foreach ($expected as $ns => $path) { + $expected[$ns] = str_replace('\\', '/', $path); + } + foreach ($actual as $ns => $path) { + $actual[$ns] = str_replace('\\', '/', $path); + } + $this->assertEquals($expected, $actual, $message); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Bar.php new file mode 100644 index 000000000..4259f1451 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Bar.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class Bar +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Baz.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Baz.php new file mode 100644 index 000000000..3ddb595e2 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Baz.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class Baz +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Foo.php new file mode 100644 index 000000000..cf0a4b741 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class Foo +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/FooBar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/FooBar.php new file mode 100644 index 000000000..bbbc81515 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/FooBar.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class FooBar +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Pearlike/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Pearlike/Bar.php new file mode 100644 index 000000000..e774cb9bf --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Pearlike/Bar.php @@ -0,0 +1,6 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\NamespaceCollision\A; + +class Bar +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/alpha/Apc/NamespaceCollision/A/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/alpha/Apc/NamespaceCollision/A/Foo.php new file mode 100644 index 000000000..184a1b1da --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/alpha/Apc/NamespaceCollision/A/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\NamespaceCollision\A; + +class Foo +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php new file mode 100644 index 000000000..3892f7068 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php @@ -0,0 +1,6 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\NamespaceCollision\A\B; + +class Bar +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/NamespaceCollision/A/B/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/NamespaceCollision/A/B/Foo.php new file mode 100644 index 000000000..450eeb50b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/NamespaceCollision/A/B/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\NamespaceCollision\A\B; + +class Foo +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/fallback/Apc/Pearlike/FooBar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/fallback/Apc/Pearlike/FooBar.php new file mode 100644 index 000000000..96f2f76c6 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/fallback/Apc/Pearlike/FooBar.php @@ -0,0 +1,6 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class FooBar +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/ClassesWithParents/A.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/ClassesWithParents/A.php new file mode 100644 index 000000000..b0f942595 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/ClassesWithParents/A.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Namespaced; + +class Bar +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Baz.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Baz.php new file mode 100644 index 000000000..0b0bbd057 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Baz.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Namespaced; + +class Baz +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Foo.php new file mode 100644 index 000000000..df5e1f4ce --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Namespaced; + +class Foo +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/WithComments.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/WithComments.php new file mode 100644 index 000000000..361e53de1 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/WithComments.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 Namespaced; + +class WithComments +{ + /** @Boolean */ + public static $loaded = true; +} + +$string = 'string should not be modified {$string}'; + +$heredoc = (<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class Pearlike_WithComments +{ + /** @Boolean */ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Pearlike2/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Pearlike2/Bar.php new file mode 100644 index 000000000..7f5f79773 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Pearlike2/Bar.php @@ -0,0 +1,6 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace NamespaceCollision\A; + +class Bar +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/A/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/A/Foo.php new file mode 100644 index 000000000..aee6a080d --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/A/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace NamespaceCollision\A; + +class Foo +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php new file mode 100644 index 000000000..c1b8dd65d --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php @@ -0,0 +1,8 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace NamespaceCollision\A\B; + +class Bar +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/A/B/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/A/B/Foo.php new file mode 100644 index 000000000..f5f2d727e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/A/B/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace NamespaceCollision\A\B; + +class Foo +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php new file mode 100644 index 000000000..4bb03dc7f --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php @@ -0,0 +1,8 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassMap; + +class SomeClass extends SomeParent implements SomeInterface +{ +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeInterface.php new file mode 100644 index 000000000..1fe5e09aa --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassMap; + +interface SomeInterface +{ +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeParent.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeParent.php new file mode 100644 index 000000000..ce2f9fc6c --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeParent.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassMap; + +abstract class SomeParent +{ +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/multipleNs.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/multipleNs.php new file mode 100644 index 000000000..c7cec646f --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/multipleNs.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 Foo\Bar; + +class A +{ +} +class B +{ +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/deps/traits.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/deps/traits.php new file mode 100644 index 000000000..82b30a6f9 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/deps/traits.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 Namespaced; + +class FooBar +{ + public static $loaded = true; +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/fallback/Namespaced2/FooBar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/fallback/Namespaced2/FooBar.php new file mode 100644 index 000000000..1036d4359 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/fallback/Namespaced2/FooBar.php @@ -0,0 +1,8 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use Symfony\Component\ClassLoader\Psr4ClassLoader; + +class Psr4ClassLoaderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @param string $className + * @dataProvider getLoadClassTests + */ + public function testLoadClass($className) + { + $loader = new Psr4ClassLoader(); + $loader->addPrefix( + 'Acme\\DemoLib', + __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'psr-4' + ); + $loader->loadClass($className); + $this->assertTrue(class_exists($className), sprintf('loadClass() should load %s', $className)); + } + + /** + * @return array + */ + public function getLoadClassTests() + { + return array( + array('Acme\\DemoLib\\Foo'), + array('Acme\\DemoLib\\Class_With_Underscores'), + array('Acme\\DemoLib\\Lets\\Go\\Deeper\\Foo'), + array('Acme\\DemoLib\\Lets\\Go\\Deeper\\Class_With_Underscores'), + ); + } + + /** + * @param string $className + * @dataProvider getLoadNonexistentClassTests + */ + public function testLoadNonexistentClass($className) + { + $loader = new Psr4ClassLoader(); + $loader->addPrefix( + 'Acme\\DemoLib', + __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'psr-4' + ); + $loader->loadClass($className); + $this->assertFalse(class_exists($className), sprintf('loadClass() should not load %s', $className)); + } + + /** + * @return array + */ + public function getLoadNonexistentClassTests() + { + return array( + array('Acme\\DemoLib\\I_Do_Not_Exist'), + array('UnknownVendor\\SomeLib\\I_Do_Not_Exist'), + ); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/WinCacheClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/WinCacheClassLoader.php new file mode 100644 index 000000000..7ba0d9df5 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/WinCacheClassLoader.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\ClassLoader; + +/** + * WinCacheClassLoader implements a wrapping autoloader cached in WinCache. + * + * It expects an object implementing a findFile method to find the file. This + * allow using it as a wrapper around the other loaders of the component (the + * ClassLoader for instance) but also around any other autoloaders following + * this convention (the Composer one for instance). + * + * // with a Symfony autoloader + * $loader = new ClassLoader(); + * $loader->addPrefix('Symfony\Component', __DIR__.'/component'); + * $loader->addPrefix('Symfony', __DIR__.'/framework'); + * + * // or with a Composer autoloader + * use Composer\Autoload\ClassLoader; + * + * $loader = new ClassLoader(); + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * $cachedLoader = new WinCacheClassLoader('my_prefix', $loader); + * + * // activate the cached autoloader + * $cachedLoader->register(); + * + * // eventually deactivate the non-cached loader if it was registered previously + * // to be sure to use the cached one. + * $loader->unregister(); + * + * @author Fabien Potencier + * @author Kris Wallsmith + * @author Artem Ryzhkov + */ +class WinCacheClassLoader +{ + private $prefix; + + /** + * A class loader object that implements the findFile() method. + * + * @var object + */ + protected $decorated; + + /** + * Constructor. + * + * @param string $prefix The WinCache namespace prefix to use. + * @param object $decorated A class loader object that implements the findFile() method. + * + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function __construct($prefix, $decorated) + { + if (!extension_loaded('wincache')) { + throw new \RuntimeException('Unable to use WinCacheClassLoader as WinCache is not enabled.'); + } + + if (!method_exists($decorated, 'findFile')) { + throw new \InvalidArgumentException('The class finder must implement a "findFile" method.'); + } + + $this->prefix = $prefix; + $this->decorated = $decorated; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + + return true; + } + } + + /** + * Finds a file by class name while caching lookups to WinCache. + * + * @param string $class A class name to resolve to file + * + * @return string|null + */ + public function findFile($class) + { + if (false === $file = wincache_ucache_get($this->prefix.$class)) { + wincache_ucache_set($this->prefix.$class, $file = $this->decorated->findFile($class), 0); + } + + return $file; + } + + /** + * Passes through all unknown calls onto the decorated object. + */ + public function __call($method, $args) + { + return call_user_func_array(array($this->decorated, $method), $args); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/XcacheClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/XcacheClassLoader.php new file mode 100644 index 000000000..5aacac69e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/XcacheClassLoader.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +/** + * XcacheClassLoader implements a wrapping autoloader cached in XCache for PHP 5.3. + * + * It expects an object implementing a findFile method to find the file. This + * allows using it as a wrapper around the other loaders of the component (the + * ClassLoader for instance) but also around any other autoloaders following + * this convention (the Composer one for instance). + * + * // with a Symfony autoloader + * $loader = new ClassLoader(); + * $loader->addPrefix('Symfony\Component', __DIR__.'/component'); + * $loader->addPrefix('Symfony', __DIR__.'/framework'); + * + * // or with a Composer autoloader + * use Composer\Autoload\ClassLoader; + * + * $loader = new ClassLoader(); + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * $cachedLoader = new XcacheClassLoader('my_prefix', $loader); + * + * // activate the cached autoloader + * $cachedLoader->register(); + * + * // eventually deactivate the non-cached loader if it was registered previously + * // to be sure to use the cached one. + * $loader->unregister(); + * + * @author Fabien Potencier + * @author Kris Wallsmith + * @author Kim Hemsø Rasmussen + */ +class XcacheClassLoader +{ + private $prefix; + + /** + * A class loader object that implements the findFile() method. + * + * @var object + */ + private $decorated; + + /** + * Constructor. + * + * @param string $prefix The XCache namespace prefix to use. + * @param object $decorated A class loader object that implements the findFile() method. + * + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function __construct($prefix, $decorated) + { + if (!extension_loaded('xcache')) { + throw new \RuntimeException('Unable to use XcacheClassLoader as XCache is not enabled.'); + } + + if (!method_exists($decorated, 'findFile')) { + throw new \InvalidArgumentException('The class finder must implement a "findFile" method.'); + } + + $this->prefix = $prefix; + $this->decorated = $decorated; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + + return true; + } + } + + /** + * Finds a file by class name while caching lookups to Xcache. + * + * @param string $class A class name to resolve to file + * + * @return string|null + */ + public function findFile($class) + { + if (xcache_isset($this->prefix.$class)) { + $file = xcache_get($this->prefix.$class); + } else { + $file = $this->decorated->findFile($class); + xcache_set($this->prefix.$class, $file); + } + + return $file; + } + + /** + * Passes through all unknown calls onto the decorated object. + */ + public function __call($method, $args) + { + return call_user_func_array(array($this->decorated, $method), $args); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/composer.json b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/composer.json new file mode 100644 index 000000000..0089e5fba --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/class-loader", + "type": "library", + "description": "Symfony ClassLoader Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\ClassLoader\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/phpunit.xml.dist b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/phpunit.xml.dist new file mode 100644 index 000000000..4856db5be --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/.gitignore b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/.gitignore new file mode 100644 index 000000000..c49a5d8df --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/CHANGELOG.md b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/CHANGELOG.md new file mode 100644 index 000000000..8feda35d5 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,32 @@ +CHANGELOG +========= + +3.0.0 +----- + + * The method `getListenerPriority($eventName, $listener)` has been added to the + `EventDispatcherInterface`. + * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()` + and `Event::getName()` have been removed. + The event dispatcher and the event name are passed to the listener call. + +2.5.0 +----- + + * added Debug\TraceableEventDispatcher (originally in HttpKernel) + * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface + * added RegisterListenersPass (originally in HttpKernel) + +2.1.0 +----- + + * added TraceableEventDispatcherInterface + * added ContainerAwareEventDispatcher + * added a reference to the EventDispatcher on the Event + * added a reference to the Event name on the event + * added fluid interface to the dispatch() method which now returns the Event + object + * added GenericEvent event class + * added the possibility for subscribers to subscribe several times for the + same event + * added ImmutableEventDispatcher diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php new file mode 100644 index 000000000..5982b85f3 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.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\EventDispatcher; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Lazily loads listeners and subscribers from the dependency injection + * container. + * + * @author Fabien Potencier + * @author Bernhard Schussek + * @author Jordan Alliot + */ +class ContainerAwareEventDispatcher extends EventDispatcher +{ + /** + * The container from where services are loaded. + * + * @var ContainerInterface + */ + private $container; + + /** + * The service IDs of the event listeners and subscribers. + * + * @var array + */ + private $listenerIds = array(); + + /** + * The services registered as listeners. + * + * @var array + */ + private $listeners = array(); + + /** + * Constructor. + * + * @param ContainerInterface $container A ContainerInterface instance + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Adds a service as event listener. + * + * @param string $eventName Event for which the listener is added + * @param array $callback The service ID of the listener service & the method + * name that has to be called + * @param int $priority The higher this value, the earlier an event listener + * will be triggered in the chain. + * Defaults to 0. + * + * @throws \InvalidArgumentException + */ + public function addListenerService($eventName, $callback, $priority = 0) + { + if (!is_array($callback) || 2 !== count($callback)) { + throw new \InvalidArgumentException('Expected an array("service", "method") argument'); + } + + $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); + } + + public function removeListener($eventName, $listener) + { + $this->lazyLoad($eventName); + + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) { + $key = $serviceId.'.'.$method; + if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { + unset($this->listeners[$eventName][$key]); + if (empty($this->listeners[$eventName])) { + unset($this->listeners[$eventName]); + } + unset($this->listenerIds[$eventName][$i]); + if (empty($this->listenerIds[$eventName])) { + unset($this->listenerIds[$eventName]); + } + } + } + } + + parent::removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + if (null === $eventName) { + return (bool) count($this->listenerIds) || (bool) count($this->listeners); + } + + if (isset($this->listenerIds[$eventName])) { + return true; + } + + return parent::hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null === $eventName) { + foreach ($this->listenerIds as $serviceEventName => $args) { + $this->lazyLoad($serviceEventName); + } + } else { + $this->lazyLoad($eventName); + } + + return parent::getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + $this->lazyLoad($eventName); + + return parent::getListenerPriority($eventName, $listener); + } + + /** + * Adds a service as event subscriber. + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name (which must implement EventSubscriberInterface) + */ + public function addSubscriberService($serviceId, $class) + { + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } elseif (is_string($params[0])) { + $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + public function getContainer() + { + return $this->container; + } + + /** + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + */ + protected function lazyLoad($eventName) + { + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) { + $listener = $this->container->get($serviceId); + + $key = $serviceId.'.'.$method; + if (!isset($this->listeners[$eventName][$key])) { + $this->addListener($eventName, array($listener, $method), $priority); + } elseif ($listener !== $this->listeners[$eventName][$key]) { + parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method)); + $this->addListener($eventName, array($listener, $method), $priority); + } + + $this->listeners[$eventName][$key] = $listener; + } + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 000000000..35df8162b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,367 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Stopwatch\Stopwatch; +use Psr\Log\LoggerInterface; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements TraceableEventDispatcherInterface +{ + protected $logger; + protected $stopwatch; + + private $called; + private $dispatcher; + private $wrappedListeners; + + /** + * Constructor. + * + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->called = array(); + $this->wrappedListeners = array(); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener) { + $listener = $wrappedListener; + unset($this->wrappedListeners[$eventName][$index]); + break; + } + } + } + + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + $this->preProcess($eventName); + $this->preDispatch($eventName, $event); + + $e = $this->stopwatch->start($eventName, 'section'); + + $this->dispatcher->dispatch($eventName, $event); + + if ($e->isStarted()) { + $e->stop(); + } + + $this->postDispatch($eventName, $event); + $this->postProcess($eventName); + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getCalledListeners() + { + $called = array(); + foreach ($this->called as $eventName => $listeners) { + foreach ($listeners as $listener) { + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + $called[$eventName.'.'.$info['pretty']] = $info; + } + } + + return $called; + } + + /** + * {@inheritdoc} + */ + public function getNotCalledListeners() + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e)); + } + + // unable to retrieve the uncalled listeners + return array(); + } + + $notCalled = array(); + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + $called = false; + if (isset($this->called[$eventName])) { + foreach ($this->called[$eventName] as $l) { + if ($l->getWrappedListener() === $listener) { + $called = true; + + break; + } + } + } + + if (!$called) { + $info = $this->getListenerInfo($listener, $eventName); + $notCalled[$eventName.'.'.$info['pretty']] = $info; + } + } + } + + uasort($notCalled, array($this, 'sortListenersByPriority')); + + return $notCalled; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return call_user_func_array(array($this->dispatcher, $method), $arguments); + } + + /** + * Called before dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function preDispatch($eventName, Event $event) + { + } + + /** + * Called after dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function postDispatch($eventName, Event $event) + { + } + + private function preProcess($eventName) + { + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $this->dispatcher->removeListener($eventName, $listener); + $info = $this->getListenerInfo($listener, $eventName); + $name = isset($info['class']) ? $info['class'] : $info['type']; + $wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->addListener($eventName, $wrappedListener); + } + } + + private function postProcess($eventName) + { + unset($this->wrappedListeners[$eventName]); + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. + continue; + } + // Unwrap listener + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener()); + + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty'])); + } + + if (!isset($this->called[$eventName])) { + $this->called[$eventName] = new \SplObjectStorage(); + } + + $this->called[$eventName]->attach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName)); + } + + if ($listener->stoppedPropagation()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName)); + } + + $skipped = true; + } + } + } + + /** + * Returns information about the listener. + * + * @param object $listener The listener + * @param string $eventName The event name + * + * @return array Information about the listener + */ + private function getListenerInfo($listener, $eventName) + { + $info = array( + 'event' => $eventName, + 'priority' => $this->getListenerPriority($eventName, $listener), + ); + if ($listener instanceof \Closure) { + $info += array( + 'type' => 'Closure', + 'pretty' => 'closure', + ); + } elseif (is_string($listener)) { + try { + $r = new \ReflectionFunction($listener); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Function', + 'function' => $listener, + 'file' => $file, + 'line' => $line, + 'pretty' => $listener, + ); + } elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) { + if (!is_array($listener)) { + $listener = array($listener, '__invoke'); + } + $class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0]; + try { + $r = new \ReflectionMethod($class, $listener[1]); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Method', + 'class' => $class, + 'method' => $listener[1], + 'file' => $file, + 'line' => $line, + 'pretty' => $class.'::'.$listener[1], + ); + } + + return $info; + } + + private function sortListenersByPriority($a, $b) + { + if (is_int($a['priority']) && !is_int($b['priority'])) { + return 1; + } + + if (!is_int($a['priority']) && is_int($b['priority'])) { + return -1; + } + + if ($a['priority'] === $b['priority']) { + return 0; + } + + if ($a['priority'] > $b['priority']) { + return -1; + } + + return 1; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php new file mode 100644 index 000000000..5483e8150 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.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\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Fabien Potencier + */ +interface TraceableEventDispatcherInterface extends EventDispatcherInterface +{ + /** + * Gets the called listeners. + * + * @return array An array of called listeners + */ + public function getCalledListeners(); + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + */ + public function getNotCalledListeners(); +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 000000000..e16627d6a --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/WrappedListener.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Fabien Potencier + */ +class WrappedListener +{ + private $listener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + + public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->name = $name; + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->called = false; + $this->stoppedPropagation = false; + } + + public function getWrappedListener() + { + return $this->listener; + } + + public function wasCalled() + { + return $this->called; + } + + public function stoppedPropagation() + { + return $this->stoppedPropagation; + } + + public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) + { + $this->called = true; + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 000000000..7e74a37aa --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + /** + * @var string + */ + protected $dispatcherService; + + /** + * @var string + */ + protected $listenerTag; + + /** + * @var string + */ + protected $subscriberTag; + + /** + * Constructor. + * + * @param string $dispatcherService Service name of the event dispatcher in processed container + * @param string $listenerTag Tag name used for listener + * @param string $subscriberTag Tag name used for subscribers + */ + public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber') + { + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + return; + } + + $definition = $container->findDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id)); + } + + foreach ($events as $event) { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) { + throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback(array( + '/(?<=\b)[a-z]/i', + '/[^a-z0-9]/i', + ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + } + + $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + } + } + + foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id)); + } + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getParameterBag()->resolveValue($def->getClass()); + + $refClass = new \ReflectionClass($class); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + if (!$refClass->implementsInterface($interface)) { + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Event.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Event.php new file mode 100644 index 000000000..8d49422cd --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Event.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\EventDispatcher; + +/** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +class Event +{ + /** + * @var bool Whether no further event listeners should be triggered + */ + private $propagationStopped = false; + + /** + * Returns whether further event listeners should be triggered. + * + * @see Event::stopPropagation() + * + * @return bool Whether propagation was already stopped for this event. + */ + public function isPropagationStopped() + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation() + { + $this->propagationStopped = true; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 000000000..e1882d232 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Jordan Alliot + */ +class EventDispatcher implements EventDispatcherInterface +{ + private $listeners = array(); + private $sorted = array(); + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + if ($listeners = $this->getListeners($eventName)) { + $this->doDispatch($listeners, $eventName, $event); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null !== $eventName) { + if (!isset($this->listeners[$eventName])) { + return array(); + } + + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach ($this->listeners as $eventName => $eventListeners) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return array_filter($this->sorted); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + return $priority; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return (bool) count($this->getListeners($eventName)); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName]); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]); + } + } + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->addListener($eventName, array($subscriber, $params)); + } elseif (is_string($params[0])) { + $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_array($params) && is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, array($subscriber, $listener[0])); + } + } else { + $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0])); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param callable[] $listeners The event listeners. + * @param string $eventName The name of the event to dispatch. + * @param Event $event The event object to pass to the event handlers/listeners. + */ + protected function doDispatch($listeners, $eventName, Event $event) + { + foreach ($listeners as $listener) { + call_user_func($listener, $event, $eventName, $this); + if ($event->isPropagationStopped()) { + break; + } + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + * + * @param string $eventName The name of the event. + */ + private function sortListeners($eventName) + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 000000000..e0ba65f95 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcherInterface.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Bernhard Schussek + */ +interface EventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + * @param Event $event The event to pass to the event handlers/listeners. + * If not supplied, an empty Event instance is created. + * + * @return Event + */ + public function dispatch($eventName, Event $event = null); + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $listener The listener + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function addListener($eventName, $listener, $priority = 0); + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events he is + * interested in and added as a listener for these events. + * + * @param EventSubscriberInterface $subscriber The subscriber. + */ + public function addSubscriber(EventSubscriberInterface $subscriber); + + /** + * Removes an event listener from the specified events. + * + * @param string $eventName The event to remove a listener from + * @param callable $listener The listener to remove + */ + public function removeListener($eventName, $listener); + + /** + * Removes an event subscriber. + * + * @param EventSubscriberInterface $subscriber The subscriber + */ + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @param string $eventName The name of the event + * + * @return array The event listeners for the specified event, or all event listeners by event name + */ + public function getListeners($eventName = null); + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + * + * @param string $eventName The name of the event + * @param callable $listener The listener + * + * @return int|null The event listener priority + */ + public function getListenerPriority($eventName, $listener); + + /** + * Checks whether an event has any registered listeners. + * + * @param string $eventName The name of the event + * + * @return bool true if the specified event has any listeners, false otherwise + */ + public function hasListeners($eventName = null); +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventSubscriberInterface.php new file mode 100644 index 000000000..ec53e54e2 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventSubscriberInterface.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\EventDispatcher; + +/** + * An EventSubscriber knows himself what events he is interested in. + * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array The event names to listen to + */ + public static function getSubscribedEvents(); +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/GenericEvent.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/GenericEvent.php new file mode 100644 index 000000000..6458180a5 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/GenericEvent.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + /** + * Event subject. + * + * @var mixed usually object or callable + */ + protected $subject; + + /** + * Array of arguments. + * + * @var array + */ + protected $arguments; + + /** + * Encapsulate an event with $subject and $args. + * + * @param mixed $subject The subject of the event, usually an object. + * @param array $arguments Arguments to store in the event. + */ + public function __construct($subject = null, array $arguments = array()) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed $subject The observer subject. + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @param string $key Key. + * + * @throws \InvalidArgumentException If key is not found. + * + * @return mixed Contents of array key. + */ + public function getArgument($key) + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); + } + + /** + * Add argument to event. + * + * @param string $key Argument name. + * @param mixed $value Value. + * + * @return GenericEvent + */ + public function setArgument($key, $value) + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Set args property. + * + * @param array $args Arguments. + * + * @return GenericEvent + */ + public function setArguments(array $args = array()) + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + * + * @param string $key Key of arguments array. + * + * @return bool + */ + public function hasArgument($key) + { + return array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key. + * + * @throws \InvalidArgumentException If key does not exist in $this->args. + * + * @return mixed + */ + public function offsetGet($key) + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set. + * @param mixed $value Value. + */ + public function offsetSet($key, $value) + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key. + */ + public function offsetUnset($key) + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key. + * + * @return bool + */ + public function offsetExists($key) + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php new file mode 100644 index 000000000..13e8572ed --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.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\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + /** + * The proxied dispatcher. + * + * @var EventDispatcherInterface + */ + private $dispatcher; + + /** + * Creates an unmodifiable proxy for an event dispatcher. + * + * @param EventDispatcherInterface $dispatcher The proxied event dispatcher. + */ + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + return $this->dispatcher->dispatch($eventName, $event); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/LICENSE b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/LICENSE new file mode 100644 index 000000000..43028bc60 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/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/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/README.md b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/README.md new file mode 100644 index 000000000..8031f4dd3 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/README.md @@ -0,0 +1,27 @@ +EventDispatcher Component +========================= + +The Symfony EventDispatcher component implements the Mediator pattern in a +simple and effective way to make your projects truly extensible. + +```php +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\Event; + +$dispatcher = new EventDispatcher(); + +$dispatcher->addListener('event_name', function (Event $event) { + // ... +}); + +$dispatcher->dispatch('event_name'); +``` + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/EventDispatcher/ + $ composer install + $ phpunit diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php new file mode 100644 index 000000000..30429d3f7 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php @@ -0,0 +1,373 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +abstract class AbstractEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + /* Some pseudo events */ + const preFoo = 'pre.foo'; + const postFoo = 'post.foo'; + const preBar = 'pre.bar'; + const postBar = 'post.bar'; + + /** + * @var EventDispatcher + */ + private $dispatcher; + + private $listener; + + protected function setUp() + { + $this->dispatcher = $this->createEventDispatcher(); + $this->listener = new TestEventListener(); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->listener = null; + } + + abstract protected function createEventDispatcher(); + + public function testInitialState() + { + $this->assertEquals(array(), $this->dispatcher->getListeners()); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddListener() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo)); + $this->assertCount(2, $this->dispatcher->getListeners()); + } + + public function testGetListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener1->name = '1'; + $listener2->name = '2'; + $listener3->name = '3'; + + $this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10); + $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10); + $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo')); + + $expected = array( + array($listener2, 'preFoo'), + array($listener3, 'preFoo'), + array($listener1, 'preFoo'), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo')); + } + + public function testGetAllListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener4 = new TestEventListener(); + $listener5 = new TestEventListener(); + $listener6 = new TestEventListener(); + + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->addListener('post.foo', $listener4, -10); + $this->dispatcher->addListener('post.foo', $listener5); + $this->dispatcher->addListener('post.foo', $listener6, 10); + + $expected = array( + 'pre.foo' => array($listener3, $listener2, $listener1), + 'post.foo' => array($listener6, $listener5, $listener4), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners()); + } + + public function testGetListenerPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + + $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1)); + $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2)); + $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2)); + $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {})); + } + + public function testDispatch() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->dispatcher->dispatch(self::preFoo); + $this->assertTrue($this->listener->preFooInvoked); + $this->assertFalse($this->listener->postFooInvoked); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent')); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo)); + $event = new Event(); + $return = $this->dispatcher->dispatch(self::preFoo, $event); + $this->assertSame($event, $return); + } + + public function testDispatchForClosure() + { + $invoked = 0; + $listener = function () use (&$invoked) { + ++$invoked; + }; + $this->dispatcher->addListener('pre.foo', $listener); + $this->dispatcher->addListener('post.foo', $listener); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(1, $invoked); + } + + public function testStopEventPropagation() + { + $otherListener = new TestEventListener(); + + // postFoo() stops the propagation, so only one listener should + // be executed + // Manually set priority to enforce $this->listener to be called first + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10); + $this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo')); + $this->dispatcher->dispatch(self::postFoo); + $this->assertTrue($this->listener->postFooInvoked); + $this->assertFalse($otherListener->postFooInvoked); + } + + public function testDispatchByPriority() + { + $invoked = array(); + $listener1 = function () use (&$invoked) { + $invoked[] = '1'; + }; + $listener2 = function () use (&$invoked) { + $invoked[] = '2'; + }; + $listener3 = function () use (&$invoked) { + $invoked[] = '3'; + }; + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(array('3', '2', '1'), $invoked); + } + + public function testRemoveListener() + { + $this->dispatcher->addListener('pre.bar', $this->listener); + $this->assertTrue($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('pre.bar', $this->listener); + $this->assertFalse($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('notExists', $this->listener); + } + + public function testAddSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]); + } + + public function testAddSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertEquals('preFoo2', $listeners[0][1]); + } + + public function testRemoveSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testRemoveSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + public function testRemoveSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + public function testEventReceivesTheDispatcherInstanceAsArgument() + { + $listener = new TestWithDispatcher(); + $this->dispatcher->addListener('test', array($listener, 'foo')); + $this->assertNull($listener->name); + $this->assertNull($listener->dispatcher); + $this->dispatcher->dispatch('test'); + $this->assertEquals('test', $listener->name); + $this->assertSame($this->dispatcher, $listener->dispatcher); + } + + /** + * @see https://bugs.php.net/bug.php?id=62976 + * + * This bug affects: + * - The PHP 5.3 branch for versions < 5.3.18 + * - The PHP 5.4 branch for versions < 5.4.8 + * - The PHP 5.5 branch is not affected + */ + public function testWorkaroundForPhpBug62976() + { + $dispatcher = $this->createEventDispatcher(); + $dispatcher->addListener('bug.62976', new CallableClass()); + $dispatcher->removeListener('bug.62976', function () {}); + $this->assertTrue($dispatcher->hasListeners('bug.62976')); + } + + public function testHasListenersWhenAddedCallbackListenerIsRemoved() + { + $listener = function () {}; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertFalse($this->dispatcher->hasListeners()); + } + + public function testGetListenersWhenAddedCallbackListenerIsRemoved() + { + $listener = function () {}; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertSame(array(), $this->dispatcher->getListeners()); + } + + public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled() + { + $this->assertFalse($this->dispatcher->hasListeners('foo')); + $this->assertFalse($this->dispatcher->hasListeners()); + } +} + +class CallableClass +{ + public function __invoke() + { + } +} + +class TestEventListener +{ + public $preFooInvoked = false; + public $postFooInvoked = false; + + /* Listener methods */ + + public function preFoo(Event $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(Event $e) + { + $this->postFooInvoked = true; + + $e->stopPropagation(); + } +} + +class TestWithDispatcher +{ + public $name; + public $dispatcher; + + public function foo(Event $e, $name, $dispatcher) + { + $this->name = $name; + $this->dispatcher = $dispatcher; + } +} + +class TestEventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo'); + } +} + +class TestEventSubscriberWithPriorities implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'pre.foo' => array('preFoo', 10), + 'post.foo' => array('postFoo'), + ); + } +} + +class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => array( + array('preFoo1'), + array('preFoo2', 10), + )); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php new file mode 100644 index 000000000..c015a2e88 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + $container = new Container(); + + return new ContainerAwareEventDispatcher($container); + } + + public function testAddAListenerService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testAddASubscriberService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.subscriber', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testPreventDuplicateListenerService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testHasListenersOnLazyLoad() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $this->assertTrue($dispatcher->hasListeners()); + + if ($dispatcher->hasListeners('onEvent')) { + $dispatcher->dispatch('onEvent'); + } + } + + public function testGetListenersOnLazyLoad() + { + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $listeners = $dispatcher->getListeners(); + + $this->assertTrue(isset($listeners['onEvent'])); + + $this->assertCount(1, $dispatcher->getListeners('onEvent')); + } + + public function testRemoveAfterDispatch() + { + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', new Event()); + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } + + public function testRemoveBeforeDispatch() + { + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } +} + +class Service +{ + public function onEvent(Event $e) + { + } +} + +class SubscriberService implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'onEvent' => array('onEvent'), + ); + } + + public function onEvent(Event $e) + { + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php new file mode 100644 index 000000000..1d4a8c850 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\Debug; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Stopwatch\Stopwatch; + +class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + public function testAddRemoveListener() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () {}); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame($listener, $listeners[0]); + + $tdispatcher->removeListener('foo', $listener); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + public function testGetListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () {}); + $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo')); + } + + public function testHasListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $this->assertFalse($dispatcher->hasListeners('foo')); + $this->assertFalse($tdispatcher->hasListeners('foo')); + + $tdispatcher->addListener('foo', $listener = function () {}); + $this->assertTrue($dispatcher->hasListeners('foo')); + $this->assertTrue($tdispatcher->hasListeners('foo')); + } + + public function testAddRemoveSubscriber() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $subscriber = new EventSubscriber(); + + $tdispatcher->addSubscriber($subscriber); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame(array($subscriber, 'call'), $listeners[0]); + + $tdispatcher->removeSubscriber($subscriber); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + public function testGetCalledListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', $listener = function () {}); + + $this->assertEquals(array(), $tdispatcher->getCalledListeners()); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 0)), $tdispatcher->getNotCalledListeners()); + + $tdispatcher->dispatch('foo'); + + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => null)), $tdispatcher->getCalledListeners()); + $this->assertEquals(array(), $tdispatcher->getNotCalledListeners()); + } + + public function testGetCalledListenersNested() + { + $tdispatcher = null; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) { + $tdispatcher = $dispatcher; + $dispatcher->dispatch('bar'); + }); + $dispatcher->addListener('bar', function (Event $event) {}); + $dispatcher->dispatch('foo'); + $this->assertSame($dispatcher, $tdispatcher); + $this->assertCount(2, $dispatcher->getCalledListeners()); + } + + public function testLogger() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function () {}); + $tdispatcher->addListener('foo', $listener2 = function () {}); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".'); + $logger->expects($this->at(1))->method('debug')->with('Notified event "foo" to listener "closure".'); + + $tdispatcher->dispatch('foo'); + } + + public function testLoggerWithStoppedEvent() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); }); + $tdispatcher->addListener('foo', $listener2 = function () {}); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".'); + $logger->expects($this->at(1))->method('debug')->with('Listener "closure" stopped propagation of the event "foo".'); + $logger->expects($this->at(2))->method('debug')->with('Listener "closure" was not called for event "foo".'); + + $tdispatcher->dispatch('foo'); + } + + public function testDispatchCallListeners() + { + $called = array(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', $listener1 = function () use (&$called) { $called[] = 'foo1'; }); + $tdispatcher->addListener('foo', $listener2 = function () use (&$called) { $called[] = 'foo2'; }); + + $tdispatcher->dispatch('foo'); + + $this->assertEquals(array('foo1', 'foo2'), $called); + } + + public function testDispatchNested() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $loop = 1; + $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) { + ++$loop; + if (2 == $loop) { + $dispatcher->dispatch('foo'); + } + }); + + $dispatcher->dispatch('foo'); + } + + public function testDispatchReusedEventNested() + { + $nestedCall = false; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) { + $dispatcher->dispatch('bar', $e); + }); + $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) { + $nestedCall = true; + }); + + $this->assertFalse($nestedCall); + $dispatcher->dispatch('foo'); + $this->assertTrue($nestedCall); + } + + public function testListenerCanRemoveItselfWhenExecuted() + { + $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) { + $dispatcher->removeListener('foo', $listener1); + }; + $eventDispatcher->addListener('foo', $listener1); + $eventDispatcher->addListener('foo', function () {}); + $eventDispatcher->dispatch('foo'); + + $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed'); + } +} + +class EventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('foo' => 'call'); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php new file mode 100644 index 000000000..0fdd6372b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; + +class RegisterListenersPassTest extends \PHPUnit_Framework_TestCase +{ + /** + * Tests that event subscribers not implementing EventSubscriberInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testEventSubscriberWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('isPublic') + ->will($this->returnValue(true)); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('stdClass')); + + $builder = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('hasDefinition', 'findTaggedServiceIds', 'getDefinition') + ); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + public function testValidEventSubscriber() + { + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('isPublic') + ->will($this->returnValue(true)); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')); + + $builder = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition') + ); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $builder->expects($this->atLeastOnce()) + ->method('findDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must be public as event listeners are lazy-loaded. + */ + public function testPrivateEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must be public as event subscribers are lazy-loaded. + */ + public function testPrivateEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must not be abstract as event listeners are lazy-loaded. + */ + public function testAbstractEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must not be abstract as event subscribers are lazy-loaded. + */ + public function testAbstractEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + public function testEventSubscriberResolvableClassName() + { + $container = new ContainerBuilder(); + + $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expected_calls = array( + array( + 'addSubscriberService', + array( + 'foo', + 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService', + ), + ), + ); + $this->assertSame($expected_calls, $definition->getMethodCalls()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class" + */ + public function testEventSubscriberUnresolvableClassName() + { + $container = new ContainerBuilder(); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } +} + +class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php new file mode 100644 index 000000000..5faa5c8be --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\EventDispatcher; + +class EventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + return new EventDispatcher(); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventTest.php new file mode 100644 index 000000000..1a6f4c4ae --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; + +/** + * Test class for Event. + */ +class EventTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\EventDispatcher\Event + */ + protected $event; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + $this->event = new Event(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + $this->event = null; + } + + public function testIsPropagationStopped() + { + $this->assertFalse($this->event->isPropagationStopped()); + } + + public function testStopPropagationAndIsPropagationStopped() + { + $this->event->stopPropagation(); + $this->assertTrue($this->event->isPropagationStopped()); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php new file mode 100644 index 000000000..aebd82dab --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\GenericEvent; + +/** + * Test class for Event. + */ +class GenericEventTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var GenericEvent + */ + private $event; + + private $subject; + + /** + * Prepares the environment before running a test. + */ + protected function setUp() + { + parent::setUp(); + + $this->subject = new \stdClass(); + $this->event = new GenericEvent($this->subject, array('name' => 'Event')); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->subject = null; + $this->event = null; + + parent::tearDown(); + } + + public function testConstruct() + { + $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event'))); + } + + /** + * Tests Event->getArgs(). + */ + public function testGetArguments() + { + // test getting all + $this->assertSame(array('name' => 'Event'), $this->event->getArguments()); + } + + public function testSetArguments() + { + $result = $this->event->setArguments(array('foo' => 'bar')); + $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event); + $this->assertSame($this->event, $result); + } + + public function testSetArgument() + { + $result = $this->event->setArgument('foo2', 'bar2'); + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + $this->assertEquals($this->event, $result); + } + + public function testGetArgument() + { + // test getting key + $this->assertEquals('Event', $this->event->getArgument('name')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetArgException() + { + $this->event->getArgument('nameNotExist'); + } + + public function testOffsetGet() + { + // test getting key + $this->assertEquals('Event', $this->event['name']); + + // test getting invalid arg + $this->setExpectedException('InvalidArgumentException'); + $this->assertFalse($this->event['nameNotExist']); + } + + public function testOffsetSet() + { + $this->event['foo2'] = 'bar2'; + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + } + + public function testOffsetUnset() + { + unset($this->event['name']); + $this->assertAttributeSame(array(), 'arguments', $this->event); + } + + public function testOffsetIsset() + { + $this->assertTrue(isset($this->event['name'])); + $this->assertFalse(isset($this->event['nameNotExist'])); + } + + public function testHasArgument() + { + $this->assertTrue($this->event->hasArgument('name')); + $this->assertFalse($this->event->hasArgument('nameNotExist')); + } + + public function testGetSubject() + { + $this->assertSame($this->subject, $this->event->getSubject()); + } + + public function testHasIterator() + { + $data = array(); + foreach ($this->event as $key => $value) { + $data[$key] = $value; + } + $this->assertEquals(array('name' => 'Event'), $data); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php new file mode 100644 index 000000000..80a7e43be --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\ImmutableEventDispatcher; + +/** + * @author Bernhard Schussek + */ +class ImmutableEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $innerDispatcher; + + /** + * @var ImmutableEventDispatcher + */ + private $dispatcher; + + protected function setUp() + { + $this->innerDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher); + } + + public function testDispatchDelegates() + { + $event = new Event(); + + $this->innerDispatcher->expects($this->once()) + ->method('dispatch') + ->with('event', $event) + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->dispatch('event', $event)); + } + + public function testGetListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('getListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->getListeners('event')); + } + + public function testHasListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('hasListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->hasListeners('event')); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddListenerDisallowed() + { + $this->dispatcher->addListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddSubscriberDisallowed() + { + $subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveListenerDisallowed() + { + $this->dispatcher->removeListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveSubscriberDisallowed() + { + $subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $this->dispatcher->removeSubscriber($subscriber); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/composer.json b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/composer.json new file mode 100644 index 000000000..12d91b926 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Symfony EventDispatcher 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.5.9" + }, + "require-dev": { + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/phpunit.xml.dist b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/phpunit.xml.dist new file mode 100644 index 000000000..ae0586e0b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/.gitignore b/modules/devshop/devshop_projects/drush/vendor/symfony/process/.gitignore new file mode 100644 index 000000000..c49a5d8df --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/CHANGELOG.md b/modules/devshop/devshop_projects/drush/vendor/symfony/process/CHANGELOG.md new file mode 100644 index 000000000..2f3c1beb7 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/CHANGELOG.md @@ -0,0 +1,40 @@ +CHANGELOG +========= + +2.5.0 +----- + + * added support for PTY mode + * added the convenience method "mustRun" + * deprecation: Process::setStdin() is deprecated in favor of Process::setInput() + * deprecation: Process::getStdin() is deprecated in favor of Process::getInput() + * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types + +2.4.0 +----- + + * added the ability to define an idle timeout + +2.3.0 +----- + + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows + * added Process::signal() + * added Process::getPid() + * added support for a TTY mode + +2.2.0 +----- + + * added ProcessBuilder::setArguments() to reset the arguments on a builder + * added a way to retrieve the standard and error output incrementally + * added Process:restart() + +2.1.0 +----- + + * added support for non-blocking processes (start(), wait(), isRunning(), stop()) + * enhanced Windows compatibility + * added Process::getExitCodeText() that returns a string representation for + the exit code returned by the process + * added ProcessBuilder diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ExceptionInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ExceptionInterface.php new file mode 100644 index 000000000..75c1c9e5d --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * Marker Interface for the Process Component. + * + * @author Johannes M. Schmitt + */ +interface ExceptionInterface +{ +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/InvalidArgumentException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/InvalidArgumentException.php new file mode 100644 index 000000000..926ee2118 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * InvalidArgumentException for the Process Component. + * + * @author Romain Neutron + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/LogicException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/LogicException.php new file mode 100644 index 000000000..be3d490dd --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * LogicException for the Process Component. + * + * @author Romain Neutron + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessFailedException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessFailedException.php new file mode 100644 index 000000000..328acfde5 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessFailedException.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\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for failed processes. + * + * @author Johannes M. Schmitt + */ +class ProcessFailedException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", + $process->getCommandLine(), + $process->getExitCode(), + $process->getExitCodeText(), + $process->getWorkingDirectory() + ); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $process->getOutput(), + $process->getErrorOutput() + ); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessTimedOutException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessTimedOutException.php new file mode 100644 index 000000000..d45114696 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessTimedOutException.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\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process times out. + * + * @author Johannes M. Schmitt + */ +class ProcessTimedOutException extends RuntimeException +{ + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf( + 'The process "%s" exceeded the timeout of %s seconds.', + $process->getCommandLine(), + $this->getExceededTimeout() + )); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return $this->timeoutType === self::TYPE_GENERAL; + } + + public function isIdleTimeout() + { + return $this->timeoutType === self::TYPE_IDLE; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/RuntimeException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/RuntimeException.php new file mode 100644 index 000000000..adead2536 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * RuntimeException for the Process Component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/ExecutableFinder.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ExecutableFinder.php new file mode 100644 index 000000000..fa11cb6e4 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ExecutableFinder.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * Generic executable finder. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ExecutableFinder +{ + private $suffixes = array('.exe', '.bat', '.cmd', '.com'); + + /** + * Replaces default suffixes of executable. + * + * @param array $suffixes + */ + public function setSuffixes(array $suffixes) + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + * + * @param string $suffix + */ + public function addSuffix($suffix) + { + $this->suffixes[] = $suffix; + } + + /** + * Finds an executable by name. + * + * @param string $name The executable name (without the extension) + * @param string $default The default to return if no executable is found + * @param array $extraDirs Additional dirs to check into + * + * @return string The executable path or default value + */ + public function find($name, $default = null, array $extraDirs = array()) + { + if (ini_get('open_basedir')) { + $searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir')); + $dirs = array(); + foreach ($searchPath as $path) { + // Silencing against https://bugs.php.net/69240 + if (@is_dir($path)) { + $dirs[] = $path; + } else { + if (basename($path) == $name && is_executable($path)) { + return $path; + } + } + } + } else { + $dirs = array_merge( + explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + } + + $suffixes = array(''); + if ('\\' === DIRECTORY_SEPARATOR) { + $pathExt = getenv('PATHEXT'); + $suffixes = $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes; + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || is_executable($file))) { + return $file; + } + } + } + + return $default; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/LICENSE b/modules/devshop/devshop_projects/drush/vendor/symfony/process/LICENSE new file mode 100644 index 000000000..43028bc60 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/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/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpExecutableFinder.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpExecutableFinder.php new file mode 100644 index 000000000..fb297825f --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpExecutableFinder.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * An executable finder specifically designed for the PHP executable. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpExecutableFinder +{ + private $executableFinder; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + * + * @param bool $includeArgs Whether or not include command arguments + * + * @return string|false The PHP executable path or false if it cannot be found + */ + public function find($includeArgs = true) + { + $args = $this->findArguments(); + $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; + + // HHVM support + if (defined('HHVM_VERSION')) { + return (getenv('PHP_BINARY') ?: PHP_BINARY).$args; + } + + // PHP_BINARY return the current sapi executable + if (defined('PHP_BINARY') && PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server', 'phpdbg')) && is_file(PHP_BINARY)) { + return PHP_BINARY.$args; + } + + if ($php = getenv('PHP_PATH')) { + if (!is_executable($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (is_executable($php)) { + return $php; + } + } + + $dirs = array(PHP_BINDIR); + if ('\\' === DIRECTORY_SEPARATOR) { + $dirs[] = 'C:\xampp\php\\'; + } + + return $this->executableFinder->find('php', false, $dirs); + } + + /** + * Finds the PHP executable arguments. + * + * @return array The PHP executable arguments + */ + public function findArguments() + { + $arguments = array(); + + if (defined('HHVM_VERSION')) { + $arguments[] = '--php'; + } elseif ('phpdbg' === PHP_SAPI) { + $arguments[] = '-qrr'; + } + + return $arguments; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpProcess.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpProcess.php new file mode 100644 index 000000000..4a2a2625f --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpProcess.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\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpProcess runs a PHP script in an independent process. + * + * $p = new PhpProcess(''); + * $p->run(); + * print $p->getOutput()."\n"; + * + * @author Fabien Potencier + */ +class PhpProcess extends Process +{ + /** + * Constructor. + * + * @param string $script The PHP script to run (as a string) + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param int $timeout The timeout in seconds + * @param array $options An array of options for proc_open + */ + public function __construct($script, $cwd = null, array $env = null, $timeout = 60, array $options = array()) + { + $executableFinder = new PhpExecutableFinder(); + if (false === $php = $executableFinder->find()) { + $php = null; + } + if ('phpdbg' === PHP_SAPI) { + $file = tempnam(sys_get_temp_dir(), 'dbg'); + file_put_contents($file, $script); + register_shutdown_function('unlink', $file); + $php .= ' '.ProcessUtils::escapeArgument($file); + $script = null; + } + + parent::__construct($php, $cwd, $env, $script, $timeout, $options); + } + + /** + * Sets the path to the PHP binary to use. + */ + public function setPhpBinary($php) + { + $this->setCommandLine($php); + } + + /** + * {@inheritdoc} + */ + public function start($callback = null) + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + + parent::start($callback); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/AbstractPipes.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/AbstractPipes.php new file mode 100644 index 000000000..1ca85739f --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/AbstractPipes.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\Process\Pipes; + +/** + * @author Romain Neutron + * + * @internal + */ +abstract class AbstractPipes implements PipesInterface +{ + /** @var array */ + public $pipes = array(); + + /** @var string */ + protected $inputBuffer = ''; + /** @var resource|null */ + protected $input; + + /** @var bool */ + private $blocked = true; + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = array(); + } + + /** + * Returns true if a system call has been interrupted. + * + * @return bool + */ + protected function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + /** + * Unblocks streams. + */ + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (null !== $this->input) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/PipesInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/PipesInterface.php new file mode 100644 index 000000000..09d3f61d6 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/PipesInterface.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\Process\Pipes; + +/** + * PipesInterface manages descriptors and pipes for the use of proc_open. + * + * @author Romain Neutron + * + * @internal + */ +interface PipesInterface +{ + const CHUNK_SIZE = 16384; + + /** + * Returns an array of descriptors for the use of proc_open. + * + * @return array + */ + public function getDescriptors(); + + /** + * Returns an array of filenames indexed by their related stream in case these pipes use temporary files. + * + * @return string[] + */ + public function getFiles(); + + /** + * Reads data in file handles and pipes. + * + * @param bool $blocking Whether to use blocking calls or not. + * @param bool $close Whether to close pipes if they've reached EOF. + * + * @return string[] An array of read data indexed by their fd. + */ + public function readAndWrite($blocking, $close = false); + + /** + * Returns if the current state has open file handles or pipes. + * + * @return bool + */ + public function areOpen(); + + /** + * Closes file handles and pipes. + */ + public function close(); +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/UnixPipes.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/UnixPipes.php new file mode 100644 index 000000000..f8a0d1997 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/UnixPipes.php @@ -0,0 +1,214 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Process; + +/** + * UnixPipes implementation uses unix pipes as handles. + * + * @author Romain Neutron + * + * @internal + */ +class UnixPipes extends AbstractPipes +{ + /** @var bool */ + private $ttyMode; + /** @var bool */ + private $ptyMode; + /** @var bool */ + private $disableOutput; + + public function __construct($ttyMode, $ptyMode, $input, $disableOutput) + { + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('/dev/null', 'c'); + + return array( + array('pipe', 'r'), + $nullstream, + $nullstream, + ); + } + + if ($this->ttyMode) { + return array( + array('file', '/dev/tty', 'r'), + array('file', '/dev/tty', 'w'), + array('file', '/dev/tty', 'w'), + ); + } + + if ($this->ptyMode && Process::isPtySupported()) { + return array( + array('pty'), + array('pty'), + array('pty'), + ); + } + + return array( + array('pipe', 'r'), + array('pipe', 'w'), // stdout + array('pipe', 'w'), // stderr + ); + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + // only stdin is left open, job has been done ! + // we can now close it + if (1 === count($this->pipes) && array(0) === array_keys($this->pipes)) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + if (empty($this->pipes)) { + return array(); + } + + $this->unblock(); + + $read = array(); + + if (null !== $this->input) { + // if input is a resource, let's add it to stream_select argument to + // fill a buffer + $r = array_merge($this->pipes, array('input' => $this->input)); + } else { + $r = $this->pipes; + } + // discard read on stdin + unset($r[0]); + + $w = isset($this->pipes[0]) ? array($this->pipes[0]) : null; + $e = null; + + // let's have a look if something changed in streams + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occurred, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = array(); + } + + return $read; + } + + // nothing has changed + if (0 === $n) { + return $read; + } + + foreach ($r as $pipe) { + // prior PHP 5.4 the array passed to stream_select is modified and + // lose key association, we have to find back the key + $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; + $data = ''; + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { + $data .= $dataread; + } + + if ('' !== $data) { + if ($type === 'input') { + $this->inputBuffer .= $data; + } else { + $read[$type] = $data; + } + } + + if (false === $data || (true === $close && feof($pipe) && '' === $data)) { + if ($type === 'input') { + // no more data to read on input resource + // use an empty buffer in the next reads + $this->input = null; + } else { + fclose($this->pipes[$type]); + unset($this->pipes[$type]); + } + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + // no input to read on resource, buffer is empty and stdin still open + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes; + } + + /** + * Creates a new UnixPipes instance. + * + * @param Process $process + * @param string|resource $input + * + * @return UnixPipes + */ + public static function create(Process $process, $input) + { + return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/WindowsPipes.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/WindowsPipes.php new file mode 100644 index 000000000..1472f8c6c --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/WindowsPipes.php @@ -0,0 +1,253 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Process; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * WindowsPipes implementation uses temporary files as handles. + * + * @see https://bugs.php.net/bug.php?id=51800 + * @see https://bugs.php.net/bug.php?id=65650 + * + * @author Romain Neutron + * + * @internal + */ +class WindowsPipes extends AbstractPipes +{ + /** @var array */ + private $files = array(); + /** @var array */ + private $fileHandles = array(); + /** @var array */ + private $readBytes = array( + Process::STDOUT => 0, + Process::STDERR => 0, + ); + /** @var bool */ + private $disableOutput; + + public function __construct($disableOutput, $input) + { + $this->disableOutput = (bool) $disableOutput; + + if (!$this->disableOutput) { + // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. + // Workaround for this problem is to use temporary files instead of pipes on Windows platform. + // + // @see https://bugs.php.net/bug.php?id=51800 + $this->files = array( + Process::STDOUT => tempnam(sys_get_temp_dir(), 'out_sf_proc'), + Process::STDERR => tempnam(sys_get_temp_dir(), 'err_sf_proc'), + ); + foreach ($this->files as $offset => $file) { + if (false === $file || false === $this->fileHandles[$offset] = fopen($file, 'rb')) { + throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + } + } + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = $input; + } + } + + public function __destruct() + { + $this->close(); + $this->removeFiles(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('NUL', 'c'); + + return array( + array('pipe', 'r'), + $nullstream, + $nullstream, + ); + } + + // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800) + // We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650 + // So we redirect output within the commandline and pass the nul device to the process + return array( + array('pipe', 'r'), + array('file', 'NUL', 'w'), + array('file', 'NUL', 'w'), + ); + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->write($blocking, $close); + + $read = array(); + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + if (0 !== fseek($fileHandle, $this->readBytes[$type])) { + continue; + } + $data = ''; + $dataread = null; + while (!feof($fileHandle)) { + if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { + $data .= $dataread; + } + } + if (0 < $length = strlen($data)) { + $this->readBytes[$type] += $length; + $read[$type] = $data; + } + + if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { + fclose($this->fileHandles[$type]); + unset($this->fileHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes && (bool) $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $handle) { + fclose($handle); + } + $this->fileHandles = array(); + } + + /** + * Creates a new WindowsPipes instance. + * + * @param Process $process The process + * @param $input + * + * @return WindowsPipes + */ + public static function create(Process $process, $input) + { + return new static($process->isOutputDisabled(), $input); + } + + /** + * Removes temporary files. + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = array(); + } + + /** + * Writes input to stdin. + * + * @param bool $blocking + * @param bool $close + */ + private function write($blocking, $close) + { + if (empty($this->pipes)) { + return; + } + + $this->unblock(); + + $r = null !== $this->input ? array('input' => $this->input) : null; + $w = isset($this->pipes[0]) ? array($this->pipes[0]) : null; + $e = null; + + // let's have a look if something changed in streams + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occurred, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = array(); + } + + return; + } + + // nothing has changed + if (0 === $n) { + return; + } + + if (null !== $w && 0 < count($r)) { + $data = ''; + while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { + $data .= $dataread; + } + + $this->inputBuffer .= $data; + + if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { + // no more data to read on input resource + // use an empty buffer in the next reads + $this->input = null; + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + // no input to read on resource, buffer is empty and stdin still open + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Process.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Process.php new file mode 100644 index 000000000..c1e732170 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Process.php @@ -0,0 +1,1515 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Pipes\PipesInterface; +use Symfony\Component\Process\Pipes\UnixPipes; +use Symfony\Component\Process\Pipes\WindowsPipes; + +/** + * Process is a thin wrapper around proc_* functions to easily + * start independent PHP processes. + * + * @author Fabien Potencier + * @author Romain Neutron + */ +class Process +{ + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + // Timeout Precision in seconds. + const TIMEOUT_PRECISION = 0.2; + + private $callback; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $options; + private $exitcode; + private $fallbackExitcode; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility = true; + private $enhanceSigchildCompatibility; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty; + private $pty; + + private $useFileHandles = false; + /** @var PipesInterface */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + * + * @var array + */ + public static $exitCodes = array( + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ); + + /** + * Constructor. + * + * @param string $commandline The command line to run + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param string|null $input The input + * @param int|float|null $timeout The timeout in seconds or null to disable + * @param array $options An array of options for proc_open + * + * @throws RuntimeException When proc_open is not installed + */ + public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) + { + if (!function_exists('proc_open')) { + throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started + // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/bug.php?id=51800 + // @see : https://bugs.php.net/bug.php?id=50524 + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->input = $input; + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR; + $this->pty = false; + $this->enhanceWindowsCompatibility = true; + $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); + $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options); + } + + public function __destruct() + { + // stop() will check if we have a process running. + $this->stop(); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return int The exit status code + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process stopped after receiving signal + * @throws LogicException In case a callback is provided and output has been disabled + */ + public function run($callback = null) + { + $this->start($callback); + + return $this->wait(); + } + + /** + * 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 callable|null $callback + * + * @return self + * + * @throws RuntimeException if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled + * @throws ProcessFailedException if the process didn't terminate successfully + */ + public function mustRun($callback = null) + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + if (0 !== $this->run($callback)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * Starts the process and returns after writing the input to STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * If there is no callback passed, the wait() method can be called + * with true as a second parameter then the callback will get all data occurred + * in (and since) the start call. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws LogicException In case a callback is provided and output has been disabled + */ + public function start($callback = null) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + if ($this->outputDisabled && null !== $callback) { + throw new LogicException('Output has been disabled, enable it to allow the use of a callback.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(); + + $commandline = $this->commandline; + + if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { + $commandline = 'cmd /V:ON /E:ON /D /C "('.$commandline.')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $commandline .= ' '.$offset.'>'.ProcessUtils::escapeArgument($filename); + } + $commandline .= '"'; + + if (!isset($this->options['bypass_shell'])) { + $this->options['bypass_shell'] = true; + } + } + + $ptsWorkaround = null; + + if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + // Workaround for the bug, when PTS functionality is enabled. + // @see : https://bugs.php.net/69442 + $ptsWorkaround = fopen(__FILE__, 'r'); + } + + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); + + if ($ptsWorkaround) { + fclose($ptsWorkaround); + } + + if (!is_resource($this->process)) { + throw new RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return Process The new process + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * + * @see start() + */ + public function restart($callback = null) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + + $process = clone $this; + $process->start($callback); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A valid PHP callback + * + * @return int The exitcode of the process + * + * @throws RuntimeException When process timed out + * @throws RuntimeException When process stopped after receiving signal + * @throws LogicException When process is not yet started + */ + public function wait($callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + if (null !== $callback) { + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $close = '\\' !== DIRECTORY_SEPARATOR || !$running; + $this->readPipes(true, $close); + } while ($running); + + while ($this->isRunning()) { + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return int|null The process id if running, null otherwise + * + * @throws RuntimeException In case --enable-sigchild is activated + */ + public function getPid() + { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php) + * + * @return Process + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated + * @throws RuntimeException In case of failure + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return Process + * + * @throws RuntimeException In case the process is already running + * @throws LogicException if an idle timeout is set + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new LogicException('Output can not be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return Process + * + * @throws RuntimeException In case the process is already running + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * Returns true in case the output is disabled, false otherwise. + * + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @return string The process output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getOutput() + { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + + return $this->stdout; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + * + * @return string The process output since the last call + */ + public function getIncrementalOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getOutput(); + + $latest = substr($data, $this->incrementalOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalOutputOffset = strlen($data); + + return $latest; + } + + /** + * Clears the process output. + * + * @return Process + */ + public function clearOutput() + { + $this->stdout = ''; + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @return string The process error output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getErrorOutput() + { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + + return $this->stderr; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + * + * @return string The process error output since the last call + */ + public function getIncrementalErrorOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getErrorOutput(); + + $latest = substr($data, $this->incrementalErrorOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalErrorOutputOffset = strlen($data); + + return $latest; + } + + /** + * Clears the process output. + * + * @return Process + */ + public function clearErrorOutput() + { + $this->stderr = ''; + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * Returns the exit code returned by the process. + * + * @return null|int The exit status code, null if the Process is not terminated + * + * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled + */ + public function getExitCode() + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return null|string A string representation for the exit status code, null if the Process is not terminated. + * + * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + * + * @return bool true if the process ended successfully, false otherwise + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @return int + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @return int + * + * @throws LogicException In case the process is not terminated + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + * + * @return bool true if the process is currently running, false otherwise + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + * + * @return bool true if status is ready, false otherwise + */ + public function isStarted() + { + return $this->status != self::STATUS_READY; + } + + /** + * Checks if the process is terminated. + * + * @return bool true if process is terminated, false otherwise + */ + public function isTerminated() + { + $this->updateStatus(false); + + return $this->status == self::STATUS_TERMINATED; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + * + * @return string The current process status + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param int|float $timeout The timeout in seconds + * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) + * + * @return int The exit-code of the process + */ + public function stop($timeout = 10, $signal = null) + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning() && !$this->isSigchildEnabled()) { + // Avoid exception here: process is supposed to be running, but it might have stopped just + // after this line. In any case, let's silently discard the error, we cannot do anything. + $this->doSignal($signal ?: 9, false); + } + } + + $this->updateStatus(false); + if ($this->processInformation['running']) { + $this->close(); + } + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @param string $line The line to append + */ + public function addOutput($line) + { + $this->lastOutputTime = microtime(true); + $this->stdout .= $line; + } + + /** + * Adds a line to the STDERR stream. + * + * @param string $line The line to append + */ + public function addErrorOutput($line) + { + $this->lastOutputTime = microtime(true); + $this->stderr .= $line; + } + + /** + * Gets the command line to be executed. + * + * @return string The command to execute + */ + public function getCommandLine() + { + return $this->commandline; + } + + /** + * Sets the command line to be executed. + * + * @param string $commandline The command to execute + * + * @return self The current Process instance + */ + public function setCommandLine($commandline) + { + $this->commandline = $commandline; + + return $this; + } + + /** + * Gets the process timeout (max. runtime). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Gets the process idle timeout (max. time since last output). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getIdleTimeout() + { + return $this->idleTimeout; + } + + /** + * Sets the process timeout (max. runtime). + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return self The current Process instance + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout($timeout) + { + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Sets the process idle timeout (max. time since last output). + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return self The current Process instance. + * + * @throws LogicException if the output is disabled + * @throws InvalidArgumentException if the timeout is negative + */ + public function setIdleTimeout($timeout) + { + if (null !== $timeout && $this->outputDisabled) { + throw new LogicException('Idle timeout can not be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @param bool $tty True to enabled and false to disable + * + * @return self The current Process instance + * + * @throws RuntimeException In case the TTY mode is not supported + */ + public function setTty($tty) + { + if ('\\' === DIRECTORY_SEPARATOR && $tty) { + throw new RuntimeException('TTY mode is not supported on Windows platform.'); + } + if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { + throw new RuntimeException('TTY mode requires /dev/tty to be readable.'); + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + * + * @return bool true if the TTY mode is enabled, false otherwise + */ + public function isTty() + { + return $this->tty; + } + + /** + * Sets PTY mode. + * + * @param bool $bool + * + * @return self + */ + public function setPty($bool) + { + $this->pty = (bool) $bool; + + return $this; + } + + /** + * Returns PTY state. + * + * @return bool + */ + public function isPty() + { + return $this->pty; + } + + /** + * Gets the working directory. + * + * @return string|null The current working directory or null on failure + */ + public function getWorkingDirectory() + { + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @param string $cwd The new working directory + * + * @return self The current Process instance + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + * + * @return array The current environment variables + */ + public function getEnv() + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * An environment variable value should be a string. + * If it is an array, the variable is ignored. + * + * That happens in PHP when 'argv' is registered into + * the $_ENV array for instance. + * + * @param array $env The new environment variables + * + * @return self The current Process instance + */ + public function setEnv(array $env) + { + // Process can not handle env values that are arrays + $env = array_filter($env, function ($value) { + return !is_array($value); + }); + + $this->env = array(); + foreach ($env as $key => $value) { + $this->env[$key] = (string) $value; + } + + return $this; + } + + /** + * Gets the contents of STDIN. + * + * @return string|null The current contents + * + * @deprecated since version 2.5, to be removed in 3.0. + * Use setInput() instead. + * This method is deprecated in favor of getInput. + */ + public function getStdin() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0. Use the getInput() method instead.', E_USER_DEPRECATED); + + return $this->getInput(); + } + + /** + * Gets the Process input. + * + * @return null|string The Process input + */ + public function getInput() + { + return $this->input; + } + + /** + * Sets the contents of STDIN. + * + * @param string|null $stdin The new contents + * + * @return self The current Process instance + * + * @deprecated since version 2.5, to be removed in 3.0. + * Use setInput() instead. + * + * @throws LogicException In case the process is running + * @throws InvalidArgumentException In case the argument is invalid + */ + public function setStdin($stdin) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0. Use the setInput() method instead.', E_USER_DEPRECATED); + + return $this->setInput($stdin); + } + + /** + * Sets the input. + * + * This content will be passed to the underlying process standard input. + * + * @param mixed $input The content + * + * @return self The current Process instance + * + * @throws LogicException In case the process is running + * + * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0. + */ + public function setInput($input) + { + if ($this->isRunning()) { + throw new LogicException('Input can not be set while the process is running.'); + } + + $this->input = ProcessUtils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * Gets the options for proc_open. + * + * @return array The current options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the options for proc_open. + * + * @param array $options The new options + * + * @return self The current Process instance + */ + public function setOptions(array $options) + { + $this->options = $options; + + return $this; + } + + /** + * Gets whether or not Windows compatibility is enabled. + * + * This is true by default. + * + * @return bool + */ + public function getEnhanceWindowsCompatibility() + { + return $this->enhanceWindowsCompatibility; + } + + /** + * Sets whether or not Windows compatibility is enabled. + * + * @param bool $enhance + * + * @return self The current Process instance + */ + public function setEnhanceWindowsCompatibility($enhance) + { + $this->enhanceWindowsCompatibility = (bool) $enhance; + + return $this; + } + + /** + * Returns whether sigchild compatibility mode is activated or not. + * + * @return bool + */ + public function getEnhanceSigchildCompatibility() + { + return $this->enhanceSigchildCompatibility; + } + + /** + * Activates sigchild compatibility mode. + * + * Sigchild compatibility mode is required to get the exit code and + * determine the success of a process when PHP has been compiled with + * the --enable-sigchild option + * + * @param bool $enhance + * + * @return self The current Process instance + */ + public function setEnhanceSigchildCompatibility($enhance) + { + $this->enhanceSigchildCompatibility = (bool) $enhance; + + return $this; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function checkTimeout() + { + if ($this->status !== self::STATUS_STARTED) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); + } + } + + /** + * Returns whether PTY is supported on the current operating system. + * + * @return bool + */ + public static function isPtySupported() + { + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + return $result = false; + } + + $proc = @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes); + if (is_resource($proc)) { + proc_close($proc); + + return $result = true; + } + + return $result = false; + } + + /** + * Creates the descriptors needed by the proc_open. + * + * @return array + */ + private function getDescriptors() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->processPipes = WindowsPipes::create($this, $this->input); + } else { + $this->processPipes = UnixPipes::create($this, $this->input); + } + $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); + + if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors = array_merge($descriptors, array(array('pipe', 'w'))); + + $this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + return $descriptors; + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callable|null $callback The user defined PHP callback + * + * @return \Closure A PHP closure + */ + protected function buildCallback($callback) + { + $that = $this; + $out = self::OUT; + $callback = function ($type, $data) use ($that, $callback, $out) { + if ($out == $type) { + $that->addOutput($data); + } else { + $that->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + + return $callback; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param bool $blocking Whether to use a blocking read call. + */ + protected function updateStatus($blocking) + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $this->captureExitCode(); + + $this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + + if (!$this->processInformation['running']) { + $this->close(); + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + * + * @return bool + */ + protected function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Validates and returns the filtered timeout. + * + * @param int|float|null $timeout + * + * @return float|null + * + * @throws InvalidArgumentException if the given timeout is a negative number + */ + private function validateTimeout($timeout) + { + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * Reads pipes, executes callback. + * + * @param bool $blocking Whether to use blocking calls or not. + * @param bool $close Whether to close file handles or not. + */ + private function readPipes($blocking, $close) + { + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 == $type) { + $this->fallbackExitcode = (int) $data; + } else { + $callback($type === self::STDOUT ? self::OUT : self::ERR, $data); + } + } + } + + /** + * Captures the exitcode if mentioned in the process information. + */ + private function captureExitCode() + { + if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { + $this->exitcode = $this->processInformation['exitcode']; + } + } + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return int The exitcode + */ + private function close() + { + $this->processPipes->close(); + if (is_resource($this->process)) { + $exitcode = proc_close($this->process); + } else { + $exitcode = -1; + } + + $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { + $this->exitcode = $this->fallbackExitcode; + } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData() + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackExitcode = null; + $this->processInformation = null; + $this->stdout = null; + $this->stderr = null; + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php) + * @param bool $throwException Whether to throw exception in case signal failed + * + * @return bool True if the signal was sent successfully, false otherwise + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated + * @throws RuntimeException In case of failure + */ + private function doSignal($signal, $throwException) + { + if (!$this->isRunning()) { + if ($throwException) { + throw new LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ($this->isSigchildEnabled()) { + if ($throwException) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } + + return false; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); + if ($exitCode) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } + + return false; + } + } + + if (true !== @proc_terminate($this->process, $signal) && '\\' !== DIRECTORY_SEPARATOR) { + if ($throwException) { + throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } + + return false; + } + + $this->latestSignal = $signal; + + return true; + } + + /** + * Ensures the process is running or terminated, throws a LogicException if the process has a not started. + * + * @param string $functionName The function name that was called. + * + * @throws LogicException If the process has not run. + */ + private function requireProcessIsStarted($functionName) + { + if (!$this->isStarted()) { + throw new LogicException(sprintf('Process must be started before calling %s.', $functionName)); + } + } + + /** + * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`. + * + * @param string $functionName The function name that was called. + * + * @throws LogicException If the process is not yet terminated. + */ + private function requireProcessIsTerminated($functionName) + { + if (!$this->isTerminated()) { + throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessBuilder.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessBuilder.php new file mode 100644 index 000000000..a782fd69e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessBuilder.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\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; + +/** + * Process builder. + * + * @author Kris Wallsmith + */ +class ProcessBuilder +{ + private $arguments; + private $cwd; + private $env = array(); + private $input; + private $timeout = 60; + private $options = array(); + private $inheritEnv = true; + private $prefix = array(); + private $outputDisabled = false; + + /** + * Constructor. + * + * @param string[] $arguments An array of arguments + */ + public function __construct(array $arguments = array()) + { + $this->arguments = $arguments; + } + + /** + * Creates a process builder instance. + * + * @param string[] $arguments An array of arguments + * + * @return ProcessBuilder + */ + public static function create(array $arguments = array()) + { + return new static($arguments); + } + + /** + * Adds an unescaped argument to the command string. + * + * @param string $argument A command argument + * + * @return ProcessBuilder + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * Adds a prefix to the command string. + * + * The prefix is preserved when resetting arguments. + * + * @param string|array $prefix A command prefix or an array of command prefixes + * + * @return ProcessBuilder + */ + public function setPrefix($prefix) + { + $this->prefix = is_array($prefix) ? $prefix : array($prefix); + + return $this; + } + + /** + * Sets the arguments of the process. + * + * Arguments must not be escaped. + * Previous arguments are removed. + * + * @param string[] $arguments + * + * @return ProcessBuilder + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * Sets the working directory. + * + * @param null|string $cwd The working directory + * + * @return ProcessBuilder + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Sets whether environment variables will be inherited or not. + * + * @param bool $inheritEnv + * + * @return ProcessBuilder + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + /** + * Sets an environment variable. + * + * Setting a variable overrides its previous value. Use `null` to unset a + * defined environment variable. + * + * @param string $name The variable name + * @param null|string $value The variable value + * + * @return ProcessBuilder + */ + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + /** + * Adds a set of environment variables. + * + * Already existing environment variables with the same name will be + * overridden by the new values passed to this method. Pass `null` to unset + * a variable. + * + * @param array $variables The variables + * + * @return ProcessBuilder + */ + public function addEnvironmentVariables(array $variables) + { + $this->env = array_replace($this->env, $variables); + + return $this; + } + + /** + * Sets the input of the process. + * + * @param mixed $input The input as a string + * + * @return ProcessBuilder + * + * @throws InvalidArgumentException In case the argument is invalid + * + * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0. + */ + public function setInput($input) + { + $this->input = ProcessUtils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * Sets the process timeout. + * + * To disable the timeout, set this value to null. + * + * @param float|null $timeout + * + * @return ProcessBuilder + * + * @throws InvalidArgumentException + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * Adds a proc_open option. + * + * @param string $name The option name + * @param string $value The option value + * + * @return ProcessBuilder + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return ProcessBuilder + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return ProcessBuilder + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + + /** + * Creates a Process instance and returns it. + * + * @return Process + * + * @throws LogicException In case no arguments have been provided + */ + public function getProcess() + { + if (0 === count($this->prefix) && 0 === count($this->arguments)) { + throw new LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = array_merge($this->prefix, $this->arguments); + $script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments)); + + if ($this->inheritEnv) { + // include $_ENV for BC purposes + $env = array_replace($_ENV, $_SERVER, $this->env); + } else { + $env = $this->env; + } + + $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); + + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessUtils.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessUtils.php new file mode 100644 index 000000000..4f30b630d --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessUtils.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin Hasoň + */ +class ProcessUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Escapes a string to be used as a shell argument. + * + * @param string $argument The argument that will be escaped + * + * @return string The escaped argument + */ + public static function escapeArgument($argument) + { + //Fix for PHP bug #43784 escapeshellarg removes % from given string + //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows + //@see https://bugs.php.net/bug.php?id=43784 + //@see https://bugs.php.net/bug.php?id=49446 + if ('\\' === DIRECTORY_SEPARATOR) { + if ('' === $argument) { + return escapeshellarg($argument); + } + + $escapedArgument = ''; + $quote = false; + foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif (self::isSurroundedBy($part, '%')) { + // Avoid environment variable expansion + $escapedArgument .= '^%"'.substr($part, 1, -1).'"^%'; + } else { + // escape trailing backslash + if ('\\' === substr($part, -1)) { + $part .= '\\'; + } + $quote = true; + $escapedArgument .= $part; + } + } + if ($quote) { + $escapedArgument = '"'.$escapedArgument.'"'; + } + + return $escapedArgument; + } + + return escapeshellarg($argument); + } + + /** + * Validates and normalizes a Process input. + * + * @param string $caller The name of method call that validates the input + * @param mixed $input The input to validate + * + * @return string The validated input + * + * @throws InvalidArgumentException In case the input is not valid + * + * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0. + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (is_resource($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + // deprecated as of Symfony 2.5, to be removed in 3.0 + if (is_object($input) && method_exists($input, '__toString')) { + @trigger_error('Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); + + return (string) $input; + } + + throw new InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); + } + + return $input; + } + + private static function isSurroundedBy($arg, $char) + { + return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/README.md b/modules/devshop/devshop_projects/drush/vendor/symfony/process/README.md new file mode 100644 index 000000000..7222fe895 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/README.md @@ -0,0 +1,65 @@ +Process Component +================= + +Process executes commands in sub-processes. + +In this example, we run a simple directory listing and get the result back: + +```php +use Symfony\Component\Process\Process; +use Symfony\Component\Process\Exception\ProcessFailedException; + +$process = new Process('ls -lsa'); +$process->setTimeout(3600); +$process->run(); +if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); +} + +print $process->getOutput(); +``` + +You can think that this is easy to achieve with plain PHP but it's not especially +if you want to take care of the subtle differences between the different platforms. + +You can simplify the code by using `mustRun()` instead of `run()`, which will +throw a `ProcessFailedException` automatically in case of a problem: + +```php +use Symfony\Component\Process\Process; + +$process = new Process('ls -lsa'); +$process->setTimeout(3600); +$process->mustRun(); + +print $process->getOutput(); +``` + +And if you want to be able to get some feedback in real-time, just pass an +anonymous function to the ``run()`` method and you will get the output buffer +as it becomes available: + +```php +use Symfony\Component\Process\Process; + +$process = new Process('ls -lsa'); +$process->run(function ($type, $buffer) { + if (Process::ERR === $type) { + echo 'ERR > '.$buffer; + } else { + echo 'OUT > '.$buffer; + } +}); +``` + +That's great if you want to execute a long running command (like rsync-ing files to a +remote server) and give feedback to the user in real-time. + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Process/ + $ composer install + $ phpunit diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/AbstractProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/AbstractProcessTest.php new file mode 100644 index 000000000..fca3729be --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/AbstractProcessTest.php @@ -0,0 +1,1196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\Pipes\PipesInterface; +use Symfony\Component\Process\Process; + +/** + * @author Robert Schönthal + */ +abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase +{ + protected static $phpBin; + + public static function setUpBeforeClass() + { + $phpBin = new PhpExecutableFinder(); + self::$phpBin = 'phpdbg' === PHP_SAPI ? 'php' : $phpBin->find(); + } + + public function testThatProcessDoesNotThrowWarningDuringRun() + { + @trigger_error('Test Error', E_USER_NOTICE); + $process = $this->getProcess(self::$phpBin." -r 'sleep(3)'"); + $process->run(); + $actualError = error_get_last(); + $this->assertEquals('Test Error', $actualError['message']); + $this->assertEquals(E_USER_NOTICE, $actualError['type']); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromConstructor() + { + $this->getProcess('', null, null, null, -1); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromSetter() + { + $p = $this->getProcess(''); + $p->setTimeout(-1); + } + + public function testFloatAndNullTimeout() + { + $p = $this->getProcess(''); + + $p->setTimeout(10); + $this->assertSame(10.0, $p->getTimeout()); + + $p->setTimeout(null); + $this->assertNull($p->getTimeout()); + + $p->setTimeout(0.0); + $this->assertNull($p->getTimeout()); + } + + public function testStopWithTimeoutIsActuallyWorking() + { + if (!extension_loaded('pcntl')) { + $this->markTestSkipped('Extension pcntl is required.'); + } + + // exec is mandatory here since we send a signal to the process + // see https://github.com/symfony/symfony/issues/5030 about prepending + // command with exec + $p = $this->getProcess('exec '.self::$phpBin.' '.__DIR__.'/NonStopableProcess.php 3'); + $p->start(); + usleep(100000); + $start = microtime(true); + $p->stop(1.1, SIGKILL); + while ($p->isRunning()) { + usleep(1000); + } + $duration = microtime(true) - $start; + + $this->assertLessThan(4, $duration); + } + + public function testAllOutputIsActuallyReadOnTermination() + { + // this code will result in a maximum of 2 reads of 8192 bytes by calling + // start() and isRunning(). by the time getOutput() is called the process + // has terminated so the internal pipes array is already empty. normally + // the call to start() will not read any data as the process will not have + // generated output, but this is non-deterministic so we must count it as + // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus + // another byte which will never be read. + $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2; + + $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize); + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code))); + + $p->start(); + // Let's wait enough time for process to finish... + // Here we don't call Process::run or Process::wait to avoid any read of pipes + usleep(500000); + + if ($p->isRunning()) { + $this->markTestSkipped('Process execution did not complete in the required time frame'); + } + + $o = $p->getOutput(); + + $this->assertEquals($expectedOutputSize, strlen($o)); + } + + public function testCallbacksAreExecutedWithStart() + { + $data = ''; + + $process = $this->getProcess('echo foo && php -r "sleep(1);" && echo foo'); + $process->start(function ($type, $buffer) use (&$data) { + $data .= $buffer; + }); + + while ($process->isRunning()) { + usleep(10000); + } + + $this->assertEquals(2, preg_match_all('/foo/', $data, $matches)); + } + + /** + * tests results from sub processes. + * + * @dataProvider responsesCodeProvider + */ + public function testProcessResponses($expected, $getter, $code) + { + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code))); + $p->run(); + + $this->assertSame($expected, $p->$getter()); + } + + /** + * tests results from sub processes. + * + * @dataProvider pipesCodeProvider + */ + public function testProcessPipes($code, $size) + { + $expected = str_repeat(str_repeat('*', 1024), $size).'!'; + $expectedLength = (1024 * $size) + 1; + + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code))); + $p->setInput($expected); + $p->run(); + + $this->assertEquals($expectedLength, strlen($p->getOutput())); + $this->assertEquals($expectedLength, strlen($p->getErrorOutput())); + } + + /** + * @dataProvider pipesCodeProvider + */ + public function testSetStreamAsInput($code, $size) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestIncomplete('This test fails with a timeout on Windows, can someone investigate please?'); + } + $expected = str_repeat(str_repeat('*', 1024), $size).'!'; + $expectedLength = (1024 * $size) + 1; + + $stream = fopen('php://temporary', 'w+'); + fwrite($stream, $expected); + rewind($stream); + + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)), null, null, null, 5); + $p->setInput($stream); + $p->run(); + + fclose($stream); + + $this->assertEquals($expectedLength, strlen($p->getOutput())); + $this->assertEquals($expectedLength, strlen($p->getErrorOutput())); + } + + public function testSetInputWhileRunningThrowsAnException() + { + $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $process->start(); + try { + $process->setInput('foobar'); + $process->stop(); + $this->fail('A LogicException should have been raised.'); + } catch (LogicException $e) { + $this->assertEquals('Input can not be set while the process is running.', $e->getMessage()); + } + $process->stop(); + } + + /** + * @dataProvider provideInvalidInputValues + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + * @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings or stream resources. + */ + public function testInvalidInput($value) + { + $process = $this->getProcess(self::$phpBin.' -v'); + $process->setInput($value); + } + + public function provideInvalidInputValues() + { + return array( + array(array()), + array(new NonStringifiable()), + ); + } + + /** + * @dataProvider provideInputValues + */ + public function testValidInput($expected, $value) + { + $process = $this->getProcess(self::$phpBin.' -v'); + $process->setInput($value); + $this->assertSame($expected, $process->getInput()); + } + + public function provideInputValues() + { + return array( + array(null, null), + array('24.5', 24.5), + array('input data', 'input data'), + ); + } + + /** + * @dataProvider provideLegacyInputValues + * @group legacy + */ + public function testLegacyValidInput($expected, $value) + { + $process = $this->getProcess(self::$phpBin.' -v'); + $process->setInput($value); + $this->assertSame($expected, $process->getInput()); + } + + public function provideLegacyInputValues() + { + return array( + array('stringifiable', new Stringifiable()), + ); + } + + public function chainedCommandsOutputProvider() + { + if ('\\' === DIRECTORY_SEPARATOR) { + return array( + array("2 \r\n2\r\n", '&&', '2'), + ); + } + + return array( + array("1\n1\n", ';', '1'), + array("2\n2\n", '&&', '2'), + ); + } + + /** + * @dataProvider chainedCommandsOutputProvider + */ + public function testChainedCommandsOutput($expected, $operator, $input) + { + $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input)); + $process->run(); + $this->assertEquals($expected, $process->getOutput()); + } + + public function testCallbackIsExecutedForOutput() + { + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('echo \'foo\';'))); + + $called = false; + $p->run(function ($type, $buffer) use (&$called) { + $called = $buffer === 'foo'; + }); + + $this->assertTrue($called, 'The callback should be executed with the output'); + } + + public function testGetErrorOutput() + { + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'))); + + $p->run(); + $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches)); + } + + public function testGetIncrementalErrorOutput() + { + // use a lock file to toggle between writing ("W") and reading ("R") the + // error stream + $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock'); + file_put_contents($lock, 'W'); + + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }'))); + + $p->start(); + while ($p->isRunning()) { + if ('R' === file_get_contents($lock)) { + $this->assertLessThanOrEqual(1, preg_match_all('/ERROR/', $p->getIncrementalErrorOutput(), $matches)); + file_put_contents($lock, 'W'); + } + usleep(100); + } + + unlink($lock); + } + + public function testFlushErrorOutput() + { + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'))); + + $p->run(); + $p->clearErrorOutput(); + $this->assertEmpty($p->getErrorOutput()); + } + + public function testGetEmptyIncrementalErrorOutput() + { + // use a lock file to toggle between writing ("W") and reading ("R") the + // output stream + $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock'); + file_put_contents($lock, 'W'); + + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }'))); + + $p->start(); + + $shouldWrite = false; + + while ($p->isRunning()) { + if ('R' === file_get_contents($lock)) { + if (!$shouldWrite) { + $this->assertLessThanOrEqual(1, preg_match_all('/ERROR/', $p->getIncrementalOutput(), $matches)); + $shouldWrite = true; + } else { + $this->assertSame('', $p->getIncrementalOutput()); + + file_put_contents($lock, 'W'); + $shouldWrite = false; + } + } + usleep(100); + } + + unlink($lock); + } + + public function testGetOutput() + { + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }'))); + + $p->run(); + $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches)); + } + + public function testGetIncrementalOutput() + { + // use a lock file to toggle between writing ("W") and reading ("R") the + // output stream + $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock'); + file_put_contents($lock, 'W'); + + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { echo \' foo \'; $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }'))); + + $p->start(); + while ($p->isRunning()) { + if ('R' === file_get_contents($lock)) { + $this->assertLessThanOrEqual(1, preg_match_all('/foo/', $p->getIncrementalOutput(), $matches)); + file_put_contents($lock, 'W'); + } + usleep(100); + } + + unlink($lock); + } + + public function testFlushOutput() + { + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}'))); + + $p->run(); + $p->clearOutput(); + $this->assertEmpty($p->getOutput()); + } + + public function testGetEmptyIncrementalOutput() + { + // use a lock file to toggle between writing ("W") and reading ("R") the + // output stream + $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock'); + file_put_contents($lock, 'W'); + + $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { echo \' foo \'; $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }'))); + + $p->start(); + + $shouldWrite = false; + + while ($p->isRunning()) { + if ('R' === file_get_contents($lock)) { + if (!$shouldWrite) { + $this->assertLessThanOrEqual(1, preg_match_all('/foo/', $p->getIncrementalOutput(), $matches)); + $shouldWrite = true; + } else { + $this->assertSame('', $p->getIncrementalOutput()); + + file_put_contents($lock, 'W'); + $shouldWrite = false; + } + } + usleep(100); + } + + unlink($lock); + } + + public function testZeroAsOutput() + { + if ('\\' === DIRECTORY_SEPARATOR) { + // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line + $p = $this->getProcess('echo | set /p dummyName=0'); + } else { + $p = $this->getProcess('printf 0'); + } + + $p->run(); + $this->assertSame('0', $p->getOutput()); + } + + public function testExitCodeCommandFailed() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX exit code'); + } + + // such command run in bash return an exitcode 127 + $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'); + $process->run(); + + $this->assertGreaterThan(0, $process->getExitCode()); + } + + public function testTTYCommand() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does have /dev/tty support'); + } + + $process = $this->getProcess('echo "foo" >> /dev/null && '.self::$phpBin.' -r "usleep(100000);"'); + $process->setTty(true); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->wait(); + + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + } + + public function testTTYCommandExitCode() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does have /dev/tty support'); + } + + $process = $this->getProcess('echo "foo" >> /dev/null'); + $process->setTty(true); + $process->run(); + + $this->assertTrue($process->isSuccessful()); + } + + public function testTTYInWindowsEnvironment() + { + if ('\\' !== DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test is for Windows platform only'); + } + + $process = $this->getProcess('echo "foo" >> /dev/null'); + $process->setTty(false); + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'TTY mode is not supported on Windows platform.'); + $process->setTty(true); + } + + public function testExitCodeTextIsNullWhenExitCodeIsNull() + { + $process = $this->getProcess(''); + $this->assertNull($process->getExitCodeText()); + } + + public function testPTYCommand() + { + if (!Process::isPtySupported()) { + $this->markTestSkipped('PTY is not supported on this operating system.'); + } + + $process = $this->getProcess('echo "foo"'); + $process->setPty(true); + $process->run(); + + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + $this->assertEquals("foo\r\n", $process->getOutput()); + } + + public function testMustRun() + { + $process = $this->getProcess('echo foo'); + + $this->assertSame($process, $process->mustRun()); + $this->assertEquals('foo'.PHP_EOL, $process->getOutput()); + } + + public function testSuccessfulMustRunHasCorrectExitCode() + { + $process = $this->getProcess('echo foo')->mustRun(); + $this->assertEquals(0, $process->getExitCode()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\ProcessFailedException + */ + public function testMustRunThrowsException() + { + $process = $this->getProcess('exit 1'); + $process->mustRun(); + } + + public function testExitCodeText() + { + $process = $this->getProcess(''); + $r = new \ReflectionObject($process); + $p = $r->getProperty('exitcode'); + $p->setAccessible(true); + + $p->setValue($process, 2); + $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText()); + } + + public function testStartIsNonBlocking() + { + $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $start = microtime(true); + $process->start(); + $end = microtime(true); + $this->assertLessThan(0.4, $end - $start); + $process->wait(); + } + + public function testUpdateStatus() + { + $process = $this->getProcess(self::$phpBin.' -v'); + $process->run(); + $this->assertTrue(strlen($process->getOutput()) > 0); + } + + public function testGetExitCodeIsNullOnStart() + { + $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"'); + $this->assertNull($process->getExitCode()); + $process->start(); + $this->assertNull($process->getExitCode()); + $process->wait(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testGetExitCodeIsNullOnWhenStartingAgain() + { + $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"'); + $process->run(); + $this->assertEquals(0, $process->getExitCode()); + $process->start(); + $this->assertNull($process->getExitCode()); + $process->wait(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testGetExitCode() + { + $process = $this->getProcess(self::$phpBin.' -v'); + $process->run(); + $this->assertSame(0, $process->getExitCode()); + } + + public function testStatus() + { + $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $this->assertFalse($process->isRunning()); + $this->assertFalse($process->isStarted()); + $this->assertFalse($process->isTerminated()); + $this->assertSame(Process::STATUS_READY, $process->getStatus()); + $process->start(); + $this->assertTrue($process->isRunning()); + $this->assertTrue($process->isStarted()); + $this->assertFalse($process->isTerminated()); + $this->assertSame(Process::STATUS_STARTED, $process->getStatus()); + $process->wait(); + $this->assertFalse($process->isRunning()); + $this->assertTrue($process->isStarted()); + $this->assertTrue($process->isTerminated()); + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + } + + public function testStop() + { + $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"'); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->stop(); + $this->assertFalse($process->isRunning()); + } + + public function testIsSuccessful() + { + $process = $this->getProcess(self::$phpBin.' -v'); + $process->run(); + $this->assertTrue($process->isSuccessful()); + } + + public function testIsSuccessfulOnlyAfterTerminated() + { + $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"'); + $process->start(); + + $this->assertFalse($process->isSuccessful()); + + while ($process->isRunning()) { + usleep(300000); + } + + $this->assertTrue($process->isSuccessful()); + } + + public function testIsNotSuccessful() + { + $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);throw new \Exception(\'BOUM\');"'); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->wait(); + $this->assertFalse($process->isSuccessful()); + } + + public function testProcessIsNotSignaled() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess(self::$phpBin.' -v'); + $process->run(); + $this->assertFalse($process->hasBeenSignaled()); + } + + public function testProcessWithoutTermSignalIsNotSignaled() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess(self::$phpBin.' -v'); + $process->run(); + $this->assertFalse($process->hasBeenSignaled()); + } + + public function testProcessWithoutTermSignal() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess(self::$phpBin.' -v'); + $process->run(); + $this->assertEquals(0, $process->getTermSignal()); + } + + public function testProcessIsSignaledIfStopped() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"'); + $process->start(); + $process->stop(); + $this->assertTrue($process->hasBeenSignaled()); + } + + public function testProcessWithTermSignal() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + // SIGTERM is only defined if pcntl extension is present + $termSignal = defined('SIGTERM') ? SIGTERM : 15; + + $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"'); + $process->start(); + $process->stop(); + + $this->assertEquals($termSignal, $process->getTermSignal()); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + if (!function_exists('posix_kill')) { + $this->markTestSkipped('Function posix_kill is required.'); + } + + $termSignal = defined('SIGKILL') ? SIGKILL : 9; + + $process = $this->getProcess('exec '.self::$phpBin.' -r "while (true) {}"'); + $process->start(); + posix_kill($process->getPid(), $termSignal); + + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'The process has been signaled with signal "9".'); + $process->wait(); + } + + public function testRestart() + { + $process1 = $this->getProcess(self::$phpBin.' -r "echo getmypid();"'); + $process1->run(); + $process2 = $process1->restart(); + + $process2->wait(); // wait for output + + // Ensure that both processed finished and the output is numeric + $this->assertFalse($process1->isRunning()); + $this->assertFalse($process2->isRunning()); + $this->assertTrue(is_numeric($process1->getOutput())); + $this->assertTrue(is_numeric($process2->getOutput())); + + // Ensure that restart returned a new process by check that the output is different + $this->assertNotEquals($process1->getOutput(), $process2->getOutput()); + } + + public function testRunProcessWithTimeout() + { + $timeout = 0.5; + $process = $this->getProcess(self::$phpBin.' -r "usleep(600000);"'); + $process->setTimeout($timeout); + $start = microtime(true); + try { + $process->run(); + $this->fail('A RuntimeException should have been raised'); + } catch (RuntimeException $e) { + } + $duration = microtime(true) - $start; + + if ('\\' === DIRECTORY_SEPARATOR) { + // Windows is a bit slower as it read file handles, then allow twice the precision + $maxDuration = $timeout + 2 * Process::TIMEOUT_PRECISION; + } else { + $maxDuration = $timeout + Process::TIMEOUT_PRECISION; + } + + $this->assertLessThan($maxDuration, $duration); + } + + public function testCheckTimeoutOnNonStartedProcess() + { + $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); + $process->checkTimeout(); + } + + public function testCheckTimeoutOnTerminatedProcess() + { + $process = $this->getProcess(self::$phpBin.' -v'); + $process->run(); + $process->checkTimeout(); + } + + public function testCheckTimeoutOnStartedProcess() + { + $timeout = 0.5; + $precision = 100000; + $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); + $process->setTimeout($timeout); + $start = microtime(true); + + $process->start(); + + try { + while ($process->isRunning()) { + $process->checkTimeout(); + usleep($precision); + } + $this->fail('A RuntimeException should have been raised'); + } catch (RuntimeException $e) { + } + $duration = microtime(true) - $start; + + $this->assertLessThan($timeout + $precision, $duration); + $this->assertFalse($process->isSuccessful()); + } + + public function testIdleTimeout() + { + $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); + $process->setTimeout(10); + $process->setIdleTimeout(0.5); + + try { + $process->run(); + + $this->fail('A timeout exception was expected.'); + } catch (ProcessTimedOutException $ex) { + $this->assertTrue($ex->isIdleTimeout()); + $this->assertFalse($ex->isGeneralTimeout()); + $this->assertEquals(0.5, $ex->getExceededTimeout()); + } + } + + public function testIdleTimeoutNotExceededWhenOutputIsSent() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestIncomplete('This test fails with a timeout on Windows, can someone investigate please?'); + } + $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 30; while ($n--) {echo "foo\n"; usleep(100000); }'))); + $process->setTimeout(2); + $process->setIdleTimeout(1); + + try { + $process->run(); + $this->fail('A timeout exception was expected.'); + } catch (ProcessTimedOutException $ex) { + $this->assertTrue($ex->isGeneralTimeout(), 'A general timeout is expected.'); + $this->assertFalse($ex->isIdleTimeout(), 'No idle timeout is expected.'); + $this->assertEquals(2, $ex->getExceededTimeout()); + } + } + + public function testStartAfterATimeout() + { + $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 1000; while ($n--) {echo \'\'; usleep(1000); }'))); + $process->setTimeout(0.1); + + try { + $process->run(); + $this->fail('A RuntimeException should have been raised.'); + } catch (RuntimeException $e) { + } + $process->start(); + usleep(1000); + $process->stop(); + } + + public function testGetPid() + { + $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $process->start(); + $this->assertGreaterThan(0, $process->getPid()); + $process->wait(); + } + + public function testGetPidIsNullBeforeStart() + { + $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"'); + $this->assertNull($process->getPid()); + } + + public function testGetPidIsNullAfterRun() + { + $process = $this->getProcess(self::$phpBin.' -v'); + $process->run(); + $this->assertNull($process->getPid()); + } + + public function testSignal() + { + if (!extension_loaded('pcntl')) { + $this->markTestSkipped('Extension pcntl is required.'); + } + + $process = $this->getProcess('exec php -f '.__DIR__.'/SignalListener.php'); + $process->start(); + usleep(500000); + $process->signal(SIGUSR1); + + while ($process->isRunning() && false === strpos($process->getOutput(), 'Caught SIGUSR1')) { + usleep(10000); + } + + $this->assertEquals('Caught SIGUSR1', $process->getOutput()); + } + + public function testExitCodeIsAvailableAfterSignal() + { + if (!extension_loaded('pcntl')) { + $this->markTestSkipped('Extension pcntl is required.'); + } + + $process = $this->getProcess('sleep 4'); + $process->start(); + $process->signal(SIGKILL); + + while ($process->isRunning()) { + usleep(10000); + } + + $this->assertFalse($process->isRunning()); + $this->assertTrue($process->hasBeenSignaled()); + $this->assertFalse($process->isSuccessful()); + $this->assertEquals(137, $process->getExitCode()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + */ + public function testSignalProcessNotRunning() + { + if (!extension_loaded('pcntl')) { + $this->markTestSkipped('Extension pcntl is required.'); + } + + $process = $this->getProcess(self::$phpBin.' -v'); + $process->signal(SIGHUP); + } + + /** + * @dataProvider provideMethodsThatNeedARunningProcess + */ + public function testMethodsThatNeedARunningProcess($method) + { + $process = $this->getProcess(self::$phpBin.' -v'); + $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method)); + $process->{$method}(); + } + + public function provideMethodsThatNeedARunningProcess() + { + return array( + array('getOutput'), + array('getIncrementalOutput'), + array('getErrorOutput'), + array('getIncrementalErrorOutput'), + array('wait'), + ); + } + + /** + * @dataProvider provideMethodsThatNeedATerminatedProcess + */ + public function testMethodsThatNeedATerminatedProcess($method) + { + $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"'); + $process->start(); + try { + $process->{$method}(); + $process->stop(0); + $this->fail('A LogicException must have been thrown'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Process\Exception\LogicException', $e); + $this->assertEquals(sprintf('Process must be terminated before calling %s.', $method), $e->getMessage()); + } + $process->stop(0); + } + + public function provideMethodsThatNeedATerminatedProcess() + { + return array( + array('hasBeenSignaled'), + array('getTermSignal'), + array('hasBeenStopped'), + array('getStopSignal'), + ); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongIntSignal() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('POSIX signals do not work on Windows'); + } + + $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); + $process->start(); + $process->signal(-4); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongNonIntSignal() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('POSIX signals do not work on Windows'); + } + + $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); + $process->start(); + $process->signal('Céphalopodes'); + } + + public function testDisableOutputDisablesTheOutput() + { + $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $this->assertFalse($p->isOutputDisabled()); + $p->disableOutput(); + $this->assertTrue($p->isOutputDisabled()); + $p->enableOutput(); + $this->assertFalse($p->isOutputDisabled()); + } + + public function testDisableOutputWhileRunningThrowsException() + { + $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p->start(); + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Disabling output while the process is running is not possible.'); + $p->disableOutput(); + } + + public function testEnableOutputWhileRunningThrowsException() + { + $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p->disableOutput(); + $p->start(); + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Enabling output while the process is running is not possible.'); + $p->enableOutput(); + } + + public function testEnableOrDisableOutputAfterRunDoesNotThrowException() + { + $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p->disableOutput(); + $p->start(); + $p->wait(); + $p->enableOutput(); + $p->disableOutput(); + } + + public function testDisableOutputWhileIdleTimeoutIsSet() + { + $process = $this->getProcess('sleep 3'); + $process->setIdleTimeout(1); + $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output can not be disabled while an idle timeout is set.'); + $process->disableOutput(); + } + + public function testSetIdleTimeoutWhileOutputIsDisabled() + { + $process = $this->getProcess('sleep 3'); + $process->disableOutput(); + $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Idle timeout can not be set while the output is disabled.'); + $process->setIdleTimeout(1); + } + + public function testSetNullIdleTimeoutWhileOutputIsDisabled() + { + $process = $this->getProcess('sleep 3'); + $process->disableOutput(); + $process->setIdleTimeout(null); + } + + /** + * @dataProvider provideStartMethods + */ + public function testStartWithACallbackAndDisabledOutput($startMethod, $exception, $exceptionMessage) + { + $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p->disableOutput(); + $this->setExpectedException($exception, $exceptionMessage); + $p->{$startMethod}(function () {}); + } + + public function provideStartMethods() + { + return array( + array('start', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'), + array('run', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'), + array('mustRun', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'), + ); + } + + /** + * @dataProvider provideOutputFetchingMethods + */ + public function testGetOutputWhileDisabled($fetchMethod) + { + $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p->disableOutput(); + $p->start(); + $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output has been disabled.'); + $p->{$fetchMethod}(); + } + + public function provideOutputFetchingMethods() + { + return array( + array('getOutput'), + array('getIncrementalOutput'), + array('getErrorOutput'), + array('getIncrementalErrorOutput'), + ); + } + + public function responsesCodeProvider() + { + return array( + //expected output / getter / code to execute + //array(1,'getExitCode','exit(1);'), + //array(true,'isSuccessful','exit();'), + array('output', 'getOutput', 'echo \'output\';'), + ); + } + + public function pipesCodeProvider() + { + $variations = array( + 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);', + 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';', + ); + + if ('\\' === DIRECTORY_SEPARATOR) { + // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650 + $sizes = array(1, 2, 4, 8); + } else { + $sizes = array(1, 16, 64, 1024, 4096); + } + + $codes = array(); + foreach ($sizes as $size) { + foreach ($variations as $code) { + $codes[] = array($code, $size); + } + } + + return $codes; + } + + /** + * provides default method names for simple getter/setter. + */ + public function methodProvider() + { + $defaults = array( + array('CommandLine'), + array('Timeout'), + array('WorkingDirectory'), + array('Env'), + array('Stdin'), + array('Input'), + array('Options'), + ); + + return $defaults; + } + + /** + * @param string $commandline + * @param null|string $cwd + * @param null|array $env + * @param null|string $input + * @param int $timeout + * @param array $options + * + * @return Process + */ + abstract protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()); +} + +class Stringifiable +{ + public function __toString() + { + return 'stringifiable'; + } +} + +class NonStringifiable +{ +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ExecutableFinderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ExecutableFinderTest.php new file mode 100644 index 000000000..812429e88 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ExecutableFinderTest.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\ExecutableFinder; + +/** + * @author Chris Smith + */ +class ExecutableFinderTest extends \PHPUnit_Framework_TestCase +{ + private $path; + + protected function tearDown() + { + if ($this->path) { + // Restore path if it was changed. + putenv('PATH='.$this->path); + } + } + + private function setPath($path) + { + $this->path = getenv('PATH'); + putenv('PATH='.$path); + } + + /** + * @requires PHP 5.4 + */ + public function testFind() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + + $this->setPath(dirname(PHP_BINARY)); + + $finder = new ExecutableFinder(); + $result = $finder->find($this->getPhpBinaryName()); + + $this->assertSamePath(PHP_BINARY, $result); + } + + public function testFindWithDefault() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + + $expected = 'defaultValue'; + + $this->setPath(''); + + $finder = new ExecutableFinder(); + $result = $finder->find('foo', $expected); + + $this->assertEquals($expected, $result); + } + + /** + * @requires PHP 5.4 + */ + public function testFindWithExtraDirs() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + + $this->setPath(''); + + $extraDirs = array(dirname(PHP_BINARY)); + + $finder = new ExecutableFinder(); + $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs); + + $this->assertSamePath(PHP_BINARY, $result); + } + + /** + * @requires PHP 5.4 + */ + public function testFindWithOpenBaseDir() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Cannot run test on windows'); + } + + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + + $this->iniSet('open_basedir', dirname(PHP_BINARY).(!defined('HHVM_VERSION') ? PATH_SEPARATOR.'/' : '')); + + $finder = new ExecutableFinder(); + $result = $finder->find($this->getPhpBinaryName()); + + $this->assertSamePath(PHP_BINARY, $result); + } + + /** + * @requires PHP 5.4 + */ + public function testFindProcessInOpenBasedir() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Cannot run test on windows'); + } + + $this->setPath(''); + $this->iniSet('open_basedir', PHP_BINARY.(!defined('HHVM_VERSION') ? PATH_SEPARATOR.'/' : '')); + + $finder = new ExecutableFinder(); + $result = $finder->find($this->getPhpBinaryName(), false); + + $this->assertSamePath(PHP_BINARY, $result); + } + + private function assertSamePath($expected, $tested) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals(strtolower($expected), strtolower($tested)); + } else { + $this->assertEquals($expected, $tested); + } + } + + private function getPhpBinaryName() + { + return basename(PHP_BINARY, '\\' === DIRECTORY_SEPARATOR ? '.exe' : ''); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/NonStopableProcess.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/NonStopableProcess.php new file mode 100644 index 000000000..54510c16a --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/NonStopableProcess.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds. + * + * @args duration Run this script with a custom duration + * + * @example `php NonStopableProcess.php 42` will run the script for 42 seconds + */ +function handleSignal($signal) +{ + switch ($signal) { + case SIGTERM: + $name = 'SIGTERM'; + break; + case SIGINT: + $name = 'SIGINT'; + break; + default: + $name = $signal.' (unknown)'; + break; + } + + echo "received signal $name\n"; +} + +declare (ticks = 1); +pcntl_signal(SIGTERM, 'handleSignal'); +pcntl_signal(SIGINT, 'handleSignal'); + +$duration = isset($argv[1]) ? (int) $argv[1] : 3; +$start = microtime(true); + +while ($duration > (microtime(true) - $start)) { + usleep(1000); +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpExecutableFinderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpExecutableFinderTest.php new file mode 100644 index 000000000..87d0efe9e --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpExecutableFinderTest.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\Process\Tests; + +use Symfony\Component\Process\PhpExecutableFinder; + +/** + * @author Robert Schönthal + */ +class PhpExecutableFinderTest extends \PHPUnit_Framework_TestCase +{ + /** + * tests find() with the env var PHP_PATH. + */ + public function testFindWithPhpPath() + { + if (defined('PHP_BINARY')) { + $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4'); + } + + $f = new PhpExecutableFinder(); + + $current = $f->find(); + + //not executable PHP_PATH + putenv('PHP_PATH=/not/executable/php'); + $this->assertFalse($f->find(), '::find() returns false for not executable PHP'); + $this->assertFalse($f->find(false), '::find() returns false for not executable PHP'); + + //executable PHP_PATH + putenv('PHP_PATH='.$current); + $this->assertEquals($f->find(), $current, '::find() returns the executable PHP'); + $this->assertEquals($f->find(false), $current, '::find() returns the executable PHP'); + } + + /** + * tests find() with the constant PHP_BINARY. + * + * @requires PHP 5.4 + */ + public function testFind() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Should not be executed in HHVM context.'); + } + + $f = new PhpExecutableFinder(); + + $current = PHP_BINARY; + $args = 'phpdbg' === PHP_SAPI ? ' -qrr' : ''; + + $this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP'); + $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP'); + } + + /** + * tests find() with the env var / constant PHP_BINARY with HHVM. + */ + public function testFindWithHHVM() + { + if (!defined('HHVM_VERSION')) { + $this->markTestSkipped('Should be executed in HHVM context.'); + } + + $f = new PhpExecutableFinder(); + + $current = getenv('PHP_BINARY') ?: PHP_BINARY; + + $this->assertEquals($current.' --php', $f->find(), '::find() returns the executable PHP'); + $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP'); + } + + /** + * tests find() with the env var PHP_PATH. + */ + public function testFindArguments() + { + $f = new PhpExecutableFinder(); + + if (defined('HHVM_VERSION')) { + $this->assertEquals($f->findArguments(), array('--php'), '::findArguments() returns HHVM arguments'); + } elseif ('phpdbg' === PHP_SAPI) { + $this->assertEquals($f->findArguments(), array('-qrr'), '::findArguments() returns phpdbg arguments'); + } else { + $this->assertEquals($f->findArguments(), array(), '::findArguments() returns no arguments'); + } + } + + /** + * tests find() with default executable. + */ + public function testFindWithSuffix() + { + if (defined('PHP_BINARY')) { + $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4'); + } + + putenv('PHP_PATH='); + putenv('PHP_PEAR_PHP_BIN='); + $f = new PhpExecutableFinder(); + + $current = $f->find(); + + //TODO maybe php executable is custom or even Windows + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertTrue(is_executable($current)); + $this->assertTrue((bool) preg_match('/'.addslashes(DIRECTORY_SEPARATOR).'php\.(exe|bat|cmd|com)$/i', $current), '::find() returns the executable PHP with suffixes'); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpProcessTest.php new file mode 100644 index 000000000..2cf79aa1a --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpProcessTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\PhpProcess; + +class PhpProcessTest extends \PHPUnit_Framework_TestCase +{ + public function testNonBlockingWorks() + { + $expected = 'hello world!'; + $process = new PhpProcess(<<start(); + $process->wait(); + $this->assertEquals($expected, $process->getOutput()); + } + + public function testCommandLine() + { + if ('phpdbg' === PHP_SAPI) { + $this->markTestSkipped('phpdbg SAPI is not supported by this test.'); + } + + $process = new PhpProcess(<<find(); + + $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP before start'); + + $process->start(); + $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start'); + + $process->wait(); + $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait'); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php new file mode 100644 index 000000000..bbd7ddfeb --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +define('ERR_SELECT_FAILED', 1); +define('ERR_TIMEOUT', 2); +define('ERR_READ_FAILED', 3); +define('ERR_WRITE_FAILED', 4); + +$read = array(STDIN); +$write = array(STDOUT, STDERR); + +stream_set_blocking(STDIN, 0); +stream_set_blocking(STDOUT, 0); +stream_set_blocking(STDERR, 0); + +$out = $err = ''; +while ($read || $write) { + $r = $read; + $w = $write; + $e = null; + $n = stream_select($r, $w, $e, 5); + + if (false === $n) { + die(ERR_SELECT_FAILED); + } elseif ($n < 1) { + die(ERR_TIMEOUT); + } + + if (in_array(STDOUT, $w) && strlen($out) > 0) { + $written = fwrite(STDOUT, (binary) $out, 32768); + if (false === $written) { + die(ERR_WRITE_FAILED); + } + $out = (binary) substr($out, $written); + } + if (null === $read && '' === $out) { + $write = array_diff($write, array(STDOUT)); + } + + if (in_array(STDERR, $w) && strlen($err) > 0) { + $written = fwrite(STDERR, (binary) $err, 32768); + if (false === $written) { + die(ERR_WRITE_FAILED); + } + $err = (binary) substr($err, $written); + } + if (null === $read && '' === $err) { + $write = array_diff($write, array(STDERR)); + } + + if ($r) { + $str = fread(STDIN, 32768); + if (false !== $str) { + $out .= $str; + $err .= $str; + } + if (false === $str || feof(STDIN)) { + $read = null; + if (!feof(STDIN)) { + die(ERR_READ_FAILED); + } + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessBuilderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessBuilderTest.php new file mode 100644 index 000000000..1b5056d1b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessBuilderTest.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\ProcessBuilder; + +class ProcessBuilderTest extends \PHPUnit_Framework_TestCase +{ + public function testInheritEnvironmentVars() + { + $_ENV['MY_VAR_1'] = 'foo'; + + $proc = ProcessBuilder::create() + ->add('foo') + ->getProcess(); + + unset($_ENV['MY_VAR_1']); + + $env = $proc->getEnv(); + $this->assertArrayHasKey('MY_VAR_1', $env); + $this->assertEquals('foo', $env['MY_VAR_1']); + } + + public function testAddEnvironmentVariables() + { + $pb = new ProcessBuilder(); + $env = array( + 'foo' => 'bar', + 'foo2' => 'bar2', + ); + $proc = $pb + ->add('command') + ->setEnv('foo', 'bar2') + ->addEnvironmentVariables($env) + ->inheritEnvironmentVariables(false) + ->getProcess() + ; + + $this->assertSame($env, $proc->getEnv()); + } + + public function testProcessShouldInheritAndOverrideEnvironmentVars() + { + $_ENV['MY_VAR_1'] = 'foo'; + + $proc = ProcessBuilder::create() + ->setEnv('MY_VAR_1', 'bar') + ->add('foo') + ->getProcess(); + + unset($_ENV['MY_VAR_1']); + + $env = $proc->getEnv(); + $this->assertArrayHasKey('MY_VAR_1', $env); + $this->assertEquals('bar', $env['MY_VAR_1']); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromSetter() + { + $pb = new ProcessBuilder(); + $pb->setTimeout(-1); + } + + public function testNullTimeout() + { + $pb = new ProcessBuilder(); + $pb->setTimeout(10); + $pb->setTimeout(null); + + $r = new \ReflectionObject($pb); + $p = $r->getProperty('timeout'); + $p->setAccessible(true); + + $this->assertNull($p->getValue($pb)); + } + + public function testShouldSetArguments() + { + $pb = new ProcessBuilder(array('initial')); + $pb->setArguments(array('second')); + + $proc = $pb->getProcess(); + + $this->assertContains('second', $proc->getCommandLine()); + } + + public function testPrefixIsPrependedToAllGeneratedProcess() + { + $pb = new ProcessBuilder(); + $pb->setPrefix('/usr/bin/php'); + + $proc = $pb->setArguments(array('-v'))->getProcess(); + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php" "-v"', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' '-v'", $proc->getCommandLine()); + } + + $proc = $pb->setArguments(array('-i'))->getProcess(); + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php" "-i"', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' '-i'", $proc->getCommandLine()); + } + } + + public function testArrayPrefixesArePrependedToAllGeneratedProcess() + { + $pb = new ProcessBuilder(); + $pb->setPrefix(array('/usr/bin/php', 'composer.phar')); + + $proc = $pb->setArguments(array('-v'))->getProcess(); + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php" "composer.phar" "-v"', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' 'composer.phar' '-v'", $proc->getCommandLine()); + } + + $proc = $pb->setArguments(array('-i'))->getProcess(); + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php" "composer.phar" "-i"', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' 'composer.phar' '-i'", $proc->getCommandLine()); + } + } + + public function testShouldEscapeArguments() + { + $pb = new ProcessBuilder(array('%path%', 'foo " bar', '%baz%baz')); + $proc = $pb->getProcess(); + + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertSame('^%"path"^% "foo \\" bar" "%baz%baz"', $proc->getCommandLine()); + } else { + $this->assertSame("'%path%' 'foo \" bar' '%baz%baz'", $proc->getCommandLine()); + } + } + + public function testShouldEscapeArgumentsAndPrefix() + { + $pb = new ProcessBuilder(array('arg')); + $pb->setPrefix('%prefix%'); + $proc = $pb->getProcess(); + + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertSame('^%"prefix"^% "arg"', $proc->getCommandLine()); + } else { + $this->assertSame("'%prefix%' 'arg'", $proc->getCommandLine()); + } + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + */ + public function testShouldThrowALogicExceptionIfNoPrefixAndNoArgument() + { + ProcessBuilder::create()->getProcess(); + } + + public function testShouldNotThrowALogicExceptionIfNoArgument() + { + $process = ProcessBuilder::create() + ->setPrefix('/usr/bin/php') + ->getProcess(); + + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php"', $process->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php'", $process->getCommandLine()); + } + } + + public function testShouldNotThrowALogicExceptionIfNoPrefix() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->getProcess(); + + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php"', $process->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php'", $process->getCommandLine()); + } + } + + public function testShouldReturnProcessWithDisabledOutput() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->disableOutput() + ->getProcess(); + + $this->assertTrue($process->isOutputDisabled()); + } + + public function testShouldReturnProcessWithEnabledOutput() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->disableOutput() + ->enableOutput() + ->getProcess(); + + $this->assertFalse($process->isOutputDisabled()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + * @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings or stream resources. + */ + public function testInvalidInput() + { + $builder = ProcessBuilder::create(); + $builder->setInput(array()); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php new file mode 100644 index 000000000..0d763a470 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Exception\ProcessFailedException; + +/** + * @author Sebastian Marek + */ +class ProcessFailedExceptionTest extends \PHPUnit_Framework_TestCase +{ + /** + * tests ProcessFailedException throws exception if the process was successful. + */ + public function testProcessFailedExceptionThrowsException() + { + $process = $this->getMock( + 'Symfony\Component\Process\Process', + array('isSuccessful'), + array('php') + ); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(true)); + + $this->setExpectedException( + '\InvalidArgumentException', + 'Expected a failed process, but the given process was successful.' + ); + + new ProcessFailedException($process); + } + + /** + * tests ProcessFailedException uses information from process output + * to generate exception message. + */ + public function testProcessFailedExceptionPopulatesInformationFromProcessOutput() + { + $cmd = 'php'; + $exitCode = 1; + $exitText = 'General error'; + $output = 'Command output'; + $errorOutput = 'FATAL: Unexpected error'; + $workingDirectory = getcwd(); + + $process = $this->getMock( + 'Symfony\Component\Process\Process', + array('isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'), + array($cmd) + ); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(false)); + + $process->expects($this->once()) + ->method('getOutput') + ->will($this->returnValue($output)); + + $process->expects($this->once()) + ->method('getErrorOutput') + ->will($this->returnValue($errorOutput)); + + $process->expects($this->once()) + ->method('getExitCode') + ->will($this->returnValue($exitCode)); + + $process->expects($this->once()) + ->method('getExitCodeText') + ->will($this->returnValue($exitText)); + + $process->expects($this->once()) + ->method('isOutputDisabled') + ->will($this->returnValue(false)); + + $process->expects($this->once()) + ->method('getWorkingDirectory') + ->will($this->returnValue($workingDirectory)); + + $exception = new ProcessFailedException($process); + + $this->assertEquals( + "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}", + $exception->getMessage() + ); + } + + /** + * Tests that ProcessFailedException does not extract information from + * process output if it was previously disabled. + */ + public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput() + { + $cmd = 'php'; + $exitCode = 1; + $exitText = 'General error'; + $workingDirectory = getcwd(); + + $process = $this->getMock( + 'Symfony\Component\Process\Process', + array('isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'), + array($cmd) + ); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(false)); + + $process->expects($this->never()) + ->method('getOutput'); + + $process->expects($this->never()) + ->method('getErrorOutput'); + + $process->expects($this->once()) + ->method('getExitCode') + ->will($this->returnValue($exitCode)); + + $process->expects($this->once()) + ->method('getExitCodeText') + ->will($this->returnValue($exitText)); + + $process->expects($this->once()) + ->method('isOutputDisabled') + ->will($this->returnValue(true)); + + $process->expects($this->once()) + ->method('getWorkingDirectory') + ->will($this->returnValue($workingDirectory)); + + $exception = new ProcessFailedException($process); + + $this->assertEquals( + "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}", + $exception->getMessage() + ); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessInSigchildEnvironment.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessInSigchildEnvironment.php new file mode 100644 index 000000000..3977bcdcf --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessInSigchildEnvironment.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Process; + +class ProcessInSigchildEnvironment extends Process +{ + protected function isSigchildEnabled() + { + return true; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessUtilsTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessUtilsTest.php new file mode 100644 index 000000000..e6564cde5 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessUtilsTest.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\Process\Tests; + +use Symfony\Component\Process\ProcessUtils; + +class ProcessUtilsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider dataArguments + */ + public function testEscapeArgument($result, $argument) + { + $this->assertSame($result, ProcessUtils::escapeArgument($argument)); + } + + public function dataArguments() + { + if ('\\' === DIRECTORY_SEPARATOR) { + return array( + array('"\"php\" \"-v\""', '"php" "-v"'), + array('"foo bar"', 'foo bar'), + array('^%"path"^%', '%path%'), + array('"<|>\\" \\"\'f"', '<|>" "\'f'), + array('""', ''), + array('"with\trailingbs\\\\"', 'with\trailingbs\\'), + ); + } + + return array( + array("'\"php\" \"-v\"'", '"php" "-v"'), + array("'foo bar'", 'foo bar'), + array("'%path%'", '%path%'), + array("'<|>\" \"'\\''f'", '<|>" "\'f'), + array("''", ''), + array("'with\\trailingbs\\'", 'with\trailingbs\\'), + ); + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildDisabledProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildDisabledProcessTest.php new file mode 100644 index 000000000..fdae5ec25 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildDisabledProcessTest.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\Process\Tests; + +class SigchildDisabledProcessTest extends AbstractProcessTest +{ + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testGetExitCode() + { + parent::testGetExitCode(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testGetExitCodeIsNullOnStart() + { + parent::testGetExitCodeIsNullOnStart(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testGetExitCodeIsNullOnWhenStartingAgain() + { + parent::testGetExitCodeIsNullOnWhenStartingAgain(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testExitCodeCommandFailed() + { + parent::testExitCodeCommandFailed(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testMustRun() + { + parent::testMustRun(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testSuccessfulMustRunHasCorrectExitCode() + { + parent::testSuccessfulMustRunHasCorrectExitCode(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testMustRunThrowsException() + { + parent::testMustRunThrowsException(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessIsSignaledIfStopped() + { + parent::testProcessIsSignaledIfStopped(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. + */ + public function testProcessWithTermSignal() + { + parent::testProcessWithTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. + */ + public function testProcessIsNotSignaled() + { + parent::testProcessIsNotSignaled(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. + */ + public function testProcessWithoutTermSignal() + { + parent::testProcessWithoutTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testCheckTimeoutOnStartedProcess() + { + parent::testCheckTimeoutOnStartedProcess(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. + */ + public function testGetPid() + { + parent::testGetPid(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. + */ + public function testGetPidIsNullBeforeStart() + { + parent::testGetPidIsNullBeforeStart(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. + */ + public function testGetPidIsNullAfterRun() + { + parent::testGetPidIsNullAfterRun(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testExitCodeText() + { + $process = $this->getProcess('qdfsmfkqsdfmqmsd'); + $process->run(); + + $process->getExitCodeText(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testExitCodeTextIsNullWhenExitCodeIsNull() + { + parent::testExitCodeTextIsNullWhenExitCodeIsNull(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testIsSuccessful() + { + parent::testIsSuccessful(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testIsSuccessfulOnlyAfterTerminated() + { + parent::testIsSuccessfulOnlyAfterTerminated(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testIsNotSuccessful() + { + parent::testIsNotSuccessful(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. + */ + public function testTTYCommandExitCode() + { + parent::testTTYCommandExitCode(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process can not be signaled. + */ + public function testSignal() + { + parent::testSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. + */ + public function testProcessWithoutTermSignalIsNotSignaled() + { + parent::testProcessWithoutTermSignalIsNotSignaled(); + } + + public function testStopWithTimeoutIsActuallyWorking() + { + $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); + } + + public function testExitCodeIsAvailableAfterSignal() + { + $this->markTestSkipped('Signal is not supported in sigchild environment'); + } + + public function testRunProcessWithTimeout() + { + $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); + } + + public function provideStartMethods() + { + return array( + array('start', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'), + array('run', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'), + array('mustRun', 'Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'), + ); + } + + /** + * {@inheritdoc} + */ + protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) + { + $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $input, $timeout, $options); + $process->setEnhanceSigchildCompatibility(false); + + return $process; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildEnabledProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildEnabledProcessTest.php new file mode 100644 index 000000000..2668a9b4b --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildEnabledProcessTest.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\Process\Tests; + +class SigchildEnabledProcessTest extends AbstractProcessTest +{ + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. + */ + public function testProcessIsSignaledIfStopped() + { + parent::testProcessIsSignaledIfStopped(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. + */ + public function testProcessWithTermSignal() + { + parent::testProcessWithTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. + */ + public function testProcessIsNotSignaled() + { + parent::testProcessIsNotSignaled(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. + */ + public function testProcessWithoutTermSignal() + { + parent::testProcessWithoutTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. + */ + public function testGetPid() + { + parent::testGetPid(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. + */ + public function testGetPidIsNullBeforeStart() + { + parent::testGetPidIsNullBeforeStart(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. + */ + public function testGetPidIsNullAfterRun() + { + parent::testGetPidIsNullAfterRun(); + } + + public function testExitCodeText() + { + $process = $this->getProcess('qdfsmfkqsdfmqmsd'); + $process->run(); + + $this->assertInternalType('string', $process->getExitCodeText()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process can not be signaled. + */ + public function testSignal() + { + parent::testSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. + */ + public function testProcessWithoutTermSignalIsNotSignaled() + { + parent::testProcessWithoutTermSignalIsNotSignaled(); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); + } + + public function testExitCodeIsAvailableAfterSignal() + { + $this->markTestSkipped('Signal is not supported in sigchild environment'); + } + + public function testStartAfterATimeout() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Restarting a timed-out process on Windows is not supported in sigchild environment'); + } + parent::testStartAfterATimeout(); + } + + public function testStopWithTimeoutIsActuallyWorking() + { + $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); + } + + public function testRunProcessWithTimeout() + { + $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); + } + + public function testCheckTimeoutOnStartedProcess() + { + $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); + } + + /** + * {@inheritdoc} + */ + protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) + { + $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $input, $timeout, $options); + $process->setEnhanceSigchildCompatibility(true); + + return $process; + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SignalListener.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SignalListener.php new file mode 100644 index 000000000..4206550f5 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SignalListener.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// required for signal handling +declare (ticks = 1); + +pcntl_signal(SIGUSR1, function () {echo 'Caught SIGUSR1'; exit;}); + +$n = 0; + +// ticks require activity to work - sleep(4); does not work +while ($n < 400) { + usleep(10000); + ++$n; +} + +return; diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SimpleProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SimpleProcessTest.php new file mode 100644 index 000000000..78f20eb10 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SimpleProcessTest.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Process; + +class SimpleProcessTest extends AbstractProcessTest +{ + private $enabledSigchild = false; + + protected function setUp() + { + ob_start(); + phpinfo(INFO_GENERAL); + + $this->enabledSigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + public function testGetExitCode() + { + $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case + parent::testGetExitCode(); + } + + public function testExitCodeCommandFailed() + { + $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case + parent::testExitCodeCommandFailed(); + } + + public function testProcessIsSignaledIfStopped() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); + parent::testProcessIsSignaledIfStopped(); + } + + public function testProcessWithTermSignal() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); + parent::testProcessWithTermSignal(); + } + + public function testProcessIsNotSignaled() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); + parent::testProcessIsNotSignaled(); + } + + public function testProcessWithoutTermSignal() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); + parent::testProcessWithoutTermSignal(); + } + + public function testExitCodeText() + { + $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case + parent::testExitCodeText(); + } + + public function testIsSuccessful() + { + $this->skipIfPHPSigchild(); // This test use PID that is not available in this case + parent::testIsSuccessful(); + } + + public function testIsNotSuccessful() + { + $this->skipIfPHPSigchild(); // This test use PID that is not available in this case + parent::testIsNotSuccessful(); + } + + public function testGetPid() + { + $this->skipIfPHPSigchild(); // This test use PID that is not available in this case + parent::testGetPid(); + } + + public function testGetPidIsNullBeforeStart() + { + $this->skipIfPHPSigchild(); // This test use PID that is not available in this case + parent::testGetPidIsNullBeforeStart(); + } + + public function testGetPidIsNullAfterRun() + { + $this->skipIfPHPSigchild(); // This test use PID that is not available in this case + parent::testGetPidIsNullAfterRun(); + } + + public function testSignal() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + parent::testSignal(); + } + + public function testProcessWithoutTermSignalIsNotSignaled() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); + parent::testProcessWithoutTermSignalIsNotSignaled(); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + $this->skipIfPHPSigchild(); // This test use PID that is not available in this case + parent::testProcessThrowsExceptionWhenExternallySignaled(); + } + + public function testExitCodeIsAvailableAfterSignal() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + parent::testExitCodeIsAvailableAfterSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Can not send signal on a non running process. + */ + public function testSignalProcessNotRunning() + { + parent::testSignalProcessNotRunning(); + } + + public function testSignalWithWrongIntSignal() + { + if ($this->enabledSigchild) { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } else { + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `-4`.'); + } + parent::testSignalWithWrongIntSignal(); + } + + public function testSignalWithWrongNonIntSignal() + { + if ($this->enabledSigchild) { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } else { + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `Céphalopodes`.'); + } + parent::testSignalWithWrongNonIntSignal(); + } + + public function testStopTerminatesProcessCleanly() + { + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + $process->stop(); + }); + $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException'); + } + + public function testKillSignalTerminatesProcessCleanly() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(defined('SIGKILL') ? SIGKILL : 9); + } + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + + public function testTermSignalTerminatesProcessCleanly() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(defined('SIGTERM') ? SIGTERM : 15); + } + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + + public function testStopWithTimeoutIsActuallyWorking() + { + $this->skipIfPHPSigchild(); + + parent::testStopWithTimeoutIsActuallyWorking(); + } + + /** + * {@inheritdoc} + */ + protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) + { + return new Process($commandline, $cwd, $env, $input, $timeout, $options); + } + + private function skipIfPHPSigchild() + { + if ($this->enabledSigchild) { + $this->markTestSkipped('Your PHP has been compiled with --enable-sigchild, this test can not be executed'); + } + } + + private function expectExceptionIfPHPSigchild($classname, $message) + { + if ($this->enabledSigchild) { + $this->setExpectedException($classname, $message); + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/composer.json b/modules/devshop/devshop_projects/drush/vendor/symfony/process/composer.json new file mode 100644 index 000000000..b3cb5186f --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Symfony Process 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" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Process\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/phpunit.xml.dist b/modules/devshop/devshop_projects/drush/vendor/symfony/process/phpunit.xml.dist new file mode 100644 index 000000000..788500084 --- /dev/null +++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/modules/devshop/devshop_projects/images/loader.gif b/modules/devshop/devshop_projects/images/loader.gif new file mode 100644 index 000000000..5b33f7e54 Binary files /dev/null and b/modules/devshop/devshop_projects/images/loader.gif differ diff --git a/modules/devshop/devshop_projects/images/loading.gif b/modules/devshop/devshop_projects/images/loading.gif new file mode 100644 index 000000000..bd6dd9741 Binary files /dev/null and b/modules/devshop/devshop_projects/images/loading.gif differ diff --git a/modules/devshop/devshop_projects/images/spinner-white.gif b/modules/devshop/devshop_projects/images/spinner-white.gif new file mode 100644 index 000000000..f27d7cd48 Binary files /dev/null and b/modules/devshop/devshop_projects/images/spinner-white.gif differ diff --git a/modules/devshop/devshop_projects/inc/add-environment-options.tpl.php b/modules/devshop/devshop_projects/inc/add-environment-options.tpl.php new file mode 100644 index 000000000..e69de29bb diff --git a/modules/devshop/devshop_projects/inc/admin.inc b/modules/devshop/devshop_projects/inc/admin.inc new file mode 100644 index 000000000..54f0120e0 --- /dev/null +++ b/modules/devshop/devshop_projects/inc/admin.inc @@ -0,0 +1,88 @@ + 'fieldset', + '#title' => t('Paths'), + ); + $form['paths']['devshop_projects_allow_custom_code_path'] = array( + '#title' => t('Allow custom code path per project.'), + '#description' => t('Allow each project to have a custom "Code Path". If not checked, project paths are set as "/var/aegir/projects/{PROJECT_NAME}.'), + '#type' => 'checkbox', + '#default_value' => variable_get('devshop_projects_allow_custom_code_path', FALSE), + ); + $form['paths']['devshop_project_base_path'] = array( + '#title' => t('Projects Base Path'), + '#type' => 'textfield', + '#description' => t('The default base path that all projects will be created in. Projects each get their own folder inside this path.'), + '#default_value' => variable_get('devshop_project_base_path', '/var/aegir/projects'), + ); + $form['paths']['devshop_project_default_drupal_path'] = array( + '#title' => t('Default path to Drupal'), + '#type' => 'textfield', + '#description' => t("If index.php isn't in the root of the git repo, you can edit the 'Path to Drupal' setting on each project. Set a default 'Path to Drupal' here. (For example, an Acquia hosted repo uses 'docroot'.)"), + '#default_value' => variable_get('devshop_project_default_drupal_path', ''), + ); + + + $form['urls'] = array( + '#type' => 'fieldset', + '#title' => t('URLs'), + ); + + $form['urls']['devshop_projects_allow_custom_base_url'] = array( + '#title' => t('Allow custom Environment Domain Name Pattern per project.'), + '#description' => t('If enabled each project can set an Environment URL Pattern.'), + '#type' => 'checkbox', + '#default_value' => variable_get('devshop_projects_allow_custom_base_url', FALSE), + ); + $form['urls']['devshop_project_environment_url_pattern'] = array( + '#title' => t('Environment Domain Name Pattern'), + '#type' => 'textfield', + '#description' => t("Each environment will have a system domain name generated for it based on it's name. Use @project for project name, @hostname for '%host', @environment for the environment's name.", array('%host' => $_SERVER['SERVER_NAME'])), + '#default_value' => variable_get('devshop_project_environment_url_pattern', '@project.@environment.@hostname'), + '#element_validate' => array( + 'devshop_project_settings_validate_environment_url_pattern', + ), + ); + +// $form['support'] = array( +// '#type' => 'fieldset', +// '#title' => t('DevShop Support'), +// ); +// $form['support']['devshop_support_widget_enable'] = array( +// '#title' => t('Show Help Widget'), +// '#description' => t('Uncheck this box if you want to hide the Help widget that appears at the bottom right of the page.'), +// '#type' => 'checkbox', +// '#default_value' => variable_get('devshop_support_widget_enable', TRUE), +// ); + + // Server settings. + $form['server'] = array( + '#type' => 'fieldset', + '#title' => t('DevShop Server'), + ); + $form['server']['devshop_public_key'] = array( + '#title' => t('Aegir User Public Key'), + '#description' => t('The public key of the aegir user on this server. If you change the SSH keys located at /var/aegir/.ssh/id_rsa.pub, then you should update this variable. This variable is for user reference only. If using GitHub API, then devshop will check to make sure the server has access using this public key.'), + '#type' => 'textarea', + '#default_value' => variable_get('devshop_public_key', ''), + ); + return system_settings_form($form); +} + +/** + * Validate that the default environment domain pattern has @environment and @project. + */ +function devshop_project_settings_validate_environment_url_pattern($element, &$form_state, $form) { + if (strpos($form_state['values']['devshop_project_environment_url_pattern'], '@environment') === FALSE || strpos($form_state['values']['devshop_project_environment_url_pattern'], '@project') === FALSE) { + form_error($element, t('The placeholders @project and @environment must be in the Environment Domain Name Pattern.')); + } +} \ No newline at end of file diff --git a/modules/devshop/devshop_projects/inc/create-wizard.inc b/modules/devshop/devshop_projects/inc/create-wizard.inc new file mode 100644 index 000000000..a4abe2daf --- /dev/null +++ b/modules/devshop/devshop_projects/inc/create-wizard.inc @@ -0,0 +1,2 @@ + NULL, + ); + + // Setup project + $project = ctools_object_cache_get('project', 'devshop_project'); + + // Setup Step. + if ($step == NULL) { + drupal_goto('projects/add/' . current(array_keys($form_info['order']))); + } + + // Create default project object + if (empty($project) || !isset($project->git_url)) { + // set form to first step -- we have no data + $step = current(array_keys($form_info['order'])); + $project = new stdClass(); + $project->step = $step; + $project->git_url = ''; + $project->nid = NULL; + $project->name = ''; + + // These are empty until we have our project name + $project->code_path = ''; + $project->base_url = ''; + + // ** set the storage object so its ready for whatever comes next + ctools_object_cache_set('project', 'devshop_project', $project); + } + else { + // Load latest project object from database and use that for the ctools cache. + $project_node = node_load($project->nid); + $project = $project_node->project; + + // If we have a name but no code_path or base URL. + if (!empty($project->name) && empty($project->code_path)) { + $project->code_path = variable_get('devshop_project_base_path', '/var/aegir/projects') . '/' . $project->name; + } + if (!empty($project->name) && empty($project->base_url)) { + $project->base_url = variable_get('devshop_project_environment_url_pattern', "@project.@environment.@hostname"); + } + + // Save the current step + $project->step = $step; + ctools_object_cache_set('project', 'devshop_project', $project); + } + + // Check verification status + if (!empty($project->nid)) { + $tasks = hosting_task_fetch_tasks($project->nid); + } + if (isset($tasks['verify']['nid'])) { + // Get "verify" task status for the project + $project->verify_task_status = isset($tasks['verify']['task_status']) ? $tasks['verify']['task_status'] : HOSTING_TASK_ERROR; + $project->verify_task_nid = $tasks['verify']['nid']; + } + else { + $project->verify_task_status = NULL; + $project->verify_task_nid = NULL; + } + + // If project verification failed, we need to ask for a new git url. + if ($project->verify_task_status == HOSTING_TASK_ERROR && !empty($project_node->nid)) { + $messages = db_query("SELECT * FROM {hosting_task_log} WHERE nid = :nid AND type LIKE :type ORDER BY vid, lid", array(':nid' => $project->verify_task_nid, ':type' => 'devshop%')); + + foreach ($messages as $message) { + if ($message->type == 'devshop_info') { + $messages_to_user[] = $message->message; + } + } + $project->verify_error = theme('devshop_ascii', array('output' => implode('\n', $messages_to_user))); + + + // If not on the first step, go to it. + if ($step != current(array_keys($form_info['order']))) { + drupal_goto('projects/add/' . current(array_keys($form_info['order']))); + } + } + else { + $project->verify_error = NULL; + } + + // All forms can access $form_state['project']; + $form_state['project'] = $project; + + // Saving the last visited step for redirects from node + $_SESSION['last_step'] = $step; + + // Generate our ctools form and output + $output = ctools_wizard_multistep_form($form_info, $step, $form_state); + return $output; +} + +/** + * The form_info for the ctools wizard + */ +function devshop_projects_create_wizard_info() { + return array( + 'id' => 'devshop_project_create', + 'path' => "projects/add/%step", + 'show trail' => TRUE, + 'show back' => TRUE, + 'show cancel' => TRUE, + 'show return' => FALSE, + 'next text' => 'Next', + 'next callback' => 'devshop_projects_create_wizard_next', + 'finish callback' => 'devshop_projects_create_wizard_finish', + 'cancel callback' => 'devshop_projects_create_wizard_cancel', + 'order' => array( + 'git_url' => t('Step 1: Source'), + 'settings' => t('Step 2: Settings'), + 'environments' => t('Step 3: Environments'), + 'sites' => t('Step 4: Install Profile'), + ), + 'forms' => array( + 'git_url' => array( + 'form id' => 'devshop_project_create_step_git' + ), + 'settings' => array( + 'form id' => 'devshop_project_create_step_settings' + ), + 'environments' => array( + 'form id' => 'devshop_project_create_step_environments' + ), + 'sites' => array( + 'form id' => 'devshop_project_create_step_sites' + ), + ), + ); +} + +/** + * WIZARD TOOLS + */ + + +/** + * NEXT callback + * Saves anything in $form_state['project'] to ctools cache. + * + * The form submit callbacks are responsible for putting data into + * $form_state['project']. + */ +function devshop_projects_create_wizard_next(&$form_state) { + $project = &$form_state['project']; + $cache = ctools_object_cache_set('project', 'devshop_project', $project); +} + + + + +/** + * CANCEL callback + * Callback generated when the 'cancel' button is clicked. + * Remove the project data cache and send back to projects page. + */ +function devshop_projects_create_wizard_cancel(&$form_state) { + // Update the cache with changes. + $project = &$form_state['project']; + ctools_object_cache_clear('project', 'devshop_project'); + + // Redirect to projects list + $form_state['redirect'] = 'projects'; + + // If we have a project node, create a "delete" hosting task + if (!empty($project->nid)) { + hosting_add_task($project->nid, 'delete'); + } + + // Message + drupal_set_message(t('Project creation cancelled.')); + + // Removing last step session variable. + unset($_SESSION['last_step']); + +} + +/** + * FINISH callback + * Callback generated when the add page process is finished. + */ +function devshop_projects_create_wizard_finish(&$form_state) { + + $project = &$form_state['project']; + $project_node = node_load($project->nid); + + // Save the extra options to the project node. + $project_node->project->install_profile = $form_state['values']['install_profile']; + + // Create the site nodes, saving to the environment. + // @TODO: Can we speed things up here by only running install for the first, + // then "Cloning" to create the rest? + foreach ($project_node->project->environments as $environment_name => &$environment) { + // @TODO: Does this set the http_server as well?? Doesn't look like it. + $db_server = $environment->db_server_nid; + $site_node = devshop_projects_create_site($project_node->project, node_load($environment->platform), $environment_name, $db_server); + $environment->site = $site_node->nid; + } + + // Set to not verify and to publish. + $project_node->no_verify = TRUE; + $project_node->status = 1; + node_save($project_node); + + ctools_object_cache_clear('project', 'devshop_project'); + $form_state['redirect'] = 'node/' . $project_node->nid; + + // Removing last step session variable. + unset($_SESSION['last_step']); + + // Friendly message + drupal_get_messages('status'); + drupal_set_message(t('Your project has been created. Your sites are being installed.')); +} + +/** + * Returns JSON showing the state of the project + */ +function devshop_projects_add_status($type = 'platform') { + $return = array(); + + // Get Project Cache + ctools_include('wizard'); + ctools_include('object-cache'); + $project = ctools_object_cache_get('project', 'devshop_project'); + + + $project_node = node_load($project->nid); + + $all_tasks_completed = TRUE; + $nids = array(); + + // When checking project... + if ($type == 'project') { + $nids = array($project_node->nid); + } + + // When checking platforms... + if ($type == 'platform') { + foreach ($project_node->project->environments as $name => $environment) { + $nids[] = $environment->platform; + } + } + + // Check verification task for all nids + foreach ($nids as $nid) { + $platform = node_load($nid); + $profiles_shortnames = hosting_get_profiles($platform->nid, 'short_name'); + if (is_array($profiles_shortnames) && !empty($profiles_shortnames)) { + $install_profiles = array_combine($profiles_shortnames, (array) hosting_get_profiles($platform->nid)); + } + else { + $install_profiles = array(); + } + + $status = _hosting_parse_error_code($platform->environment->last_task_node->task_status); + if ($status == 'Queued') { + $status = '...'; + } + $return['tasks'][$nid] = array( + 'status' => $status, + 'version' => $platform->release->version, + 'profiles' => implode(', ', $install_profiles), + ); + // If task is not completed, mark all tasks not complete. + if ($platform->environment->last_task_node->task_status == HOSTING_TASK_SUCCESS || $platform->environment->last_task_node->task_status == HOSTING_TASK_ERROR || $platform->environment->last_task_node->task_status == HOSTING_TASK_WARNING) { + continue; + } + else { + $all_tasks_completed = FALSE; + } + + } + $return['tasks_complete'] = $all_tasks_completed; + drupal_json_output($return); + exit; +} + +/** + * Helper for adding reloading feature to form + */ +function devshop_form_reloader(&$form, $type = 'platform') { + // Add JS that reloads the page when tasks finish. + $form['item'] = array( + '#type' => 'item', + '#value' => '', + '#weight' => 10 + ); + $settings['devshopReload'] = array( + 'type' => $type, + 'delay' => 1000, + ); + + drupal_add_js($settings, array('type' => 'setting')); + drupal_add_js(drupal_get_path('module', 'devshop_projects') . '/inc/reload.js'); +} diff --git a/modules/devshop/devshop_projects/inc/create/create.js b/modules/devshop/devshop_projects/inc/create/create.js new file mode 100644 index 000000000..96a0ce5bd --- /dev/null +++ b/modules/devshop/devshop_projects/inc/create/create.js @@ -0,0 +1,50 @@ +/** + * Start New Project: + * + * Step 1: Takes the entered name and creates a base url and code path from it. + */ +(function ($) { + Drupal.behaviors.createStep1 = { + attach: function (context, settings) { + + $( "#edit-title" ).keyup(function(event) { + + // Extract project name and base path and URL + var projectName = $(this).val(); + var base_path = $('#edit-code-path').attr('data-base_path') + '/' + projectName; + var base_url = projectName + '.' + $('#edit-base-url').attr('data-base_url'); + + $('#edit-code-path').val(base_path); + $('#edit-base-url').val(base_url); + + }); + + } + }; +}(jQuery)); + +/** + * Step 2: Settings + */ +(function ($) { + Drupal.behaviors.createStep2 = { + attach: function (context, settings) { + + // Hide unless + $('#edit-project-settings-live-live-domain-www-wrapper').hide(); + $('#edit-project-settings-live-environment-aliases-wrapper').hide(); + + $('#edit-project-settings-live-live-domain').keyup(function(){ + if ($(this).val()){ + $('#edit-project-settings-live-live-domain-www-wrapper').show(); + $('#edit-project-settings-live-environment-aliases-wrapper').show(); + } + else { + $('#edit-project-settings-live-live-domain-www-wrapper').hide(); + $('#edit-project-settings-live-environment-aliases-wrapper').hide(); + } + }); + + } + }; +}(jQuery)); diff --git a/modules/devshop/devshop_projects/inc/create/step-1.inc b/modules/devshop/devshop_projects/inc/create/step-1.inc new file mode 100644 index 000000000..2528e49a5 --- /dev/null +++ b/modules/devshop/devshop_projects/inc/create/step-1.inc @@ -0,0 +1,242 @@ +verify_error) { + $form['note'] = array( + '#markup' => t('We were unable to connect to your git repository. Check the messages, edit your settings, and try again.'), + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + $form['error'] = array( + '#markup' => $project->verify_error, + ); + + // Check for "host key" + if (strpos($project->verify_error, 'Host key verification failed')) { + $form['help'] = array( + '#markup' => t('Looks like you need to authorize this host. SSH into the server as aegir user and run the command git ls-remote !repo.
    Add StrictHostKeyChecking no to your ~/.ssh/config file to avoid this for all domains in the future.', array( + '!repo' => $project->git_url, + )), + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + } + } + + if (empty($project->name)) { + $form['title'] = array( + '#type' => 'textfield', + '#title' => t('Project Code Name'), + '#required' => TRUE, + '#suffix' => '

    ' . t('Choose a unique name for your project. Only letters and numbers are allowed.') . '

    ', + '#size' => 40, + '#maxlength' => 255, + ); + } + else { + $form['title'] = array( + '#type' => 'value', + '#value' => $project->name, + ); + $form['title_display'] = array( + '#type' => 'item', + '#title' => t('Project Code Name'), + '#markup' => $project->name, + ); + } + + $username = variable_get('aegir_user', 'aegir'); + + $tips[] = t('Use the "ssh" url whenever possible.'); + $tips[] = t('Code will be cloned to the server using the aegir user. Check the "Repository Access" for SSH information.'); + $tips[] = t('An entire Drupal codebase must be present in the repository, but does not have to be in the root. See Path to Drupal setting on the next page.'); + $tips[] = t('If a composer.json file is found in the root of the project, composer install will be run automatically.'); + $tips = theme('item_list', array('items' => $tips)); + + $form['git_url'] = array( + '#type' => 'textfield', + '#required' => 1, + '#title' => t('Git URL'), + '#suffix' => '

    ' . t('Enter the Git URL for your project') . '

    ' . $tips, + '#default_value' => $project->git_url, + '#maxlength' => 1024, + ); +// +// // Project code path. +// $form['code_path'] = array( +// '#type' => variable_get('devshop_projects_allow_custom_code_path', FALSE) ? 'textfield' : 'value', +// '#title' => t('Code path'), +// '#description' => t('The absolute path on the filesystem that will be used to create all platforms within this project. There must not be a file or directory at this path.'), +// '#required' => variable_get('devshop_projects_allow_custom_code_path', FALSE), +// '#size' => 40, +// '#default_value' => $project->code_path, +// '#maxlength' => 255, +// '#attributes' => array( +// 'data-base_path' => variable_get('devshop_project_base_path', '/var/aegir/projects'), +// ), +// ); +// +// // Project base url +// $form['base_url'] = array( +// '#type' => variable_get('devshop_projects_allow_custom_base_url', FALSE) ? 'textfield' : 'value', +// '#title' => t('Base URL'), +// '#description' => t('All sites will be under a subdomain of this domain.'), +// '#required' => variable_get('devshop_projects_allow_custom_base_url', FALSE), +// '#size' => 40, +// '#default_value' => $project->base_url, +// '#maxlength' => 255, +// '#attributes' => array( +// 'data-base_url' => devshop_projects_url($project->name, ''), +// ), +// ); + + // Display helpful tips for connecting. + $pubkey = variable_get('devshop_public_key', ''); + + // If we don't yet have the server's public key saved as a variable... + if (empty($pubkey)) { + $output = t("This DevShop doesn't yet know your server's public SSH key. To import it, run the following command on your server as aegir user:"); + $command = 'drush @hostmaster vset devshop_public_key "$(cat ~/.ssh/id_rsa.pub)" --yes'; + $output .= "
    "; + } + else { + // @TODO: Make this Translatable + $output = <<If you haven't granted this server access to your Git repository, you should do so now using it's public SSH key. + +HTML; + } + + // Add info about connecting to Repo + $form['connect'] = array( + '#type' => 'fieldset', + '#title' => t('Repository Access'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['connect']['public_key'] = array( + '#markup' => $output, + ); + return $form; +} + +/** + * STEP 1: Validate + */ +function devshop_project_create_step_git_validate(&$form, &$form_state) { + $project = &$form_state['project']; + + if (empty($project->nid)) { + + // PROJECT NAME + // No spaces or special characters allowed. + $project_name = strtolower(trim($form_state['values']['title'])); // domain names are case-insensitive + + // Set the value to our trimmed and lowercased proejct name. + form_set_value($form['title'], $project_name, $form_state); + + // Check for bad characters. + if (!ctype_alnum($project_name)) { + form_set_error('title', t("You have not specified a valid project codename. Only numbers and letters are allowed.")); + } + + // Check for duplicate project name here. + // hosting_context_load() only works if a node exists with that ID. + if (db_query("SELECT nid FROM {hosting_context} WHERE name = :name", array(':name' => 'project_' . $project_name))->fetch()) { + form_set_error('title', t('The name @project is in use. Please try again.', array( + '@project' => $project_name, + ))); + } + } + +// +// // CODE PATH & BASE URL +// // Code path and base url must be unique +// if (db_query('SELECT n.nid FROM {hosting_devshop_project} d LEFT JOIN {node} n ON d.nid = n.nid WHERE status = :status AND code_path = :code_path;', array(':status' => 1, ':code_path' => $form_state['values']['code_path']))->fetchField()) { +// form_set_error('code_path', t('Another project already has this code path. Code path must be unique.')); +// } +// if (db_query('SELECT n.nid FROM {hosting_devshop_project} d LEFT JOIN {node} n ON d.nid = n.nid WHERE status = :status AND base_url = :base_url;', array(':status' => 1, ':base_url' => $form_state['values']['base_url']))->fetchField()) { +// form_set_error('base_url', t('Another project already has this base url. Base URL must be unique.')); +// } +// +// // If custom code paths are not allowed, set the value here using the project name. +// if (!variable_get('devshop_projects_allow_custom_code_path', FALSE)) { +// form_set_value($form['code_path'], variable_get('devshop_project_base_path', '/var/aegir/projects') . '/' . $project_name, $form_state); +// } +// +// // If custom base URLs are not allowed, set the value here using the project name. +// if (!variable_get('devshop_projects_allow_custom_base_url', FALSE)) { +// form_set_value($form['base_url'], devshop_projects_url($project_name), $form_state); +// } +} + +/** + * STEP 1: Submit + */ +function devshop_project_create_step_git_submit(&$from, &$form_state) { + global $user; + + $project = &$form_state['project']; + + // If the project already exists, this means the git url has changed... + if ($project->nid) { + // Change the git url and save the node. Verification will run again. + $node = node_load($project->nid); + $node->project->git_url = $form_state['values']['git_url']; + $node->project->code_path = $form_state['values']['code_path']; + $node->project->base_url = $form_state['values']['base_url']; + node_save($node); + } + // Create new project node + elseif (empty($project->nid)) { + // Create the project node now. We will update it with the chosen path. + // This is so we can check for branches and save the hosting_context as soon + // as possible. + $node = new stdClass; + $node->title = $form_state['values']['title']; + + $node->type = 'project'; + $node->status = 0; + $node->uid = $user->uid; + $node->name = $user->name; + + // Create project object + $project->name = $form_state['values']['title']; + $project->git_url = $form_state['values']['git_url']; + $project->code_path = $form_state['values']['code_path']; + $project->base_url = $form_state['values']['base_url']; + + // @TODO: We will clone the code for step 2 and look for drupal. + $project->drupal_path = variable_get('devshop_projects_default_drupal_path', ''); + + // Attach to node + $node->project = $project; + + // Save project node, triggering verification. + if ($node = node_submit($node)) { + node_save($node); + } + + // Save NID to ctools object cache. + if ($node->nid) { + $project->nid = $node->nid; + } + } + + // Remove default "task" messages. + drupal_get_messages(); +} diff --git a/modules/devshop/devshop_projects/inc/create/step-2.inc b/modules/devshop/devshop_projects/inc/create/step-2.inc new file mode 100644 index 000000000..c28271f3e --- /dev/null +++ b/modules/devshop/devshop_projects/inc/create/step-2.inc @@ -0,0 +1,64 @@ +nid); + + $node_type = 'project'; + $form_id = $node_type . '_node_form'; + $form_state['build_info']['args'] = array($node); + $form_state['build_info']['files'][] = array('inc', 'node', 'node.pages'); + module_load_include('inc', 'node', 'node.pages'); + $project_form = drupal_retrieve_form($form_id, $form_state); + drupal_prepare_form($form_id, $project_form, $form_state); + // Remove node buttons + $actions = $project_form['actions']; + unset($project_form['actions']); + + // Merge project node form into ctools wizard form. + $form = array_merge($project_form, $form); + + // Make sure the project isn't re-verified on this page. + $form['no_verify'] = array( + '#type' => 'value', + '#value' => TRUE, + ); + + // Add additional additional submit and validate handlers for the node form. + $form['buttons']['next']['#submit'] = $actions['submit']['#submit']; + $form['buttons']['next']['#submit'][] = 'ctools_wizard_submit'; + + drupal_add_js(drupal_get_path('module', 'devshop_projects') . '/inc/create/create.js'); + + // Remove "live environment" selector. + $form['project']['codebase']['live_environment']['#type'] = 'value'; + $form['project']['codebase']['live_environment']['#suffix'] = ''; + + return $form; +} + +/** + * STEP 2: Validate + */ +function devshop_project_create_step_settings_validate(&$from, &$form_state) { + // Nothing is needed here. We've replaced our validator with node_form_validate, + // which passes through to devshop_projects_validate() +} + +/** + * STEP 2: Submit + */ +function devshop_project_create_step_settings_submit(&$from, &$form_state) { + // Nothing is needed here. We've replaced our validator with node_form_validate, + +} diff --git a/modules/devshop/devshop_projects/inc/create/step-3.inc b/modules/devshop/devshop_projects/inc/create/step-3.inc new file mode 100644 index 000000000..4309c40ec --- /dev/null +++ b/modules/devshop/devshop_projects/inc/create/step-3.inc @@ -0,0 +1,402 @@ +verify_task_status == HOSTING_TASK_QUEUED || $project->verify_task_status == HOSTING_TASK_PROCESSING) { + $note = '

    ' . t('Please wait while we connect to your repository and determine any branches.') . '

    '; + $form['note'] = array( + '#markup' => $note, + ); + $form['#no_next'] = TRUE; + + // Add code to reload the page when complete. + devshop_form_reloader($form, 'project'); + return $form; + } + + $form['nid'] = array( + '#type' => 'value', + '#value' => $project->nid, + ); + $form['project'] = array( + '#tree' => TRUE, + ); + $form['project']['environments'] = array( + '#theme' => 'devshop_projects_create_settings_form', + '#tree' => TRUE, + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + + // If there was a problem with loading branches or tags, hide the form and show a link to re-verify. + if (empty($project->settings->git['branches']) && empty($project->settings->git['tags'])) { + $form['error'] = array( + '#markup' => t('Unable to read branches or tags. Please !verify.', array( + '!verify' => l(t('Re-verify the project'), "hosting_confirm/{$project->nid}/project_verify", array( + 'query' => array('token' => drupal_get_token($user->uid)), + 'attributes' => array( + 'class' => array('btn btn-success'), + ), + )) + )), + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + return $form; + } + + // Ensure a blank row exists (happens when using 'Back' button). + if (!isset($project->environments['NEW']) || !is_array($project->environments['NEW'])) { + $project->environments['NEW'] = array(); + } + foreach ($project->environments as $name => $environment) { + // No platforms exist yet + if ($name == 'NEW') { + $env_title = ''; + } + else { + $env_title = $name; + } + + $form['project']['environments'][$name] = array( + '#tree' => TRUE, + '#type' => 'fieldset', + '#theme' => 'devshop_projects_settings_table', + ); + + // Environment properties + // Generate field prefix and suffix using domain name pattern. + if (variable_get('devshop_projects_allow_custom_base_url')) { + $pattern = $project->base_url; + } + else { + $pattern = variable_get('devshop_project_environment_url_pattern', '@project.@environment.@hostname'); + } + + $labels = explode('@environment', strtr($pattern, array( + '@project' => $project->name, + '@hostname' => $_SERVER['SERVER_NAME'], + ))); + $form['project']['environments'][$name]['name'] = array( + '#type' => 'textfield', + '#default_value' => $env_title, + '#size' => 6, + '#maxlength' => 64, + '#attributes' => array( + 'placeholder' => t('name'), + ), + '#field_prefix' => '
    http://' . $labels[0] . '
    ', + '#field_suffix' => '
    ' . $labels[1] .'
    +
    ', + ); + $form['project']['environments'][$name]['site'] = array( + '#type' => 'value', + '#value' => isset($environment->site) ? $environment->site : NULL, + ); + $form['project']['environments'][$name]['platform'] = array( + '#type' => 'value', + '#value' => isset($environment->platform) ? $environment->platform : NULL, + ); + $form['project']['environments'][$name]['git_ref'] = array( + '#type' => 'select', + '#options' => devshop_projects_git_ref_options($project), + '#default_value' => isset($environment->git_ref) ? $environment->git_ref : 'HEAD', + ); + + // HTTP Server select. + $http_servers = hosting_get_servers('http'); + + // Determine default web server. + $default_web_server = isset($environment->settings, $environment->settings->web_server) ? + $environment->settings->web_server : + $project->settings->default_environment['web_server']; + + // Fallback if environment or default not found. + if (empty($default_web_server)) { + $default_web_server = key($http_servers); + } + + if (count($http_servers) == 1) { + $form['project']['environments'][$name]['settings']['web_server'] = array( + '#type' => 'value', + '#value' => $default_web_server, + ); + } + elseif (count($http_servers) > 1) { + $form['project']['environments'][$name]['settings']['web_server'] = array( + '#title' => t('Web server'), + '#type' => 'select', + '#options' => $http_servers, + '#default_value' => $default_web_server + ); + } + + // DB Server select. + $db_servers = hosting_get_servers('db'); + + // Determine default db server. + $default_db_server = isset($environment->settings, $environment->settings->db_server) ? + $environment->settings->db_server : + $project->settings->default_environment['db_server']; + + // Fallback if environment or default not found. + if (empty($default_db_server) || !isset($db_servers[$default_db_server])) { + $default_db_server = key($db_servers); + } + + if (count($db_servers) == 1) { + $form['project']['environments'][$name]['settings']['db_server'] = array( + '#type' => 'value', + '#default_value' => $default_db_server, + ); + } + elseif (count($db_servers) > 1) { + $form['project']['environments'][$name]['settings']['db_server'] = array( + '#title' => t('Database server'), + '#type' => 'select', + '#options' => $db_servers, + '#default_value' => $default_db_server + ); + } + + // Add Environment + $form['add_environment'] = array( + '#type' => 'submit', + '#value' => t('Add environment'), + '#name' => 'add_environment', + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + } + $form['project']['environments']['NEW']['name']['#attributes']['autofocus'] = 'autofocus'; + return $form; +} + + +/** + * STEP 3: Validate + */ +function devshop_project_create_step_environments_validate(&$form, &$form_state) { + $project = &$form_state['project']; + + $values = &$form_state['values']; + + if (isset($values['project'])) { + + // Check environment titles + foreach ($values['project']['environments'] as $env => $env_settings) { + // Check for illegal chars + if ($env != 'NEW' && !empty($env_settings['name'])) { + if (!preg_match('!^[a-z0-9_]+$!', $env_settings['name'])) { + $form_item = 'environments][' . $env . '][name'; + form_set_error($form_item, t('The environment name must contain only lowercase letters, numbers, and underscores.')); + } + } + + // Check for unique names. + if ($env == 'NEW' && array_key_exists($env_settings['name'], $values['project']['environments'])) { + $form_item = 'environments][' . $env . '][name'; + form_set_error($form_item, t('The environment name must be unique.')); + } + if ($env != $env_settings['name'] && array_key_exists($env_settings['name'], $values['project']['environments'])) { + $form_item = 'environments][' . $env . '][name'; + form_set_error($form_item, t('The environment name must be unique.')); + } + + } + + // Reject if empty + if (count($values['project']['environments']) < 1) { + if ($form_state['clicked_button']['#name'] == 'add_environment') { + form_set_error('project][environments][NEW][name', t('Name this environment before you add another.')); + } + else { + form_set_error('project][environments][NEW][name', t('You must add at least one environment.')); + } + } + } + + +} + +///** +// * Functionality for add a new environment. +// */ +//function devshop_projects_save_environments($form, &$form_state) { +// +// $environments = $form_state['values']['project']['environments']; +// +// if (!empty($environments['NEW']['name'])) { +// $new_environment_name = $environments['NEW']['name']; +// $environments[$new_environment_name] = $environments['NEW']; +// } +// unset($environments['NEW']); +// +// // Delete all environments +// db_delete('hosting_devshop_project_environment') +// ->condition('project_nid', $form_state['values']['nid']) +// ->execute(); +// +// // Create all environments +// foreach ($environments as $environment) { +// $name = trim($environment['name']); +// if (!empty($name)) { +// $id = db_insert('hosting_devshop_project_environment') +// ->fields(array( +// 'project_nid' => $form_state['values']['nid'], +// 'name' => $environment['name'], +// 'git_ref' => $environment['git_ref'], +// 'platform' => $environment['platform'], +// 'settings' => serialize($environment['settings']), +// )) +// ->execute(); +// } +// } +// +// // Go back to the same page. +// if ($form_state['clicked_button']['#name'] == 'add_environment') { +// drupal_goto('projects/add/environments'); +// } +//} + +/** + * STEP 3: SUBMIT + */ +function devshop_project_create_step_environments_submit(&$form, &$form_state) { + // Get project and reset properties.. + $project_node = node_load($form_state['values']['nid'], NULL, TRUE); + $project = $project_node->project; + if (isset($form_state['project']->code_path) && empty($project->code_path)) { + $project->code_path = $form_state['project']->code_path; + } + if (isset($form_state['project']->drupal_path) && empty($project->drupal_path)) { + $project->drupal_path = $form_state['project']->drupal_path; + } + + // Save environments data + $environments = $form_state['values']['project']['environments']; + if (!empty($environments['NEW']['name'])) { + $new_environment_name = $environments['NEW']['name']; + $environments[$new_environment_name] = $environments['NEW']; + } + unset($environments['NEW']); + + foreach ($environments as $name => $environment) { + if ($name != $environment['name']) { + $environments[$environment['name']] = $environment; + unset($environments[$name]); + } + } + + // Delete all existing environments + db_delete('hosting_devshop_project_environment') + ->condition('project_nid', $project_node->nid) + ->execute(); + + // Create all environments + foreach ($environments as $name => $environment) { + $environment = (object) $environment; + $environment->settings = (object) $environment->settings; + $name = trim($environment->name); + + if (!empty($name)) { + $id = db_insert('hosting_devshop_project_environment') + ->fields(array( + 'project_nid' => $form_state['values']['nid'], + 'name' => $environment->name, + 'platform' => $environment->platform, + 'settings' => serialize($environment->settings), + )) + ->execute(); + } + + // Create platform node if we don't have one. + if (empty($environment->platform)) { + //If platform hasn't been created yet, build platform node + $platform = new stdClass; + $platform->type = 'platform'; + $platform->title = $project->name . '_' . $name; + + // Platform publish_path + + // If drupal path is set, repo path and platform path are different. Append drupal_path to repo path to create publish path. + if ($project->drupal_path) { + $platform->git['repo_path'] = $project->code_path . '/' . $environment->name; + $platform->publish_path = $project->code_path . '/' . $environment->name . '/' . $project->drupal_path; + } + else { + $platform->git['repo_path'] = $project->code_path . '/' . $environment->name; + $platform->publish_path = $project->code_path . '/' . $environment->name; + } + + // Git information + $platform->git['repo_url'] = $project->git_url; + $platform->git['git_ref'] = $environment->git_ref; + + // Other attributes + $platform->web_server = $environment->settings->web_server; + $platform->db_server = $environment->settings->db_server; + + // Save the node + global $user; + node_object_prepare($platform); + $platform->language = LANGUAGE_NONE; // Or e.g. 'en' if locale is enabled + $platform->uid = $user->uid; + $platform->status = 1; + $platform->promote = 0; + $platform->comment = 0; + $platform = node_submit($platform); + node_save($platform); + + if (!$platform->nid) { + form_set_error('projects', t('An unknown problem has occured: the platform node was not created for the %env environment.', array('%env' => $environment->name))); + } + $environment->platform = $platform->nid; + + // Update environment with our platform + db_update('hosting_devshop_project_environment') + ->fields(array( + 'platform' => $environment->platform, + )) + ->condition('project_nid', $form_state['values']['nid']) + ->condition('name', $environment->name) + ->execute(); + } + } + + // Go back to the same page. + if ($form_state['clicked_button']['#name'] == 'add_environment') { + drupal_goto('projects/add/environments'); + } + + // For all removed platforms, trigger a delete task + $removed_environments = array_diff_key($project->environments, $environments); + foreach ($removed_environments as $environment_name => $settings) { + $platform_nid = $settings->platform; + $platform_node = node_load($platform_nid); + + // Determine what to do here based on task status... + // if verify task hasn't even run yet (and has never run) we can just delete + // the platform node. + if (empty($platform_node->verified)) { + node_delete($platform_node); + } + // Create 'delete' task for the removed platform + else if ($platform_nid) { + hosting_add_task($platform_nid, 'delete'); + } + } + + + // Remove default "task" messages. + drupal_set_message('Environments saved! Now preparing codebases...'); +} diff --git a/modules/devshop/devshop_projects/inc/create/step-4.inc b/modules/devshop/devshop_projects/inc/create/step-4.inc new file mode 100644 index 000000000..0e0f9967f --- /dev/null +++ b/modules/devshop/devshop_projects/inc/create/step-4.inc @@ -0,0 +1,262 @@ + 'value', + '#value' => $project->nid, + ); + + // Display the platforms + $rows = array(); + $header = array( + t('Name'), + t('Branch'), + t('Version'), + t('Install Profiles'), + t('Status') + ); + $all_tasks_queued = TRUE; + $all_tasks_succeeded = TRUE; + $modals = ''; + foreach ($project->environments as $name => $environment) { + + // Get platform and latest verify task. + $platform_nid = $environment->platform; + $platform = node_load($platform_nid); + $tasks = devshop_get_tasks($environment, 'verify'); + $task = current($tasks['verify']); + + if (empty($task)) { + $row = array( + $environment->name, + $environment->git_ref, + t('Error: Platform node was not saved. Unable to continue.'), + '', '', + ); + $rows[] = $row; + continue; + } + + // Build a table. + $row = array(); + $row['name'] = $name; + $row['branch'] = $environment->git_ref; + $row['version'] = t('...'); + + // If platform verified successfully: + if ($task->task_status == HOSTING_TASK_SUCCESS || $task->task_status == HOSTING_TASK_WARNING) { + + // It's not really ready until we get a version. + if (empty($platform->release->version)) { + $completed = FALSE; + $row['version'] = '...'; + $row['profiles'] = '...'; + } + else { + $row['version'] = $platform->release->version; + } + + // Collect install profiles + $profiles_shortnames = hosting_get_profiles($platform->nid, 'short_name'); + + if (is_array($profiles_shortnames) && !empty($profiles_shortnames)) { + $profiles[$name] = array_combine($profiles_shortnames, (array) hosting_get_profiles($platform->nid)); + $row['profiles'] = implode(', ', $profiles[$name]); + } + else { + $profiles[$name] = array(); + } + + // If no tasks have failed, save available profiles + if ($all_tasks_succeeded) { + if (empty($available_profiles)) { + $available_profiles = $profiles[$name]; + } + else { + $available_profiles = array_intersect_key($available_profiles, $profiles[$name]); + } + } + } + // If platform verification failed: + elseif ($task->task_status == HOSTING_TASK_ERROR) { + $completed = TRUE; + $all_tasks_succeeded = FALSE; + $available_profiles = array(); + + $errors = db_query("SELECT * FROM {hosting_task_log} WHERE nid = :nid AND (type LIKE :type OR type = :error) ORDER BY vid, lid", array(':nid' => $task->nid, ':type' => 'devshop%', ':error' => 'error')); + + foreach ($errors as $error) { + if ($error->type == 'devshop_info' || $error->type == 'error') { + $messages_to_user[] = $error->message; + } + $messages[] = $error; + } + $errors_rendered = theme('devshop_ascii', array('output' => implode('\n', $messages_to_user))); + + $codebase_verification_failed = t('Codebase Verification Failed'); + $close = t('Close'); + $view_logs = t('View Logs'); + $logs_url = url("node/{$task->nid}"); + + $button = << + + $codebase_verification_failed + +HTML; + + $modals .= << + +HTML; + + $row['version'] = array( + 'data' => $button, + 'colspan' => 2, + ); + } + // If platform is still processing: + elseif ($task->task_status == HOSTING_TASK_PROCESSING || $task->task_status == HOSTING_TASK_QUEUED) { + $completed = FALSE; + $row['version'] = "..."; + $row['profiles'] = "..."; + } + + // If a single task is not queued, $all_tasks_queued == FALSE + if ($task->task_status != HOSTING_TASK_QUEUED) { + $all_tasks_queued = FALSE; + } + + // If a single task is not queued, $all_tasks_queued == FALSE + if ($task->task_status == HOSTING_TASK_ERROR) { + $task_failed = TRUE; + } + + // Add hosting task status. + $status = _hosting_parse_error_code($task->task_status); + if ($status == 'Queued') { + $status = '...'; + } + $row['status'] = l("{$status}", "node/{$task->nid}", array('html' => TRUE)); + + // Store rows for display + $rows[] = $row; + } // end foreach platform + + // Output our table. + $form['platforms'] = array( + '#markup' => theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('table')))), + ); + + // Not completed means show all tasks are not completed (or errored) + if (!$completed) { + $form['#no_finish'] = TRUE; + $note = '

    ' . t('Please wait while we clone your repo and verify your drupal code.') . '

    '; + + $form['help'] = array( + '#markup' => $note, + ); + + // Add code to reload the page when complete. + devshop_form_reloader($form, 'platform'); + return $form; + } + elseif ($task_failed) { + $project->no_finish = TRUE; + $form['error'] = array( + '#prefix' => '

    ', + '#markup' => t('Something went wrong when trying to clone your codebase.'), + '#suffix' => '

    ', + ); + $form['retry'] = array( + '#type' => 'submit', + '#value' => t('Retry'), + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + $form['#no_finish'] = TRUE; + } + + // If ready... + else { + + // If no common profiles found, just set to standard. + if (count($available_profiles) == 0) { + $available_profiles['standard'] = 'No default profile.'; + $default_profile = 'standard'; + } + + $project->no_finish = FALSE; + + // Install Profile + // Sensible default? + // Lets go with standard for now... we can update later. + if (isset($available_profiles['standard'])) { + $default_profile = 'standard'; + } + // If 'drupal' profile exists, it is likely drupal6! + elseif (isset($available_profiles['drupal'])) { + $default_profile = 'drupal'; + } + + $form['install_profile'] = array( + '#type' => 'radios', + '#options' => $available_profiles, + '#title' => t('Project Install Profile'), + '#required' => 1, + '#description' => t('All sites in your project must use the same installation profile, and it must exist in all branches. Choose the installation profile for this project.'), + '#default_value' => $default_profile, + ); + } + + $form['#suffix'] = $modals; + return $form; +} + +/** + * STEP 4: Validate + */ +function devshop_project_create_step_sites_validate(&$from, &$form_state) { + if ($form_state['triggering_element']['#value'] == t('Retry')) { + foreach ($form_state['project']->environments as $name => $environment) { + $task = current($environment->tasks['verify']); + if (isset($task->nid)) { + hosting_task_retry($task->nid); + } + } + } + elseif (empty($form_state['values']['install_profile'])) { + form_set_error('install_profile', t('You must choose an install profile. Please wait for all environments to verify.')); + } +} diff --git a/modules/devshop/devshop_projects/inc/drush.inc b/modules/devshop/devshop_projects/inc/drush.inc new file mode 100644 index 000000000..f807a7c90 --- /dev/null +++ b/modules/devshop/devshop_projects/inc/drush.inc @@ -0,0 +1,45 @@ +environments as $name => $environment) { + + $remote_user = 'aegir'; + $remote_host = $environment->remote_host; + $root = $environment->root; + $uri = $environment->uri; + $file_path = "sites/{$environment->uri}/files"; + + $output .= << '$root', + 'uri' => '$uri', + 'remote-user' => '$remote_user', + 'remote-host' => '$remote_host', + 'path-aliases' => array( + '%files' => '$file_path', + ), +); + +PHP; + } + + return $output; +} + + +/** + * Downloads the drush aliases for this site. + * @param $project + */ +function devshop_project_drush_aliases_page($node) { + $project = $node->project; + $filename = $project->name . '.aliases.drushrc.php'; + header("Content-Disposition: attachment; filename='$filename'"); + print devshop_project_aliases($project); +} diff --git a/modules/devshop/devshop_projects/inc/forms.inc b/modules/devshop/devshop_projects/inc/forms.inc new file mode 100644 index 000000000..4eb612d72 --- /dev/null +++ b/modules/devshop/devshop_projects/inc/forms.inc @@ -0,0 +1,1699 @@ +project; + + // Save last project data + $form['old'] = array( + '#value' => $node, + '#type' => 'value', + ); + + // Project Settings + // Every value under $form['project'] gets serialized and saved into a project's "data" column. + $form['project'] = array( + '#tree' => TRUE, + ); + + // Hidden fields that can't change. + $form['title'] = array( + '#type' => 'textfield', + '#title' => t('Project Code Name'), + '#required' => TRUE, + '#description' => t('Choose a unique name for your project.'), + '#size' => 40, + '#default_value' => $node->title, + '#maxlength' => 255, + ); + $form['project']['git_url'] = array( + '#type' => 'value', + '#value' => $project->git_url, + ); + $form['project']['codebase'] = array( + '#type' => 'fieldset', + '#title' => t('General Settings'), + '#group' => 'project_settings', + '#tree' => FALSE, + '#weight' => -11, + ); + $form['project']['codebase']['code_path'] = array( + '#type' => 'textfield', + '#title' => t('Base path'), + '#description' => t('All environments in this project will be installed inside this path. Changing this will only affect new environments. Existing environments will not change.'), + '#required' => TRUE, + '#size' => 40, + '#default_value' => $project->code_path, + '#maxlength' => 255, + '#weight' => 2, + '#parents' => array( + 'project', + 'code_path', + ), + ); + + $items = []; + $items[] = t('Enter the relative path to the exposed document root within your repository. Leave blank if index.php is in the root. Common paths are docroot or web.'); + $items[] = t('If you are using the drupal-composer project for Drupal 8, the default document root is "web".'); + + $form['project']['codebase']['drupal_path'] = array( + '#type' => 'textfield', + '#title' => t('Path to Drupal'), + '#description' => theme('item_list', ['items' => $items]), + '#size' => 40, + '#default_value' => $project->drupal_path, + '#maxlength' => 255, + '#weight' => 3, + '#parents' => array( + 'project', + 'drupal_path', + ), + ); +// This was accidentally merged from the makefiles branch. +// $form['project']['codebase']['makefile_path'] = array( +// '#type' => 'textfield', +// '#title' => t('Path to Makefile'), +// '#description' => t('Enter the relative path to a makefile inside your repository. Leave blank if a full Drupal codebasee is in the repository.'), +// '#size' => 40, +// '#default_value' => $project->settings->makefile_path, +// '#maxlength' => 255, +// '#weight' => 4, +// '#parents' => array( +// 'project', +// 'settings', +// 'makefile_path', +// ), +// ); + $form['project']['codebase']['base_url'] = array( + '#type' => 'textfield', + '#title' => t('Environment Domain Name Pattern'), + '#description' => t("Each environment will have a system domain name generated for it based on it's name. Use @project for project name, @hostname for '%host', @environment for the environment's name.", array('%host' => $_SERVER['SERVER_NAME'])) . '' . t('Changing this will only affect new environments.'). '', + '#required' => TRUE, + '#size' => 40, + '#default_value' => $project->base_url, + '#maxlength' => 255, + '#weight' => 4, + '#access' => variable_get('devshop_projects_allow_custom_base_url', FALSE), + '#parents' => array( + 'project', + 'base_url', + ), + ); + + // Don't allow editing + if ($node->nid) { + + // Title + $form['title']['#value'] = $form['title']['#default_value']; + $form['title']['#type'] = 'value'; + + } + + // Prevent editing code path unless allowed. + if (!variable_get('devshop_projects_allow_custom_code_path', FALSE)) { + $form['project']['codebase']['code_path']['#type'] = 'value'; + $form['project']['codebase']['code_path']['#value'] = $form['project']['codebase']['code_path']['#default_value']; + } + + // Project Settings + $form['project']['settings'] = array( + '#weight' => -10, + ); + + // Project Settings Vertical Tabs + $form['project_settings'] = array( + '#type' => 'vertical_tabs', + '#weight' => -11, + ); + + // Save git branches and tags + $form['project']['settings']['git']['branches'] = array( + '#type' => 'value', + '#value' => isset($project->settings->git['branches']) ? $project->settings->git['branches'] : NULL, + ); + $form['project']['settings']['git']['tags'] = array( + '#type' => 'value', + '#value' => isset($project->settings->git['tags']) ? $project->settings->git['tags'] : NULL, + ); + + // Live Environment settings. + $form['project']['settings']['live'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => arg(1) != $project->nid, + '#title' => t('Domain Name Settings'), + '#group' => 'project_settings', + ); + + // Live Environment + $environments = array_keys($project->environments); + if (empty($environments)) { + $environments_options = array(); + } + else { + $environments_options = array_combine($environments, $environments); + $environments_options[''] = t('None'); + } + + $form['project']['codebase']['live_environment'] = array( + '#type' => 'select', + '#title' => t('Primary Environment'), + '#suffix' => '

    ' . t('Select the primary environment for this project, typically the live environment. This environment will be marked with a icon. This is the environment that will be cloned when pull requests are created.') . '

    ', + '#options' => $environments_options, + '#default_value' => isset($project->settings->live) ? $project->settings->live['live_environment'] : '', + '#parents' => array( + 'project', + 'settings', + 'live', + 'live_environment', + ), + ); + + // Live Domain + $form['project']['settings']['live']['live_domain'] = array( + '#type' => 'textfield', + '#title' => t('Live Domain'), + '#description' => t('The live domain for this project. Used only for links and when creating subdomain aliases for other environments. You must still add the Domain to your live environment manually.'), + '#size' => 40, + '#default_value' => isset($project->settings->live) ? $node->project->settings->live['live_domain'] : '', + ); + + // Use aliases + $form['project']['settings']['live']['environment_aliases'] = array( + '#type' => 'checkbox', + '#title' => t('For new environments, create subdomains under Live Domain.'), + '#description' => t('When new environments are created, automatically add a domain name such as http://ENVIRONMENT.LIVEDOMAIN.com. Does not affect existing environments. Does not remove domains when disabled.'), + '#default_value' => isset($project->settings->live) ? $project->settings->live['environment_aliases'] : FALSE, + ); + + // Pull Code Method + $form['project']['settings']['deploy'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => arg(1) != $project->nid, + '#title' => t('Deployment Automation'), + '#description' => t('Configure how code is delivered to the servers. Post Deploy hooks are configured per environment.'), + '#weight' => -9, + '#group' => 'project_settings', + ); + + $form['project']['settings']['deploy']['method'] = array( + '#title' => 'Deploy Code Method', + '#type' => 'radios', + '#description' => t('Choose the method used to deploy code to the server.'), + '#default_value' => isset($project->settings->deploy['method']) ? $project->settings->deploy['method'] : 'webhook', + ); + + // Commit Webhook + $form['project']['settings']['deploy']['method']['#options']['webhook'] = t('Immediate Deployment'); + $form['project']['settings']['deploy']['method']['#options']['webhook'] .= '
    ' . t('Recommended. Deploy code as it is delivered to your repository.') . ' ' . t('Requires setting up a webhook with your git repository host.') . '' . '
    '; + + // Queue + $queues = hosting_get_queues(); + if ($queues['pull']['enabled']) { + $form['project']['settings']['deploy']['method']['#options']['queue'] = t('Queued Deployment'); + + $t = array(); + $t['@freq'] = format_interval($queues['pull']['frequency'], 1); + + $form['project']['settings']['deploy']['method']['#options']['queue'] .= '
    '; + $form['project']['settings']['deploy']['method']['#options']['queue'] .= t('Git pull code every @freq. Use only if repository webhooks are not available.', $t); + + + if (user_access('administer hosting queues')) { + $form['project']['settings']['deploy']['method']['#options']['queue'] .= ' ' . l(t("Pull Queue configured to run every @freq.", $t), 'admin/hosting/queues'); + } + else { + $form['project']['settings']['deploy']['method']['#options']['queue'] .= ' ' . t("Pull Queue configured to run every @freq.", $t); + } + + $form['project']['settings']['deploy']['method']['#options']['queue'] .= '
    '; + + } + + // Manual Pull + $form['project']['settings']['deploy']['method']['#options']['manual'] = t('Manual Deployment'); + $form['project']['settings']['deploy']['method']['#options']['manual'] .= '
    ' . t('Deploy code to servers manually via devshop or drush.'); + + $form['project']['settings']['deploy']['method']['#options']['manual'] .= ' ' . t('Not recommended. All environments must be manually updated.') . '' . '
    '; + + // Add link to hosting queues admin if the user can access them. + if (!$queues['deploy']['enabled'] && user_access('administer hosting queues')) { + $form['project']['settings']['deploy']['queue_admin'] = array( + '#value' => t('The !link is disabled. Enable it to allow projects to pull code in the queue.', array( + '!link' => l(t('Pull Queue'), 'admin/hosting/queues'), + )), + '#prefix' => '

    ', + '#suffix' => '

    ', + ); + } + + // Load deploy hooks form element. + $form['project']['settings']['deploy']['default_hooks'] = devshop_environment_deploy_hooks_form($project); + + // Deploy hooks configuration + $form['project']['settings']['deploy']['default_hooks']['label'] = array( + '#value' => t('Default Deploy Hooks:'), + '#prefix' => '', + '#weight' => -100, + ); + $form['project']['settings']['deploy']['default_hooks']['description'] = array( + '#value' => t('New environments will run these actions when new code or data is deployed.'), + '#prefix' => '

    ', + '#suffix' => '

    ', + '#weight' => -100, + ); + + $form['project']['settings']['deploy']['default_hooks']['allow_environment_deploy_config'] = array( + '#type' => 'checkbox', + '#title' => t('Allow environment-specific deploy hook configuration.'), + '#default_value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['allow_environment_deploy_config'] : FALSE, + '#description' => t('Each environment can be configured to have different deploy hooks. Be sure to check your environments settings if you enable this.' ), + '#parents' => array( + 'project', + 'settings', + 'deploy', + 'allow_environment_deploy_config', + ), + ); + $form['project']['settings']['deploy']['default_hooks']['allow_environment_deploy_hooks_override'] = array( + '#type' => 'checkbox', + '#title' => t('Allow users to override hooks when deploying manually.'), + '#default_value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['allow_environment_deploy_hooks_override'] : FALSE, + '#description' => t('Check this box to allow users to override the hooks that are run on manual deployments. If left unchecked, all environments will run the deploy hooks configured above on every deployment.' ), + '#parents' => array( + 'project', + 'settings', + 'deploy', + 'allow_environment_deploy_hooks_override', + ), + ); + + // @TODO: is there a better way to save certain values? We lose data without these. + $form['project']['settings']['deploy']['last_webhook'] = array( + '#type' => 'value', + '#value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['last_webhook'] : NULL, + ); + $form['project']['settings']['deploy']['last_webhook_status'] = array( + '#type' => 'value', + '#value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['last_webhook_status'] : NULL, + ); + $form['project']['settings']['deploy']['last_webhook_ip'] = array( + '#type' => 'value', + '#value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['last_webhook_ip'] : NULL, + ); + + //All settings git pull in project page + $form['project']['settings']['default_environment'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => arg(1) != $project->nid, + '#title' => t('Default Environment Settings'), + '#description' => t('These settings will be used when creating new environments.'), + '#group' => 'project_settings', + ); + + // Install Profile + $available_profiles = array(); + foreach ($project->environments as $name => $environment) { + // Passing null to hosting_get_profiles returns all profiles. + if (is_null($environment->platform)) { + continue; + } + $profiles_shortnames = hosting_get_profiles($environment->platform, 'short_name'); + if (is_array($profiles_shortnames) && !empty($profiles_shortnames)) { + $profiles[$name] = array_combine($profiles_shortnames, (array)hosting_get_profiles($environment->platform)); + } else { + $profiles[$name] = array(); + } + if (empty($available_profiles)) { + $available_profiles = $profiles[$name]; + } else { + $available_profiles = array_intersect_key($available_profiles, $profiles[$name]); + } + } + + $form['project']['settings']['default_environment']['install_profile'] = array( + '#type' => 'radios', + '#options' => $available_profiles, + '#title' => t('Default Install Profile'), + '#required' => 1, + '#description' => t('New environments will be created using this install profile. Existing environments will not be affected.'), + '#default_value' => $node->project->install_profile, + '#access' => count($available_profiles), + ); + + // HTTP Server select. + $http_servers = hosting_get_servers('http'); + if (count($http_servers)) { + $form['project']['settings']['default_environment']['web_server'] = array( + '#title' => t('Web server'), + '#type' => 'select', + '#options' => $http_servers, + '#default_value' => isset($project->settings->default_environment) ? $project->settings->default_environment['web_server'] : current($http_servers), + ); + } + + // DB Server select. + $db_servers = hosting_get_servers('db'); + if (count($db_servers)) { + $form['project']['settings']['default_environment']['db_server'] = array( + '#title' => t('Database server'), + '#type' => 'select', + '#options' => $db_servers, + '#default_value' => isset($project->settings->default_environment) ? $project->settings->default_environment['db_server'] : current($db_servers), + ); + } + + // Solr Server Select + $solr_servers = hosting_get_servers('solr'); + if (count($solr_servers)) { + $form['project']['settings']['default_environment']['solr_server'] = array( + '#title' => t('Solr server'), + '#type' => 'select', + '#options' => $solr_servers, + '#default_value' => $project->settings->default_environment['solr_server'], + ); + } + + // Force the servers for this project's environments + $form['project']['settings']['default_environment']['force_default_servers'] = array( + '#title' => t('Force new environments to use these servers.'), + '#description' => t('When new environments are created, they will use the servers selected above.'), + '#type' => 'checkbox', + '#default_value' => $project->settings->default_environment['force_default_servers'], + ); + return $form; +} + +/** + * Implementation of hook_validate(). + * + * This function is no longer used since we have a ctools wizard for + * project creation. + */ +function devshop_projects_validate($node, &$form, &$form_state) { + + // Code path and base url must be unique + if (db_query('SELECT n.nid FROM {hosting_devshop_project} d LEFT JOIN {node} n ON d.nid = n.nid WHERE status = :status AND code_path = :code_path AND n.nid != :nid;', array(':status' => 1, ':code_path' => $form_state['values']['project']['code_path'], ':nid' => $form_state['values']['nid']))->fetchField()) { + form_set_error('code_path', t('The code path %path is in use by another project. Please enter a different path.', array( + '%path' => $form_state['values']['project']['code_path'], + ))); + } + // If custom domain pattern is allowed, AND domain pattern does not have "@project" in it, AND domain pattern is not the default AND it is found on another project, throw an error. + if (variable_get('devshop_projects_allow_custom_base_url', FALSE) && strpos($form_state['values']['project']['base_url'], '@project') === FALSE && $form_state['values']['project']['base_url'] != variable_get('devshop_project_environment_url_pattern', '@project.@environment.@hostname') && db_query('SELECT n.nid FROM {hosting_devshop_project} d LEFT JOIN {node} n ON d.nid = n.nid WHERE status = :status AND base_url = :base_url AND n.nid != :nid;;', array(':status' => 1, ':base_url' => $form_state['values']['project']['base_url'], ':nid' => $form_state['values']['nid']))->fetchField()) { + form_set_error('base_url', t('The Environment Domain Pattern %url is in use by another project. Please enter a different Environment Domain Pattern.', array( + '%url' => $form_state['values']['project']['base_url'], + ))); + } + + // "Environment Domain Name Pattern" (Base URL) + // Ensure @environment is included. + if (strpos($form_state['values']['project']['base_url'], '@environment') === FALSE ) { + form_set_error('project][base_url', t('Environment Domain Name Pattern must include @environment placeholder.')); + } +} + +/** + * Implements hook_form_alter(). + */ +function devshop_projects_form_alter(&$form, &$form_state, $form_id) { + + // Removing unneccesary fieldgroups + if ($form_id == 'project_node_form') { + global $user; + + $project = $form['#node']; + unset($form['menu']); + unset($form['revision_information']); + unset($form['author']); + unset($form['options']); + unset($form['actions']['delete']); + unset($form['actions']['preview']); + if (isset($form['retry']['#value'])) { + $form['actions']['submit']['#value'] = t('Save and Retry'); + } + + //Add button for delete project + $form['actions']['delete'] = array( + '#markup' => l(' ' . t('Delete Project'), 'hosting_confirm/' . $project->nid . '/project_delete', array( + 'query' => array('token' => drupal_get_token($user->uid)), + 'attributes' => array( + 'class' => array('btn btn-danger pull-right'), + ), + 'html' => TRUE, + )), + '#weight' => 10, + '#access' => drupal_valid_path('hosting_confirm/' . $project->nid . '/project_delete'), + ); + } + + // Create Project Wizard + if ( + $form_id == 'devshop_project_create_step_git' || + $form_id == 'devshop_project_create_step_sites' || + $form_id == 'devshop_project_create_step_settings' || + $form_id == 'devshop_project_create_step_environments' + ){ + if (isset($form['#no_finish'])) { + unset($form['buttons']['return']); + } + if (isset($form['#no_next'])) { + unset($form['buttons']['next']); + } + + if (isset($form['buttons']['next']['#value'])) { + $form['buttons']['next']['#value'] .= ' '; + } + + if (isset($form['buttons']['return']['#value'])) { + $form['buttons']['return']['#value'] = ' ' . t('Create Project & Environments'); + } + + $form['buttons']['next']['#attributes'] = + $form['buttons']['return']['#attributes'] = array( + 'class' => array( + 'btn btn-success', + ), + ); + $form['buttons']['previous']['#attributes'] = array( + 'class' => array( + 'btn btn-default', + ), + ); + $form['buttons']['cancel']['#attributes'] = array( + 'class' => array( + 'btn btn-link', + ), + ); + + } + + // Hosting Task Forms + if ($form_id == 'hosting_task_confirm_form') { + switch ($form['task']['#value']) { + + // Migrate Form: used for changing database server. + case 'migrate': + + // To change the database server, we use the migrate form. + if ($_GET['deploy'] == 'stack') { + drupal_set_title(t('Change Database Server')); + $site_node = node_load($form['nid']['#value']); + $environment = $site_node->environment; + $form['help']['#weight'] = 100; + $form['help']['#value'] = t("Are you sure you want to change this environment's database server?"); + + // Hide "URI" field unless "rename" GET parameter is added. + if (!isset($_GET['rename'])) { + $form['parameters']['new_uri']['#type'] = 'value'; + $form['parameters']['new_uri']['#value'] = $form['parameters']['new_uri']['#default_value']; + } + + // Display something helpful + $form['old'] = array( + '#type' => 'item', + '#title' => t('Current Database Server'), + '#value' => l($environment->servers['db']['name'], 'hosting/c/server_' . $environment->servers['db']['nid']), + '#weight' => '-1', + ); + } + if (isset($_GET['rename'])) { + drupal_set_title(t('Change Domain Name for environment @env in project @proj?', array( + '@env' => $environment->name, + '@proj' => $environment->project_name, + ))); + $form['help']['#value'] = t("Are you sure you want to change this environment's domain name?"); + $form['submit']['#value'] = t('Change Domain'); + } + if (isset($_GET['rename']) || $_GET['deploy'] == 'stack') { + foreach ($form['parameters'] as $key => $element) { + if (is_int($key)) { + // Don't unset the target platform. Just hide it. + if (isset($element['target_platform']['#default_value'])) { + $form['parameters'][$key]['target_platform']['#type'] = 'value'; + $form['parameters'][$key]['target_platform']['#value'] = $form['parameters'][$key]['target_platform']['#default_value']; + } + else { + unset($form['parameters'][$key]); + } + } + } + } + break; + + // Deploy task form + case 'devshop-deploy': + + // Alter title of deploy task. + $node = node_load($form['nid']['#value']); + drupal_set_title(t('Deploy code to Environment "@env"', array('@env' => $node->environment->name)), PASS_THROUGH); + $form['actions']['cancel']['#value'] = l(t('Cancel'), "node/{$node->project->nid}"); + break; + + // Sync task form + case 'sync': + if (isset($_GET['source'])) { + $node = node_load($form['nid']['#value']); + + if ($_GET['source'] == 'other') { + $source = ''; + } + else { + $source = node_load($_GET['source']); + if (isset($source->nid) && $source->type != 'site') { + break; + } + } + + drupal_set_title(t('Sync Data to Environment "@env"', array('@env' => $node->environment->name)), PASS_THROUGH); + $environment = $node->environment; + + // If source is empty, ask for an alias. + if (empty($source)) { + $form['parameters']['source'] = array( + '#type' => 'textfield', + '#title' => t('Drush Alias for Source'), + '#description' => t('Enter a full drush alias to use as the source. Make sure to include the "@" character at the beginning.'), + ); + $form['parameters']['destination'] = array( + '#type' => 'value', + '#value' => $node->environment->system_alias, + ); + } + // If a source is specified, add help and set it as a value. + else { + + $source_environment = $source->environment; + + $form['parameters']['source'] = array( + '#type' => 'value', + '#value' => $source->environment->system_alias, + ); + $form['parameters']['destination'] = array( + '#type' => 'value', + '#value' => $node->environment->system_alias, + ); + } + + // Don't copy modules and themes. + unset($form['parameters']['modules']); + unset($form['parameters']['themes']); + unset($form['parameters']['libraries']); + + // Better output + $form['parameters']['database']['#prefix'] = ''; + $form['parameters']['registry-rebuild']['#prefix'] = ''; + $form['parameters']['backup']['#weight'] = -11; + + $form['actions']['submit']['#value'] = '' . t('Sync Data'); + $form['actions']['submit']['#attributes'] = array( + 'class' => array( + 'btn-success' + ), + ); + $form['actions']['cancel']['#value'] = l(t('Cancel'), "node/{$node->project->nid}"); + + $form['warning']['#markup'] = '
    ' . t('Clicking "Sync Data" will DESTROY the database for the environment !link.', array('!link' => l($environment->url, $environment->url, array('attributes' => array('target' => '_blank'))))) . '
    '; + + + $destroyed = t('This environment will be destroyed.'); + $source = t('This environment will be copied.'); + + $html = << + +
    + + + +
    + + +HTML; + + $form['notes'] = array( + '#markup' => $html, + '#weight' => -10, + ); + } + break; + case 'delete': + $node = node_load($form['nid']['#value']); + if ($node->type == 'project') { + + // Set a better title. + drupal_set_title(''); + $form['title'] = array( + '#weight' => -10, + '#markup' => '

    ' .t('Delete Project: !project', array( + '!project' => l($node->project->name, "node/{$node->nid}"), + )) . '

    ' + ); + + // Get a list of environments. + foreach ($node->project->environments as $environment) { + $items[] = l($environment->uri, 'http://' . $environment->uri); + + } + $environments = theme('item_list', array('items' => $items)); + + // Display a scary warning message. + $form['help']['#markup'] = '
    ' . t('Are you sure you wish to destroy the project !project and all of these environments?', array( + '!project' => l($node->project->name, "node/{$node->nid}"), + )) . $environments . '
    '; + + // Make the button big + $form['actions']['submit']['#value'] = t('Delete Project & Environments'); + $form['actions']['submit']['#attributes']['class'][] = 'btn btn-large btn-danger'; + + } + break; + } + } + + // Platform "Edit" page. + if ($form_id == 'platform_node_form') { + + // Set to values + $platform_node = node_load($form['nid']['#value']); + if (empty($platform_node->project)) { + return; + } + + $form['title']['#type'] = 'value'; + unset($form['info']); + + if (isset($platform_node->project)) { + // Hide makefile + $form['frommakefile']['#access'] = FALSE; + $form['git']['pull_url']['#access'] = FALSE; + } + + // If switching webservers... + if ($_GET['web_server']) { + $environment = $platform_node->environment; + $form['help'] = array( + '#markup' => t("Are you sure you want to change this site's web server? NOTE: You will have to change DNS as well!"), + '#weight' => -10, + ); + + $web_server = $_GET['web_server']; + $web_server_nid = db_query('SELECT nid FROM {node} WHERE type = :type && title = :title', array(':type' => "server", ':title' => $web_server))->fetchField(); + + // Set values + $form['web_server']['#default_value'] = $web_server_nid; + $form['web_server']['#type'] = 'hidden'; + + // Load servers to display IP addresses. + $old_server = node_load($environment->servers['http']['nid']); + $new_server = node_load($web_server_nid); + + // Display something helpful + $form['old'] = array( + '#type' => 'item', + '#title' => t('Current Web Server'), + '#markup' => l($environment->servers['http']['name'], 'node/' . $environment->servers['http']['nid']) . '
    ' . implode($old_server->ip_addresses, '
    '), + '#weight' => '-1', + ); + + // Display something helpful + $form['new'] = array( + '#type' => 'item', + '#title' => t('New Web Server'), + '#markup' => l($web_server, "node/$web_server_nid") . '
    ' . implode($new_server->ip_addresses, '
    '), + '#weight' => '0', + ); + } + } +} + +/** + * Implements hook_form_FORM_ID_alter() for node_site_form + * + * "Environment" Settings form. + */ +function devshop_projects_form_site_node_form_alter(&$form, &$form_state, $form_id) { + + $node = $form['#node']; + + $form['environment'] = array( + '#weight' => -9, + '#tree' => true, + ); + + // On site create page, use devshop_projects_create_environment_form() + if (arg(1) == 'add') { + devshop_projects_create_environment_form($form, $form_state, $form_id); + + $project = devshop_projects_load_by_name(arg(3)); + } + + // Only act on site nodes that are in projects. + elseif (!isset($node->environment)) { + return; + } + else { + + // Get environment object. (Convert if it is an array. This happens when clicking "Add an Alias". + $environment = (object) $node->environment; + $project = $node->project; + + // Remove aegir's helpful info panel. + unset($form['info']); + + // Values + $form['environment']['project_nid'] = array( + '#value' => $environment->project_nid, + '#type' => 'value', + ); + $form['environment']['project_name'] = array( + '#value' => $environment->project_name, + '#type' => 'value', + ); + $form['environment']['name'] = array( + '#value' => $environment->name, + '#type' => 'value', + ); + $form['environment']['git_ref'] = array( + '#value' => $environment->git_ref, + '#type' => 'value', + ); + $form['environment']['site'] = array( + '#value' => $environment->site, + '#type' => 'value', + ); + $form['environment']['platform'] = array( + '#value' => $environment->platform, + '#type' => 'value', + ); + + $form['environment']['last_task'] = array( + '#type' => 'value', + '#value' => $environment->last_task, + ); + $form['form_title'] = array( + '#markup' => t('Environment Settings'), + '#prefix' => '

    ', + '#suffix' => '

    ', + '#weight' => -1000, + ); + } + + // User-configurable Settings + $form['environment']['settings'] = array( + '#type' => 'fieldset', + '#title' => t('Environment Settings'), + '#tree' => true, + ); + $form['environment']['settings']['locked'] = array( + '#type' => 'checkbox', + '#title' => t('Lock Database'), + '#default_value' => isset($environment->settings->locked) ? $environment->settings->locked : FALSE, + '#description' => t('Prevent devshop users from destroying the database.') . '

    Drush users may still overwrite the database.

    ', + ); + + $form['environment']['settings']['pull_disabled'] = array( + '#type' => 'checkbox', + '#title' => t('Disable Deploy on Commit'), + '#default_value' => isset($environment->settings->pull_disabled) ? $environment->settings->pull_disabled : FALSE, + '#description' => t('Do not pull code to the server on commit & push.'), + ); + + // Load deploy hooks form element. + $form['environment']['settings']['deploy'] = devshop_environment_deploy_hooks_form($project, $environment); + + // UX Improvements + // Project Settings Vertical Tabs + $form['environment_settings'] = array( + '#type' => 'vertical_tabs', + '#weight' => -11, + ); + + $form['environment']['settings']['#title'] = t('General Settings'); + $form['environment']['settings']['#group'] = 'environment_settings'; + $form['environment']['settings']['#weight'] = -100; + $form['environment']['settings']['deploy']['#group'] = 'environment_settings'; + $form['hosting_backup_queue']['#group'] = 'environment_settings'; + $form['http_basic_auth']['#group'] = 'environment_settings'; + $form['http_basic_auth']['#title'] = t('Password Protection'); + $form['hosting_logs']['#group'] = 'environment_settings'; + + // Have to add new element for Domains because of the way drupal ajax callback works. + $form['domains'] = array( + '#title' => t('Domain Names'), + '#type' => 'fieldset', + '#group' => 'environment_settings', + ); + $form['domains']['aliases_wrapper'] = $form['aliases_wrapper']; + $form['domains']['aliases_wrapper']['#type'] = 'container'; + $form['domains']['aliases_wrapper']['add_alias']['#ajax']['callback'] = 'devshop_hosting_alias_add_alias_callback'; + unset($form['aliases_wrapper']); + + $form['environment']['settings']['client'] = $form['client']; + $form['environment']['settings']['client']['#tree'] = FALSE; + unset($form['client']); + + $form['environment']['settings']['site_language'] = $form['site_language']; + $form['environment']['settings']['site_language']['#tree'] = FALSE; + unset($form['site_language']); + + if (isset($form['hosting_ssl_wrapper'])) { + $form['hosting_ssl_wrapper']['#group'] = 'environment_settings'; + $form['hosting_ssl_wrapper']['#title'] = t('HTTPS'); + $form['hosting_ssl_wrapper']['#prefix'] = ''; + $form['hosting_ssl_wrapper']['#suffix'] = ''; + } + if (isset($form['hosting_https_wrapper'])) { + $form['hosting_https_wrapper']['#group'] = 'environment_settings'; + $form['hosting_https_wrapper']['#title'] = t('HTTPS'); + $form['hosting_https_wrapper']['#prefix'] = ''; + $form['hosting_https_wrapper']['#suffix'] = ''; + } + + // Ensure values are saved. + if (!isset($form['install_method'])) { + $form['install_method'] = array( + '#type' => 'value', + '#value' => $environment->settings->install_method, + ); + } + + // Add our own submit handler. + $form['actions']['submit']['#submit'][] = 'devshop_projects_environment_settings_submit'; + +} + + +/** + * Ajax callback for returning "Add Alias" form element, since we moved ours to a sub fieldset. + */ +function devshop_hosting_alias_add_alias_callback($form, $form_state) { + return $form['domains']['aliases_wrapper']; +} + + +/** + * Form alters for "site_node_form", which is our "Create Environment" form. + * + * Code that only applies to "site create" form should go here. If it needs to + * be in create and edit, it should go in devshop_projects_form_site_node_form_alter() + * + * This is invoked from devshop_projects_form_site_node_form_alter() + * + * @see devshop_projects_form_site_node_form_alter() + * + */ +function devshop_projects_create_environment_form(&$form, &$form_state, $form_id) { + + // Look for project. If there is none, return. + $project_node = devshop_projects_load_by_name(arg(3)); + if ($project_node->type != 'project') { + return; + } + $project = $project_node->project; + + // Wrap the form in a container so we can use grid + $form['#prefix'] = '
    '; + $form['#suffix'] = '
    '; + + $form['form_title'] = array( + '#markup' => t('Create New Environment'), + '#prefix' => '

    ', + '#suffix' => '

    ', + '#weight' => -1000, + ); + + // Add our own validator and submit handlers. + $form['actions']['submit']['#value'] = t('Create New Environment'); +// array_unshift($form['actions']['submit']['#submit'], 'devshop_projects_create_environment_form_submit'); + + // Generate field prefix and suffix using domain name pattern. + if (variable_get('devshop_projects_allow_custom_base_url')) { + $pattern = $project->base_url; + } + else { + $pattern = variable_get('devshop_project_environment_url_pattern', '@project.@environment.@hostname'); + } + + $labels = explode('@environment', strtr($pattern, array( + '@project' => $project_node->title, + '@hostname' => $_SERVER['SERVER_NAME'], + ))); + + // Hide the "Domain Name" field, as this will be generated + $form['title']['#access'] = false; + $form['title']['#required'] = false; + + $form['environment_name'] = array( + '#title' => t('Environment Name'), + '#type' => 'textfield', + '#description' => t('Enter a system name for your environment. For consistency, you might want to match the branch name.'), + '#required' => TRUE, + '#field_prefix' => '
    http://' . $labels[0] . '
    ', + '#field_suffix' => '
    ' . $labels[1] .'
    +
    ', + '#size' => 10, + '#maxlength' => 64, + '#weight' => -101, + '#attributes' => array( + 'data-placement' => 'bottom', + ), + '#element_validate' => array( + 'devshop_projects_create_environment_form_validate_name' + ), + '#wrapper_attributes' => array( + 'class' => array('col-sm-8 col-md-8'), + ), + ); + + $branch_options = devshop_projects_git_ref_options($project); + $form['git_ref'] = array( + '#title' => t('Branch or Tag'), + '#type' => 'select', + '#description' => t('The git reference to checkout for this environment. You can change this later using the "Deploy Code" button.'), + '#options' => $branch_options, + '#required' => TRUE, + '#weight' => -100, + '#wrapper_attributes' => array( + 'class' => array('col-sm-4 col-md-4'), + ), + ); + + $form['install_method'] = array( + '#type' => 'fieldset', + '#weight' => -99, + '#tree' => TRUE, + '#attributes' => array( + 'class' => array('clearfix'), + ), + ); + $form['install_method']['method'] = array( + '#type' => 'radios', + '#required' => TRUE, + '#title' => t('Install Method'), + '#process' => array('devshop_environment_method_process'), + '#weight' => -99, + '#options' => array( + 'profile' => t('Drupal Profile'), + 'manual' => t('Empty Database'), + 'clone' => t('Clone Environment'), + 'import' => t('Import Database'), + ), + ); + + $form['install_method']['none'] = array( + '#type' => 'container', + '#weight' => -98, + '#states' => array( + 'visible' => array( + ':input[name="install_method[method]"]' => array('value' => 'manual'), + ), + ), + 'note' => array( + '#markup' => t('An empty database will be created. You can install Drupal manually by visiting install.php, by using Drush, or you can manually import a database.'), + ), + ); + + $form['install_method']['profile'] = array( + '#type' => 'radios', + '#title' => t('Drupal Install Profile'), + '#description' => t('These install profiles were found in other environments in this project. If you choose an install profile that does not exist in the codebase, the "Standard" profile will be used.'), + '#description_display' => 'before', + '#weight' => -98, + '#required' => TRUE, + '#options' => devshop_environment_add_form_profile_options($project), + '#states' => array( + 'visible' => array( + ':input[name="install_method[method]"]' => array('value' => 'profile'), + ), + ), + ); + + // Set default profile. + if (count($form['install_method']['profile']['#options']) == 1 || empty($project->install_profile)) { + $form['install_method']['profile']['#default_value'] = '_other'; + } + else { + $form['install_method']['profile']['#default_value'] = $project->install_profile; + } + + $form['install_method']['profile_other'] = array( + '#type' => 'textfield', + '#title' => t('Other Install Profile'), + '#title_display' => 'invisible', + '#description' => t('Enter the exact name the install profile to use. It must already exist in your git branch or tag.'), + '#element_validate' => array( + 'devshop_projects_create_environment_form_validate_profile' + ), + '#attributes' => array( + 'placeholder' => t('Profile Name'), + ), + '#weight' => -97, + '#states' => array( + 'visible' => array( + ':input[name="install_method[method]"]' => array('value' => 'profile'), + ':input[name="install_method[profile]"]' => array('value' => '_other'), + ), + ), + ); + + // Generate environments options. + foreach ($project->environments as $e => $environment) { + $link = l(t('View') . ' ', $environment->url, array( + 'html' => TRUE, + 'attributes' => array( + 'target' => '_blank', + 'class' => array('btn-text'), + ), + )) . ''; + $git_ref = $environment->git_ref; + $icon = $environment->git_ref_type == 'tag'? 'tag': 'code-fork'; + $environment_options[$environment->system_alias] = "{$environment->name} $git_ref $link"; + } + $environment_options['_other'] = t('Other Drush Alias'); + + if (count($environment_options)) { + $form['install_method']['clone_source'] = array( + '#type' => 'radios', + '#title' => t('Environment to clone'), + '#weight' => -98, + '#required' => TRUE, + '#options' => $environment_options, + '#default_value' => key($environment_options), + '#states' => array( + 'visible' => array( + ':input[name="install_method[method]"]' => array('value' => 'clone'), + ), + ), + ); + } + + // Detect clone link. Set defaults. + if (arg(4) == 'clone') { + $environment_to_clone = arg(5); + $form['install_method']['method']['#default_value'] = 'clone'; + $form['install_method']['clone_source']['#default_value'] = $project->environments[$environment_to_clone]->system_alias; + $form['git_ref']['#default_value'] = $project->environments[$environment_to_clone]->git_ref; + } + + // Detect fork link. + // @TODO: Get "forking" back in place for the next beta. +// if (arg(4) == 'fork') { +// $environment_to_clone = arg(5); +// $form['install_method']['method']['#default_value'] = 'clone'; +// $form['install_method']['clone_source']['#default_value'] = $project->environments[$environment_to_clone]->system_alias; +// $form['git_ref']['#default_value'] = $project->environments[$environment_to_clone]->git_ref; +// $form['git_ref']['#title'] = t('Base Branch or Tag'); +// $form['git_ref']['#description'] = t('The git reference you want to create a new branch from.'); +// +// $form['git_ref_new'] = array( +// '#title' => t('New branch name'), +// '#description' => t('New branch name'), +// ); +// } + + + $form['install_method']['clone_source_drush'] = array( + '#type' => 'textfield', + '#title' => t('Drush Alias'), + '#description' => t('Enter a Drush alias you would like to Sync your database and files from. It must already exist on the server.'), + '#element_validate' => array( + 'devshop_projects_create_environment_form_validate_clone_source' + ), + '#weight' => -98, + '#attributes' => array( + 'placeholder' => t('@drush.alias'), + ), + '#states' => array( + 'visible' => array( + ':input[name="install_method[clone_source]"]' => array('value' => '_other'), + ':input[name="install_method[method]"]' => array('value' => 'clone'), + ), + ), + ); + + $form['install_method']['import'] = array( + '#type' => 'textfield', + '#title' => t('Path to SQL'), + '#description' => + t('Enter either a remote MySQL address (such as mysql://username:password@host/database), or an absolute path to an SQL dump (such as /var/aegir/site-backup.sql).') . + '' . t('This string is stored in plain text. Use with caution.') . '' + , + '#weight' => -98, + '#states' => array( + 'visible' => array( + ':input[name="install_method[method]"]' => array('value' => 'import'), + ), + ), + '#element_validate' => array( + 'devshop_projects_create_environment_form_validate_import' + ), + ); + + $form['server_stack'] = array( + '#type' => 'fieldset', + '#title' => t('Server Stack'), + '#group' => 'environment_settings', + ); + + $form['server_stack']['db_server'] = $form['db_server']; + $form['server_stack']['db_server']['#group'] = 'environment_settings'; + $form['server_stack']['db_server']['#tree'] = FALSE; + unset($form['db_server']); + + $web_servers = hosting_get_servers('http'); + $form['server_stack']['web_server'] = array( + '#type' => 'radios', + '#title' => t('Web server'), + '#description' => t('The web server the site will be hosted on.'), + '#options' => $web_servers, + '#required' => TRUE, + '#tree' => FALSE, + '#default_value' => $project->settings->default_environment['web_server'], + ); + + // Force the new environment to use the "default" servers. + if ($project->settings->default_environment['force_default_servers']) { + $form['server_stack']['#description'] = t('All environments in this project must use this server stack.'); + + $web_server_nid = $project->settings->default_environment['web_server']; + $form['server_stack']['web_server']['#type'] = 'value'; + $form['server_stack']['web_server']['#value'] = $web_server_nid; + $form['server_stack']['web_server_label'] = array( + '#type' => 'item', + '#title' => t('Web server'), + '#markup' => l($web_servers[$web_server_nid], "node/$web_server_nid", array('attributes' => array('target' => '_blank'))), + ); + + $db_servers = hosting_get_servers('db'); + $db_server_nid = $project->settings->default_environment['db_server']; + $form['server_stack']['db_server']['#type'] = 'value'; + $form['server_stack']['db_server']['#value'] = $db_server_nid; + $form['server_stack']['db_server_label'] = array( + '#type' => 'item', + '#title' => t('Database server'), + '#markup' => l($db_servers[$db_server_nid], "node/$db_server_nid", array('attributes' => array('target' => '_blank'))), + ); + } + + $form['project_nid'] = array( + '#value' => $project->nid, + '#type' => 'value', + ); + + $form['platform_node'] = array( + '#type' => 'value', + '#value' => NULL, + ); + + + // Remove platform and profile options. + // @TODO: Patch Aegir so we don't have to load all of these platform options. + $form['platform']['#access'] = FALSE; + $form['profile']['#access'] = FALSE; + $form['git']['#access'] = FALSE; +} + +/** + * Validator for the domain name field: Sets domain according to pattern. + * @param $element + * @param $form_state + * @param $form + */ +function devshop_projects_create_environment_form_validate_name($element, &$form_state, $form) { + + if (!empty($element['#value'])) { + $project_node = node_load($form_state['values']['project_nid']); + $project = $project_node->project; + + // Check existence of the environment name + if (!empty($project->environments[$element['#value']])) { + form_set_error('environment_name', t('There is already an environment named %name in this project. Please choose a different name.', array('%name' => $form_state['values']['environment_name']))); + } + + // Check for illegal chars + if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) { + form_set_error('environment_name', t('The environment name must contain only lowercase letters, numbers, and underscores.')); + } + + // Generate field prefix and suffix using domain name pattern. + form_set_value($form['title'], devshop_environment_url($project, $element['#value']), $form_state); + + // Attach a new platform node. + $platform_node = devshop_prepare_platform_node($project, $element['#value'], $form_state['values']['git_ref'], $form_state['values']['web_server']); + + // Set value in form_state. + form_set_value($form['platform_node'], $platform_node, $form_state); + form_set_value($form['profile'], 0, $form_state); + form_set_value($form['platform'], 0, $form_state); + + } +} + +/** + * Helper to prepare the platform object to be attached to the site node. + * + * Example: + * + * $platform = devshop_prepare_platform_node($project, $environment_name, $git_ref, $web_server, $prepare_node); + * if ($platform = node_submit($platform)) { + * node_save($platform); + * } + * + * @param $project + * A fully loaded project object. + * + * @param $environment_name + * The name of the environment. + * + * @param $git_ref + * The desired git reference for the environment. + * + * @param $web_server + * The NID of a web server to use for this platform. + * + * @return stdClass + * A populated object ready for node_submit and node_save. + */ +function devshop_prepare_platform_node($project, $environment_name, $git_ref, $web_server) { + $platform_node = new stdClass(); + $platform_node->type = 'platform'; + $platform_node->title = $project->name . '_' . $environment_name; + + // Specify GIT Url for hosting_git.module + $platform_node->git['repo_url'] = $project->git_url; + $platform_node->git['git_ref'] = $git_ref; + + // Enable git pull queue for this platform if chosen. + if ($project->settings->deploy['method'] == 'queue') { + $platform_node->git['pull_method'] = HOSTING_GIT_PULL_QUEUE; + } + // For devshop "webhook" and "manual" methods, disable git pull for now. + // @TODO: Remove devshop "code deploy" task in exchange for hosting_git_pull module. + else { + $platform_node->git['pull_method'] = HOSTING_GIT_PULL_DISABLED; + } + + + // Determine which web server to use. + // If no web server was set and project has a default, make sure we set it. + if (empty($web_server) && !empty($project->settings->default_environment['web_server'])) { + $platform_node->web_server = $project->settings->default_environment['web_server']; + } + else { + $platform_node->web_server = $web_server; + } + + $platform_node->git['repo_path'] = $project->code_path . '/' . $environment_name; + + // Append drupal_path to repo_path if there is one. If not, repo_path is publish_path. + if ($project->drupal_path) { + $platform_node->publish_path = $platform_node->git['repo_path'] . '/' . $project->drupal_path; + $platform_node->git['repo_docroot'] = $project->drupal_path; + } + else { + $platform_node->publish_path = $platform_node->git['repo_path']; + } + return $platform_node; +} + +/** + * Validator for the "other profile" field. + * + * @param $element + * @param $form_state + * @param $form + */ +function devshop_projects_create_environment_form_validate_profile($element, &$form_state, $form) { + if ($form_state['values']['install_method']['method'] == 'profile' && $form_state['values']['install_method']['profile'] =='_other' && empty($element['#value'])) { + form_set_error('install_method][profile_other', t('You must enter a profile name.')); + } +} + +/** + * Validator for the "clone_source" field. + * + * @param $element + * @param $form_state + * @param $form + */ +function devshop_projects_create_environment_form_validate_clone_source($element, &$form_state, $form) { + if ($form_state['values']['install_method']['method'] == 'clone' && $form_state['values']['install_method']['clone_source'] =='_other' && empty($element['#value'])) { + form_set_error('install_method][clone_source_drush', t('You must enter a drush alias.')); + } +} + +/** + * Validator for the "Path to SQL" field. + * + * @param $element + * @param $form_state + * @param $form + */ +function devshop_projects_create_environment_form_validate_import($element, &$form_state, $form) { + + $url_components = parse_url($element['#value']); + + if ($form_state['values']['install_method']['method'] == 'import' && empty($element['#value'])) { + form_set_error('install_method][import', t('If using the "Import Database" Install Method, you must enter a MySQL connection URL or absolute path to an SQL file.')); + } + elseif ($url_components['scheme'] == 'mysql') { + $database = array( + 'database' => ltrim($url_components['path'], '/'), + 'username' => $url_components['user'], + 'password' => $url_components['pass'], + 'host' => $url_components['host'], + 'driver' => 'mysql', // replace with your database driver + ); + Database::addConnectionInfo('devshop_remote_db', 'default', $database); + + try { + db_set_active('devshop_remote_db'); + $tables = db_query('SHOW TABLES'); + drupal_set_message(t('Database connection successful.')); + } + catch (\PDOException $e) { + form_set_error('install_method][import', t('Unable to connect to the database: %e', array('%e' => $e->getMessage()))); + } + finally { + db_set_active(); + } + } +} + +/** + * Validator for site_node_form for node creation, when creating an environment + * in a project. + */ +function devshop_projects_create_environment_form_submit($form, &$form_state) { + + // @TODO: Clean up install_method values. + +} + +/** + * Form element processor for Install Method radio buttons. + * @param $element + * @return array + */ +function devshop_environment_method_process($element) { + $element = form_process_radios($element); + + foreach (element_children($element) as $i) { + $element[$i]['#label_attributes']['class'] = array( + 'btn btn-link' + ); + $element[$i]['#wrapper_attributes']['class'] = array( + 'install-method-wrapper' + ); + } + + return $element; +} + +/** + * Return all available install profiles found in all platforms for this project. + */ +function devshop_environment_add_form_profile_options($project) { + + + foreach ($project->environments as $e => $environment) { + + $packages = hosting_package_instances_load(array( + 'package_type' => 'profile', + 'rid' => $environment->platform, + )); + + foreach ($packages as $instance) { + $options[$instance->short_name] = $instance->title; + $options[$instance->short_name] .= ' ' . $instance->description . ''; + } + } + $options['_other'] = t('Other') . ' ' . t('Choose another profile.') . ''; + return $options; +} + +/** + * Helper to output the forms selection for Deploy Hooks. + * + * Used in the Projects settings form, environment settings form, deploy task form, and sync form. + * + * @param $project + * @param null $environment + * @return array + */ +function devshop_environment_deploy_hooks_form($project, $environment = NULL, $task_type = NULL) { + + // If environment not specified, just grab one. + if (!$environment) { + $environment = current($project->environments); + $is_environment_form = FALSE; + } + else { + $is_environment_form = TRUE; + } + $return = array( + '#type' => 'fieldset', + '#group' => 'project_settings', + '#title' => t('Deployment Hooks'), + '#description' => t('Deployment hooks are run whenever your codebase changes. It is recommended to always enable database updates and cache clearing.'), + '#weight' => -10, + ); + + $return['#project'] = $project; + $return['#environment'] = $environment; + + // If we are on the project creation wizard, set some sane defaults + if (current_path() == 'projects/add/settings') { + $environment_update = FALSE; + $environment_cache = FALSE; + $environment_revert = FALSE; + $environment_dothooks = FALSE; + $environment_acquia_hooks = FALSE; + $environment_composer_install = FALSE; + $project_update = TRUE; + $project_cache = TRUE; + $project_revert = FALSE; + $project_dothooks = FALSE; + $project_acquia_hooks = FALSE; + $project_composer_install = FALSE; + } + else { + $environment_update = isset($environment->settings->deploy) ? $environment->settings->deploy['update'] : FALSE; + $environment_cache = isset($environment->settings->deploy) ? $environment->settings->deploy['cache'] : FALSE; + $environment_revert = isset($environment->settings->deploy) ? $environment->settings->deploy['revert'] : FALSE; + $environment_dothooks = isset($environment->settings->deploy) ? $environment->settings->deploy['dothooks'] : FALSE; + $environment_acquia_hooks = isset($environment->settings->deploy) ? $environment->settings->deploy['acquia_hooks'] : FALSE; + $environment_composer_install = isset($environment->settings->deploy) ? $environment->settings->deploy['composer'] : FALSE; + $project_update = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['update'] : FALSE; + $project_cache = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['cache'] : FALSE; + $project_revert = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['revert'] : FALSE; + $project_dothooks = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['dothooks'] : FALSE; + $project_acquia_hooks = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['acquia_hooks'] : FALSE; + $project_composer_install = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['composer'] : FALSE; + } + + $return['update'] = array( + '#type' => 'checkbox', + '#title' => t('Run database updates.'), + '#default_value' => empty($environment->settings->deploy) ? $project_update : $environment_update, + ); + $return['cache'] = array( + '#type' => 'checkbox', + '#title' => t('Clear all caches.'), + '#default_value' => empty($environment->settings->deploy)? $project_cache : $environment_cache, + ); + $return['revert'] = array( + '#type' => 'checkbox', + '#title' => t('Revert all features.'), + '#description' => t('If features is enabled, revert all of them.'), + '#default_value' => empty($environment->settings->deploy)? $project_revert : $environment_revert, + ); + // Look for .hooks or .hooks.yml + if (!$is_environment_form || isset($project->settings->deploy, $project->settings->deploy['default_hooks']['dothooks'])) { + $return['dothooks'] = array( + '#type' => 'checkbox', + '#title' => t('Run deploy commands in the .hooks file.'), + '#default_value' => empty($environment->settings->deploy) ? $project_dothooks : $environment_dothooks, + '#description' => t('You can add your desired deploy hooks to a file in the root folder of your project. This is recommended as it gives your developers control over what happens when their code is deployed. See the !dothooks for more information. If you use a .hooks file you probably want to uncheck the deploy hooks here.', array( + '!dothooks' => l(t('.hooks documentation'), 'admin/help/devshop_dothooks'), + )), + ); + } + + // Look for acquia cloud hooks. + if (module_exists('devshop_acquia') && (!$is_environment_form || isset($project->settings->deploy, $project->settings->deploy['default_hooks']['acquia_hooks']))) { + $return['acquia_hooks'] = array( + '#type' => 'checkbox', + '#title' => t('Run Acquia Cloud Hooks'), + '#description' => '

    ' . t('Acquia Cloud Hooks were detected in your project. Check this box to run all acquia cloud hooks.') . '

    ' . t('Available cloud hooks are: "post-code-update" when deploying code via git, "post-code-deploy" when deploying code manually, "post-db-copy" when running a sync task with a database, and "post-files-copy" when running a sync task with files.') . '

    ', + '#default_value' => empty($environment->settings->deploy) ? $project_acquia_hooks : $environment_acquia_hooks, + ); + } + + // Allow other modules to alter the deploy hooks form elements. + // @TODO: Create a new hook: "hook_devshop_deploy_hooks()" + drupal_alter('devshop_deploy_hooks_form_elements', $return, $is_environment_form); + + // Disable deploy hooks on environment form, deploy task form ,and sync task form. + if ( + // Environment form + ($is_environment_form && !$project->settings->deploy['allow_environment_deploy_config']) || + ($task_type == 'devshop-deploy' && !$project->settings->deploy['allow_environment_deploy_hooks_override']) + ) { + + foreach (element_children($return) as $i) { + $return[$i]['#type'] = 'value'; + $return[$i]['#value'] = $project->settings->deploy['default_hooks'][$i]; + + $return["{$i}_display"] = array( + '#type' => 'markup', + '#markup' => t($return[$i]['#title']), + '#prefix' => '
    ' . ($return[$i]['#default_value']? '': ''), + '#suffix' => '
    ' + ); + } + $return['#description'] = t('Environment-specific deploy hook configuration is not allowed.'); + + $project_node = node_load($project->nid); + if (node_access('update', $project_node)) { + $return['#description'] .= t('Visit !link to change.', array( + '!link' => l(t('Project Settings'), "node/{$project->nid}/edit", array( + 'fragment' => 'edit-project-settings-deploy-default-hooks', + )), + )); + } + } + return $return; +} + +/** + * Submit handler for site/environment settings page. + */ +function devshop_projects_environment_settings_submit($form, &$form_state) { + + // Save the environment record. + $site = $form_state['node']; + + // Prepare record for saving + // When creating a new environment, there is no environment value. + if (empty($form_state['values']['environment']['name'])) { + $environment = new stdClass(); + $environment->project_nid = $form_state['values']['project_nid']; + $environment->name = $form_state['values']['environment_name']; + $environment->site = $site->nid; + $environment->platform = $site->platform; + } + else { + $environment = (object) $form_state['values']['environment']; + } + + $form_state['values']['environment']['settings']['install_method'] = $form_state['values']['install_method']; + + $environment->settings = serialize($form_state['values']['environment']['settings']); + + // Get platform verify task + $tasks = hosting_get_tasks('rid', $site->nid); + $environment->last_task = current($tasks)->nid; + + // Save environment record. + // If environment already exists, set primary keys. + $project = node_load($environment->project_nid); + if (isset($project->project->environments[$environment->name])) { + $primary_keys = array('project_nid', 'name'); + } + else { + $primary_keys = array(); + } + + if (drupal_write_record('hosting_devshop_project_environment', $environment, $primary_keys)) { + + // Remove the "Site dev.drupal.devshop.local has been updated. + drupal_get_messages('status'); + + // Send our own message + if (!empty($primary_keys)) { + drupal_set_message(t('Settings saved for environment !env.', array('!env' => l($environment->name, "node/{$environment->site}")))); + } + else { + drupal_set_message(t('Environment !env created in project %project.', array( + '!env' => l($environment->name, "node/{$environment->site}"), + '%project' => $project->project_name, + ))); + } + } + else { + drupal_set_message(t('Environment was not saved for %env in project %project!', array('%env' => $environment->name, '%project' => $project->project_name))); + + watchdog('error', 'Environment record not saved: ' . print_r($environment, 1)); + } + + $form_state['redirect'] = 'node/' . $environment->project_nid; +} + + +/** + * Implements hook_form_alter(). + */ +function devshop_projects_form_server_node_form_alter(&$form, &$form_state, $form_id) { + + $items = array(); + $items[] = t('To create a new server, you must first connect it to this one via SSH.'); + $items[] = t("You must grant %aegir access to your new server by adding it's SSH Key (below) to ~/.ssh/authorized_keys.", array( + '%aegir' => "aegir@" . $_SERVER['HTTP_HOST'], + )); + $items[] = t('If you wish to provision a new server, add the key to the +root user, then run the devshop remote:install command.'); + $items[] = t('If you have already provisioned the server, add the key to the aegir user.'); + + $form['note'] = array( + '#markup' => theme('item_list', array('items' => $items)), + '#weight' => -100, + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + + $form['key'] = array( + '#type' => 'item', + '#title' => t(' SSH Public Key for %aegir', array( + '%aegir' => "aegir@" . $_SERVER['HTTP_HOST'], + )), + '#markup' => '
    ' . variable_get('devshop_public_key', '') . '
    ', + '#weight' => -99, + ); +} + +function devshop_projects_form_hosting_task_update_status_form_alter(&$form, &$form_state, $form_id){ + $form['update-status']['#value'] = t('Cancel'); +} + +/** + * Implements hook_hosting_site_options_alter(). + */ +function devshop_projects_hosting_site_options_alter(&$return, $node) { + $return['profile'][] = 0; + $return['platform'][] = 0; +} \ No newline at end of file diff --git a/modules/devshop/devshop_projects/inc/logs.inc b/modules/devshop/devshop_projects/inc/logs.inc new file mode 100644 index 000000000..8e182da2b --- /dev/null +++ b/modules/devshop/devshop_projects/inc/logs.inc @@ -0,0 +1,15 @@ +project->environments[$environment]; + + $env = t('Environment Tasks Log'); + $output = "

    {$environment->name} {$env}

    "; + $output .= views_embed_view('hosting_task_log', 'page_1', $environment->site); + + return $output; +} diff --git a/modules/devshop/devshop_projects/inc/nodes.inc b/modules/devshop/devshop_projects/inc/nodes.inc new file mode 100644 index 000000000..615c55a26 --- /dev/null +++ b/modules/devshop/devshop_projects/inc/nodes.inc @@ -0,0 +1,350 @@ + 'DevShop Project', + "base" => 'devshop_projects', + "description" => t('A project is a website with multiple environments.'), + "has_title" => TRUE, + "title_label" => t('Project Codename'), + "locked" => TRUE, + ); + return $types; +} + +/** + * Implements hook_node_load(). + * + * Loads data into Project nodes. + */ +function devshop_projects_node_load($nodes, $types) { + + // Only act on project and related nodes. + $types = array( + 'project', + 'site', + 'platform', + 'task', + ); + if (count(array_intersect($types, $types)) == 0) { + return; + } + + global $user; + $project = new stdClass(); + + $refs_types = db_select('node', 'n') + ->fields('n', array('nid', 'type')) + ->condition('type', array('site', 'platform', 'project', 'server'), 'IN') + ->execute() + ->fetchAllKeyed(); + + foreach ($nodes as $nid => &$node) { + + // Tasks + if ($node->type == 'task') { + + // Prepare bootstrap-compatible things. + // Load up task types to get our language. + $task_types = hosting_available_tasks(); + + // In Aegir, "executed" and "delta" does not get updated properly. + // Handle "delta" being not computed yet + if ($node->task_status == HOSTING_TASK_PROCESSING) { + $node->duration = format_interval(time() - $node->executed, 1); + } + elseif ($node->task_status == HOSTING_TASK_QUEUED) { + $node->duration = t('Queued for %time', + array('%time' => format_interval(time() - $node->changed), 1)); + } + else { + $node->duration = format_interval($node->delta, 1); + } + + // To test out constantly changing tasks, uncomment this line. + // $node->task_status = rand(-1, 3); + $node->status_class = devshop_task_status_class($node->task_status); + $node->status_name = _hosting_parse_error_code($node->task_status); + + // Override "ago" text. + $node->ago = format_interval(time() - $node->executed, + 1) . ' ' . t('ago'); + if ($node->task_status == HOSTING_TASK_QUEUED) { + $node->ago = t('Queued for %time', + array('%time' => format_interval(time() - $node->changed))); + } + elseif ($node->task_status == HOSTING_TASK_PROCESSING) { + $node->ago = format_interval(time() - $node->changed); + } + + // ISO 8601 + if ($node->executed == 0) { + $executed = $node->created; + } + else { + $executed = $node->executed; + } + $node->task_timestamp = date('c', $executed); + $node->task_date = format_date($executed); + + // Set task type + $node->type_name = $task_types[$refs_types[$node->rid]][$node->task_type]['title']; + + // If platform's first verify... + if (isset($node->ref) && $node->ref->type == 'platform' && $node->ref->verified == 0) { + $node->type_name = t('Cloning Codebase'); + } + + // Get Icon + $node->icon = devshop_task_status_icon($node->task_status); + + // Get desired URL + $node->url = url("node/{$node->nid}"); + + } + // Projects + elseif ($node->type == 'project') { + + // Load the "hosting context". The unique name in the aegir system. + $node->project_name = $node->title; + $node->hosting_name = 'project_' . $node->title; + + $node->project = devshop_project_load($node); + } + elseif ($node->type == 'site') { + + // @TODO: Check if this is ever needed now that install_method is in core aegir. + // Load install method into an easy to access property. + if (empty($node->install_method) && isset($node->environment->settings->install_method)) { + $node->install_method = $node->environment->settings->install_method['method']; + } + + // If profile ID is empty but name is stored, load to $node->profile_name so that provision will load it. + if (empty($node->profile) && !empty($node->environment->settings->install_method['profile'])) { + $node->profile_name = $node->environment->settings->install_method['profile']; + } + } + } +} + +/** + * Implementation of hook_insert(). + * + * 1. Saves data into our table. + * 2. Saves a hosting context name. + * 3. Adds a "Verify" task for this project. + * + * @see hosting_platform_insert() + */ +function devshop_projects_node_insert($node) { + + // On task insert, save last task. + if ($node->type == 'task') { + $site = node_load($node->rid); + + // @TODO: task_type should be task_status... but the system still works. Was this never running? It's possible the other hooks that save last task info is all we need. + if (($site->type == 'site' || $site->type == 'platform') && isset($site->environment) && ($node->task_type == HOSTING_TASK_QUEUED || $node->task_type == HOSTING_TASK_PROCESSING)) { + $site->environment->last_task = $node->nid; + devshop_environment_save_last_task($site->environment); + } + + // When a task is created or updated that has a project, redirect to the project. + if (isset($site->project) && arg(0) == 'hosting_confirm' && arg(1) == $site->nid) { + drupal_goto("node/{$site->project->nid}"); + } + } + + if ($node->type != 'project') { + return; + } + + if (!isset($node->no_verify)) { + hosting_add_task($node->nid, 'verify'); + } + + $info = new stdClass(); + $info->nid = $node->nid; + $info->git_url = $node->project->git_url; + $info->code_path = hosting_path_normalize($node->project->code_path); + $info->drupal_path = hosting_path_normalize($node->project->drupal_path); + $info->base_url = $node->project->base_url; + $info->install_profile = $node->project->install_profile; + + // Save serialized data, minus environments + $info->settings = serialize($node->project->settings); + + drupal_write_record('hosting_devshop_project', $info); + + // Save hosting context + if (!$node->old_vid) { + + // Save the "hosting context", which includes setting the path alias. + $name = ($node->hosting_name) ? $node->hosting_name : $node->title; + + // Ensure "project_" prefix on hosting context name. + if (strpos($name, 'project_') !== 0) { + $context_name = 'project_' . $name; + } + else { + $context_name = $name; + } + + hosting_context_register($node->nid, $context_name); + + // Replace the alias created by hosting_context_register. + $path['source'] = "node/{$node->nid}"; + $path['alias'] = "project/{$name}"; + path_save($path); + } + + // @TODO: The wizard always creates the project before the environments. + // Not sure if we need this, but we might to enable importing via drush. + // Save Environment records. + if (!empty($node->project->environments)) { + foreach ($node->project->environments as $name => $environment) { + // Ensure correct data types + $environment = (object) $environment; + $environment->settings = (array) $environment->settings; + + $info = new stdClass(); + $info->project_nid = $node->nid; + $info->name = $name; + $info->site = $environment->site; + $info->platform = $environment->platform; + + // Remove primary settings from settings array before saving. + unset($environment->settings['git_ref']); + unset($environment->settings['site']); + unset($environment->settings['platform']); + $info->settings = serialize($environment->settings); + + // Save environment record. + drupal_write_record('hosting_devshop_project_environment', $info); + } + } +} + +/** + * Implementation of hook_update(). + * + * 1. Updates our table. + * 2. Adds a "Verify" task for this project. + * + */ +function devshop_projects_node_update($node) { + + + // On task insert, save last task. + if ($node->type == 'task') { + $site = node_load($node->rid); + + // When a task is created or updated that has a project, redirect to the project. + if (isset($site->project) && arg(0) == 'hosting_confirm' && arg(1) == $site->nid) { + drupal_goto("node/{$site->project->nid}"); + } + } + + if ($node->type != 'project') { + return; + } + + $project = (object) $node->project; + $project->settings = (object) $project->settings; + + $info = new stdClass(); + $info->nid = $node->nid; + $info->git_url = $project->git_url; + $info->code_path = hosting_path_normalize($project->code_path); + $info->drupal_path = hosting_path_normalize($project->drupal_path); + $info->base_url = $project->base_url; + $info->install_profile = $project->install_profile? $project->install_profile: $project->settings->default_environment['install_profile']; + + // Save serialized data, minus environments + $info->settings = serialize($project->settings); + + // Write project record. + drupal_write_record('hosting_devshop_project', $info, 'nid'); + if (!isset($node->no_verify) || $node->no_verify == FALSE) { + hosting_add_task($node->nid, 'verify'); + } +} + +/** + * Implements hook_node_delete(). + */ +function devshop_projects_node_delete($node) { + + // When a platform or site node is FULLY deleted, remove the environment record. + // This should never happen, site and platform nodes are never fully deleted. + if (($node->type == 'site' || $node->type == 'platform') && !empty($node->project)) { + db_delete('hosting_devshop_project_environment') + ->condition($node->type, $node->nid) + ->execute(); + } +} + +/** + * Implementation of hook_delete(). + */ +function devshop_projects_delete($node) { + + db_delete('hosting_devshop_project') + ->condition('nid', $node->nid) + ->execute(); + + db_delete('hosting_devshop_project_environment') + ->condition('project_nid', $node->nid) + ->execute(); + + hosting_context_delete($node->nid); + hosting_task_delete_related_tasks($node->nid); +} + +/** + * @param $environment + */ +function devshop_environment_save(stdClass &$environment) { + $environment->settings = (array) $environment->settings; + + // Prepare record for saving + $info = new stdClass(); + $info->project_nid = $environment->project_nid; + $info->name = $environment->name; + $info->site = $environment->site; + $info->platform = $environment->platform; + $info->settings = serialize($environment->settings); + $info->last_task = $environment->last_task; + + // Save environment record. + if (drupal_write_record('hosting_devshop_project_environment', $info, array('project_nid', 'name'))) { + watchdog('ok', 'Environment record saved: ' . print_r($info, 1)); + return TRUE; + } + else { + watchdog('error', 'Environment record not saved: ' . print_r($info, 1)); + return FALSE; + } +} + +/** + * Helper to save last task info. + */ +function devshop_environment_save_last_task($environment) { + + db_update('hosting_devshop_project_environment') + ->fields(array( + 'last_task' => $environment->last_task, + )) + ->condition('name', $environment->name) + ->condition('project_nid', $environment->project_nid) + ->execute(); +} diff --git a/modules/devshop/devshop_projects/inc/queue.inc b/modules/devshop/devshop_projects/inc/queue.inc new file mode 100644 index 000000000..0534e01f9 --- /dev/null +++ b/modules/devshop/devshop_projects/inc/queue.inc @@ -0,0 +1,62 @@ + 'batch', +// 'name' => t('Deploy Queue'), +// 'description' => t('Deploy Queue: Runs a Deploy task on projects configured to use "Manual Deployment" for environments that are not set to "Disable Deploy on Commit".'), +// 'total_items' => count(devshop_projects_get_deploy_queue_environments()), +// 'frequency' => strtotime("5 minutes", 0), +// 'singular' => t('environment'), +// 'plural' => t('environments'), +// ); +// return $items; +//} +// +///** +// * Get the environments to be pulled in the queue. +// * +// * @param $limit +// * Limit to a maximum of this number of platforms. +// * @return +// * An array of site nodes that have a pull queue enabled. +// * +// */ +//function devshop_projects_get_deploy_queue_environments() { +// +// $results = db_query("SELECT nid FROM {hosting_devshop_project}")->fetchCol(); +// $environments = array(); +// foreach ($results as $nid) { +// $node = node_load($nid); +// if (!isset($node->project)) { +// continue; +// } +// $project = $node->project; +// if (isset($project->settings->deploy) && $project->settings->deploy['method'] == 'queue'){ +// foreach ($project->environments as $environment) { +// if ($project->settings->git['refs'][$environment->git_ref] == 'branch' && $environment->settings->pull_disabled == 0) { +// $environments[] = $environment; +// } +// } +// } +// } +// return $environments; +//} +// +///** +// * Implements hosting_QUEUE_TYPE_queue(). +// */ +//function hosting_deploy_queue($count) { +// $environments = devshop_projects_get_deploy_queue_environments($count); +// foreach ($environments as $environment) { +// $args = array(); +// $args['git_ref'] = $environment->git_ref; +// $args['update'] = $environment->settings->deploy['update']; +// $args['revert'] = $environment->settings->deploy['revert']; +// $args['cache'] = $environment->settings->deploy['cache']; +// hosting_add_task($environment->site, 'devshop-deploy', $args); +// } +//} diff --git a/modules/devshop/devshop_projects/inc/reload.js b/modules/devshop/devshop_projects/inc/reload.js new file mode 100644 index 000000000..1fb75b18b --- /dev/null +++ b/modules/devshop/devshop_projects/inc/reload.js @@ -0,0 +1,32 @@ +(function ($) { + Drupal.behaviors.devshopReload = { + attach: function (context, settings) { + setTimeout('Drupal.behaviors.devshopReload.checkProject()', Drupal.settings.devshopReload.delay); + }, + checkProject: function() { + console.log('Checking...'); + var url = '/projects/add/status/' + Drupal.settings.devshopReload.type; + $.getJSON(url, function (data) { + $.each(data, function (i, platform) { + if (platform.version) { + jQuery('#version-' + i).html(platform.version); + } + if (platform.profiles) { + jQuery('#profiles-' + i).html(platform.profiles); + } + if (platform.status) { + if (platform.status == 'Processing') { + platform.status += ' '; + } + jQuery('#status-' + i).html(platform.status); + } + }); + if (data.tasks_complete){ + document.location.reload(); + } else { + setTimeout("Drupal.behaviors.devshopReload.checkProject()", Drupal.settings.devshopReload.delay); + } + }); + } + }; +}(jQuery)); diff --git a/modules/devshop/devshop_projects/inc/task-ajax.js b/modules/devshop/devshop_projects/inc/task-ajax.js new file mode 100644 index 000000000..fe8170016 --- /dev/null +++ b/modules/devshop/devshop_projects/inc/task-ajax.js @@ -0,0 +1,194 @@ +(function ($) { + Drupal.behaviors.devshopTasks = { + attach: function (context, settings) { + setTimeout("Drupal.behaviors.devshopTasks.checkTasks()", 1000); + }, + checkTasks: function () { + var url = '/devshop/tasks'; + if (Drupal.settings.devshopTask) { + url = '/devshop/tasks?task=' + Drupal.settings.devshopTask; + } + + $.getJSON(url, function (data) { + + var lastTaskStatus = null; + $.each(data, function (key, task) { + var environment_id = '#' + task.project_name + '-' + task.environment; + var task_id = '#task-display-' + task.nid; + var new_class = 'environment-tasks-alert alert-' + task.status_class; + + var $alert_div = $(task_id + '.environment-tasks-alert'); + + // Project Node Page + if (Drupal.settings.devshopTask == null) { + + // If $alert_div does not exist, create it. + if ($alert_div.length == 0) { + $lastTaskWrapper = $('.last-task-alert', environment_id); + $lastTaskWrapper.html(task.rendered); + + $('.environment-task-logs .tasks-wrapper', environment_id).prepend(task.rendered); + + $alert_div = $(task_id + '.environment-tasks-alert'); + + $("time.timeago", $alert_div).timeago(); + } + + // Set class of wrapper div + $alert_div.attr('class', new_class); + + // Set or remove active class from environment div. + if (task.status_class == 'queued' || task.status_class == 'processing') { + $(environment_id).addClass('active'); + } + else { + $(environment_id).removeClass('active'); + } + + // Remove any status classes and add current status + $(environment_id).removeClass('task-queued'); + $(environment_id).removeClass('task-processing'); + $(environment_id).removeClass('task-success'); + $(environment_id).removeClass('task-error'); + $(environment_id).removeClass('task-warning'); + $(environment_id).addClass('task-' + task.status_class); + + // Set value of label span + $('.alert-link > .type-name', $alert_div).html(task.type_name); + + // If queued or processing, make label empty. + if (task.status_class == 'queued' || task.status_class == 'processing') { + if (task.status_class == 'queued') { + $('.alert-link > .status-name', $alert_div).html(''); + } + else { + $('.alert-link > .status-name', $alert_div).html(task.status_name); + } + $('.alert-link .ago-icon', $alert_div).removeClass('fa-calendar'); + $('.alert-link .ago-icon', $alert_div).addClass('fa-clock-o'); + } + else { + $('.alert-link > .status-name', $alert_div).html(task.status_name); + + $('.alert-link .ago-icon', $alert_div).removeClass('fa-clock-o'); + $('.alert-link .ago-icon', $alert_div).addClass('fa-calendar'); + } + + // Set value of "ago" + $('.alert-link .ago', $alert_div).html(task.ago); + + // Change icon. + $('.alert-link > .fa', $alert_div).attr('class', 'fa fa-' + task.icon); + + // Change href + $('.alert-link', $alert_div).attr('href', task.url); + + // Change "processing" div + } + // Task Node Page + else if (Drupal.settings.devshopTask == task.nid) { + + $badge = $('.label.task-status', '#node-' + task.nid); + + // Change Badge + var html = ' ' + task.status_name; + $badge.html(html); + + // Change Class + $badge.attr('class', 'label label-default task-status label-' + task.status_class); + + // Reload Logs + $logs = $('#task-logs', '#node-' + task.nid); + $logs.html(task.logs); + + // @TODO: + // Change Duration + $('.duration .duration-text', '#node-' + task.nid).html(task.duration); + + + // If task is not processing or queued, hide follow link. + if (task.task_status != 0 && task.task_status != -1) { + // Scroll down one last time if checked. + if ($('#follow').prop('checked')) { + window.scrollTo(0, document.body.scrollHeight); + } + $('.follow-logs-checkbox').remove(); + $('.edit-update-status').remove(); + $('.running-indicator').remove(); + Drupal.settings.lastTaskStopped = true; + } + else { + // Scroll down if follow checkbox is checked. + if ($('#follow').prop('checked')) { + window.scrollTo(0, document.body.scrollHeight); + } + } + + // If running, set text to indicate + if (task.task_status == -1) { + $('.running-indicator .running-label').text('Processing...'); + $('.running-indicator .fa-gear').addClass('fa-spin'); + } + + // If the last task was complete, and this task is complete, stop the autoloader. + if (Drupal.settings.lastTaskStopped && (task.task_status != -1 && task.task_status != 0)) { + console.log('Task is not processing or queued. Stopping the autoloader.'); + Drupal.settings.lastTaskStopped = true; + } + } + + // Projects List Page: Update badges. + if ($('body.page-projects').length) { + var id = '#badge-' + task.project_name + '-' + task.environment; + + // Set class of badge + $(id).attr('class', 'btn btn-small alert-' + task.status_class); + + // Set title + var title = task.type_name + ': ' + task.status_class; + $(id).attr('title', title); + + // Change icon. + $('.fa', id).attr('class', 'fa fa-' + task.icon); + } + + // Update global tasks list. + var task_id = '#task-' + task.project_name + '-' + task.environment; + + // Set class of badge + $(task_id).attr('class', 'list-group-item list-group-item-' + task.status_class); + + // Set value of "ago" + $('small.task-ago', task_id).html(task.ago); + + // Change icon. + $('.fa', task_id).attr('class', 'fa fa-' + task.icon); + + }); + + // Activate or de-activate global tasks icon. + var gear_class = 'fa fa-gear'; + if ($('.list-group-item-queued', '.devshop-tasks').length) { + gear_class += ' active-task'; + } + if ($('.list-group-item-processing', '.devshop-tasks').length) { + gear_class += ' active-task fa-spin'; + } + + // Set class for global gear icon. + $('i.fa', '#navbar-main .task-list-button').attr('class', gear_class); + + // Set count + var count = $('.list-group-item-queued', '.devshop-tasks').length + $('.list-group-item-processing', '.devshop-tasks').length; + if (count == 0) { + count = ''; + } + $('.count', '.task-list-button').html(count); + + if (Drupal.settings.lastTaskStopped != true) { + setTimeout("Drupal.behaviors.devshopTasks.checkTasks()", 1000); + } + }); + } + } +}(jQuery)); diff --git a/modules/devshop/devshop_projects/inc/tasks-ajax.inc b/modules/devshop/devshop_projects/inc/tasks-ajax.inc new file mode 100644 index 000000000..60eea1398 --- /dev/null +++ b/modules/devshop/devshop_projects/inc/tasks-ajax.inc @@ -0,0 +1,91 @@ +last_task; + } + + // Look up any remaining queued or processing tasks. + $query = db_select('node', 'n'); + $query->join('hosting_task', 't', 'n.vid=t.vid'); + $query + ->fields('n', array('nid')) + ->condition('type', 'task') + ->orderBy('n.vid', 'DESC') + ->addTag('node_access'); + + $or = db_or(); + $or->condition('task_status', HOSTING_TASK_PROCESSING); + $or->condition('task_status', HOSTING_TASK_QUEUED); + $query->condition($or); + + if (!empty($nids)) { + $query->condition('n.nid', $nids, 'NOT IN'); + } + $extras = $query->execute(); + foreach ($extras as $row) { + $nids[] = $row->nid; + } + + foreach ($nids as $nid) { + $task_node = node_load($nid); + + if (isset($task_node->site)) { + $task_node->site_node = node_load($task_node->site); + + if ($task_node->site_node->site_status == HOSTING_SITE_DELETED) { + continue; + } + } + + // If no last task node was found, skip. + if (empty($task_node)) { + continue; + } + + // If page is requesting this task's logs, load them. + if (isset($_GET['task']) && $_GET['task'] == $task_node->nid) { + $messages = devshop_task_get_messages($task_node); + if (count($messages)) { + $task_node->logs = implode("\n", $messages); + } + } + + // Output a rendered task node + $task_node->rendered = theme('devshop_task', array('task' => $task_node)); + + $output[] = $task_node; + } + + print json_encode($output); + exit; +} diff --git a/modules/devshop/devshop_projects/inc/theme.inc b/modules/devshop/devshop_projects/inc/theme.inc new file mode 100644 index 000000000..21bdc07eb --- /dev/null +++ b/modules/devshop/devshop_projects/inc/theme.inc @@ -0,0 +1,504 @@ + array( + 'variables' => array( + 'form' => NULL, + ), + ), + 'devshop_projects_create_settings_form' => array( + 'render element' => 'form', + ), + 'devshop_project_nav' => array( + 'variables' => array( + 'node' => NULL, + 'settings_active' => NULL, + 'logs_active' => NULL, + 'branches_class' => NULL, + ), + 'template' => 'project-nav', + ), + 'devshop_project_add_status' => array( + 'variables' => array( + 'project' => NULL, + ), + 'template' => 'project-add', + ), + 'devshop_ascii' => array( + 'variables' => array( + 'output' => '', + ), + ), + 'devshop_task' => array( + 'variables' => array( + 'task' => NULL, + ), + 'template' => 'task', + ), + 'devshop_add_environment_options' => array( + 'render element' => 'form', + 'template' => 'add-environment-options', + 'path' => drupal_get_path('module', 'devshop_projects') . '/includes', + ), + ); +} + +/** + * Theme function for environments settings + */ +function theme_devshop_projects_settings_form($form) { + $rows = array(); + $header = array(); + $header[] = t('Environment'); + foreach (element_children($form) as $env_name) { + $row = array(); + $row[] = $env_name . drupal_render($form[$env_name]['git_ref']); + foreach (element_children($form[$env_name]['settings']) as $setting) { + if (!isset($header[$setting])) { + $header[$setting] = $form[$env_name]['settings'][$setting]['#title']; + } + $form[$env_name]['settings'][$setting]['#title'] = ''; + $row[] = drupal_render($form[$env_name]['settings'][$setting]); + } + $rows[] = $row; + } + $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'project-settings-table'))); + return $output; +} + +/** + * Theme function for create environments settings + * @TODO: Fold into theme_devshop_projects_settings_form() + */ +function theme_devshop_projects_create_settings_form($vars) { + + $form = $vars['form']; + $rows = array(); + $header = array(); + foreach (element_children($form) as $env_name) { + $row = array(); + $header['name'] = 'Name'; + $header['git_ref'] = t('Branch/Tag'); + + $name_element = $form[$env_name]['name']; + $git_ref_element = $form[$env_name]['git_ref']; + $settings_element = $form[$env_name]['settings']; + + $row[] = drupal_render($name_element); + $row[] = drupal_render($git_ref_element); + + foreach (element_children($settings_element) as $setting) { + if (!isset($header[$setting])) { + $header[$setting] = isset($form[$env_name]['settings'][$setting]['#title']) ? $form[$env_name]['settings'][$setting]['#title'] : ''; + } + $form[$env_name]['settings'][$setting]['#title'] = ''; + + $element = $form[$env_name]['settings'][$setting]; + $row[] = drupal_render($element); + } + $rows[] = $row; + } + $output = theme('table', array( + 'header' => $header, + 'rows' => $rows, + 'attributes' => array( + 'id' => 'project-settings-table', + 'class' => array('table'), + ) + )); + $output .= '

    ' . t('Create as many new environments as you would like. For example: "dev", "test", and "live". You can create more later on if needed.') . '

    '; + + return $output; +} + +/** + * Preprocess page + */ +function devshop_projects_preprocess_node(&$vars) { + + $node = $vars['node']; + + // On project node edit page + if (isset($vars['node']) && $vars['node']->type == 'project') { + + $vars['drush_aliases'] = devshop_project_aliases($vars['node']->project); + $vars['aliases_url'] = url("node/{$vars['node']->nid}/aliases"); + + if (module_exists('aegir_ssh') && user_access('manage own SSH public keys')) { + global $user; + $vars['access_note'] = t('NOTE: To access these environments with drush remotely, make sure you have uploaded your public SSH key under !link.', array( + '!link' => l('My Account > SSH Keys', "user/$user->uid/ssh-keys"), + )); + } + else { + $vars['access_note'] = t('NOTE: To access these environments with drush remotely, ask an administrator to add your public SSH key to the file /var/aegir/.ssh/authorized_keys.'); + } + } + + // On task node page + if (isset($vars['node']) && $vars['node']->type == 'task' && ($vars['page'] || current_path() == "hosting/task/{$node->nid}")){ + // Prepare task icon, class, and label. + $icon = devshop_task_status_icon($vars['node']->task_status); + $vars['task_label'] = " " . _hosting_parse_error_code($vars['node']->task_status); + $vars['task_label_class'] = devshop_task_status_class($vars['node']->task_status); + $vars['task_icon'] = devshop_task_status_icon($vars['node']->task_status); + + // Load ref node + $ref_node = node_load($vars['rid']); + if ($ref_node->type == 'site') { + $vars['site_url'] = l($ref_node->environment->url, $ref_node->environment->url); + } + + // Add a "Retry" button, unless it's a clone task. Retrying a clone can be bad if an old clone task is restarted, it uses the latest task arguments, not the one you are viewing. + if ($vars['node']->task_type != 'clone' && user_access('retry failed tasks') && ($vars['node']->task_status == HOSTING_TASK_ERROR)) { + $vars['retry'] = drupal_get_form('hosting_task_retry_form', $vars['node']->nid); + } + + // Show duration + if ($vars['node']->task_status == HOSTING_TASK_QUEUED) { + $vars['duration'] = t('Queued for %time', array('%time' => format_interval(REQUEST_TIME - $vars['node']->changed))); + $vars['date'] = date('D M j Y', $vars['node']->changed); + $vars['executed'] = ''; + } + else { + + if ($vars['node']->task_status == HOSTING_TASK_PROCESSING) { + $vars['duration'] = format_interval(REQUEST_TIME - $vars['node']->executed, 1); + $vars['executed'] = ''; + } + else { + $vars['duration'] = format_interval($vars['node']->delta, 1); + $vars['executed'] = format_interval(REQUEST_TIME - $vars['node']->executed) . ' ' . t('ago'); + } + $vars['date'] = date('D M j Y', $vars['node']->executed); + } + + // Load Logs + $messages = devshop_task_get_messages($vars['node']); + $vars['messages'] = implode("\n", $messages); + switch ($vars['task_type']) { + case 'devshop-deploy': + $vars['type'] = 'Deploy'; + break; + case 'verify': + case 'test': + case 'sync': + case 'install': + default: + $vars['type'] = ucfirst($vars['task_type']); + break; + } + + // Add "Follow Logs" button. + if ($vars['node']->task_status == HOSTING_TASK_PROCESSING || $vars['node']->task_status == HOSTING_TASK_QUEUED ) { + $vars['follow_checkbox'] = array( + '#type' => 'markup', + '#markup' => '