diff --git a/composer.json b/composer.json index 7ce3db6..9d8e373 100644 --- a/composer.json +++ b/composer.json @@ -3,8 +3,17 @@ "description": "Drupal is an open source content management platform powering millions of websites and applications.", "type": "drupal-core", "license": "GPL-2.0+", + "repositories": [ + { + "type":"git", + "url": "https://github.com/larowlan/MinkGoutteDriver.git" + } + ], "require": { "php": ">=5.4.2", + "behat/mink": "~1.5", + "behat/mink-goutte-driver": "dev-master", + "fabpot/goutte": "dev-master", "sdboyer/gliph": "0.1.*", "symfony/class-loader": "2.4.*", "symfony/css-selector": "2.4.*", diff --git a/composer.lock b/composer.lock index fdc46fe..224a646 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,164 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "e1a7eeed7d09f5c76f642f07b7a0a579", + "hash": "14e02fec8f9fb930688b0709cf0bc5f3", "packages": [ { + "name": "behat/mink", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Mink.git", + "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Mink/zipball/0769e6d9726c140a54dbf827a438c0f9912749fe", + "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/css-selector": "~2.0" + }, + "suggest": { + "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", + "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", + "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", + "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "1.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Mink": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Web acceptance testing framework for PHP 5.3", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "testing", + "web" + ], + "time": "2013-04-13 23:39:27" + }, + { + "name": "behat/mink-browserkit-driver", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/MinkBrowserKitDriver.git", + "reference": "63960c8fcad4529faad1ff33e950217980baa64c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/MinkBrowserKitDriver/zipball/63960c8fcad4529faad1ff33e950217980baa64c", + "reference": "63960c8fcad4529faad1ff33e950217980baa64c", + "shasum": "" + }, + "require": { + "behat/mink": "~1.5.0", + "php": ">=5.3.1", + "symfony/browser-kit": "~2.0", + "symfony/dom-crawler": "~2.0" + }, + "require-dev": { + "silex/silex": "@dev" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Mink\\Driver": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Symfony2 BrowserKit driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "Mink", + "Symfony2", + "browser", + "testing" + ], + "time": "2013-04-13 23:46:30" + }, + { + "name": "behat/mink-goutte-driver", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/larowlan/MinkGoutteDriver.git", + "reference": "488f7f02b1e907888f4b156b635693daf51d760c" + }, + "require": { + "behat/mink": "~1.5@dev", + "behat/mink-browserkit-driver": "~1.1@dev", + "fabpot/goutte": "dev-master", + "php": ">=5.4" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Mink\\Driver": "src/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Goutte driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "goutte", + "headless", + "testing" + ], + "time": "2014-06-04 04:35:02" + }, + { "name": "doctrine/annotations", "version": "dev-master", "source": { @@ -458,6 +613,55 @@ "time": "2013-12-30 22:31:37" }, { + "name": "fabpot/goutte", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/fabpot/Goutte.git", + "reference": "b12c3f7ec68d8814b50444cfe142fd0a056557f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fabpot/Goutte/zipball/b12c3f7ec68d8814b50444cfe142fd0a056557f9", + "reference": "b12c3f7ec68d8814b50444cfe142fd0a056557f9", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "4.*", + "php": ">=5.4.0", + "symfony/browser-kit": "~2.1", + "symfony/css-selector": "~2.1", + "symfony/dom-crawler": "~2.1" + }, + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-0": { + "Goutte": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A simple PHP Web Scraper", + "homepage": "https://github.com/fabpot/Goutte", + "keywords": [ + "scraper" + ], + "time": "2014-07-22 13:24:11" + }, + { "name": "guzzlehttp/guzzle", "version": "4.1.3", "source": { @@ -1464,6 +1668,63 @@ "time": "2013-10-14 15:32:46" }, { + "name": "symfony/browser-kit", + "version": "v2.5.2", + "target-dir": "Symfony/Component/BrowserKit", + "source": { + "type": "git", + "url": "https://github.com/symfony/BrowserKit.git", + "reference": "b5e807a669333ac9e7def19ef39a6e542786010d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/b5e807a669333ac9e7def19ef39a6e542786010d", + "reference": "b5e807a669333ac9e7def19ef39a6e542786010d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/dom-crawler": "~2.0" + }, + "require-dev": { + "symfony/css-selector": "~2.0", + "symfony/process": "~2.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\BrowserKit\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "http://symfony.com", + "time": "2014-07-09 09:05:48" + }, + { "name": "symfony/class-loader", "version": "v2.4.1", "target-dir": "Symfony/Component/ClassLoader", @@ -1680,6 +1941,61 @@ "time": "2014-01-01 09:02:49" }, { + "name": "symfony/dom-crawler", + "version": "v2.5.2", + "target-dir": "Symfony/Component/DomCrawler", + "source": { + "type": "git", + "url": "https://github.com/symfony/DomCrawler.git", + "reference": "b3d748f9d7ae77890d935bb97445c666b83dd59e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/b3d748f9d7ae77890d935bb97445c666b83dd59e", + "reference": "b3d748f9d7ae77890d935bb97445c666b83dd59e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/css-selector": "~2.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\DomCrawler\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "http://symfony.com", + "time": "2014-07-09 09:05:48" + }, + { "name": "symfony/event-dispatcher", "version": "v2.4.1", "target-dir": "Symfony/Component/EventDispatcher", @@ -2423,14 +2739,12 @@ "time": "2013-06-12 19:46:58" } ], - "packages-dev": [ - - ], - "aliases": [ - - ], + "packages-dev": [], + "aliases": [], "minimum-stability": "stable", "stability-flags": { + "behat/mink-goutte-driver": 20, + "fabpot/goutte": 20, "symfony/yaml": 20, "doctrine/common": 20, "doctrine/annotations": 20, @@ -2441,7 +2755,5 @@ "platform": { "php": ">=5.4.2" }, - "platform-dev": [ - - ] + "platform-dev": [] } diff --git a/core/modules/block/src/Tests/BlockHtmlTest.php b/core/modules/block/src/Tests/BlockHtmlTest.php index 5e508e5..0bb68f7 100644 --- a/core/modules/block/src/Tests/BlockHtmlTest.php +++ b/core/modules/block/src/Tests/BlockHtmlTest.php @@ -7,14 +7,14 @@ namespace Drupal\block\Tests; -use Drupal\simpletest\WebTestBase; +use Drupal\simpletest\BrowserTestBase; /** * Tests block HTML ID validity. * * @group block */ -class BlockHtmlTest extends WebTestBase { +class BlockHtmlTest extends BrowserTestBase { /** * Modules to enable. @@ -45,11 +45,10 @@ function testHtml() { // Ensure that a block's ID is converted to an HTML valid ID, and that // block-specific attributes are added to the same DOM element. - $this->assertFieldByXPath('//div[@id="block-test-html-block" and @data-custom-attribute="foo"]', NULL, 'HTML ID and attributes for test block are valid and on the same DOM element.'); + $this->assertXPath('//div[@id="block-test-html-block" and @data-custom-attribute="foo"]', 'HTML ID and attributes for test block are valid and on the same DOM element.'); // Ensure expected markup for a menu block. - $elements = $this->xpath('//div[contains(@class, :div-class)]/div/ul[contains(@class, :ul-class)]/li', array(':div-class' => 'block-system', ':ul-class' => 'menu')); - $this->assertTrue(!empty($elements), 'The proper block markup was found.'); + $this->assertXPath('//div[contains(@class, "block-system")]/div/ul[contains(@class, "menu")]/li', 'The proper block markup was found.'); } } diff --git a/core/modules/simpletest/src/BrowserTestBase.php b/core/modules/simpletest/src/BrowserTestBase.php new file mode 100644 index 0000000..458db38 --- /dev/null +++ b/core/modules/simpletest/src/BrowserTestBase.php @@ -0,0 +1,261 @@ +skipClasses[__CLASS__] = TRUE; + } + + protected function setUp() { + parent::setUp(); + if (!isset($this->mink)) { + $driver = new GoutteDriver(); + $session = new Session($driver); + $this->mink = new Mink(); + $this->mink->registerSession('goutte', $session); + $this->mink->setDefaultSessionName('goutte'); + } + } + + protected function tearDown() { + parent::tearDown(); + $this->mink->resetSessions(); + } + + /** + * Retrieves a Drupal path or an absolute path. + * + * @param string $path + * Drupal path or URL to load into internal browser + * @param array $options + * Options to be forwarded to the url generator. + * @param array $headers + * An array containing additional HTTP request headers + * + * @return string + * The retrieved HTML string, also available as $this->getRawContent() + */ + protected function drupalGet($path, array $options = array(), array $headers = array()) { + $options['absolute'] = TRUE; + + // The URL generator service is not necessarily available yet; e.g., in + // interactive installer tests. + if ($this->container->has('url_generator')) { + $url = $this->container->get('url_generator')->generateFromPath($path, $options); + } + else { + $url = $this->getAbsoluteUrl($path); + } + + $session = $this->mink->getSession(); + + // Set request headers. + $session->setRequestHeader('user-agent', drupal_generate_test_ua($this->databasePrefix)); + foreach ($headers as $name => $value) { + $session->setRequestHeader($name, $value); + } + + $session->visit($url); + + // Restore default request headers. + foreach ($headers as $name => $value) { + $session->setRequestHeader($name, NULL); + } + + $out = $session->getPage()->getContent(); + + // Ensure that any changes to variables in the other thread are picked up. + $this->refreshVariables(); + + $verbose = 'GET request to: ' . $path . + '
Ending URL: ' . $session->getCurrentUrl(); + if ($this->dumpHeaders) { + $headers = $session->getResponseHeaders(); + $verbose .= '
Headers:
' . String::checkPlain(var_export(array_map('trim', $headers), TRUE)) . '
'; + } + $verbose .= '
' . $out; + + $this->verbose($verbose); + return $out; + } + + /** + * Log in a user with the internal browser. + * + * If a user is already logged in, then the current user is logged out before + * logging in the specified user. + * + * Please note that neither the current user nor the passed-in user object is + * populated with data of the logged in user. If you need full access to the + * user object after logging in, it must be updated manually. If you also need + * access to the plain-text password of the user (set by drupalCreateUser()), + * e.g. to log in the same user again, then it must be re-assigned manually. + * For example: + * @code + * // Create a user. + * $account = $this->drupalCreateUser(array()); + * $this->drupalLogin($account); + * // Load real user object. + * $pass_raw = $account->pass_raw; + * $account = user_load($account->id()); + * $account->pass_raw = $pass_raw; + * @endcode + * + * @param \Drupal\Core\Session\AccountInterface $account + * User object representing the user to log in. + * + * @see drupalCreateUser() + */ + protected function drupalLogin(AccountInterface $account) { + if ($this->loggedInUser) { + $this->drupalLogout(); + } + + $this->drupalGet('user'); + $page = $this->mink->getSession()->getPage(); + $page->fillField('name', $account->getUsername()); + $page->fillField('pass', $account->pass_raw); + $page->pressButton(t('Log in')); + + $this->session_id = $this->mink->getSession()->getCookie($this->getSessionName()); + + // @see WebTestBase::drupalUserIsLoggedIn() + if (isset($this->session_id)) { + $account->session_id = $this->session_id; + } + $pass = $this->assert($this->drupalUserIsLoggedIn($account), format_string('User %name successfully logged in.', array('%name' => $account->getUsername())), 'User login'); + if ($pass) { + $this->loggedInUser = $account; + $this->container->get('current_user')->setAccount($account); + // @todo Temporary workaround for not being able to use synchronized + // services in non dumped container. + $this->container->get('access_subscriber')->setCurrentUser($account); + } + } + + /** + * Logs a user out of the internal browser and confirms. + * + * Confirms logout by checking the login page. + */ + protected function drupalLogout() { + // Make a request to the logout page, and redirect to the user page, the + // idea being if you were properly logged out you should be seeing a login + // screen. + $this->drupalGet('user/logout', array('query' => array('destination' => 'user'))); + $this->assertResponse(200, 'User was logged out.'); + $pass = $this->assertField('name', 'Username field found.', 'Logout'); + $pass = $pass && $this->assertField('pass', 'Password field found.', 'Logout'); + + if ($pass) { + // @see WebTestBase::drupalUserIsLoggedIn() + unset($this->loggedInUser->session_id); + $this->loggedInUser = FALSE; + $this->container->get('current_user')->setAccount(new AnonymousUserSession()); + } + } + + /** + * Asserts the page responds with the specified response code. + * + * @param $code + * Response code. For example 200 is a successful page request. For a list + * of all codes see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html. + * @param $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Browser'; most tests do not override + * this default. + * + * @return bool + * Assertion result. + */ + protected function assertResponse($code, $message = '', $group = 'Browser') { + $status_code = $this->mink->getSession()->getStatusCode(); + $match = is_array($code) ? in_array($status_code, $code) : $status_code == $code; + return $this->assertTrue($match, $message ? $message : String::format('HTTP response expected !code, actual !status_code', array('!code' => $code, '!status_code' => $status_code)), $group); + } + + /** + * Asserts that an elements exists in the current page by the given XPath. + * + * @param string $xpath + * XPath used to find the element. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param string $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Other'; most tests do not override + * this default. + * + * @return bool + * TRUE on pass, FALSE on fail. + */ + protected function assertXPath($xpath, $message = '', $group = 'Other') { + return $this->assertNotNull($this->mink->getSession()->getPage()->find('xpath', $xpath), $message, $group); + } + + /** + * Asserts that a field exists with the given name or ID. + * + * @param string $field + * Name or ID of field to assert. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param string $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Other'; most tests do not override + * this default. + * + * @return bool + * TRUE on pass, FALSE on fail. + */ + protected function assertField($field, $message = '', $group = 'Other') { + return $this->assertTrue($this->mink->getSession()->getPage()->hasField($field), $message, $group); + } +} diff --git a/core/modules/simpletest/src/FunctionalTestBase.php b/core/modules/simpletest/src/FunctionalTestBase.php new file mode 100644 index 0000000..4ddd44f --- /dev/null +++ b/core/modules/simpletest/src/FunctionalTestBase.php @@ -0,0 +1,1193 @@ +skipClasses[__CLASS__] = TRUE; + } + + /** + * Get a node from the database based on its title. + * + * @param $title + * A node title, usually generated by $this->randomName(). + * @param $reset + * (optional) Whether to reset the entity cache. + * + * @return \Drupal\node\NodeInterface + * A node entity matching $title. + */ + protected function drupalGetNodeByTitle($title, $reset = FALSE) { + if ($reset) { + \Drupal::entityManager()->getStorage('node')->resetCache(); + } + $nodes = entity_load_multiple_by_properties('node', array('title' => $title)); + // Load the first node returned from the database. + $returned_node = reset($nodes); + return $returned_node; + } + + /** + * Creates a node based on default settings. + * + * @param array $settings + * (optional) An associative array of settings for the node, as used in + * entity_create(). Override the defaults by specifying the key and value + * in the array, for example: + * @code + * $this->drupalCreateNode(array( + * 'title' => t('Hello, world!'), + * 'type' => 'article', + * )); + * @endcode + * The following defaults are provided: + * - body: Random string using the default filter format: + * @code + * $settings['body'][0] = array( + * 'value' => $this->randomName(32), + * 'format' => filter_default_format(), + * ); + * @endcode + * - title: Random string. + * - comment: CommentItemInterface::OPEN. + * - promote: NODE_NOT_PROMOTED. + * - log: Empty string. + * - status: NODE_PUBLISHED. + * - sticky: NODE_NOT_STICKY. + * - type: 'page'. + * - langcode: LanguageInterface::LANGCODE_NOT_SPECIFIED. + * - uid: The currently logged in user, or the user running test. + * - revision: 1. (Backwards-compatible binary flag indicating whether a + * new revision should be created; use 1 to specify a new revision.) + * + * @return \Drupal\node\NodeInterface + * The created node entity. + */ + protected function drupalCreateNode(array $settings = array()) { + // Populate defaults array. + $settings += array( + 'body' => array(array()), + 'title' => $this->randomName(8), + 'promote' => NODE_NOT_PROMOTED, + 'revision' => 1, + 'log' => '', + 'status' => NODE_PUBLISHED, + 'sticky' => NODE_NOT_STICKY, + 'type' => 'page', + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + ); + + // Use the original node's created time for existing nodes. + if (isset($settings['created']) && !isset($settings['date'])) { + $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O'); + } + + // If the node's user uid is not specified manually, use the currently + // logged in user if available, or else the user running the test. + if (!isset($settings['uid'])) { + if ($this->loggedInUser) { + $settings['uid'] = $this->loggedInUser->id(); + } + else { + $user = \Drupal::currentUser() ?: new AnonymousUserSession(); + $settings['uid'] = $user->id(); + } + } + + // Merge body field value and format separately. + $settings['body'][0] += array( + 'value' => $this->randomName(32), + 'format' => filter_default_format(), + ); + + $node = entity_create('node', $settings); + if (!empty($settings['revision'])) { + $node->setNewRevision(); + } + $node->save(); + + return $node; + } + + /** + * Creates a custom content type based on default settings. + * + * @param array $values + * An array of settings to change from the defaults. + * Example: 'type' => 'foo'. + * + * @return \Drupal\node\Entity\NodeType + * Created content type. + */ + protected function drupalCreateContentType(array $values = array()) { + // Find a non-existent random type name. + if (!isset($values['type'])) { + do { + $id = strtolower($this->randomName(8)); + } while (node_type_load($id)); + } + else { + $id = $values['type']; + } + $values += array( + 'type' => $id, + 'name' => $id, + ); + $type = entity_create('node_type', $values); + $status = $type->save(); + \Drupal::service('router.builder')->rebuild(); + + $this->assertEqual($status, SAVED_NEW, String::format('Created content type %type.', array('%type' => $type->id()))); + + return $type; + } + + /** + * Builds the renderable view of an entity. + * + * Entities postpone the composition of their renderable arrays to #pre_render + * functions in order to maximize cache efficacy. This means that the full + * rendable array for an entity is constructed in drupal_render(). Some tests + * require the complete renderable array for an entity outside of the + * drupal_render process in order to verify the presence of specific values. + * This method isolates the steps in the render process that produce an + * entity's renderable array. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to prepare a renderable array for. + * @param string $view_mode + * (optional) The view mode that should be used to build the entity. + * @param null $langcode + * (optional) For which language the entity should be prepared, defaults to + * the current content language. + * @param bool $reset + * (optional) Whether to clear the cache for this entity. + * @return array + * + * @see drupal_render() + */ + protected function drupalBuildEntityView(EntityInterface $entity, $view_mode = 'full', $langcode = NULL, $reset = FALSE) { + $render_controller = $this->container->get('entity.manager')->getViewBuilder($entity->getEntityTypeId()); + if ($reset) { + $render_controller->resetCache(array($entity->id())); + } + $elements = $render_controller->view($entity, $view_mode, $langcode); + // If the default values for this element have not been loaded yet, populate + // them. + if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) { + $elements += element_info($elements['#type']); + } + + // Make any final changes to the element before it is rendered. This means + // that the $element or the children can be altered or corrected before the + // element is rendered into the final text. + if (isset($elements['#pre_render'])) { + foreach ($elements['#pre_render'] as $callable) { + $elements = call_user_func($callable, $elements); + } + } + return $elements; + } + + /** + * Creates a block instance based on default settings. + * + * @param string $plugin_id + * The plugin ID of the block type for this block instance. + * @param array $settings + * (optional) An associative array of settings for the block entity. + * Override the defaults by specifying the key and value in the array, for + * example: + * @code + * $this->drupalPlaceBlock('system_powered_by_block', array( + * 'label' => t('Hello, world!'), + * )); + * @endcode + * The following defaults are provided: + * - label: Random string. + * - ID: Random string. + * - region: 'sidebar_first'. + * - theme: The default theme. + * - visibility: Empty array. + * - cache: array('max_age' => 0). + * + * @return \Drupal\block\Entity\Block + * The block entity. + * + * @todo + * Add support for creating custom block instances. + */ + protected function drupalPlaceBlock($plugin_id, array $settings = array()) { + $settings += array( + 'plugin' => $plugin_id, + 'region' => 'sidebar_first', + 'id' => strtolower($this->randomName(8)), + 'theme' => \Drupal::config('system.theme')->get('default'), + 'label' => $this->randomName(8), + 'visibility' => array(), + 'weight' => 0, + 'cache' => array( + 'max_age' => 0, + ), + ); + foreach (array('region', 'id', 'theme', 'plugin', 'weight') as $key) { + $values[$key] = $settings[$key]; + // Remove extra values that do not belong in the settings array. + unset($settings[$key]); + } + foreach ($settings['visibility'] as $id => $visibility) { + $settings['visibility'][$id]['id'] = $id; + } + $values['settings'] = $settings; + $block = entity_create('block', $values); + $block->save(); + return $block; + } + + /** + * Gets a list files that can be used in tests. + * + * @param $type + * File type, possible values: 'binary', 'html', 'image', 'javascript', + * 'php', 'sql', 'text'. + * @param $size + * File size in bytes to match. Please check the tests/files folder. + * + * @return array + * List of files that match filter. + */ + protected function drupalGetTestFiles($type, $size = NULL) { + if (empty($this->generatedTestFiles)) { + // Generate binary test files. + $lines = array(64, 1024); + $count = 0; + foreach ($lines as $line) { + simpletest_generate_file('binary-' . $count++, 64, $line, 'binary'); + } + + // Generate text test files. + $lines = array(16, 256, 1024, 2048, 20480); + $count = 0; + foreach ($lines as $line) { + simpletest_generate_file('text-' . $count++, 64, $line); + } + + // Copy other test files from simpletest. + $original = drupal_get_path('module', 'simpletest') . '/files'; + $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/'); + foreach ($files as $file) { + file_unmanaged_copy($file->uri, PublicStream::basePath()); + } + + $this->generatedTestFiles = TRUE; + } + + $files = array(); + // Make sure type is valid. + if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) { + $files = file_scan_directory('public://', '/' . $type . '\-.*/'); + + // If size is set then remove any files that are not of that size. + if ($size !== NULL) { + foreach ($files as $file) { + $stats = stat($file->uri); + if ($stats['size'] != $size) { + unset($files[$file->uri]); + } + } + } + } + usort($files, array($this, 'drupalCompareFiles')); + return $files; + } + + /** + * Compare two files based on size and file name. + */ + protected function drupalCompareFiles($file1, $file2) { + $compare_size = filesize($file1->uri) - filesize($file2->uri); + if ($compare_size) { + // Sort by file size. + return $compare_size; + } + else { + // The files were the same size, so sort alphabetically. + return strnatcmp($file1->name, $file2->name); + } + } + + /** + * Create a user with a given set of permissions. + * + * @param array $permissions + * Array of permission names to assign to user. Note that the user always + * has the default permissions derived from the "authenticated users" role. + * @param string $name + * The user name. + * + * @return \Drupal\user\Entity\User|false + * A fully loaded user object with pass_raw property, or FALSE if account + * creation fails. + */ + protected function drupalCreateUser(array $permissions = array(), $name = NULL) { + // Create a role with the given permission set, if any. + $rid = FALSE; + if ($permissions) { + $rid = $this->drupalCreateRole($permissions); + if (!$rid) { + return FALSE; + } + } + + // Create a user assigned to that role. + $edit = array(); + $edit['name'] = !empty($name) ? $name : $this->randomName(); + $edit['mail'] = $edit['name'] . '@example.com'; + $edit['pass'] = user_password(); + $edit['status'] = 1; + if ($rid) { + $edit['roles'] = array($rid); + } + + $account = entity_create('user', $edit); + $account->save(); + + $this->assertTrue($account->id(), String::format('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), 'User login'); + if (!$account->id()) { + return FALSE; + } + + // Add the raw password so that we can log in as this user. + $account->pass_raw = $edit['pass']; + return $account; + } + + /** + * Creates a role with specified permissions. + * + * @param array $permissions + * Array of permission names to assign to role. + * @param string $rid + * (optional) The role ID (machine name). Defaults to a random name. + * @param string $name + * (optional) The label for the role. Defaults to a random string. + * @param integer $weight + * (optional) The weight for the role. Defaults NULL so that entity_create() + * sets the weight to maximum + 1. + * + * @return string + * Role ID of newly created role, or FALSE if role creation failed. + */ + protected function drupalCreateRole(array $permissions, $rid = NULL, $name = NULL, $weight = NULL) { + // Generate a random, lowercase machine name if none was passed. + if (!isset($rid)) { + $rid = strtolower($this->randomName(8)); + } + // Generate a random label. + if (!isset($name)) { + // In the role UI role names are trimmed and random string can start or + // end with a space. + $name = trim($this->randomString(8)); + } + + // Check the all the permissions strings are valid. + if (!$this->checkPermissions($permissions)) { + return FALSE; + } + + // Create new role. + $role = entity_create('user_role', array( + 'id' => $rid, + 'label' => $name, + )); + if (!is_null($weight)) { + $role->set('weight', $weight); + } + $result = $role->save(); + + $this->assertIdentical($result, SAVED_NEW, String::format('Created role ID @rid with name @name.', array( + '@name' => var_export($role->label(), TRUE), + '@rid' => var_export($role->id(), TRUE), + )), 'Role'); + + if ($result === SAVED_NEW) { + // Grant the specified permissions to the role, if any. + if (!empty($permissions)) { + user_role_grant_permissions($role->id(), $permissions); + $assigned_permissions = entity_load('user_role', $role->id())->getPermissions(); + $missing_permissions = array_diff($permissions, $assigned_permissions); + if (!$missing_permissions) { + $this->pass(String::format('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), 'Role'); + } + else { + $this->fail(String::format('Failed to create permissions: @perms', array('@perms' => implode(', ', $missing_permissions))), 'Role'); + } + } + return $role->id(); + } + else { + return FALSE; + } + } + + /** + * Checks whether a given list of permission names is valid. + * + * @param array $permissions + * The permission names to check. + * + * @return bool + * TRUE if the permissions are valid, FALSE otherwise. + */ + protected function checkPermissions(array $permissions) { + $available = array_keys(\Drupal::moduleHandler()->invokeAll('permission')); + $valid = TRUE; + foreach ($permissions as $permission) { + if (!in_array($permission, $available)) { + $this->fail(String::format('Invalid permission %permission.', array('%permission' => $permission)), 'Role'); + $valid = FALSE; + } + } + return $valid; + } + + /** + * Returns whether a given user account is logged in. + * + * @param \Drupal\user\UserInterface $account + * The user account object to check. + * + * @return bool + * Return TRUE if the user is logged in, FALSE otherwise. + */ + protected function drupalUserIsLoggedIn($account) { + if (!isset($account->session_id)) { + return FALSE; + } + // The session ID is hashed before being stored in the database. + // @see \Drupal\Core\Session\SessionHandler::read() + return (bool) db_query("SELECT sid FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => Crypt::hashBase64($account->session_id)))->fetchField(); + } + + /** + * Returns whether the test is being executed from within a test site. + * + * Mainly used by recursive tests (i.e. to test the testing framework). + * + * @return bool + * TRUE if this test was instantiated in a request within the test site, + * FALSE otherwise. + * + * @see \Drupal\Core\DrupalKernel::bootConfiguration() + */ + protected function isInChildSite() { + return DRUPAL_TEST_IN_CHILD_SITE; + } + + /** + * Gets an array containing all emails sent during this test case. + * + * @param $filter + * An array containing key/value pairs used to filter the emails that are + * returned. + * + * @return array + * An array containing email messages captured during the current test. + */ + protected function drupalGetMails($filter = array()) { + $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array(); + $filtered_emails = array(); + + foreach ($captured_emails as $message) { + foreach ($filter as $key => $value) { + if (!isset($message[$key]) || $message[$key] != $value) { + continue 2; + } + } + $filtered_emails[] = $message; + } + + return $filtered_emails; + } + + /** + * Asserts that the most recently sent email message has the given value. + * + * The field in $name must have the content described in $value. + * + * @param $name + * Name of field or message property to assert. Examples: subject, body, + * id, ... + * @param mixed $value + * Value of the field to assert. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param string $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Email'; most tests do not override + * this default. + * + * @return bool + * TRUE on pass, FALSE on fail. + */ + protected function assertMail($name, $value = '', $message = '', $group = 'Email') { + $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array(); + $email = end($captured_emails); + return $this->assertTrue($email && isset($email[$name]) && $email[$name] == $value, $message, $group); + } + + /** + * Asserts that the most recently sent email message has the string in it. + * + * @param string $field_name + * Name of field or message property to assert: subject, body, id, ... + * @param string $string + * String to search for. + * @param int $email_depth + * Number of emails to search for string, starting with most recent. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param string $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Other'; most tests do not override + * this default. + * + * @return bool + * TRUE on pass, FALSE on fail. + */ + protected function assertMailString($field_name, $string, $email_depth, $message = '', $group = 'Other') { + $mails = $this->drupalGetMails(); + $string_found = FALSE; + for ($i = count($mails) -1; $i >= count($mails) - $email_depth && $i >= 0; $i--) { + $mail = $mails[$i]; + // Normalize whitespace, as we don't know what the mail system might have + // done. Any run of whitespace becomes a single space. + $normalized_mail = preg_replace('/\s+/', ' ', $mail[$field_name]); + $normalized_string = preg_replace('/\s+/', ' ', $string); + $string_found = (FALSE !== strpos($normalized_mail, $normalized_string)); + if ($string_found) { + break; + } + } + if (!$message) { + $message = format_string('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $string)); + } + return $this->assertTrue($string_found, $message, $group); + } + + /** + * Asserts that the most recently sent email message has the pattern in it. + * + * @param string $field_name + * Name of field or message property to assert: subject, body, id, ... + * @param string $regex + * Pattern to search for. + * @param string$message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param string $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Other'; most tests do not override + * this default. + * + * @return bool + * TRUE on pass, FALSE on fail. + */ + protected function assertMailPattern($field_name, $regex, $message = '', $group = 'Other') { + $mails = $this->drupalGetMails(); + $mail = end($mails); + $regex_found = preg_match("/$regex/", $mail[$field_name]); + if (!$message) { + $message = format_string('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $regex)); + } + return $this->assertTrue($regex_found, $message, $group); + } + + /** + * Outputs to verbose the most recent $count emails sent. + * + * @param $count + * Optional number of emails to output. + */ + protected function verboseEmail($count = 1) { + $mails = $this->drupalGetMails(); + for ($i = count($mails) -1; $i >= count($mails) - $count && $i >= 0; $i--) { + $mail = $mails[$i]; + $this->verbose('Email:
' . print_r($mail, TRUE) . '
'); + } + } + + /** + * Sets up a Drupal site for running functional and integration tests. + * + * Installs Drupal with the installation profile specified in + * \Drupal\simpletest\WebTestBase::$profile into the prefixed database. + + * Afterwards, installs any additional modules specified in the static + * \Drupal\simpletest\WebTestBase::$modules property of each class in the + * class hierarchy. + * + * After installation all caches are flushed and several configuration values + * are reset to the values of the parent site executing the test, since the + * default values may be incompatible with the environment in which tests are + * being executed. + */ + protected function setUp() { + // When running tests through the Simpletest UI (vs. on the command line), + // Simpletest's batch conflicts with the installer's batch. Batch API does + // not support the concept of nested batches (in which the nested is not + // progressive), so we need to temporarily pretend there was no batch. + // Backup the currently running Simpletest batch. + $this->originalBatch = batch_get(); + + // Define information about the user 1 account. + $this->root_user = new UserSession(array( + 'uid' => 1, + 'name' => 'admin', + 'mail' => 'admin@example.com', + 'pass_raw' => $this->randomName(), + )); + + // Some tests (SessionTest and SessionHttpsTest) need to examine whether the + // proper session cookies were set on a response. Because the child site + // uses the same session name as the test runner, it is necessary to make + // that available to test-methods. + $this->session_name = $this->originalSessionName; + + // Reset the static batch to remove Simpletest's batch operations. + $batch = &batch_get(); + $batch = array(); + + // Get parameters for install_drupal() before removing global variables. + $parameters = $this->installParameters(); + + // Prepare installer settings that are not install_drupal() parameters. + // Copy and prepare an actual settings.php, so as to resemble a regular + // installation. + // Not using File API; a potential error must trigger a PHP warning. + $directory = DRUPAL_ROOT . '/' . $this->siteDirectory; + copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php'); + + // All file system paths are created by System module during installation. + // @see system_requirements() + // @see TestBase::prepareEnvironment() + $settings['settings']['file_public_path'] = (object) array( + 'value' => $this->public_files_directory, + 'required' => TRUE, + ); + // Save the original site directory path, so that extensions in the + // site-specific directory can still be discovered in the test site + // environment. + // @see \Drupal\Core\SystemListing::scan() + $settings['settings']['test_parent_site'] = (object) array( + 'value' => $this->originalSite, + 'required' => TRUE, + ); + // Add the parent profile's search path to the child site's search paths. + // @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories() + $settings['conf']['simpletest.settings']['parent_profile'] = (object) array( + 'value' => $this->originalProfile, + 'required' => TRUE, + ); + $this->writeSettings($settings); + // Allow for test-specific overrides. + $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php'; + if (file_exists($settings_testing_file)) { + // Copy the testing-specific settings.php overrides in place. + copy($settings_testing_file, $directory . '/settings.testing.php'); + // Add the name of the testing class to settings.php and include the + // testing specific overrides + file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) ."';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' ."\n", FILE_APPEND); + } + $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml'; + if (file_exists($settings_services_file)) { + // Copy the testing-specific service overrides in place. + copy($settings_services_file, $directory . '/services.yml'); + } + + // Since Drupal is bootstrapped already, install_begin_request() will not + // bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to + // reload the newly written custom settings.php manually. + Settings::initialize($directory); + + // Execute the non-interactive installer. + require_once DRUPAL_ROOT . '/core/includes/install.core.inc'; + install_drupal($parameters); + + // Import new settings.php written by the installer. + Settings::initialize($directory); + foreach ($GLOBALS['config_directories'] as $type => $path) { + $this->configDirectories[$type] = $path; + } + + // After writing settings.php, the installer removes write permissions + // from the site directory. To allow drupal_generate_test_ua() to write + // a file containing the private key for drupal_valid_test_ua(), the site + // directory has to be writable. + // TestBase::restoreEnvironment() will delete the entire site directory. + // Not using File API; a potential error must trigger a PHP warning. + chmod($directory, 0777); + + $request = \Drupal::request(); + $this->kernel = DrupalKernel::createFromRequest($request, drupal_classloader(), 'prod', TRUE); + $this->kernel->prepareLegacyRequest($request); + // Force the container to be built from scratch instead of loaded from the + // disk. This forces us to not accidently load the parent site. + $container = $this->kernel->rebuildContainer(); + + $config = $container->get('config.factory'); + + // Manually create and configure private and temporary files directories. + // While these could be preset/enforced in settings.php like the public + // files directory above, some tests expect them to be configurable in the + // UI. If declared in settings.php, they would no longer be configurable. + file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY); + file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY); + $config->get('system.file') + ->set('path.private', $this->private_files_directory) + ->set('path.temporary', $this->temp_files_directory) + ->save(); + + // Manually configure the test mail collector implementation to prevent + // tests from sending out emails and collect them in state instead. + // While this should be enforced via settings.php prior to installation, + // some tests expect to be able to test mail system implementations. + $config->get('system.mail') + ->set('interface.default', 'test_mail_collector') + ->save(); + + // By default, verbosely display all errors and disable all production + // environment optimizations for all tests to avoid needless overhead and + // ensure a sane default experience for test authors. + // @see https://drupal.org/node/2259167 + $config->get('system.logging') + ->set('error_level', 'verbose') + ->save(); + $config->get('system.performance') + ->set('css.preprocess', FALSE) + ->set('js.preprocess', FALSE) + ->save(); + + // Restore the original Simpletest batch. + $batch = &batch_get(); + $batch = $this->originalBatch; + + // Collect modules to install. + $class = get_class($this); + $modules = array(); + while ($class) { + if (property_exists($class, 'modules')) { + $modules = array_merge($modules, $class::$modules); + } + $class = get_parent_class($class); + } + if ($modules) { + $modules = array_unique($modules); + $success = $container->get('module_handler')->install($modules, TRUE); + $this->assertTrue($success, String::format('Enabled modules: %modules', array('%modules' => implode(', ', $modules)))); + $this->rebuildContainer(); + } + + // Reset/rebuild all data structures after enabling the modules, primarily + // to synchronize all data structures and caches between the test runner and + // the child site. + // Affects e.g. file_get_stream_wrappers(). + // @see \Drupal\Core\DrupalKernel::bootCode() + // @todo Test-specific setUp() methods may set up further fixtures; find a + // way to execute this after setUp() is done, or to eliminate it entirely. + $this->resetAll(); + $this->kernel->prepareLegacyRequest($request); + + // Temporary fix so that when running from run-tests.sh we don't get an + // empty current path which would indicate we're on the home page. + $path = current_path(); + if (empty($path)) { + _current_path('run-tests'); + } + } + + /** + * Returns the parameters that will be used when Simpletest installs Drupal. + * + * @see install_drupal() + * @see install_state_defaults() + */ + protected function installParameters() { + $connection_info = Database::getConnectionInfo(); + $driver = $connection_info['default']['driver']; + $connection_info['default']['prefix'] = $connection_info['default']['prefix']['default']; + unset($connection_info['default']['driver']); + unset($connection_info['default']['namespace']); + unset($connection_info['default']['pdo']); + unset($connection_info['default']['init_commands']); + $parameters = array( + 'interactive' => FALSE, + 'parameters' => array( + 'profile' => $this->profile, + 'langcode' => 'en', + ), + 'forms' => array( + 'install_settings_form' => array( + 'driver' => $driver, + $driver => $connection_info['default'], + ), + 'install_configure_form' => array( + 'site_name' => 'Drupal', + 'site_mail' => 'simpletest@example.com', + 'account' => array( + 'name' => $this->root_user->name, + 'mail' => $this->root_user->getEmail(), + 'pass' => array( + 'pass1' => $this->root_user->pass_raw, + 'pass2' => $this->root_user->pass_raw, + ), + ), + // form_type_checkboxes_value() requires NULL instead of FALSE values + // for programmatic form submissions to disable a checkbox. + 'update_status_module' => array( + 1 => NULL, + 2 => NULL, + ), + ), + ), + ); + return $parameters; + } + + /** + * Rewrites the settings.php file of the test site. + * + * @param array $settings + * An array of settings to write out, in the format expected by + * drupal_rewrite_settings(). + * + * @see drupal_rewrite_settings() + */ + protected function writeSettings(array $settings) { + include_once DRUPAL_ROOT . '/core/includes/install.inc'; + $filename = $this->siteDirectory . '/settings.php'; + // system_requirements() removes write permissions from settings.php + // whenever it is invoked. + // Not using File API; a potential error must trigger a PHP warning. + chmod($filename, 0666); + drupal_rewrite_settings($settings, $filename); + } + + /** + * Queues custom translations to be written to settings.php. + * + * Use WebTestBase::writeCustomTranslations() to apply and write the queued + * translations. + * + * @param string $langcode + * The langcode to add translations for. + * @param array $values + * Array of values containing the untranslated string and its translation. + * For example: + * @code + * array( + * '' => array('Sunday' => 'domingo'), + * 'Long month name' => array('March' => 'marzo'), + * ); + * @endcode + * Pass an empty array to remove all existing custom translations for the + * given $langcode. + */ + protected function addCustomTranslations($langcode, array $values) { + // If $values is empty, then the test expects all custom translations to be + // cleared. + if (empty($values)) { + $this->customTranslations[$langcode] = array(); + } + // Otherwise, $values are expected to be merged into previously passed + // values, while retaining keys that are not explicitly set. + else { + foreach ($values as $context => $translations) { + foreach ($translations as $original => $translation) { + $this->customTranslations[$langcode][$context][$original] = $translation; + } + } + } + } + + /** + * Writes custom translations to the test site's settings.php. + * + * Use TestBase::addCustomTranslations() to queue custom translations before + * calling this method. + */ + protected function writeCustomTranslations() { + $settings = array(); + foreach ($this->customTranslations as $langcode => $values) { + $settings_key = 'locale_custom_strings_' . $langcode; + + // Update in-memory settings directly. + $this->settingsSet($settings_key, $values); + + $settings['settings'][$settings_key] = (object) array( + 'value' => $values, + 'required' => TRUE, + ); + } + // Only rewrite settings if there are any translation changes to write. + if (!empty($settings)) { + $this->writeSettings($settings); + } + } + + /** + * Rebuilds \Drupal::getContainer(). + * + * Use this to build a new kernel and service container. For example, when the + * list of enabled modules is changed via the internal browser, in which case + * the test process still contains an old kernel and service container with an + * old module list. + * + * @see TestBase::prepareEnvironment() + * @see TestBase::restoreEnvironment() + * + * @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable + * changes are immediately reflected in \Drupal::getContainer(). Until then, + * tests can invoke this workaround when requiring services from newly + * enabled modules to be immediately available in the same request. + */ + protected function rebuildContainer() { + // Maintain the current global request object. + $request = \Drupal::request(); + // Rebuild the kernel and bring it back to a fully bootstrapped state. + $this->container = $this->kernel->rebuildContainer(); + + // The request context is normally set by the router_listener from within + // its KernelEvents::REQUEST listener. In the simpletest parent site this + // event is not fired, therefore it is necessary to updated the request + // context manually here. + $this->container->get('router.request_context')->fromRequest($request); + + // Make sure the url generator has a request object, otherwise calls to + // $this->drupalGet() will fail. + $this->prepareRequestForGenerator(); + } + + /** + * Resets all data structures after having enabled new modules. + * + * This method is called by \Drupal\simpletest\WebTestBase::setUp() after + * enabling the requested modules. It must be called again when additional + * modules are enabled later. + */ + protected function resetAll() { + // Clear all database and static caches and rebuild data structures. + drupal_flush_all_caches(); + $this->container = \Drupal::getContainer(); + + // Reset static variables and reload permissions. + $this->refreshVariables(); + } + + /** + * Refreshes in-memory configuration and state information. + * + * Useful after a page request is made that changes configuration or state in + * a different thread. + * + * In other words calling a settings page with $this->drupalPostForm() with a + * changed value would update configuration to reflect that change, but in the + * thread that made the call (thread running the test) the changed values + * would not be picked up. + * + * This method clears the cache and loads a fresh copy. + */ + protected function refreshVariables() { + // Clear the tag cache. + drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache'); + drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::deletedTags'); + drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::invalidatedTags'); + + $this->container->get('config.factory')->reset(); + $this->container->get('state')->resetCache(); + } + + /** + * Cleans up after testing. + * + * Deletes created files and temporary files directory, deletes the tables + * created by setUp(), and resets the database prefix. + */ + protected function tearDown() { + // Destroy the testing kernel. + if (isset($this->kernel)) { + $this->kernel->shutdown(); + } + parent::tearDown(); + // Ensure that internal logged in variable is reset. + $this->loggedInUser = FALSE; + } + + /** + * Creates a mock request and sets it on the generator. + * + * This is used to manipulate how the generator generates paths during tests. + * It also ensures that calls to $this->drupalGet() will work when running + * from run-tests.sh because the url generator no longer looks at the global + * variables that are set there but relies on getting this information from a + * request object. + * + * @param bool $clean_urls + * Whether to mock the request using clean urls. + * @param $override_server_vars + * An array of server variables to override. + * + * @return Request + * The mocked request object. + */ + protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = array()) { + $generator = $this->container->get('url_generator'); + $request = Request::createFromGlobals(); + $server = $request->server->all(); + if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) { + // We need this for when the test is executed by run-tests.sh. + // @todo Remove this once run-tests.sh has been converted to use a Request + // object. + $cwd = getcwd(); + $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']); + $base_path = rtrim($server['REQUEST_URI'], '/'); + } + else { + $base_path = $request->getBasePath(); + } + if ($clean_urls) { + $request_path = $base_path ? $base_path . '/user' : 'user'; + } + else { + $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user'; + } + $server = array_merge($server, $override_server_vars); + + $request = Request::create($request_path, 'GET', array(), array(), array(), $server); + $this->container->get('request_stack')->push($request); + $generator->updateFromRequest(); + return $request; + } + + /** + * Returns the session name in use on the child site. + * + * @return string + * The name of the session cookie. + */ + public function getSessionName() { + return $this->session_name; + } + + /** + * Takes a path and returns an absolute path. + * + * @param $path + * A path from the internal browser content. + * + * @return string + * The $path with $base_url prepended, if necessary. + */ + protected function getAbsoluteUrl($path) { + global $base_url, $base_path; + + $parts = parse_url($path); + if (empty($parts['host'])) { + // Ensure that we have a string (and no xpath object). + $path = (string) $path; + // Strip $base_path, if existent. + $length = strlen($base_path); + if (substr($path, 0, $length) === $base_path) { + $path = substr($path, $length); + } + // Ensure that we have an absolute path. + if ($path[0] !== '/') { + $path = '/' . $path; + } + // Finally, prepend the $base_url. + $path = $base_url . $path; + } + return $path; + } +} diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php index e6fe068..15c6fa1 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -55,6 +55,11 @@ protected $databasePrefix = NULL; /** + * Whether the files were copied to the test files directory. + */ + protected $generatedTestFiles = FALSE; + + /** * The site directory of the original parent site. * * @var string @@ -148,10 +153,82 @@ /** * The settings array. + * + * @var array */ protected $originalSettings; /** + * The global config array of the original parent site. + * + * @var array + */ + protected $originalConfig; + + /** + * The global conf array of the original parent site. + * + * @var array + */ + protected $originalConf; + + /** + * The global config_directories of the original parent site. + * + * @var array + */ + protected $originalConfigDirectories; + + /** + * The container of the original parent site. + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $originalContainer; + + /** + * The shutdown callbacks of the original parent site. + * + * @var array + */ + protected $originalShutdownCallbacks; + + /** + * The profile of the original parent site. + * + * @var string|null + */ + protected $originalProfile; + + /** + * The language of the original parent site. + * + * @var \Drupal\Core\Language\LanguageInterface + */ + protected $originalLanguage; + + /** + * The user of the original parent site. + * + * @var \Drupal\Core\Session\AccountProxyInterface + */ + protected $originalUser; + + /** + * HTTP auth method for simpletest browser. + * + * @var int + */ + protected $httpauth_method; + + /** + * HTTP auth credentials for simpletest browser. + * + * @var string + */ + protected $httpauth_credentials; + + /** * The public file directory for the test environment. * * This is set in TestBase::prepareEnvironment(). @@ -161,6 +238,33 @@ protected $public_files_directory; /** + * The private file directory for the test environment. + * + * This is set in TestBase::prepareEnvironment(). + * + * @var string + */ + protected $private_files_directory; + + /** + * The temp file directory for the test environment. + * + * This is set in TestBase::prepareEnvironment(). + * + * @var string + */ + protected $temp_files_directory; + + /** + * The translation file directory for the test environment. + * + * This is set in TestBase::prepareEnvironment(). + * + * @var string + */ + protected $translation_files_directory; + + /** * Whether to die in case any test assertion fails. * * @var boolean @@ -220,7 +324,7 @@ public function __construct($test_id = NULL) { /** * Checks the matching requirements for Test. * - * @return + * @return array * Array of errors containing a list of unmet requirements. */ protected function checkRequirements() { @@ -249,6 +353,9 @@ protected function checkRequirements() { * by passing in an associative array as $caller. Key 'file' is * the name of the source file, 'line' is the line number and 'function' * is the caller function itself. + * + * @return bool + * TRUE if the assert passed, FALSE otherwise. */ protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) { // Convert boolean status to string status. @@ -305,7 +412,14 @@ protected function assert($status, $message = '', $group = 'Other', array $calle * the method behaves just like \Drupal\simpletest\TestBase::assert() in terms * of storing the assertion. * - * @return + * @param $test_id + * @param $test_class + * @param $status + * @param string $message + * @param string $group + * @param array $caller + * + * @return int * Message ID of the stored assertion. * * @see \Drupal\simpletest\TestBase::assert() @@ -346,7 +460,7 @@ public static function insertAssert($test_id, $test_class, $status, $message = ' * @param $message_id * Message ID of the assertion to delete. * - * @return + * @return bool * TRUE if the assertion was deleted, FALSE otherwise. * * @see \Drupal\simpletest\TestBase::insertAssert() @@ -390,7 +504,7 @@ public static function getDatabaseConnection() { /** * Cycles through backtrace until the first non-assertion method is found. * - * @return + * @return array * Array representing the true caller. */ protected function getAssertionCall() { @@ -427,7 +541,7 @@ protected function getAssertionCall() { * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertTrue($value, $message = '', $group = 'Other') { @@ -452,7 +566,7 @@ protected function assertTrue($value, $message = '', $group = 'Other') { * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertFalse($value, $message = '', $group = 'Other') { @@ -475,7 +589,7 @@ protected function assertFalse($value, $message = '', $group = 'Other') { * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertNull($value, $message = '', $group = 'Other') { @@ -498,7 +612,7 @@ protected function assertNull($value, $message = '', $group = 'Other') { * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertNotNull($value, $message = '', $group = 'Other') { @@ -523,7 +637,7 @@ protected function assertNotNull($value, $message = '', $group = 'Other') { * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertEqual($first, $second, $message = '', $group = 'Other') { @@ -548,7 +662,7 @@ protected function assertEqual($first, $second, $message = '', $group = 'Other') * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertNotEqual($first, $second, $message = '', $group = 'Other') { @@ -573,7 +687,7 @@ protected function assertNotEqual($first, $second, $message = '', $group = 'Othe * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertIdentical($first, $second, $message = '', $group = 'Other') { @@ -598,7 +712,7 @@ protected function assertIdentical($first, $second, $message = '', $group = 'Oth * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertNotIdentical($first, $second, $message = '', $group = 'Other') { @@ -623,7 +737,7 @@ protected function assertNotIdentical($first, $second, $message = '', $group = ' * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertIdenticalObject($object1, $object2, $message = '', $group = 'Other') { @@ -667,7 +781,7 @@ protected function assertNoErrorsLogged() { * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * TRUE. */ protected function pass($message = NULL, $group = 'Other') { @@ -688,7 +802,7 @@ protected function pass($message = NULL, $group = 'Other') { * translate this string. Defaults to 'Other'; most tests do not override * this default. * - * @return + * @return bool * FALSE. */ protected function fail($message = NULL, $group = 'Other') { @@ -711,7 +825,7 @@ protected function fail($message = NULL, $group = 'Other') { * @param $caller * The caller of the error. * - * @return + * @return bool * FALSE. */ protected function error($message = '', $group = 'Other', array $caller = NULL) { @@ -1260,6 +1374,8 @@ public function errorHandler($severity, $message, $file = NULL, $line = NULL) { /** * Handle exceptions. * + * @param \Exception $exception + * * @see set_exception_handler */ protected function exceptionHandler($exception) { @@ -1422,7 +1538,7 @@ protected function getRandomGenerator() { * An associative array of parameters, keyed by parameter name, and whose * values are arrays of parameter values. * - * @return + * @return array * A list of permutations, which is an array of arrays. Each inner array * contains the full list of parameters that have been passed, but with a * single value only. diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index ac2f0a0..532c9fb 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -9,39 +9,21 @@ use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\Crypt; -use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\String; -use Drupal\Core\DrupalKernel; -use Drupal\Core\Database\Database; -use Drupal\Core\Database\ConnectionNotDefinedException; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AnonymousUserSession; -use Drupal\Core\Session\UserSession; -use Drupal\Core\Site\Settings; -use Drupal\Core\StreamWrapper\PublicStream; -use Drupal\Core\Datetime\DrupalDateTime; use Drupal\block\Entity\Block; -use Symfony\Component\HttpFoundation\Request; /** * Test case for typical Drupal tests. * * @ingroup testing */ -abstract class WebTestBase extends TestBase { +abstract class WebTestBase extends FunctionalTestBase { use AssertContentTrait; /** - * The profile to install as a basis for testing. - * - * @var string - */ - protected $profile = 'testing'; - - /** * The URL currently loaded in the internal browser. * * @var string @@ -73,13 +55,6 @@ protected $dumpHeaders = FALSE; /** - * The current user logged in using the internal browser. - * - * @var bool - */ - protected $loggedInUser = FALSE; - - /** * The current cookie file used by cURL. * * We do not reuse the cookies in further runs, so we do not need a file @@ -96,45 +71,11 @@ protected $additionalCurlOptions = array(); /** - * The original user, before it was changed to a clean uid = 1 for testing. - * - * @var object - */ - protected $originalUser = NULL; - - /** - * The original shutdown handlers array, before it was cleaned for testing. - * - * @var array - */ - protected $originalShutdownCallbacks = array(); - - /** - * HTTP authentication method. - */ - protected $httpauth_method = CURLAUTH_BASIC; - - /** - * HTTP authentication credentials (:). - */ - protected $httpauth_credentials = NULL; - - /** - * The current session name, if available. - */ - protected $session_name = NULL; - - /** * The current session ID, if available. */ protected $session_id = NULL; /** - * Whether the files were copied to the test files directory. - */ - protected $generatedTestFiles = FALSE; - - /** * The maximum number of redirects to follow when handling responses. */ protected $maximumRedirects = 5; @@ -145,18 +86,6 @@ protected $redirect_count; /** - * The kernel used in this test. - * - * @var \Drupal\Core\DrupalKernel - */ - protected $kernel; - - /** - * The config directories used in this test. - */ - protected $configDirectories = array(); - - /** * Cookies to set on curl requests. * * @var array @@ -164,13 +93,6 @@ protected $curlCookies = array(); /** - * An array of custom translations suitable for drupal_rewrite_settings(). - * - * @var array - */ - protected $customTranslations; - - /** * Constructor for \Drupal\simpletest\WebTestBase. */ function __construct($test_id = NULL) { @@ -179,244 +101,6 @@ function __construct($test_id = NULL) { } /** - * Get a node from the database based on its title. - * - * @param $title - * A node title, usually generated by $this->randomName(). - * @param $reset - * (optional) Whether to reset the entity cache. - * - * @return \Drupal\node\NodeInterface - * A node entity matching $title. - */ - function drupalGetNodeByTitle($title, $reset = FALSE) { - if ($reset) { - \Drupal::entityManager()->getStorage('node')->resetCache(); - } - $nodes = entity_load_multiple_by_properties('node', array('title' => $title)); - // Load the first node returned from the database. - $returned_node = reset($nodes); - return $returned_node; - } - - /** - * Creates a node based on default settings. - * - * @param array $settings - * (optional) An associative array of settings for the node, as used in - * entity_create(). Override the defaults by specifying the key and value - * in the array, for example: - * @code - * $this->drupalCreateNode(array( - * 'title' => t('Hello, world!'), - * 'type' => 'article', - * )); - * @endcode - * The following defaults are provided: - * - body: Random string using the default filter format: - * @code - * $settings['body'][0] = array( - * 'value' => $this->randomName(32), - * 'format' => filter_default_format(), - * ); - * @endcode - * - title: Random string. - * - comment: CommentItemInterface::OPEN. - * - promote: NODE_NOT_PROMOTED. - * - log: Empty string. - * - status: NODE_PUBLISHED. - * - sticky: NODE_NOT_STICKY. - * - type: 'page'. - * - langcode: LanguageInterface::LANGCODE_NOT_SPECIFIED. - * - uid: The currently logged in user, or the user running test. - * - revision: 1. (Backwards-compatible binary flag indicating whether a - * new revision should be created; use 1 to specify a new revision.) - * - * @return \Drupal\node\NodeInterface - * The created node entity. - */ - protected function drupalCreateNode(array $settings = array()) { - // Populate defaults array. - $settings += array( - 'body' => array(array()), - 'title' => $this->randomName(8), - 'promote' => NODE_NOT_PROMOTED, - 'revision' => 1, - 'log' => '', - 'status' => NODE_PUBLISHED, - 'sticky' => NODE_NOT_STICKY, - 'type' => 'page', - 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, - ); - - // Use the original node's created time for existing nodes. - if (isset($settings['created']) && !isset($settings['date'])) { - $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O'); - } - - // If the node's user uid is not specified manually, use the currently - // logged in user if available, or else the user running the test. - if (!isset($settings['uid'])) { - if ($this->loggedInUser) { - $settings['uid'] = $this->loggedInUser->id(); - } - else { - $user = \Drupal::currentUser() ?: new AnonymousUserSession(); - $settings['uid'] = $user->id(); - } - } - - // Merge body field value and format separately. - $settings['body'][0] += array( - 'value' => $this->randomName(32), - 'format' => filter_default_format(), - ); - - $node = entity_create('node', $settings); - if (!empty($settings['revision'])) { - $node->setNewRevision(); - } - $node->save(); - - return $node; - } - - /** - * Creates a custom content type based on default settings. - * - * @param array $values - * An array of settings to change from the defaults. - * Example: 'type' => 'foo'. - * - * @return \Drupal\node\Entity\NodeType - * Created content type. - */ - protected function drupalCreateContentType(array $values = array()) { - // Find a non-existent random type name. - if (!isset($values['type'])) { - do { - $id = strtolower($this->randomName(8)); - } while (node_type_load($id)); - } - else { - $id = $values['type']; - } - $values += array( - 'type' => $id, - 'name' => $id, - ); - $type = entity_create('node_type', $values); - $status = $type->save(); - \Drupal::service('router.builder')->rebuild(); - - $this->assertEqual($status, SAVED_NEW, String::format('Created content type %type.', array('%type' => $type->id()))); - - return $type; - } - - /** - * Builds the renderable view of an entity. - * - * Entities postpone the composition of their renderable arrays to #pre_render - * functions in order to maximize cache efficacy. This means that the full - * rendable array for an entity is constructed in drupal_render(). Some tests - * require the complete renderable array for an entity outside of the - * drupal_render process in order to verify the presence of specific values. - * This method isolates the steps in the render process that produce an - * entity's renderable array. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity to prepare a renderable array for. - * @param string $view_mode - * (optional) The view mode that should be used to build the entity. - * @param null $langcode - * (optional) For which language the entity should be prepared, defaults to - * the current content language. - * @param bool $reset - * (optional) Whether to clear the cache for this entity. - * @return array - * - * @see drupal_render() - */ - protected function drupalBuildEntityView(EntityInterface $entity, $view_mode = 'full', $langcode = NULL, $reset = FALSE) { - $render_controller = $this->container->get('entity.manager')->getViewBuilder($entity->getEntityTypeId()); - if ($reset) { - $render_controller->resetCache(array($entity->id())); - } - $elements = $render_controller->view($entity, $view_mode, $langcode); - // If the default values for this element have not been loaded yet, populate - // them. - if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) { - $elements += element_info($elements['#type']); - } - - // Make any final changes to the element before it is rendered. This means - // that the $element or the children can be altered or corrected before the - // element is rendered into the final text. - if (isset($elements['#pre_render'])) { - foreach ($elements['#pre_render'] as $callable) { - $elements = call_user_func($callable, $elements); - } - } - return $elements; - } - - /** - * Creates a block instance based on default settings. - * - * @param string $plugin_id - * The plugin ID of the block type for this block instance. - * @param array $settings - * (optional) An associative array of settings for the block entity. - * Override the defaults by specifying the key and value in the array, for - * example: - * @code - * $this->drupalPlaceBlock('system_powered_by_block', array( - * 'label' => t('Hello, world!'), - * )); - * @endcode - * The following defaults are provided: - * - label: Random string. - * - ID: Random string. - * - region: 'sidebar_first'. - * - theme: The default theme. - * - visibility: Empty array. - * - cache: array('max_age' => 0). - * - * @return \Drupal\block\Entity\Block - * The block entity. - * - * @todo - * Add support for creating custom block instances. - */ - protected function drupalPlaceBlock($plugin_id, array $settings = array()) { - $settings += array( - 'plugin' => $plugin_id, - 'region' => 'sidebar_first', - 'id' => strtolower($this->randomName(8)), - 'theme' => \Drupal::config('system.theme')->get('default'), - 'label' => $this->randomName(8), - 'visibility' => array(), - 'weight' => 0, - 'cache' => array( - 'max_age' => 0, - ), - ); - foreach (array('region', 'id', 'theme', 'plugin', 'weight') as $key) { - $values[$key] = $settings[$key]; - // Remove extra values that do not belong in the settings array. - unset($settings[$key]); - } - foreach ($settings['visibility'] as $id => $visibility) { - $settings['visibility'][$id]['id'] = $id; - } - $values['settings'] = $settings; - $block = entity_create('block', $values); - $block->save(); - return $block; - } - - /** * Checks to see whether a block appears on the page. * * @param \Drupal\block\Entity\Block $block @@ -452,213 +136,6 @@ protected function findBlockInstance(Block $block) { } /** - * Gets a list files that can be used in tests. - * - * @param $type - * File type, possible values: 'binary', 'html', 'image', 'javascript', - * 'php', 'sql', 'text'. - * @param $size - * File size in bytes to match. Please check the tests/files folder. - * - * @return - * List of files that match filter. - */ - protected function drupalGetTestFiles($type, $size = NULL) { - if (empty($this->generatedTestFiles)) { - // Generate binary test files. - $lines = array(64, 1024); - $count = 0; - foreach ($lines as $line) { - simpletest_generate_file('binary-' . $count++, 64, $line, 'binary'); - } - - // Generate text test files. - $lines = array(16, 256, 1024, 2048, 20480); - $count = 0; - foreach ($lines as $line) { - simpletest_generate_file('text-' . $count++, 64, $line); - } - - // Copy other test files from simpletest. - $original = drupal_get_path('module', 'simpletest') . '/files'; - $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/'); - foreach ($files as $file) { - file_unmanaged_copy($file->uri, PublicStream::basePath()); - } - - $this->generatedTestFiles = TRUE; - } - - $files = array(); - // Make sure type is valid. - if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) { - $files = file_scan_directory('public://', '/' . $type . '\-.*/'); - - // If size is set then remove any files that are not of that size. - if ($size !== NULL) { - foreach ($files as $file) { - $stats = stat($file->uri); - if ($stats['size'] != $size) { - unset($files[$file->uri]); - } - } - } - } - usort($files, array($this, 'drupalCompareFiles')); - return $files; - } - - /** - * Compare two files based on size and file name. - */ - protected function drupalCompareFiles($file1, $file2) { - $compare_size = filesize($file1->uri) - filesize($file2->uri); - if ($compare_size) { - // Sort by file size. - return $compare_size; - } - else { - // The files were the same size, so sort alphabetically. - return strnatcmp($file1->name, $file2->name); - } - } - - /** - * Create a user with a given set of permissions. - * - * @param array $permissions - * Array of permission names to assign to user. Note that the user always - * has the default permissions derived from the "authenticated users" role. - * @param string $name - * The user name. - * - * @return \Drupal\user\Entity\User|false - * A fully loaded user object with pass_raw property, or FALSE if account - * creation fails. - */ - protected function drupalCreateUser(array $permissions = array(), $name = NULL) { - // Create a role with the given permission set, if any. - $rid = FALSE; - if ($permissions) { - $rid = $this->drupalCreateRole($permissions); - if (!$rid) { - return FALSE; - } - } - - // Create a user assigned to that role. - $edit = array(); - $edit['name'] = !empty($name) ? $name : $this->randomName(); - $edit['mail'] = $edit['name'] . '@example.com'; - $edit['pass'] = user_password(); - $edit['status'] = 1; - if ($rid) { - $edit['roles'] = array($rid); - } - - $account = entity_create('user', $edit); - $account->save(); - - $this->assertTrue($account->id(), String::format('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), 'User login'); - if (!$account->id()) { - return FALSE; - } - - // Add the raw password so that we can log in as this user. - $account->pass_raw = $edit['pass']; - return $account; - } - - /** - * Creates a role with specified permissions. - * - * @param array $permissions - * Array of permission names to assign to role. - * @param string $rid - * (optional) The role ID (machine name). Defaults to a random name. - * @param string $name - * (optional) The label for the role. Defaults to a random string. - * @param integer $weight - * (optional) The weight for the role. Defaults NULL so that entity_create() - * sets the weight to maximum + 1. - * - * @return string - * Role ID of newly created role, or FALSE if role creation failed. - */ - protected function drupalCreateRole(array $permissions, $rid = NULL, $name = NULL, $weight = NULL) { - // Generate a random, lowercase machine name if none was passed. - if (!isset($rid)) { - $rid = strtolower($this->randomName(8)); - } - // Generate a random label. - if (!isset($name)) { - // In the role UI role names are trimmed and random string can start or - // end with a space. - $name = trim($this->randomString(8)); - } - - // Check the all the permissions strings are valid. - if (!$this->checkPermissions($permissions)) { - return FALSE; - } - - // Create new role. - $role = entity_create('user_role', array( - 'id' => $rid, - 'label' => $name, - )); - if (!is_null($weight)) { - $role->set('weight', $weight); - } - $result = $role->save(); - - $this->assertIdentical($result, SAVED_NEW, String::format('Created role ID @rid with name @name.', array( - '@name' => var_export($role->label(), TRUE), - '@rid' => var_export($role->id(), TRUE), - )), 'Role'); - - if ($result === SAVED_NEW) { - // Grant the specified permissions to the role, if any. - if (!empty($permissions)) { - user_role_grant_permissions($role->id(), $permissions); - $assigned_permissions = entity_load('user_role', $role->id())->getPermissions(); - $missing_permissions = array_diff($permissions, $assigned_permissions); - if (!$missing_permissions) { - $this->pass(String::format('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), 'Role'); - } - else { - $this->fail(String::format('Failed to create permissions: @perms', array('@perms' => implode(', ', $missing_permissions))), 'Role'); - } - } - return $role->id(); - } - else { - return FALSE; - } - } - - /** - * Checks whether a given list of permission names is valid. - * - * @param array $permissions - * The permission names to check. - * - * @return bool - * TRUE if the permissions are valid, FALSE otherwise. - */ - protected function checkPermissions(array $permissions) { - $available = array_keys(\Drupal::moduleHandler()->invokeAll('permission')); - $valid = TRUE; - foreach ($permissions as $permission) { - if (!in_array($permission, $available)) { - $this->fail(String::format('Invalid permission %permission.', array('%permission' => $permission)), 'Role'); - $valid = FALSE; - } - } - return $valid; - } - - /** * Log in a user with the internal browser. * * If a user is already logged in, then the current user is logged out before @@ -711,21 +188,6 @@ protected function drupalLogin(AccountInterface $account) { } /** - * Returns whether a given user account is logged in. - * - * @param \Drupal\user\UserInterface $account - * The user account object to check. - */ - protected function drupalUserIsLoggedIn($account) { - if (!isset($account->session_id)) { - return FALSE; - } - // The session ID is hashed before being stored in the database. - // @see \Drupal\Core\Session\SessionHandler::read() - return (bool) db_query("SELECT sid FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => Crypt::hashBase64($account->session_id)))->fetchField(); - } - - /** * Generate a token for the currently logged in user. */ protected function drupalGetToken($value = '') { @@ -756,420 +218,15 @@ protected function drupalLogout() { } /** - * Returns the session name in use on the child site. - * - * @return string - * The name of the session cookie. - */ - public function getSessionName() { - return $this->session_name; - } - - /** - * Sets up a Drupal site for running functional and integration tests. - * - * Installs Drupal with the installation profile specified in - * \Drupal\simpletest\WebTestBase::$profile into the prefixed database. - - * Afterwards, installs any additional modules specified in the static - * \Drupal\simpletest\WebTestBase::$modules property of each class in the - * class hierarchy. - * - * After installation all caches are flushed and several configuration values - * are reset to the values of the parent site executing the test, since the - * default values may be incompatible with the environment in which tests are - * being executed. - */ - protected function setUp() { - // When running tests through the Simpletest UI (vs. on the command line), - // Simpletest's batch conflicts with the installer's batch. Batch API does - // not support the concept of nested batches (in which the nested is not - // progressive), so we need to temporarily pretend there was no batch. - // Backup the currently running Simpletest batch. - $this->originalBatch = batch_get(); - - // Define information about the user 1 account. - $this->root_user = new UserSession(array( - 'uid' => 1, - 'name' => 'admin', - 'mail' => 'admin@example.com', - 'pass_raw' => $this->randomName(), - )); - - // Some tests (SessionTest and SessionHttpsTest) need to examine whether the - // proper session cookies were set on a response. Because the child site - // uses the same session name as the test runner, it is necessary to make - // that available to test-methods. - $this->session_name = $this->originalSessionName; - - // Reset the static batch to remove Simpletest's batch operations. - $batch = &batch_get(); - $batch = array(); - - // Get parameters for install_drupal() before removing global variables. - $parameters = $this->installParameters(); - - // Prepare installer settings that are not install_drupal() parameters. - // Copy and prepare an actual settings.php, so as to resemble a regular - // installation. - // Not using File API; a potential error must trigger a PHP warning. - $directory = DRUPAL_ROOT . '/' . $this->siteDirectory; - copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php'); - - // All file system paths are created by System module during installation. - // @see system_requirements() - // @see TestBase::prepareEnvironment() - $settings['settings']['file_public_path'] = (object) array( - 'value' => $this->public_files_directory, - 'required' => TRUE, - ); - // Save the original site directory path, so that extensions in the - // site-specific directory can still be discovered in the test site - // environment. - // @see \Drupal\Core\SystemListing::scan() - $settings['settings']['test_parent_site'] = (object) array( - 'value' => $this->originalSite, - 'required' => TRUE, - ); - // Add the parent profile's search path to the child site's search paths. - // @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories() - $settings['conf']['simpletest.settings']['parent_profile'] = (object) array( - 'value' => $this->originalProfile, - 'required' => TRUE, - ); - $this->writeSettings($settings); - // Allow for test-specific overrides. - $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php'; - if (file_exists($settings_testing_file)) { - // Copy the testing-specific settings.php overrides in place. - copy($settings_testing_file, $directory . '/settings.testing.php'); - // Add the name of the testing class to settings.php and include the - // testing specific overrides - file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) ."';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' ."\n", FILE_APPEND); - } - $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml'; - if (file_exists($settings_services_file)) { - // Copy the testing-specific service overrides in place. - copy($settings_services_file, $directory . '/services.yml'); - } - - // Since Drupal is bootstrapped already, install_begin_request() will not - // bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to - // reload the newly written custom settings.php manually. - Settings::initialize($directory); - - // Execute the non-interactive installer. - require_once DRUPAL_ROOT . '/core/includes/install.core.inc'; - install_drupal($parameters); - - // Import new settings.php written by the installer. - Settings::initialize($directory); - foreach ($GLOBALS['config_directories'] as $type => $path) { - $this->configDirectories[$type] = $path; - } - - // After writing settings.php, the installer removes write permissions - // from the site directory. To allow drupal_generate_test_ua() to write - // a file containing the private key for drupal_valid_test_ua(), the site - // directory has to be writable. - // TestBase::restoreEnvironment() will delete the entire site directory. - // Not using File API; a potential error must trigger a PHP warning. - chmod($directory, 0777); - - $request = \Drupal::request(); - $this->kernel = DrupalKernel::createFromRequest($request, drupal_classloader(), 'prod', TRUE); - $this->kernel->prepareLegacyRequest($request); - // Force the container to be built from scratch instead of loaded from the - // disk. This forces us to not accidently load the parent site. - $container = $this->kernel->rebuildContainer(); - - $config = $container->get('config.factory'); - - // Manually create and configure private and temporary files directories. - // While these could be preset/enforced in settings.php like the public - // files directory above, some tests expect them to be configurable in the - // UI. If declared in settings.php, they would no longer be configurable. - file_prepare_directory($this->private_files_directory, FILE_CREATE_DIRECTORY); - file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY); - $config->get('system.file') - ->set('path.private', $this->private_files_directory) - ->set('path.temporary', $this->temp_files_directory) - ->save(); - - // Manually configure the test mail collector implementation to prevent - // tests from sending out emails and collect them in state instead. - // While this should be enforced via settings.php prior to installation, - // some tests expect to be able to test mail system implementations. - $config->get('system.mail') - ->set('interface.default', 'test_mail_collector') - ->save(); - - // By default, verbosely display all errors and disable all production - // environment optimizations for all tests to avoid needless overhead and - // ensure a sane default experience for test authors. - // @see https://drupal.org/node/2259167 - $config->get('system.logging') - ->set('error_level', 'verbose') - ->save(); - $config->get('system.performance') - ->set('css.preprocess', FALSE) - ->set('js.preprocess', FALSE) - ->save(); - - // Restore the original Simpletest batch. - $batch = &batch_get(); - $batch = $this->originalBatch; - - // Collect modules to install. - $class = get_class($this); - $modules = array(); - while ($class) { - if (property_exists($class, 'modules')) { - $modules = array_merge($modules, $class::$modules); - } - $class = get_parent_class($class); - } - if ($modules) { - $modules = array_unique($modules); - $success = $container->get('module_handler')->install($modules, TRUE); - $this->assertTrue($success, String::format('Enabled modules: %modules', array('%modules' => implode(', ', $modules)))); - $this->rebuildContainer(); - } - - // Reset/rebuild all data structures after enabling the modules, primarily - // to synchronize all data structures and caches between the test runner and - // the child site. - // Affects e.g. file_get_stream_wrappers(). - // @see \Drupal\Core\DrupalKernel::bootCode() - // @todo Test-specific setUp() methods may set up further fixtures; find a - // way to execute this after setUp() is done, or to eliminate it entirely. - $this->resetAll(); - $this->kernel->prepareLegacyRequest($request); - - // Temporary fix so that when running from run-tests.sh we don't get an - // empty current path which would indicate we're on the home page. - $path = current_path(); - if (empty($path)) { - _current_path('run-tests'); - } - } - - /** - * Returns the parameters that will be used when Simpletest installs Drupal. - * - * @see install_drupal() - * @see install_state_defaults() - */ - protected function installParameters() { - $connection_info = Database::getConnectionInfo(); - $driver = $connection_info['default']['driver']; - $connection_info['default']['prefix'] = $connection_info['default']['prefix']['default']; - unset($connection_info['default']['driver']); - unset($connection_info['default']['namespace']); - unset($connection_info['default']['pdo']); - unset($connection_info['default']['init_commands']); - $parameters = array( - 'interactive' => FALSE, - 'parameters' => array( - 'profile' => $this->profile, - 'langcode' => 'en', - ), - 'forms' => array( - 'install_settings_form' => array( - 'driver' => $driver, - $driver => $connection_info['default'], - ), - 'install_configure_form' => array( - 'site_name' => 'Drupal', - 'site_mail' => 'simpletest@example.com', - 'account' => array( - 'name' => $this->root_user->name, - 'mail' => $this->root_user->getEmail(), - 'pass' => array( - 'pass1' => $this->root_user->pass_raw, - 'pass2' => $this->root_user->pass_raw, - ), - ), - // form_type_checkboxes_value() requires NULL instead of FALSE values - // for programmatic form submissions to disable a checkbox. - 'update_status_module' => array( - 1 => NULL, - 2 => NULL, - ), - ), - ), - ); - return $parameters; - } - - /** - * Rewrites the settings.php file of the test site. - * - * @param array $settings - * An array of settings to write out, in the format expected by - * drupal_rewrite_settings(). - * - * @see drupal_rewrite_settings() - */ - protected function writeSettings(array $settings) { - include_once DRUPAL_ROOT . '/core/includes/install.inc'; - $filename = $this->siteDirectory . '/settings.php'; - // system_requirements() removes write permissions from settings.php - // whenever it is invoked. - // Not using File API; a potential error must trigger a PHP warning. - chmod($filename, 0666); - drupal_rewrite_settings($settings, $filename); - } - - /** - * Queues custom translations to be written to settings.php. - * - * Use WebTestBase::writeCustomTranslations() to apply and write the queued - * translations. - * - * @param string $langcode - * The langcode to add translations for. - * @param array $values - * Array of values containing the untranslated string and its translation. - * For example: - * @code - * array( - * '' => array('Sunday' => 'domingo'), - * 'Long month name' => array('March' => 'marzo'), - * ); - * @endcode - * Pass an empty array to remove all existing custom translations for the - * given $langcode. - */ - protected function addCustomTranslations($langcode, array $values) { - // If $values is empty, then the test expects all custom translations to be - // cleared. - if (empty($values)) { - $this->customTranslations[$langcode] = array(); - } - // Otherwise, $values are expected to be merged into previously passed - // values, while retaining keys that are not explicitly set. - else { - foreach ($values as $context => $translations) { - foreach ($translations as $original => $translation) { - $this->customTranslations[$langcode][$context][$original] = $translation; - } - } - } - } - - /** - * Writes custom translations to the test site's settings.php. - * - * Use TestBase::addCustomTranslations() to queue custom translations before - * calling this method. - */ - protected function writeCustomTranslations() { - $settings = array(); - foreach ($this->customTranslations as $langcode => $values) { - $settings_key = 'locale_custom_strings_' . $langcode; - - // Update in-memory settings directly. - $this->settingsSet($settings_key, $values); - - $settings['settings'][$settings_key] = (object) array( - 'value' => $values, - 'required' => TRUE, - ); - } - // Only rewrite settings if there are any translation changes to write. - if (!empty($settings)) { - $this->writeSettings($settings); - } - } - - /** - * Rebuilds \Drupal::getContainer(). - * - * Use this to build a new kernel and service container. For example, when the - * list of enabled modules is changed via the internal browser, in which case - * the test process still contains an old kernel and service container with an - * old module list. - * - * @see TestBase::prepareEnvironment() - * @see TestBase::restoreEnvironment() - * - * @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable - * changes are immediately reflected in \Drupal::getContainer(). Until then, - * tests can invoke this workaround when requiring services from newly - * enabled modules to be immediately available in the same request. - */ - protected function rebuildContainer() { - // Maintain the current global request object. - $request = \Drupal::request(); - // Rebuild the kernel and bring it back to a fully bootstrapped state. - $this->container = $this->kernel->rebuildContainer(); - - // The request context is normally set by the router_listener from within - // its KernelEvents::REQUEST listener. In the simpletest parent site this - // event is not fired, therefore it is necessary to updated the request - // context manually here. - $this->container->get('router.request_context')->fromRequest($request); - - // Make sure the url generator has a request object, otherwise calls to - // $this->drupalGet() will fail. - $this->prepareRequestForGenerator(); - } - - /** - * Resets all data structures after having enabled new modules. - * - * This method is called by \Drupal\simpletest\WebTestBase::setUp() after - * enabling the requested modules. It must be called again when additional - * modules are enabled later. - */ - protected function resetAll() { - // Clear all database and static caches and rebuild data structures. - drupal_flush_all_caches(); - $this->container = \Drupal::getContainer(); - - // Reset static variables and reload permissions. - $this->refreshVariables(); - } - - /** - * Refreshes in-memory configuration and state information. - * - * Useful after a page request is made that changes configuration or state in - * a different thread. - * - * In other words calling a settings page with $this->drupalPostForm() with a - * changed value would update configuration to reflect that change, but in the - * thread that made the call (thread running the test) the changed values - * would not be picked up. - * - * This method clears the cache and loads a fresh copy. - */ - protected function refreshVariables() { - // Clear the tag cache. - drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache'); - drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::deletedTags'); - drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::invalidatedTags'); - - $this->container->get('config.factory')->reset(); - $this->container->get('state')->resetCache(); - } - - /** * Cleans up after testing. * * Deletes created files and temporary files directory, deletes the tables * created by setUp(), and resets the database prefix. */ protected function tearDown() { - // Destroy the testing kernel. - if (isset($this->kernel)) { - $this->kernel->shutdown(); - } parent::tearDown(); - // Ensure that internal logged in variable and cURL options are reset. - $this->loggedInUser = FALSE; + // Ensure that internal cURL options are reset. $this->additionalCurlOptions = array(); // Close the CURL handler and reset the cookies array used for upgrade @@ -1415,21 +472,6 @@ protected function curlClose() { } /** - * Returns whether the test is being executed from within a test site. - * - * Mainly used by recursive tests (i.e. to test the testing framework). - * - * @return bool - * TRUE if this test was instantiated in a request within the test site, - * FALSE otherwise. - * - * @see \Drupal\Core\DrupalKernel::bootConfiguration() - */ - protected function isInChildSite() { - return DRUPAL_TEST_IN_CHILD_SITE; - } - - /** * Retrieves a Drupal path or an absolute path. * * @param $path @@ -2258,37 +1300,6 @@ protected function clickLink($label, $index = 0) { } /** - * Takes a path and returns an absolute path. - * - * @param $path - * A path from the internal browser content. - * - * @return - * The $path with $base_url prepended, if necessary. - */ - protected function getAbsoluteUrl($path) { - global $base_url, $base_path; - - $parts = parse_url($path); - if (empty($parts['host'])) { - // Ensure that we have a string (and no xpath object). - $path = (string) $path; - // Strip $base_path, if existent. - $length = strlen($base_path); - if (substr($path, 0, $length) === $base_path) { - $path = substr($path, $length); - } - // Ensure that we have an absolute path. - if ($path[0] !== '/') { - $path = '/' . $path; - } - // Finally, prepend the $base_url. - $path = $base_url . $path; - } - return $path; - } - - /** * Get the current URL from the cURL handler. * * @return @@ -2409,32 +1420,6 @@ protected function drupalGetSettings() { } /** - * Gets an array containing all emails sent during this test case. - * - * @param $filter - * An array containing key/value pairs used to filter the emails that are - * returned. - * - * @return - * An array containing email messages captured during the current test. - */ - protected function drupalGetMails($filter = array()) { - $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array(); - $filtered_emails = array(); - - foreach ($captured_emails as $message) { - foreach ($filter as $key => $value) { - if (!isset($message[$key]) || $message[$key] != $value) { - continue 2; - } - } - $filtered_emails[] = $message; - } - - return $filtered_emails; - } - - /** * Sets the raw HTML content. * * @deprecated 8.x @@ -2537,165 +1522,4 @@ protected function assertNoResponse($code, $message = '', $group = 'Browser') { $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code; return $this->assertFalse($match, $message ? $message : String::format('HTTP response not expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), $group); } - - /** - * Asserts that the most recently sent email message has the given value. - * - * The field in $name must have the content described in $value. - * - * @param $name - * Name of field or message property to assert. Examples: subject, body, - * id, ... - * @param $value - * Value of the field to assert. - * @param $message - * (optional) A message to display with the assertion. Do not translate - * messages: use format_string() to embed variables in the message text, not - * t(). If left blank, a default message will be displayed. - * @param $group - * (optional) The group this message is in, which is displayed in a column - * in test output. Use 'Debug' to indicate this is debugging output. Do not - * translate this string. Defaults to 'Email'; most tests do not override - * this default. - * - * @return - * TRUE on pass, FALSE on fail. - */ - protected function assertMail($name, $value = '', $message = '', $group = 'Email') { - $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array(); - $email = end($captured_emails); - return $this->assertTrue($email && isset($email[$name]) && $email[$name] == $value, $message, $group); - } - - /** - * Asserts that the most recently sent email message has the string in it. - * - * @param $field_name - * Name of field or message property to assert: subject, body, id, ... - * @param $string - * String to search for. - * @param $email_depth - * Number of emails to search for string, starting with most recent. - * @param $message - * (optional) A message to display with the assertion. Do not translate - * messages: use format_string() to embed variables in the message text, not - * t(). If left blank, a default message will be displayed. - * @param $group - * (optional) The group this message is in, which is displayed in a column - * in test output. Use 'Debug' to indicate this is debugging output. Do not - * translate this string. Defaults to 'Other'; most tests do not override - * this default. - * - * @return - * TRUE on pass, FALSE on fail. - */ - protected function assertMailString($field_name, $string, $email_depth, $message = '', $group = 'Other') { - $mails = $this->drupalGetMails(); - $string_found = FALSE; - for ($i = count($mails) -1; $i >= count($mails) - $email_depth && $i >= 0; $i--) { - $mail = $mails[$i]; - // Normalize whitespace, as we don't know what the mail system might have - // done. Any run of whitespace becomes a single space. - $normalized_mail = preg_replace('/\s+/', ' ', $mail[$field_name]); - $normalized_string = preg_replace('/\s+/', ' ', $string); - $string_found = (FALSE !== strpos($normalized_mail, $normalized_string)); - if ($string_found) { - break; - } - } - if (!$message) { - $message = format_string('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $string)); - } - return $this->assertTrue($string_found, $message, $group); - } - - /** - * Asserts that the most recently sent email message has the pattern in it. - * - * @param $field_name - * Name of field or message property to assert: subject, body, id, ... - * @param $regex - * Pattern to search for. - * @param $message - * (optional) A message to display with the assertion. Do not translate - * messages: use format_string() to embed variables in the message text, not - * t(). If left blank, a default message will be displayed. - * @param $group - * (optional) The group this message is in, which is displayed in a column - * in test output. Use 'Debug' to indicate this is debugging output. Do not - * translate this string. Defaults to 'Other'; most tests do not override - * this default. - * - * @return - * TRUE on pass, FALSE on fail. - */ - protected function assertMailPattern($field_name, $regex, $message = '', $group = 'Other') { - $mails = $this->drupalGetMails(); - $mail = end($mails); - $regex_found = preg_match("/$regex/", $mail[$field_name]); - if (!$message) { - $message = format_string('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $regex)); - } - return $this->assertTrue($regex_found, $message, $group); - } - - /** - * Outputs to verbose the most recent $count emails sent. - * - * @param $count - * Optional number of emails to output. - */ - protected function verboseEmail($count = 1) { - $mails = $this->drupalGetMails(); - for ($i = count($mails) -1; $i >= count($mails) - $count && $i >= 0; $i--) { - $mail = $mails[$i]; - $this->verbose('Email:
' . print_r($mail, TRUE) . '
'); - } - } - - /** - * Creates a mock request and sets it on the generator. - * - * This is used to manipulate how the generator generates paths during tests. - * It also ensures that calls to $this->drupalGet() will work when running - * from run-tests.sh because the url generator no longer looks at the global - * variables that are set there but relies on getting this information from a - * request object. - * - * @param bool $clean_urls - * Whether to mock the request using clean urls. - * @param $override_server_vars - * An array of server variables to override. - * - * @return $request - * The mocked request object. - */ - protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = array()) { - $generator = $this->container->get('url_generator'); - $request = Request::createFromGlobals(); - $server = $request->server->all(); - if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) { - // We need this for when the test is executed by run-tests.sh. - // @todo Remove this once run-tests.sh has been converted to use a Request - // object. - $cwd = getcwd(); - $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']); - $base_path = rtrim($server['REQUEST_URI'], '/'); - } - else { - $base_path = $request->getBasePath(); - } - if ($clean_urls) { - $request_path = $base_path ? $base_path . '/user' : 'user'; - } - else { - $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user'; - } - $server = array_merge($server, $override_server_vars); - - $request = Request::create($request_path, 'GET', array(), array(), array(), $server); - $this->container->get('request_stack')->push($request); - $generator->updateFromRequest(); - return $request; - } } diff --git a/core/vendor/behat/mink-browserkit-driver/.gitignore b/core/vendor/behat/mink-browserkit-driver/.gitignore new file mode 100644 index 0000000..7579f74 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/.gitignore @@ -0,0 +1,2 @@ +vendor +composer.lock diff --git a/core/vendor/behat/mink-browserkit-driver/.travis.yml b/core/vendor/behat/mink-browserkit-driver/.travis.yml new file mode 100644 index 0000000..c67da81 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/.travis.yml @@ -0,0 +1,14 @@ +language: php + +php: [5.3, 5.4, 5.5] + +env: + - SYMFONY_VERSION='2.1.*' + - SYMFONY_VERSION='2.2.*' + +before_script: + - curl http://getcomposer.org/installer | php + - php composer.phar require --no-update symfony/symfony=$SYMFONY_VERSION + - php composer.phar install --dev --prefer-source + +script: phpunit -v diff --git a/core/vendor/behat/mink-browserkit-driver/LICENSE b/core/vendor/behat/mink-browserkit-driver/LICENSE new file mode 100644 index 0000000..3365ae6 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012-2013 Konstantin Kudryashov + +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/core/vendor/behat/mink-browserkit-driver/README.md b/core/vendor/behat/mink-browserkit-driver/README.md new file mode 100755 index 0000000..6d3a7bf --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/README.md @@ -0,0 +1,48 @@ +Mink BrowserKit Driver +====================== + +- [![Build Status](https://secure.travis-ci.org/Behat/MinkBrowserKitDriver.png?branch=master)](http://travis-ci.org/Behat/MinkBrowserKitDriver) + +Usage Example +------------- + +``` php + new Session(new BrowserKitDriver(new Client($app))), +)); + +$mink->getSession('silex')->getPage()->findLink('Chat')->click(); +``` + +Installation +------------ + +``` json +{ + "require": { + "behat/mink": "1.4.*", + "behat/mink-browserkit-driver": "1.0.*" + } +} +``` + +``` bash +$> curl http://getcomposer.org/installer | php +$> php composer.phar install +``` + +Maintainers +----------- + +* Konstantin Kudryashov [everzet](http://github.com/everzet) +* Other [awesome developers](https://github.com/Behat/MinkBrowserKitDriver/graphs/contributors) diff --git a/core/vendor/behat/mink-browserkit-driver/composer.json b/core/vendor/behat/mink-browserkit-driver/composer.json new file mode 100644 index 0000000..af8d25b --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/composer.json @@ -0,0 +1,39 @@ +{ + "name": "behat/mink-browserkit-driver", + "description": "Symfony2 BrowserKit driver for Mink framework", + "keywords": ["Symfony2", "Mink", "testing", "browser"], + "homepage": "http://mink.behat.org/", + "type": "mink-driver", + "license": "MIT", + + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + + "require": { + "php": ">=5.3.1", + "behat/mink": "~1.5.0", + "symfony/browser-kit": "~2.0", + "symfony/dom-crawler": "~2.0" + }, + + "require-dev": { + "silex/silex": "@dev" + }, + + "autoload": { + "psr-0": { + "Behat\\Mink\\Driver": "src/" + } + }, + + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/core/vendor/behat/mink-browserkit-driver/phpunit.xml.dist b/core/vendor/behat/mink-browserkit-driver/phpunit.xml.dist new file mode 100644 index 0000000..c7cfc6d --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/Behat/Mink/Driver + + + + + + ./src/Behat/Mink/Driver + + + diff --git a/core/vendor/behat/mink-browserkit-driver/src/Behat/Mink/Driver/BrowserKitDriver.php b/core/vendor/behat/mink-browserkit-driver/src/Behat/Mink/Driver/BrowserKitDriver.php new file mode 100644 index 0000000..58bd26a --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/src/Behat/Mink/Driver/BrowserKitDriver.php @@ -0,0 +1,751 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Symfony2 BrowserKit driver. + * + * @author Konstantin Kudryashov + */ +class BrowserKitDriver extends CoreDriver +{ + private $session; + private $client; + private $forms = array(); + private $started = false; + private $removeScriptFromUrl = true; + private $removeHostFromUrl = false; + + /** + * Initializes Goutte driver. + * + * @param Client $client BrowserKit client instance + */ + public function __construct(Client $client = null) + { + $this->client = $client; + $this->client->followRedirects(true); + } + + /** + * Returns BrowserKit HTTP client instance. + * + * @return Client + */ + public function getClient() + { + return $this->client; + } + + /** + * Sets driver's current session. + * + * @param Session $session + */ + public function setSession(Session $session) + { + $this->session = $session; + } + + /** + * Tells driver to remove hostname from URL. + * + * @param Boolean $remove + */ + public function setRemoveHostFromUrl($remove = true) + { + $this->removeHostFromUrl = (bool) $remove; + } + + /** + * Tells driver to remove scriptname from URL. + * + * @param Boolean $remove + */ + public function setRemoveScriptFromUrl($remove = true) + { + $this->removeScriptFromUrl = (bool) $remove; + } + + /** + * Starts driver. + */ + public function start() + { + $this->started = true; + } + + /** + * Checks whether driver is started. + * + * @return Boolean + */ + public function isStarted() + { + return $this->started; + } + + /** + * Stops driver. + */ + public function stop() + { + $this->client->restart(); + $this->started = false; + $this->forms = array(); + } + + /** + * Resets driver. + */ + public function reset() + { + $this->client->restart(); + $this->forms = array(); + } + + /** + * Visit specified URL. + * + * @param string $url url of the page + */ + public function visit($url) + { + $this->client->request('GET', $this->prepareUrl($url)); + $this->forms = array(); + } + + /** + * Returns current URL address. + * + * @return string + */ + public function getCurrentUrl() + { + return $this->client->getRequest()->getUri(); + } + + /** + * Reloads current page. + */ + public function reload() + { + $this->client->reload(); + $this->forms = array(); + } + + /** + * Moves browser forward 1 page. + */ + public function forward() + { + $this->client->forward(); + $this->forms = array(); + } + + /** + * Moves browser backward 1 page. + */ + public function back() + { + $this->client->back(); + $this->forms = array(); + } + + /** + * Sets HTTP Basic authentication parameters + * + * @param string|Boolean $user user name or false to disable authentication + * @param string $password password + */ + public function setBasicAuth($user, $password) + { + $this->client->setServerParameter('PHP_AUTH_USER', $user); + $this->client->setServerParameter('PHP_AUTH_PW', $password); + } + + /** + * Sets specific request header on client. + * + * @param string $name + * @param string $value + */ + public function setRequestHeader($name, $value) + { + switch (strtolower($name)) { + case 'accept': + $name = 'HTTP_ACCEPT'; + break; + case 'accept-charset': + $name = 'HTTP_ACCEPT_CHARSET'; + break; + case 'accept-encoding': + $name = 'HTTP_ACCEPT_ENCODING'; + break; + case 'accept-language': + $name = 'HTTP_ACCEPT_LANGUAGE'; + break; + case 'connection': + $name = 'HTTP_CONNECTION'; + break; + case 'host': + $name = 'HTTP_HOST'; + break; + case 'user-agent': + $name = 'HTTP_USER_AGENT'; + break; + case 'authorization': + $name = 'PHP_AUTH_DIGEST'; + break; + } + + $this->client->setServerParameter($name, $value); + } + + /** + * Returns last response headers. + * + * @return array + */ + public function getResponseHeaders() + { + return $this->getResponse()->getHeaders(); + } + + /** + * Sets cookie. + * + * @param string $name + * @param string $value + */ + public function setCookie($name, $value = null) + { + $jar = $this->client->getCookieJar(); + + if (null === $value) { + if (null !== $jar->get($name)) { + $jar->expire($name); + } + + return; + } + + $jar->set(new Cookie($name, $value)); + } + + /** + * Returns cookie by name. + * + * @param string $name + * + * @return string|null + */ + public function getCookie($name) + { + // Note that the following doesn't work well because + // Symfony\Component\BrowserKit\CookieJar stores cookies by name, + // path, AND domain and if you don't fill them all in correctly then + // you won't get the value that you're expecting. + // + // $jar = $this->client->getCookieJar(); + // + // if (null !== $cookie = $jar->get($name)) { + // return $cookie->getValue(); + // } + + $allValues = $this->client->getCookieJar()->allValues($this->getCurrentUrl()); + + if (isset($allValues[$name])) { + return $allValues[$name]; + } else { + return null; + } + } + + /** + * Returns last response status code. + * + * @return integer + */ + public function getStatusCode() + { + return $this->getResponse()->getStatus(); + } + + /** + * Returns last response content. + * + * @return string + */ + public function getContent() + { + return $this->getResponse()->getContent(); + } + + /** + * Finds elements with specified XPath query. + * + * @param string $xpath + * + * @return array array of NodeElements + */ + public function find($xpath) + { + $nodes = $this->getCrawler()->filterXPath($xpath); + + $elements = array(); + foreach ($nodes as $i => $node) { + $elements[] = new NodeElement(sprintf('(%s)[%d]', $xpath, $i + 1), $this->session); + } + + return $elements; + } + + /** + * Returns element's tag name by it's XPath query. + * + * @param string $xpath + * + * @return string + */ + public function getTagName($xpath) + { + return $this->getCrawlerNode($this->getCrawler()->filterXPath($xpath)->eq(0))->nodeName; + } + + /** + * Returns element's text by it's XPath query. + * + * @param string $xpath + * + * @return string + */ + public function getText($xpath) + { + $text = $this->getCrawler()->filterXPath($xpath)->eq(0)->text(); + $text = str_replace("\n", ' ', $text); + $text = preg_replace('/ {2,}/', ' ', $text); + + return trim($text); + } + + /** + * Returns element's html by it's XPath query. + * + * @param string $xpath + * + * @return string + */ + public function getHtml($xpath) + { + $node = $this->getCrawlerNode($this->getCrawler()->filterXPath($xpath)->eq(0)); + $text = $node->ownerDocument->saveXML($node); + + // cut the tag itself (making innerHTML out of outerHTML) + $text = preg_replace('/^\<[^\>]+\>|\<[^\>]+\>$/', '', $text); + + return $text; + } + + /** + * Returns element's attribute by it's XPath query. + * + * @param string $xpath + * @param string $name + * + * @return mixed + */ + public function getAttribute($xpath, $name) + { + $value = $this->getCrawler()->filterXPath($xpath)->eq(0)->attr($name); + + return '' !== $value ? $value : null; + } + + /** + * Returns element's value by it's XPath query. + * + * @param string $xpath + * + * @return mixed + */ + public function getValue($xpath) + { + if (in_array($this->getAttribute($xpath, 'type'), array('submit', 'image', 'button'))) { + return $this->getAttribute($xpath, 'value'); + } + + try { + $field = $this->getFormField($xpath); + } catch (\InvalidArgumentException $e) { + return $this->getAttribute($xpath, 'value'); + } + + $value = $field->getValue(); + + if ($field instanceof Field\ChoiceFormField && 'checkbox' === $field->getType()) { + $value = '1' == $value; + } + + return $value; + } + + /** + * Sets element's value by it's XPath query. + * + * @param string $xpath + * @param string $value + */ + public function setValue($xpath, $value) + { + $this->getFormField($xpath)->setValue($value); + } + + /** + * Checks checkbox by it's XPath query. + * + * @param string $xpath + */ + public function check($xpath) + { + $this->getFormField($xpath)->tick(); + } + + /** + * Unchecks checkbox by it's XPath query. + * + * @param string $xpath + */ + public function uncheck($xpath) + { + $this->getFormField($xpath)->untick(); + } + + /** + * Selects option from select field located by it's XPath query. + * + * @param string $xpath + * @param string $value + * @param Boolean $multiple + */ + public function selectOption($xpath, $value, $multiple = false) + { + $field = $this->getFormField($xpath); + + if ($multiple) { + $oldValue = (array) $field->getValue(); + $oldValue[] = $value; + $value = $oldValue; + } + + $field->select($value); + } + + /** + * Clicks button or link located by it's XPath query. + * + * @param string $xpath + * + * @throws ElementNotFoundException + * @throws DriverException + */ + public function click($xpath) + { + if (!count($nodes = $this->getCrawler()->filterXPath($xpath))) { + throw new ElementNotFoundException( + $this->session, 'link or button', 'xpath', $xpath + ); + } + $node = $nodes->eq(0); + $type = $this->getCrawlerNode($node)->nodeName; + + if ('a' === $type) { + $this->client->click($node->link()); + } elseif('input' === $type || 'button' === $type) { + $form = $node->form(); + $formId = $this->getFormNodeId($form->getFormNode()); + + if (isset($this->forms[$formId])) { + $this->mergeForms($form, $this->forms[$formId]); + } + + // remove empty file fields from request + foreach ($form->getFiles() as $name => $field) { + if (empty($field['name']) && empty($field['tmp_name'])) { + $form->remove($name); + } + } + + $this->client->submit($form); + } else { + throw new DriverException(sprintf( + 'Goutte driver supports clicking on inputs and links only. But "%s" provided', $type + )); + } + + $this->forms = array(); + } + + /** + * Checks whether checkbox checked located by it's XPath query. + * + * @param string $xpath + * + * @return Boolean + */ + public function isChecked($xpath) + { + return (bool) $this->getValue($xpath); + } + + /** + * Attaches file path to file field located by it's XPath query. + * + * @param string $xpath + * @param string $path + */ + public function attachFile($xpath, $path) + { + $this->getFormField($xpath)->upload($path); + } + + protected function getResponse() + { + $response = $this->getClient()->getResponse(); + + if ($response instanceof Response) { + return $response; + } + + // due to a bug, the HttpKernel client implementation returns the HttpFoundation response + // The conversion logic is copied from Symfony\Component\HttpKernel\Client::filterResponse + if ($response instanceof HttpFoundationResponse) { + $headers = $response->headers->all(); + if ($response->headers->getCookies()) { + $cookies = array(); + foreach ($response->headers->getCookies() as $cookie) { + $cookies[] = new Cookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + $headers['Set-Cookie'] = $cookies; + } + + // this is needed to support StreamedResponse + ob_start(); + $response->sendContent(); + $content = ob_get_clean(); + + return new Response($content, $response->getStatusCode(), $headers); + } + + throw new \LogicException(sprintf( + 'The BrowserKit client returned an unsupported response implementation: %s', + get_class($response) + )); + } + + /** + * Prepares URL for visiting. + * Removes "*.php/" from urls and then passes it to GoutteDriver::visit(). + * + * @param string $url + * + * @return string + */ + protected function prepareUrl($url) + { + return preg_replace('#(https?\://[^/]+)(/[^/\.]+\.php)?#', + ($this->removeHostFromUrl ? '' : '$1').($this->removeScriptFromUrl ? '' : '$2'), $url + ); + } + + /** + * Returns form field from XPath query. + * + * @param string $xpath + * + * @return FormField + * + * @throws ElementNotFoundException + * @throws \LogicException + */ + protected function getFormField($xpath) + { + if (!count($crawler = $this->getCrawler()->filterXPath($xpath))) { + throw new ElementNotFoundException( + $this->session, 'form field', 'xpath', $xpath + ); + } + + $fieldNode = $this->getCrawlerNode($crawler); + $fieldName = str_replace('[]', '', $fieldNode->getAttribute('name')); + $formNode = $fieldNode; + + // we will access our element by name next, but that's not unique, so we need to know wich is ou element + $elements = $this->getCrawler()->filterXPath('//*[@name=\''.$fieldNode->getAttribute('name').'\']'); + $position = 0; + if(count($elements) > 1) { + // more than one element contains this name ! + // so we need to find the position of $fieldNode + foreach($elements as $key => $element) { + if($element->getAttribute('id') == $fieldNode->getAttribute('id')) { + $position = $key; + } + } + } + + do { + // use the ancestor form element + if (null === $formNode = $formNode->parentNode) { + throw new \LogicException('The selected node does not have a form ancestor.'); + } + } while ('form' != $formNode->nodeName); + + $formId = $this->getFormNodeId($formNode); + + // check if form already exists + if (isset($this->forms[$formId])) { + if (is_array($this->forms[$formId][$fieldName])) { + return $this->forms[$formId][$fieldName][$position]; + } + + return $this->forms[$formId][$fieldName]; + } + + // find form button + if (null === $buttonNode = $this->findFormButton($formNode)) { + throw new ElementNotFoundException( + $this->session, 'form submit button for field with xpath "'.$xpath.'"' + ); + } + + $this->forms[$formId] = new Form($buttonNode, $this->client->getRequest()->getUri()); + + if (is_array($this->forms[$formId][$fieldName])) { + return $this->forms[$formId][$fieldName][$position]; + } + + return $this->forms[$formId][$fieldName]; + } + + /** + * Returns form node unique identifier. + * + * @param \DOMElement $form + * + * @return mixed + */ + private function getFormNodeId(\DOMElement $form) + { + return md5($form->getLineNo() . $form->getNodePath() . $form->nodeValue); + } + + /** + * Finds form submit button inside form node. + * + * @param \DOMElement $form + * + * @return \DOMElement + */ + private function findFormButton(\DOMElement $form) + { + $document = new \DOMDocument('1.0', 'UTF-8'); + $node = $document->importNode($form, true); + $root = $document->appendChild($document->createElement('_root')); + + $root->appendChild($node); + $xpath = new \DOMXPath($document); + + foreach ($xpath->query('descendant::input | descendant::button', $root) as $node) { + if ('button' == $node->nodeName || in_array($node->getAttribute('type'), array('submit', 'button', 'image'))) { + return $node; + } + } + + return null; + } + + /** + * Merges second form values into first one. + * + * @param Form $to merging target + * @param Form $from merging source + */ + private function mergeForms(Form $to, Form $from) + { + foreach ($from->all() as $name => $field) { + $fieldReflection = new \ReflectionObject($field); + $nodeReflection = $fieldReflection->getProperty('node'); + $valueReflection = $fieldReflection->getProperty('value'); + + $nodeReflection->setAccessible(true); + $valueReflection->setAccessible(true); + + if (!($field instanceof Field\InputFormField && in_array( + $nodeReflection->getValue($field)->getAttribute('type'), + array('submit', 'button', 'image') + ))) { + $valueReflection->setValue($to[$name], $valueReflection->getValue($field)); + } + } + } + + /** + * Returns DOMNode from crawler instance. + * + * @param Crawler $crawler + * @param integer $num number of node from crawler + * + * @return \DOMNode + */ + private function getCrawlerNode(Crawler $crawler, $num = 0) + { + foreach ($crawler as $i => $node) { + if ($num == $i) { + return $node; + } + } + + return null; + } + + /** + * Returns crawler instance (got from client). + * + * @return Crawler + * + * @throws DriverException + */ + private function getCrawler() + { + $crawler = $this->client->getCrawler(); + + if (null === $crawler) { + throw new DriverException('Crawler can\'t be initialized. Did you started driver?'); + } + + return $crawler; + } +} diff --git a/core/vendor/behat/mink-browserkit-driver/tests/Behat/Mink/Driver/BrowserKitDriverTest.php b/core/vendor/behat/mink-browserkit-driver/tests/Behat/Mink/Driver/BrowserKitDriverTest.php new file mode 100644 index 0000000..b0ac0c0 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/Behat/Mink/Driver/BrowserKitDriverTest.php @@ -0,0 +1,28 @@ +setRemoveScriptFromUrl(false); + + return $driver; + } + + protected function pathTo($path) + { + $path = preg_replace('#quoted$#', 'quoted=', $path); + + return 'http://localhost'.$path; + } +} diff --git a/core/vendor/behat/mink-browserkit-driver/tests/app.php b/core/vendor/behat/mink-browserkit-driver/tests/app.php new file mode 100644 index 0000000..45300b7 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/app.php @@ -0,0 +1,37 @@ +register(new \Silex\Provider\SessionServiceProvider()); + +$def = realpath(__DIR__.'/../vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures'); +$ovr = realpath(__DIR__.'/web-fixtures'); +$cbk = function($file) use($app, $def, $ovr) { + $file = str_replace('.file', '.php', $file); + $path = file_exists($ovr.'/'.$file) ? $ovr.'/'.$file : $def.'/'.$file; + $resp = null; + + ob_start(); + include($path); + $content = ob_get_clean(); + + if ($resp) { + if ('' === $resp->getContent()) { + $resp->setContent($content); + } + + return $resp; + } + + return $content; +}; + +$app->get('/{file}', $cbk); +$app->post('/{file}', $cbk); + +$app['debug'] = true; +$app['exception_handler']->disable(); +$app['session.test'] = true; + +return $app; diff --git a/core/vendor/behat/mink-browserkit-driver/tests/bootstrap.php b/core/vendor/behat/mink-browserkit-driver/tests/bootstrap.php new file mode 100644 index 0000000..c81abe5 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/bootstrap.php @@ -0,0 +1,9 @@ + + + + Advanced form save + + + +request->all(); + $FILES = $request->files->all(); + + if (isset($POST['select_multiple_numbers']) && false !== strpos($POST['select_multiple_numbers'][0], ',')) { + $POST['select_multiple_numbers'] = explode(',', $POST['select_multiple_numbers'][0]); + } + + $POST['agreement'] = (isset($POST['agreement']) && ('1' === $POST['agreement'] || 'on' === $POST['agreement'])) ? 'on' : 'off'; + echo str_replace('>', '', var_export($POST, true)) . "\n"; + if (isset($FILES['about']) && file_exists($FILES['about']->getPathname())) { + echo file_get_contents($FILES['about']->getPathname()); + } else { + echo "no file"; + } + +?> + + diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/basic_form_post.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/basic_form_post.php new file mode 100644 index 0000000..1efe45e --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/basic_form_post.php @@ -0,0 +1,14 @@ + + + + + Basic Form Saving + + + +

Anket for request->get('first_name') ?>

+ + Firstname: request->get('first_name') ?> + Lastname: request->get('last_name') ?> + + diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/basic_get_form.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/basic_get_form.php new file mode 100644 index 0000000..fd2817d --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/basic_get_form.php @@ -0,0 +1,23 @@ + + + + Basic Get Form + + + +

Basic Get Form Page

+ +
+ query->all(); + echo isset($GET['q']) && $GET['q'] ? $GET['q'] : 'No search query' + ?> +
+ +
+ + + +
+ + diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/cookie_page1.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/cookie_page1.php new file mode 100644 index 0000000..62a51f9 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/cookie_page1.php @@ -0,0 +1,17 @@ +headers->setCookie($cook); +?> + + + + basic form + + + + + basic page with cookie set from server side + + diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/cookie_page2.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/cookie_page2.php new file mode 100644 index 0000000..22a7dab --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/cookie_page2.php @@ -0,0 +1,18 @@ + + + + Basic Form + + + + + Previous cookie: cookies->has('srvr_cookie')) { + echo $app['request']->cookies->get('srvr_cookie'); + } else { + echo 'NO'; + } + ?> + + diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/cookie_page3.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/cookie_page3.php new file mode 100644 index 0000000..f24d587 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/cookie_page3.php @@ -0,0 +1,20 @@ +cookies->has('foo'); +$resp = new Symfony\Component\HttpFoundation\Response(); +$cook = new Symfony\Component\HttpFoundation\Cookie('foo', 'bar'); +$resp->headers->setCookie($cook); + +?> + + + + HttpOnly Cookie Test + + + + + + + diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/headers.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/headers.php new file mode 100644 index 0000000..b829425 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/headers.php @@ -0,0 +1,10 @@ + + + + Headers page + + + + server->all()); ?> + + diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/issue130.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/issue130.php new file mode 100644 index 0000000..2079673 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/issue130.php @@ -0,0 +1,14 @@ + + + + + + query->get('p')) { + echo 'Go to 2'; + } else { + echo ''.$app['request']->headers->get('referer').''; + } + ?> + diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/issue140.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/issue140.php new file mode 100644 index 0000000..ec76de6 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/issue140.php @@ -0,0 +1,20 @@ + + + + + + isMethod('POST')) { + $resp = new Symfony\Component\HttpFoundation\Response(); + $cook = new Symfony\Component\HttpFoundation\Cookie('tc', $app['request']->request->get('cookie_value')); + $resp->headers->setCookie($cook); + } + else if ($app['request']->query->has('show_value')) { + echo $app['request']->cookies->get('tc'); + return; + } ?> +
+ + +
+ diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/print_cookies.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/print_cookies.php new file mode 100644 index 0000000..78a9121 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/print_cookies.php @@ -0,0 +1,25 @@ + + + + Cookies page + + + + cookies->all(); + unset($cookies['MOCKSESSID']); + + if (isset($cookies['srvr_cookie'])) { + $srvrCookie = $cookies['srvr_cookie']; + unset($cookies['srvr_cookie']); + $cookies['_SESS'] = ''; + $cookies['srvr_cookie'] = $srvrCookie; + } + + foreach ($cookies as $name => $val) { + $cookies[$name] = (string) $val; + } + echo str_replace(array('>'), '', var_export($cookies, true)); + ?> + + diff --git a/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/redirector.php b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/redirector.php new file mode 100644 index 0000000..2255b10 --- /dev/null +++ b/core/vendor/behat/mink-browserkit-driver/tests/web-fixtures/redirector.php @@ -0,0 +1,3 @@ +getSession(); + + if ($app['request']->query->has('login')) { + $session->migrate(); + } +?> + + + + Session Test + + + + +
getId() ?>
+ + diff --git a/core/vendor/behat/mink-goutte-driver b/core/vendor/behat/mink-goutte-driver new file mode 160000 index 0000000..488f7f0 --- /dev/null +++ b/core/vendor/behat/mink-goutte-driver @@ -0,0 +1 @@ +Subproject commit 488f7f02b1e907888f4b156b635693daf51d760c diff --git a/core/vendor/behat/mink/.gitignore b/core/vendor/behat/mink/.gitignore new file mode 100644 index 0000000..66de342 --- /dev/null +++ b/core/vendor/behat/mink/.gitignore @@ -0,0 +1,5 @@ +*.tgz +*.phar +phpunit.xml +composer.lock +vendor diff --git a/core/vendor/behat/mink/.travis.yml b/core/vendor/behat/mink/.travis.yml new file mode 100644 index 0000000..834dc0a --- /dev/null +++ b/core/vendor/behat/mink/.travis.yml @@ -0,0 +1,16 @@ +language: php + +php: [5.3, 5.4, 5.5] + +env: + - CSS_SELECTOR_VERSION='2.0.*' + - CSS_SELECTOR_VERSION='2.1.*' + - CSS_SELECTOR_VERSION='2.2.*' + - CSS_SELECTOR_VERSION='2.3.*@dev' + +before_script: + - curl http://getcomposer.org/installer | php + - php composer.phar require --no-update symfony/css-selector=$CSS_SELECTOR_VERSION + - php composer.phar install --dev --prefer-source + +script: phpunit -v diff --git a/core/vendor/behat/mink/CHANGES.md b/core/vendor/behat/mink/CHANGES.md new file mode 100644 index 0000000..3010fb6 --- /dev/null +++ b/core/vendor/behat/mink/CHANGES.md @@ -0,0 +1,204 @@ +1.5.0 / 2013-04-14 +================== + + * Add `CoreDriver` to simplify future drivers improvements + * Add `Mink::isSessionStarted()` method + * Fix multibite string `preg_replace` bugs + * Fix handling of whitespaces in `WebAssert::pageText...()` methods + +1.4.3 / 2013-03-02 +================== + + * Bump dependencies constraints + +1.4.2 / 2013-02-13 +================== + + * Fix wrong test case to ensure that core drivers work as expected + +1.4.1 / 2013-02-10 +================== + + * Update dependencies + * Add ElementException to element actions + * Rel attribute support for named selectors + * Add hasClass() helper to traversable elements + * Add getScreenshot() method to session + * Name attr support in named selector for button + * Fix for bunch of bugs + +1.4.0 / 2012-05-40 +================== + + * New `Session::selectWindow()` and `Session::selectIFrame()` methods + * New built-in `WebAssert` class + * Fixed DocBlocks (autocompletion in any IDE now should just work) + * Moved Behat-related code into `Behat\MinkExtension` + * Removed PHPUnit test case class + * Updated composer dependencies to not require custom repository anymore + * All drivers moved into separate packages + +1.3.3 / 2012-03-23 +================== + + * Prevent exceptions in `__toString()` + * Added couple of useful step definitions for Behat + * Fixed issues #168, #211, #212, #208 + * Lot of small bug fixes and improvements + * Fixed dependencies and composer installation routine + +1.3.2 / 2011-12-21 +================== + + * Fixed webdriver registration in MinkContext + +1.3.1 / 2011-12-21 +================== + + * Fixed Composer package + +1.3.0 / 2011-12-21 +================== + + * Brand new Selenium2Driver (webdriver session) + * Multiselect bugfixes + * ZombieDriver back in the business + * Composer now manages dependencies + * Some MinkContext steps got fixes + * Lots of bug fixes and cleanup + +1.2.0 / 2011-11-04 +================== + + * Brand new SeleniumDriver (thanks @alexandresalome) + * Multiselect support (multiple options selection), including new Behat steps + * Ability to select option by it's text (in addition to value) + * ZombieDriver updates + * Use SuiteHooks to populate parameters (no need to call parent __construct anymore) + * Updated Goutte and all vendors + * Lot of bugfixes and new tests + +1.1.1 / 2011-08-12 +================== + + * Fixed Zombie.js server termination on Linux + * Fixed base_url usage for external URLs + +1.1.0 / 2011-08-08 +================== + + * Added Zombie.js driver (thanks @b00giZm) + * Added pt translation (thanks Daniel Gomes) + * Refactored MinkContext and MinkTestCase + +1.0.3 / 2011-08-02 +================== + + * File uploads for empty fields fixed (GoutteDriver) + * Lazy sessions restart + * `show_tmp_dir` option in MinkContext + * Updated to stable Symfony2 components + * SahiClient connection limit bumped to 60 seconds + * Dutch language support + +1.0.2 / 2011-07-22 +================== + + * ElementHtmlException fixed (thanks @Stof) + +1.0.1 / 2011-07-21 +================== + + * Fixed buggy assertions in MinkContext + +1.0.0 / 2011-07-20 +================== + + * Added missing tests for almost everything + * Hude speedup for SahiDriver + * Support for Behat 2.0 contexts + * Bundled PHPUnit TestCase + * Deep element traversing + * Correct behavior of getText() method + * New getHtml() method + * Basic HTTP auth support + * Soft and hard session resetting + * Cookies management + * Browser history interactions (reload(), back(), forward()) + * Weaverryan'd exception messages + * Huge amount of bugfixes and small additions + +0.3.2 / 2011-06-20 +================== + + * Fixed file uploads in Goutte driver + * Fixed setting of long texts into fields + * Added getPlainText() (returns text without tags and whitespaces) method to the element's API + * Start_url is now optional parameter + * Default session (if needed) name now need to be always specified by hands with setDefaultSessionName() + * default_driver => default_session + * Updated Symfony Components + +0.3.1 / 2011-05-17 +================== + + * Small SahiClient update (it generates SID now if no provided) + * setActiveSessionName => setDefaultSessionName method rename + +0.3.0 / 2011-05-17 +================== + + * Rewritten from scratch Mink drivers handler. Now it's sessions handler. And Mink now + sessions-centric tool. See examples in readme. Much cleaner API now. + +0.2.4 / 2011-05-12 +================== + + * Fixed wrong url locator function + * Fixed wrong regex in `should see` step + * Fixed delimiters use in `should see` step + * Added url-match step for checking urls against regex + +0.2.3 / 2011-05-01 +================== + + * Updated SahiClient with new version, which is faster and cleaner with it's exceptions + +0.2.2 / 2011-05-01 +================== + + * Ability to use already started browser as SahiDriver aim + * Added japanese translation for bundled steps (thanks @hidenorigoto) + * 10 seconds limit for browser connection in SahiDriver + +0.2.1 / 2011-04-21 +================== + + * Fixed some bundled step definitions + +0.2.0 / 2011-04-21 +================== + + * Additional step definitions + * Support for extended drivers configuration through behat.yml environment parameters + * Lots of new named selectors + * Bug fixes + * Small improvements + +0.1.2 / 2011-04-08 +================== + + * Fixed Sahi url escaping + +0.1.1 / 2011-04-06 +================== + + * Fixed should/should_not steps + * Added spanish translation + * Fixed forms to use element + * Fixed small UnsupportedByDriverException issue + +0.1.0 / 2011-04-04 +================== + + * Initial release diff --git a/core/vendor/behat/mink/CONTRIBUTING.md b/core/vendor/behat/mink/CONTRIBUTING.md new file mode 100644 index 0000000..f3f6119 --- /dev/null +++ b/core/vendor/behat/mink/CONTRIBUTING.md @@ -0,0 +1,28 @@ +Contributing +------------ + +Mink is an open source, community-driven project. If you'd like to contribute, feel free to do this, but remember to follow this few simple rules: + +- Make your feature addition or bug fix, +- __Always__ as base for your changes use `develop` branch (all new development happens here, `master` branch is for releases & hotfixes only), +- Add tests for those changes (please look into `tests/` folder for some examples). This is important so we don't break it in a future version unintentionally, +- Commit your code, but do not mess with `CHANGES.md`, +- __Remember__: when you create Pull Request, always select `develop` branch as target, otherwise it will be closed. + +Running tests +------------- + +Make sure that you don't break anything with your changes by running: + +```bash +$> phpunit +``` + +Behat integration and translated languages +------------------------------------------ + +Behat integration altogether with translations have moved into separate +project called `MinkExtension`. It's an extension to Behat 2.4. This will +lead to much faster release cycles as `MinkExtension` doesn't have actual +releases - any accepted PR about language translation or new step definitions +will immediately go into live. diff --git a/core/vendor/behat/mink/LICENSE b/core/vendor/behat/mink/LICENSE new file mode 100644 index 0000000..14f15e8 --- /dev/null +++ b/core/vendor/behat/mink/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2011-2013 Konstantin Kudryashov + +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/core/vendor/behat/mink/README.md b/core/vendor/behat/mink/README.md new file mode 100644 index 0000000..909dd7d --- /dev/null +++ b/core/vendor/behat/mink/README.md @@ -0,0 +1,67 @@ +Mink +==== + +- [stable (master)](https://github.com/Behat/Mink) ([![Master Build +Status](https://secure.travis-ci.org/Behat/Mink.png?branch=master)](http://travis-ci.org/Behat/Mink)) - staging branch. Last stable version. +- [development (develop)](https://github.com/Behat/Mink/tree/develop) ([![Develop Build Status](https://secure.travis-ci.org/Behat/Mink.png?branch=develop)](http://travis-ci.org/Behat/Mink)) - development branch. Development happens here and you should send your PRs here too. + +Useful Links +------------ + +- The main website with documentation is at [http://mink.behat.org](http://mink.behat.org) +- Official Google Group is at [http://groups.google.com/group/behat](http://groups.google.com/group/behat) +- IRC channel on [#freenode](http://freenode.net/) is `#behat` +- [Note on Patches/Pull Requests](CONTRIBUTING.md) + +Usage Example +------------- + +``` php + new Session(new GoutteDriver(new GoutteClient())), + 'goutte2' => new Session(new GoutteDriver(new GoutteClient())), + 'custom' => new Session(new MyCustomDriver($startUrl)) +)); + +// set the default session name +$mink->setDefaultSessionName('goutte2'); + +// visit a page +$mink->visit($startUrl); + +// call to getSession() without argument will always return a default session if has one (goutte2 here) +$mink->getSession()->getPage()->findLink('Downloads')->click(); +echo $mink->getSession()->getPage()->getContent(); + +// call to getSession() with argument will return session by its name +$mink->getSession('custom')->getPage()->findLink('Downloads')->click(); +echo $mink->getSession('custom')->getPage()->getContent(); + +// this all is done to make possible mixing sessions +$mink->getSession('goutte1')->getPage()->findLink('Chat')->click(); +$mink->getSession('goutte2')->getPage()->findLink('Chat')->click(); +``` + +Install Dependencies +-------------------- + +``` bash +$> curl http://getcomposer.org/installer | php +$> php composer.phar install +``` + +Contributors +------------ + +* Konstantin Kudryashov [everzet](http://github.com/everzet) [lead developer] +* Other [awesome developers](https://github.com/Behat/Mink/graphs/contributors) diff --git a/core/vendor/behat/mink/behat.yml b/core/vendor/behat/mink/behat.yml new file mode 100644 index 0000000..2c6287b --- /dev/null +++ b/core/vendor/behat/mink/behat.yml @@ -0,0 +1,6 @@ +default: + context: + parameters: + javascript_session: selenium + base_url: http://test.mink.dev/ + show_cmd: open %s diff --git a/core/vendor/behat/mink/bin/release b/core/vendor/behat/mink/bin/release new file mode 100755 index 0000000..cb309cf --- /dev/null +++ b/core/vendor/behat/mink/bin/release @@ -0,0 +1,52 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Release script. + * + * Usage: bin/release 0.2.0 beta + * + * @author Konstantin Kudryashov + */ + +use Behat\Mink\Compiler; + +if (!isset($argv[1])) { + throw new RuntimeException('You must provide version.'); +} +$version = $argv[1]; + +if (!isset($argv[2])) { + throw new RuntimeException('You must provide stability status (alpha/beta/stable).'); +} +$stability = $argv[2]; + +system('rm -rf vendor composer.lock'); +system('cp composer.json composer.json.back'); +system('composer require --no-update "behat/mink-goutte-driver=@dev"'); +system('composer require --no-update "behat/mink-sahi-driver=@dev"'); +system('composer require --no-update "behat/mink-selenium-driver=@dev"'); +system('composer require --no-update "behat/mink-selenium2-driver=@dev"'); +system('composer require --no-update "behat/mink-zombie-driver=@dev"'); +system('composer install --prefer-dist'); + +require_once __DIR__ . '/../vendor/autoload.php'; + +system('rm *.phar'); +$phar = new Compiler\PharCompiler(); +$phar->compile($version); +system("cp mink-$version.phar mink.phar"); +echo "PHAR package compiled: mink-$version.phar\n"; + +system('mv composer.json.back composer.json'); +system('rm -rf vendor composer.lock'); +system('composer install --dev --prefer-dist'); +exit(0); diff --git a/core/vendor/behat/mink/composer.json b/core/vendor/behat/mink/composer.json new file mode 100644 index 0000000..200c0a9 --- /dev/null +++ b/core/vendor/behat/mink/composer.json @@ -0,0 +1,40 @@ +{ + "name": "behat/mink", + "description": "Web acceptance testing framework for PHP 5.3", + "keywords": ["web", "testing", "browser"], + "homepage": "http://mink.behat.org/", + "type": "library", + "license": "MIT", + + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + + "require": { + "php": ">=5.3.1", + "symfony/css-selector": "~2.0" + }, + + "suggest": { + "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", + "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", + "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", + "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)" + }, + + "autoload": { + "psr-0": { + "Behat\\Mink": "src/" + } + }, + + "extra": { + "branch-alias": { + "dev-develop": "1.5.x-dev" + } + } +} diff --git a/core/vendor/behat/mink/phpdoc.ini.dist b/core/vendor/behat/mink/phpdoc.ini.dist new file mode 100644 index 0000000..3fef75d --- /dev/null +++ b/core/vendor/behat/mink/phpdoc.ini.dist @@ -0,0 +1,125 @@ +; Default configuration file for PHPDoctor + +; This config file will cause PHPDoctor to generate API documentation of +; itself. + + +; PHPDoctor settings +; ----------------------------------------------------------------------------- + +; Names of files to parse. This can be a single filename, or a comma separated +; list of filenames. Wildcards are allowed. + +files = "*.php" + +; Names of files or directories to ignore. This can be a single filename, or a +; comma separated list of filenames. Wildcards are NOT allowed. + +ignore = "CVS, .svn, .git, _compiled" + +; The directory to look for files in, if not used the PHPDoctor will look in +; the current directory (the directory it is run from). + +source_path = "./src" + +; If you do not want PHPDoctor to look in each sub directory for files +; uncomment this line. + +;subdirs = off + +; Set how loud PHPDoctor is as it runs. Quiet mode suppresses all output other +; than warnings and errors. Verbose mode outputs additional messages during +; execution. + +;quiet = on +;verbose = on + +; Select the doclet to use for generating output. + +doclet = standard +;doclet = debug + +; The directory to find the doclet in. Doclets are expected to be in a +; directory named after themselves at the location given. + +;doclet_path = ./doclets + +; The directory to find taglets in. Taglets allow you to make PHPDoctor handle +; new tags and to alter the behavour of existing tags and their output. + +;taglet_path = ./taglets + +; If the code you are parsing does not use package tags or not all elements +; have package tags, use this setting to place unbound elements into a +; particular package. + +default_package = "Behat\Mink" + +; Specifies the name of a HTML file containing text for the overview +; documentation to be placed on the overview page. The path is relative to +; "source_path" unless an absolute path is given. + +overview = readme.html + +; Package comments will be looked for in a file named package.html in the same +; directory as the first source file parsed in that package or in the directory +; given below. If package comments are placed in the directory given below then +; they should be named ".html". + +package_comment_dir = ./ + +; Parse out global variables and/or global constants? + +;globals = off +;constants = off + +; Generate documentation for all class members + +;private = on + +; Generate documentation for public and protected class members + +;protected = on + +; Generate documentation for only public class members + +public = on + +; Use the PEAR compatible handling of the docblock first sentence + +;pear_compat = on + +; Standard doclet settings +; ----------------------------------------------------------------------------- + +; The directory to place generated documentation in. If the given path is +; relative to it will be relative to "source_path". + +d = "api" + +; Specifies the title to be placed in the HTML tag. + +windowtitle = "Behat\Mink" + +; Specifies the title to be placed near the top of the overview summary file. + +doctitle = "Behat\Mink: browser emulators abstraction library for PHP" + +; Specifies the header text to be placed at the top of each output file. The +; header will be placed to the right of the upper navigation bar. + +header = "Behat\Mink" + +; Specifies the footer text to be placed at the bottom of each output file. The +; footer will be placed to the right of the lower navigation bar. + +footer = "Behat\Mink" + +; Specifies the text to be placed at the bottom of each output file. The text +; will be placed at the bottom of the page, below the lower navigation bar. + +;bottom = "This document was generated by <a href="http://phpdoctor.sourceforge.net/">PHPDoctor: The PHP Documentation Creator</a>" + +; Create a class tree? + +tree = on diff --git a/core/vendor/behat/mink/phpunit.xml.dist b/core/vendor/behat/mink/phpunit.xml.dist new file mode 100644 index 0000000..fb1440b --- /dev/null +++ b/core/vendor/behat/mink/phpunit.xml.dist @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<phpunit backupGlobals="false" + backupStaticAttributes="false" + colors="true" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + processIsolation="false" + stopOnFailure="false" + syntaxCheck="false" + bootstrap="vendor/autoload.php" +> + <testsuites> + <testsuite name="Behat Mink test suite"> + <directory>./tests/Behat/Mink/</directory> + </testsuite> + </testsuites> + + <filter> + <whitelist> + <directory>./src/Behat/Mink/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/core/vendor/behat/mink/src/Behat/Mink/ClassLoader/MapFileClassLoader.php b/core/vendor/behat/mink/src/Behat/Mink/ClassLoader/MapFileClassLoader.php new file mode 100644 index 0000000..d71a31d --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/ClassLoader/MapFileClassLoader.php @@ -0,0 +1,76 @@ +<?php + +namespace Behat\Mink\ClassLoader; + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * A class loader that uses a mapping file to look up paths. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class MapFileClassLoader +{ + private $map = array(); + + /** + * Constructor. + * + * @param string $file Path to class mapping file + */ + public function __construct($file) + { + $this->map = require $file; + } + + /** + * Registers this instance as an autoloader. + * + * @param Boolean $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 ('\\' === $class[0]) { + $class = substr($class, 1); + } + + 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 ('\\' === $class[0]) { + $class = substr($class, 1); + } + + if (isset($this->map[$class])) { + return $this->map[$class]; + } + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Compiler/PharCompiler.php b/core/vendor/behat/mink/src/Behat/Mink/Compiler/PharCompiler.php new file mode 100644 index 0000000..07a0590 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Compiler/PharCompiler.php @@ -0,0 +1,138 @@ +<?php + +namespace Behat\Mink\Compiler; + +use Symfony\Component\Finder\Finder; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * behat.phar package compiler. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class PharCompiler +{ + /** + * Behat lib directory. + * + * @var string + */ + private $libPath; + + /** + * Initializes compiler. + */ + public function __construct() + { + $this->libPath = realpath(__DIR__ . '/../../../../'); + } + + /** + * Compiles phar archive. + * + * @param string $version + */ + public function compile($version) + { + if (file_exists($package = "mink-$version.phar")) { + unlink($package); + } + + // create phar + $phar = new \Phar($package, 0, 'mink.phar'); + $phar->setSignatureAlgorithm(\Phar::SHA1); + $phar->startBuffering(); + + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->name('*.php') + ->name('*.xliff') + ->name('*.xml') + ->name('*.js') + ->name('*.feature') + ->name('LICENSE') + ->name('LICENSE.txt') + ->notName('test') + ->notName('tests') + ->exclude(array( + 'Compiler', + 'finder', + 'test', + 'tests', + 'vendor', + )) + ->in($this->libPath . '/src') + ->in($this->libPath . '/vendor/symfony') + ->in($this->libPath . '/vendor/composer') + ->in($this->libPath . '/vendor/alexandresalome') + ->in($this->libPath . '/vendor/behat') + ->in($this->libPath . '/vendor/fabpot') + ->in($this->libPath . '/vendor/kriswallsmith') + ->in($this->libPath . '/vendor/guzzle') + ->in($this->libPath . '/vendor/instaclick') + ; + + foreach ($finder as $file) { + if (!$file instanceof \SplFileInfo) { + $file = new \SplFileInfo($file); + } + + $this->addFileToPhar($file, $phar); + } + $this->addFileToPhar(new \SplFileInfo('vendor/autoload.php'), $phar); + + // stub + $phar->setStub($this->getStub($version)); + $phar->stopBuffering(); + + unset($phar); + } + + /** + * Adds a file to phar archive. + * + * @param SplFileInfo $file file info + * @param Phar $phar phar packager + */ + protected function addFileToPhar(\SplFileInfo $file, \Phar $phar) + { + $path = str_replace($this->libPath . '/', '', $file->getRealPath()); + $phar->addFromString($path, file_get_contents($file)); + } + + /** + * Returns autoloader stub. + * + * @param string $version + * + * @return string + */ + protected function getStub($version) + { + return sprintf(<<<'EOF' +<?php + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +Phar::mapPhar('mink.phar'); +require_once 'phar://mink.phar/vendor/autoload.php'; + +__HALT_COMPILER(); +EOF + , $version); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Driver/CoreDriver.php b/core/vendor/behat/mink/src/Behat/Mink/Driver/CoreDriver.php new file mode 100644 index 0000000..35c55d2 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Driver/CoreDriver.php @@ -0,0 +1,306 @@ +<?php + +/* + * This file is part of the Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Behat\Mink\Driver; + +use Behat\Mink\Exception\UnsupportedDriverActionException; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Core driver. + * All other drivers should extend this class for future compatibility. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +abstract class CoreDriver implements DriverInterface +{ + /** + * Reloads current page. + */ + public function reload() + { + throw new UnsupportedDriverActionException('Page reloading is not supported by %s', $this); + } + + /** + * Moves browser forward 1 page. + */ + public function forward() + { + throw new UnsupportedDriverActionException('Forward action is not supported by %s', $this); + } + + /** + * Moves browser backward 1 page. + */ + public function back() + { + throw new UnsupportedDriverActionException('Backward action is not supported by %s', $this); + } + + /** + * Sets HTTP Basic authentication parameters + * + * @param string|Boolean $user user name or false to disable authentication + * @param string $password password + */ + public function setBasicAuth($user, $password) + { + throw new UnsupportedDriverActionException('Basic auth setup is not supported by %s', $this); + } + + /** + * Switches to specific browser window. + * + * @param string $name window name (null for switching back to main window) + */ + public function switchToWindow($name = null) + { + throw new UnsupportedDriverActionException('Windows management is not supported by %s', $this); + } + + /** + * Switches to specific iFrame. + * + * @param string $name iframe name (null for switching back) + */ + public function switchToIFrame($name = null) + { + throw new UnsupportedDriverActionException('iFrames management is not supported by %s', $this); + } + + /** + * Sets specific request header on client. + * + * @param string $name + * @param string $value + */ + public function setRequestHeader($name, $value) + { + throw new UnsupportedDriverActionException('Request headers manipulation is not supported by %s', $this); + } + + /** + * Returns last response headers. + * + * @return array + */ + public function getResponseHeaders() + { + throw new UnsupportedDriverActionException('Response headers are not available from %s', $this); + } + + /** + * Sets cookie. + * + * @param string $name + * @param string $value + */ + public function setCookie($name, $value = null) + { + throw new UnsupportedDriverActionException('Cookies manipulation is not supported by %s', $this); + } + + /** + * Returns cookie by name. + * + * @param string $name + * + * @return string|null + */ + public function getCookie($name) + { + throw new UnsupportedDriverActionException('Cookies are not available from %s', $this); + } + + /** + * Returns last response status code. + * + * @return integer + */ + public function getStatusCode() + { + throw new UnsupportedDriverActionException('Status code is not available from %s', $this); + } + + /** + * Capture a screenshot of the current window. + * + * @return string screenshot of MIME type image/* depending + * on driver (e.g., image/png, image/jpeg) + */ + public function getScreenshot() + { + throw new UnsupportedDriverActionException('Screenshots are not supported by %s', $this); + } + + /** + * Double-clicks button or link located by it's XPath query. + * + * @param string $xpath + */ + public function doubleClick($xpath) + { + throw new UnsupportedDriverActionException('Double-clicking is not supported by %s', $this); + } + + /** + * Right-clicks button or link located by it's XPath query. + * + * @param string $xpath + */ + public function rightClick($xpath) + { + throw new UnsupportedDriverActionException('Right-clicking is not supported by %s', $this); + } + + /** + * Checks whether element visible located by it's XPath query. + * + * @param string $xpath + * + * @return Boolean + */ + public function isVisible($xpath) + { + throw new UnsupportedDriverActionException('Element visibility check is not supported by %s', $this); + } + + /** + * Simulates a mouse over on the element. + * + * @param string $xpath + */ + public function mouseOver($xpath) + { + throw new UnsupportedDriverActionException('Mouse manipulations are not supported by %s', $this); + } + + /** + * Brings focus to element. + * + * @param string $xpath + */ + public function focus($xpath) + { + throw new UnsupportedDriverActionException('Mouse manipulations are not supported by %s', $this); + } + + /** + * Removes focus from element. + * + * @param string $xpath + */ + public function blur($xpath) + { + throw new UnsupportedDriverActionException('Mouse manipulations are not supported by %s', $this); + } + + /** + * Presses specific keyboard key. + * + * @param string $xpath + * @param mixed $char could be either char ('b') or char-code (98) + * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') + */ + public function keyPress($xpath, $char, $modifier = null) + { + throw new UnsupportedDriverActionException('Keyboard manipulations are not supported by %s', $this); + } + + /** + * Pressed down specific keyboard key. + * + * @param string $xpath + * @param mixed $char could be either char ('b') or char-code (98) + * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') + */ + public function keyDown($xpath, $char, $modifier = null) + { + throw new UnsupportedDriverActionException('Keyboard manipulations are not supported by %s', $this); + } + + /** + * Pressed up specific keyboard key. + * + * @param string $xpath + * @param mixed $char could be either char ('b') or char-code (98) + * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') + */ + public function keyUp($xpath, $char, $modifier = null) + { + throw new UnsupportedDriverActionException('Keyboard manipulations are not supported by %s', $this); + } + + /** + * Drag one element onto another. + * + * @param string $sourceXpath + * @param string $destinationXpath + */ + public function dragTo($sourceXpath, $destinationXpath) + { + throw new UnsupportedDriverActionException('Mouse manipulations are not supported by %s', $this); + } + + /** + * Executes JS script. + * + * @param string $script + */ + public function executeScript($script) + { + throw new UnsupportedDriverActionException('JS is not supported by %s', $this); + } + + /** + * Evaluates JS script. + * + * @param string $script + * + * @return mixed + */ + public function evaluateScript($script) + { + throw new UnsupportedDriverActionException('JS is not supported by %s', $this); + } + + /** + * Waits some time or until JS condition turns true. + * + * @param integer $time time in milliseconds + * @param string $condition JS condition + * + * @throws UnsupportedDriverActionException + */ + public function wait($time, $condition) + { + throw new UnsupportedDriverActionException('JS is not supported by %s', $this); + } + + /** + * Set the dimensions of the window. + * + * @param integer $width set the window width, measured in pixels + * @param integer $height set the window height, measured in pixels + * @param string $name window name (null for the main window) + */ + public function resizeWindow($width, $height, $name = null) + { + throw new UnsupportedDriverActionException('Window resizing is not supported by %s', $this); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Driver/DriverInterface.php b/core/vendor/behat/mink/src/Behat/Mink/Driver/DriverInterface.php new file mode 100644 index 0000000..42c51a8 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Driver/DriverInterface.php @@ -0,0 +1,377 @@ +<?php + +namespace Behat\Mink\Driver; + +use Behat\Mink\Session; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Driver interface. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +interface DriverInterface +{ + /** + * Sets driver's current session. + * + * @param Session $session + */ + public function setSession(Session $session); + + /** + * Starts driver. + */ + public function start(); + + /** + * Checks whether driver is started. + * + * @return Boolean + */ + public function isStarted(); + + /** + * Stops driver. + */ + public function stop(); + + /** + * Resets driver. + */ + public function reset(); + + /** + * Visit specified URL. + * + * @param string $url url of the page + */ + public function visit($url); + + /** + * Returns current URL address. + * + * @return string + */ + public function getCurrentUrl(); + + /** + * Reloads current page. + */ + public function reload(); + + /** + * Moves browser forward 1 page. + */ + public function forward(); + + /** + * Moves browser backward 1 page. + */ + public function back(); + + /** + * Sets HTTP Basic authentication parameters + * + * @param string|Boolean $user user name or false to disable authentication + * @param string $password password + */ + public function setBasicAuth($user, $password); + + /** + * Switches to specific browser window. + * + * @param string $name window name (null for switching back to main window) + */ + public function switchToWindow($name = null); + + /** + * Switches to specific iFrame. + * + * @param string $name iframe name (null for switching back) + */ + public function switchToIFrame($name = null); + + /** + * Sets specific request header on client. + * + * @param string $name + * @param string $value + */ + public function setRequestHeader($name, $value); + + /** + * Returns last response headers. + * + * @return array + */ + public function getResponseHeaders(); + + /** + * Sets cookie. + * + * @param string $name + * @param string $value + */ + public function setCookie($name, $value = null); + + /** + * Returns cookie by name. + * + * @param string $name + * + * @return string|null + */ + public function getCookie($name); + + /** + * Returns last response status code. + * + * @return integer + */ + public function getStatusCode(); + + /** + * Returns last response content. + * + * @return string + */ + public function getContent(); + + /** + * Capture a screenshot of the current window. + * + * @return string screenshot of MIME type image/* depending + * on driver (e.g., image/png, image/jpeg) + */ + public function getScreenshot(); + + /** + * Finds elements with specified XPath query. + * + * @param string $xpath + * + * @return array array of NodeElements + */ + public function find($xpath); + + /** + * Returns element's tag name by it's XPath query. + * + * @param string $xpath + * + * @return string + */ + public function getTagName($xpath); + + /** + * Returns element's text by it's XPath query. + * + * @param string $xpath + * + * @return string + */ + public function getText($xpath); + + /** + * Returns element's html by it's XPath query. + * + * @param string $xpath + * + * @return string + */ + public function getHtml($xpath); + + /** + * Returns element's attribute by it's XPath query. + * + * @param string $xpath + * @param string $name + * + * @return mixed + */ + public function getAttribute($xpath, $name); + + /** + * Returns element's value by it's XPath query. + * + * @param string $xpath + * + * @return mixed + */ + public function getValue($xpath); + + /** + * Sets element's value by it's XPath query. + * + * @param string $xpath + * @param string $value + */ + public function setValue($xpath, $value); + + /** + * Checks checkbox by it's XPath query. + * + * @param string $xpath + */ + public function check($xpath); + + /** + * Unchecks checkbox by it's XPath query. + * + * @param string $xpath + */ + public function uncheck($xpath); + + /** + * Checks whether checkbox checked located by it's XPath query. + * + * @param string $xpath + * + * @return Boolean + */ + public function isChecked($xpath); + + /** + * Selects option from select field located by it's XPath query. + * + * @param string $xpath + * @param string $value + * @param Boolean $multiple + */ + public function selectOption($xpath, $value, $multiple = false); + + /** + * Clicks button or link located by it's XPath query. + * + * @param string $xpath + */ + public function click($xpath); + + /** + * Double-clicks button or link located by it's XPath query. + * + * @param string $xpath + */ + public function doubleClick($xpath); + + /** + * Right-clicks button or link located by it's XPath query. + * + * @param string $xpath + */ + public function rightClick($xpath); + + /** + * Attaches file path to file field located by it's XPath query. + * + * @param string $xpath + * @param string $path + */ + public function attachFile($xpath, $path); + + /** + * Checks whether element visible located by it's XPath query. + * + * @param string $xpath + * + * @return Boolean + */ + public function isVisible($xpath); + + /** + * Simulates a mouse over on the element. + * + * @param string $xpath + */ + public function mouseOver($xpath); + + /** + * Brings focus to element. + * + * @param string $xpath + */ + public function focus($xpath); + + /** + * Removes focus from element. + * + * @param string $xpath + */ + public function blur($xpath); + + /** + * Presses specific keyboard key. + * + * @param string $xpath + * @param mixed $char could be either char ('b') or char-code (98) + * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') + */ + public function keyPress($xpath, $char, $modifier = null); + + /** + * Pressed down specific keyboard key. + * + * @param string $xpath + * @param mixed $char could be either char ('b') or char-code (98) + * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') + */ + public function keyDown($xpath, $char, $modifier = null); + + /** + * Pressed up specific keyboard key. + * + * @param string $xpath + * @param mixed $char could be either char ('b') or char-code (98) + * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') + */ + public function keyUp($xpath, $char, $modifier = null); + + /** + * Drag one element onto another. + * + * @param string $sourceXpath + * @param string $destinationXpath + */ + public function dragTo($sourceXpath, $destinationXpath); + + /** + * Executes JS script. + * + * @param string $script + */ + public function executeScript($script); + + /** + * Evaluates JS script. + * + * @param string $script + * + * @return mixed + */ + public function evaluateScript($script); + + /** + * Waits some time or until JS condition turns true. + * + * @param integer $time time in milliseconds + * @param string $condition JS condition + */ + public function wait($time, $condition); + + /** + * Set the dimensions of the window. + * + * @param integer $width set the window width, measured in pixels + * @param integer $height set the window height, measured in pixels + * @param string $name window name (null for the main window) + */ + public function resizeWindow($width, $height, $name = null); +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Element/DocumentElement.php b/core/vendor/behat/mink/src/Behat/Mink/Element/DocumentElement.php new file mode 100644 index 0000000..c618725 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Element/DocumentElement.php @@ -0,0 +1,53 @@ +<?php + +namespace Behat\Mink\Element; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Document element. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class DocumentElement extends TraversableElement +{ + /** + * Returns XPath for handled element. + * + * @return string + */ + public function getXpath() + { + return '//html'; + } + + /** + * Returns document content. + * + * @return string + */ + public function getContent() + { + return trim($this->getSession()->getDriver()->getContent()); + } + + /** + * Check whether document has specified content. + * + * @param string $content + * + * @return Boolean + */ + public function hasContent($content) + { + return $this->has('named', array( + 'content', $this->getSession()->getSelectorsHandler()->xpathLiteral($content) + )); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Element/Element.php b/core/vendor/behat/mink/src/Behat/Mink/Element/Element.php new file mode 100644 index 0000000..e3dd87c --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Element/Element.php @@ -0,0 +1,113 @@ +<?php + +namespace Behat\Mink\Element; + +use Behat\Mink\Session; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Base element. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +abstract class Element implements ElementInterface +{ + private $session; + + /** + * Initialize element. + * + * @param Session $session + */ + public function __construct(Session $session) + { + $this->session = $session; + } + + /** + * Returns element session. + * + * @return Session + */ + public function getSession() + { + return $this->session; + } + + /** + * Checks whether element with specified selector exists. + * + * @param string $selector selector engine name + * @param string $locator selector locator + * + * @return Boolean + */ + public function has($selector, $locator) + { + return null !== $this->find($selector, $locator); + } + + /** + * Finds first element with specified selector. + * + * @param string $selector selector engine name + * @param string $locator selector locator + * + * @return NodeElement|null + */ + public function find($selector, $locator) + { + $items = $this->findAll($selector, $locator); + + return count($items) ? current($items) : null; + } + + /** + * Finds all elements with specified selector. + * + * @param string $selector selector engine name + * @param string $locator selector locator + * + * @return array + */ + public function findAll($selector, $locator) + { + $xpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($selector, $locator); + + // add parent xpath before element selector + if (0 === strpos($xpath, '/')) { + $xpath = $this->getXpath().$xpath; + } else { + $xpath = $this->getXpath().'/'.$xpath; + } + + return $this->getSession()->getDriver()->find($xpath); + } + + /** + * Returns element text (inside tag). + * + * @return string|null + */ + public function getText() + { + return $this->getSession()->getDriver()->getText($this->getXpath()); + } + + /** + * Returns element html. + * + * @return string|null + */ + public function getHtml() + { + return $this->getSession()->getDriver()->getHtml($this->getXpath()); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Element/ElementInterface.php b/core/vendor/behat/mink/src/Behat/Mink/Element/ElementInterface.php new file mode 100644 index 0000000..987f0d0 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Element/ElementInterface.php @@ -0,0 +1,79 @@ +<?php + +namespace Behat\Mink\Element; + +use Behat\Mink\Session; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Element interface. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +interface ElementInterface +{ + /** + * Returns XPath for handled element. + * + * @return string + */ + public function getXpath(); + + /** + * Returns element's session. + * + * @return Session + */ + public function getSession(); + + /** + * Checks whether element with specified selector exists. + * + * @param string $selector selector engine name + * @param string $locator selector locator + * + * @return Boolean + */ + public function has($selector, $locator); + + /** + * Finds first element with specified selector. + * + * @param string $selector selector engine name + * @param string $locator selector locator + * + * @return NodeElement|null + */ + public function find($selector, $locator); + + /** + * Finds all elements with specified selector. + * + * @param string $selector selector engine name + * @param string $locator selector locator + * + * @return array + */ + public function findAll($selector, $locator); + + /** + * Returns element text (inside tag). + * + * @return string|null + */ + public function getText(); + + /** + * Returns element html. + * + * @return string|null + */ + public function getHtml(); +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Element/NodeElement.php b/core/vendor/behat/mink/src/Behat/Mink/Element/NodeElement.php new file mode 100644 index 0000000..c12c410 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Element/NodeElement.php @@ -0,0 +1,318 @@ +<?php + +namespace Behat\Mink\Element; + +use Behat\Mink\Session, + Behat\Mink\Driver\DriverInterface, + Behat\Mink\Element\ElementInterface, + Behat\Mink\Exception\ElementException, + Behat\Mink\Exception\ElementNotFoundException; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Page element node. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class NodeElement extends TraversableElement +{ + private $xpath; + + /** + * Initializes node element. + * + * @param string $xpath element xpath + * @param Session $session session instance + */ + public function __construct($xpath, Session $session) + { + $this->xpath = $xpath; + + parent::__construct($session); + } + + /** + * Returns XPath for handled element. + * + * @return string + */ + public function getXpath() + { + return $this->xpath; + } + + /** + * Returns parent element to the current one. + * + * @return NodeElement + */ + public function getParent() + { + return $this->find('xpath', '..'); + } + + /** + * Returns current node tag name. + * + * @return string + */ + public function getTagName() + { + return $this->getSession()->getDriver()->getTagName($this->getXpath()); + } + + /** + * Returns element value. + * + * @return mixed + */ + public function getValue() + { + return $this->getSession()->getDriver()->getValue($this->getXpath()); + } + + /** + * Sets node value. + * + * @param string $value + */ + public function setValue($value) + { + try { + $this->getSession()->getDriver()->setValue($this->getXpath(), $value); + } catch (\Exception $exception) { + throw new ElementException($this, $exception); + } + } + + /** + * Checks whether element has attribute with specified name. + * + * @param string $name + * + * @return Boolean + */ + public function hasAttribute($name) + { + return null !== $this->getSession()->getDriver()->getAttribute($this->getXpath(), $name); + } + + /** + * Returns specified attribute value. + * + * @param string $name + * + * @return mixed|null + */ + public function getAttribute($name) + { + return $this->getSession()->getDriver()->getAttribute($this->getXpath(), $name); + } + + /** + * Clicks current node. + */ + public function click() + { + try { + $this->getSession()->getDriver()->click($this->getXpath()); + } catch (\Exception $exception) { + throw new ElementException($this, $exception); + } + } + + /** + * Presses current button. + */ + public function press() + { + $this->click(); + } + + /** + * Double-clicks current node. + */ + public function doubleClick() + { + try { + $this->getSession()->getDriver()->doubleClick($this->getXpath()); + } catch (\Exception $exception) { + throw new ElementException($this, $exception); + } + } + + /** + * Right-clicks current node. + */ + public function rightClick() + { + try { + $this->getSession()->getDriver()->rightClick($this->getXpath()); + } catch (\Exception $exception) { + throw new ElementException($this, $exception); + } + } + + /** + * Checks current node if it's a checkbox field. + */ + public function check() + { + try { + $this->getSession()->getDriver()->check($this->getXpath()); + } catch (\Exception $exception) { + throw new ElementException($this, $exception); + } + } + + /** + * Unchecks current node if it's a checkbox field. + */ + public function uncheck() + { + try { + $this->getSession()->getDriver()->uncheck($this->getXpath()); + } catch (\Exception $exception) { + throw new ElementException($this, $exception); + } + } + + /** + * Checks whether current node is checked if it's a checkbox field. + * + * @return Boolean + */ + public function isChecked() + { + return (Boolean) $this->getSession()->getDriver()->isChecked($this->getXpath()); + } + + /** + * Selects current node specified option if it's a select field. + * + * @param string $option + * @param Boolean $multiple + * + * @throws ElementNotFoundException + */ + public function selectOption($option, $multiple = false) + { + if ('select' !== $this->getTagName()) { + $this->getSession()->getDriver()->selectOption($this->getXpath(), $option, $multiple); + + return; + } + + $opt = $this->find('named', array( + 'option', $this->getSession()->getSelectorsHandler()->xpathLiteral($option) + )); + + if (null === $opt) { + throw new ElementNotFoundException( + $this->getSession(), 'select option', 'value|text', $option + ); + } + + $this->getSession()->getDriver()->selectOption( + $this->getXpath(), $opt->getValue(), $multiple + ); + } + + /** + * Attach file to current node if it's a file input. + * + * @param string $path path to file (local) + */ + public function attachFile($path) + { + try { + $this->getSession()->getDriver()->attachFile($this->getXpath(), $path); + } catch (\Exception $exception) { + throw new ElementException($this, $exception); + } + } + + /** + * Checks whether current node is visible on page. + * + * @return Boolean + */ + public function isVisible() + { + return (Boolean) $this->getSession()->getDriver()->isVisible($this->getXpath()); + } + + /** + * Simulates a mouse over on the element. + */ + public function mouseOver() + { + $this->getSession()->getDriver()->mouseOver($this->getXpath()); + } + + /** + * Drags current node onto other node. + * + * @param ElementInterface $destination other node + */ + public function dragTo(ElementInterface $destination) + { + $this->getSession()->getDriver()->dragTo($this->getXpath(), $destination->getXpath()); + } + + /** + * Brings focus to element. + */ + public function focus() + { + $this->getSession()->getDriver()->focus($this->getXpath()); + } + + /** + * Removes focus from element. + */ + public function blur() + { + $this->getSession()->getDriver()->blur($this->getXpath()); + } + + /** + * Presses specific keyboard key. + * + * @param mixed $char could be either char ('b') or char-code (98) + * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') + */ + public function keyPress($char, $modifier = null) + { + $this->getSession()->getDriver()->keyPress($this->getXpath(), $char, $modifier); + } + + /** + * Pressed down specific keyboard key. + * + * @param mixed $char could be either char ('b') or char-code (98) + * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') + */ + public function keyDown($char, $modifier = null) + { + $this->getSession()->getDriver()->keyDown($this->getXpath(), $char, $modifier); + } + + /** + * Pressed up specific keyboard key. + * + * @param mixed $char could be either char ('b') or char-code (98) + * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') + */ + public function keyUp($char, $modifier = null) + { + $this->getSession()->getDriver()->keyUp($this->getXpath(), $char, $modifier); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Element/TraversableElement.php b/core/vendor/behat/mink/src/Behat/Mink/Element/TraversableElement.php new file mode 100644 index 0000000..175aaea --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Element/TraversableElement.php @@ -0,0 +1,329 @@ +<?php + +namespace Behat\Mink\Element; + +use Behat\Mink\Exception\ElementNotFoundException; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Traversable element. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +abstract class TraversableElement extends Element +{ + /** + * Finds element by it's id. + * + * @param string $id element id + * + * @return NodeElement|null + */ + public function findById($id) + { + $id = $this->getSession()->getSelectorsHandler()->xpathLiteral($id); + + return $this->find('xpath', "//*[@id=$id]"); + } + + /** + * Checks whether document has a link with specified locator. + * + * @param string $locator link id, title, text or image alt + * + * @return Boolean + */ + public function hasLink($locator) + { + return null !== $this->findLink($locator); + } + + /** + * Finds link with specified locator. + * + * @param string $locator link id, title, text or image alt + * + * @return NodeElement|null + */ + public function findLink($locator) + { + return $this->find('named', array( + 'link', $this->getSession()->getSelectorsHandler()->xpathLiteral($locator) + )); + } + + /** + * Clicks link with specified locator. + * + * @param string $locator link id, title, text or image alt + * + * @throws ElementNotFoundException + */ + public function clickLink($locator) + { + $link = $this->findLink($locator); + + if (null === $link) { + throw new ElementNotFoundException( + $this->getSession(), 'link', 'id|title|alt|text', $locator + ); + } + + $link->click(); + } + + /** + * Checks whether document has a button (input[type=submit|image|button], button) with specified locator. + * + * @param string $locator button id, value or alt + * + * @return Boolean + */ + public function hasButton($locator) + { + return null !== $this->findButton($locator); + } + + /** + * Checks whether an element has a named CSS class + * + * @param string $className Name of the class + * + * @return boolean + */ + public function hasClass($className) + { + if ($this->hasAttribute('class')) { + return in_array($className, explode(' ', $this->getAttribute('class'))); + } + + return false; + } + + /** + * Finds button (input[type=submit|image|button], button) with specified locator. + * + * @param string $locator button id, value or alt + * + * @return NodeElement|null + */ + public function findButton($locator) + { + return $this->find('named', array( + 'button', $this->getSession()->getSelectorsHandler()->xpathLiteral($locator) + )); + } + + /** + * Presses button (input[type=submit|image|button], button) with specified locator. + * + * @param string $locator button id, value or alt + * + * @throws ElementNotFoundException + */ + public function pressButton($locator) + { + $button = $this->findButton($locator); + + if (null === $button) { + throw new ElementNotFoundException( + $this->getSession(), 'button', 'id|name|title|alt|value', $locator + ); + } + + $button->press(); + } + + /** + * Checks whether document has a field (input, textarea, select) with specified locator. + * + * @param string $locator input id, name or label + * + * @return Boolean + */ + public function hasField($locator) + { + return null !== $this->findField($locator); + } + + /** + * Finds field (input, textarea, select) with specified locator. + * + * @param string $locator input id, name or label + * + * @return NodeElement|null + */ + public function findField($locator) + { + return $this->find('named', array( + 'field', $this->getSession()->getSelectorsHandler()->xpathLiteral($locator) + )); + } + + /** + * Fills in field (input, textarea, select) with specified locator. + * + * @param string $locator input id, name or label + * @param string $value value + * + * @throws ElementNotFoundException + */ + public function fillField($locator, $value) + { + $field = $this->findField($locator); + + if (null === $field) { + throw new ElementNotFoundException( + $this->getSession(), 'form field', 'id|name|label|value', $locator + ); + } + + $field->setValue($value); + } + + /** + * Checks whether document has a checkbox with specified locator, which is checked. + * + * @param string $locator input id, name or label + * + * @return Boolean + */ + public function hasCheckedField($locator) + { + $field = $this->findField($locator); + + return null !== $field && $field->isChecked(); + } + + /** + * Checks whether document has a checkbox with specified locator, which is unchecked. + * + * @param string $locator input id, name or label + * + * @return Boolean + */ + public function hasUncheckedField($locator) + { + $field = $this->findField($locator); + + return null !== $field && !$field->isChecked(); + } + + /** + * Checks checkbox with specified locator. + * + * @param string $locator input id, name or label + * + * @throws ElementNotFoundException + */ + public function checkField($locator) + { + $field = $this->findField($locator); + + if (null === $field) { + throw new ElementNotFoundException( + $this->getSession(), 'form field', 'id|name|label|value', $locator + ); + } + + $field->check(); + } + + /** + * Unchecks checkbox with specified locator. + * + * @param string $locator input id, name or label + * + * @throws ElementNotFoundException + */ + public function uncheckField($locator) + { + $field = $this->findField($locator); + + if (null === $field) { + throw new ElementNotFoundException( + $this->getSession(), 'form field', 'id|name|label|value', $locator + ); + } + + $field->uncheck(); + } + + /** + * Checks whether document has a select field with specified locator. + * + * @param string $locator select id, name or label + * + * @return Boolean + */ + public function hasSelect($locator) + { + return $this->has('named', array( + 'select', $this->getSession()->getSelectorsHandler()->xpathLiteral($locator) + )); + } + + /** + * Selects option from select field with specified locator. + * + * @param string $locator input id, name or label + * @param string $value option value + * @param Boolean $multiple select multiple options + * + * @throws ElementNotFoundException + */ + public function selectFieldOption($locator, $value, $multiple = false) + { + $field = $this->findField($locator); + + if (null === $field) { + throw new ElementNotFoundException( + $this->getSession(), 'form field', 'id|name|label|value', $locator + ); + } + + $field->selectOption($value, $multiple); + } + + /** + * Checks whether document has a table with specified locator. + * + * @param string $locator table id or caption + * + * @return Boolean + */ + public function hasTable($locator) + { + return $this->has('named', array( + 'table', $this->getSession()->getSelectorsHandler()->xpathLiteral($locator) + )); + } + + /** + * Attach file to file field with specified locator. + * + * @param string $locator input id, name or label + * @param string $path path to file + * + * @throws ElementNotFoundException + */ + public function attachFileToField($locator, $path) + { + $field = $this->findField($locator); + + if (null === $field) { + throw new ElementNotFoundException( + $this->getSession(), 'form field', 'id|name|label|value', $locator + ); + } + + $field->attachFile($path); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Exception/DriverException.php b/core/vendor/behat/mink/src/Behat/Mink/Exception/DriverException.php new file mode 100644 index 0000000..91597d7 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Exception/DriverException.php @@ -0,0 +1,31 @@ +<?php + +namespace Behat\Mink\Exception; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink driver exception. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class DriverException extends Exception +{ + /** + * Initializes exception. + * + * @param string $message + * @param int $code + * @param \Exception|null $previous + */ + public function __construct($message, $code = 0, \Exception $previous = null) + { + parent::__construct($message, null, $code, $previous); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementException.php b/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementException.php new file mode 100644 index 0000000..8e21f47 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementException.php @@ -0,0 +1,59 @@ +<?php + +namespace Behat\Mink\Exception; + +use Behat\Mink\Element\Element; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * A standard way for elements to re-throw exceptions + * + * @author Chris Worfolk <xmeltrut@gmail.com> + */ +class ElementException extends Exception +{ + private $element; + + /** + * Initialises exception. + * + * @param Element $element optional message + * @param \Exception $exception exception + */ + public function __construct(Element $element, \Exception $exception) + { + $this->element = $element; + + parent::__construct(sprintf("Exception thrown by %s\n%s", + $element->getXpath(), + $exception->getMessage() + )); + } + + /** + * Override default toString so we don't send a full backtrace in verbose mode. + * + * @return string + */ + public function __toString() + { + return $this->getMessage(); + } + + /** + * Get the element that caused the exception + * + * @return Element + */ + public function getElement() + { + return $this->element; + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementHtmlException.php b/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementHtmlException.php new file mode 100644 index 0000000..33e24f7 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementHtmlException.php @@ -0,0 +1,65 @@ +<?php + +namespace Behat\Mink\Exception; + +use Behat\Mink\Session, + Behat\Mink\Element\Element; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink's element HTML exception. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ElementHtmlException extends ExpectationException +{ + /** + * Element instance. + * + * @var Element + */ + protected $element; + + /** + * Initializes exception. + * + * @param string $message optional message + * @param Session $session session instance + * @param Element $element element + * @param \Exception $exception expectation exception + */ + public function __construct($message = null, Session $session, Element $element, \Exception $exception = null) + { + $this->element = $element; + + parent::__construct($message, $session, $exception); + } + + /** + * Returns exception message with additional context info. + * + * @return string + */ + public function __toString() + { + try { + $pageText = $this->trimString($this->element->getHtml()); + $string = sprintf("%s\n\n%s%s", + $this->getMessage(), + $this->getResponseInfo(), + $this->pipeString($pageText."\n") + ); + } catch (\Exception $e) { + return $this->getMessage(); + } + + return $string; + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementNotFoundException.php b/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementNotFoundException.php new file mode 100644 index 0000000..6f77eae --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementNotFoundException.php @@ -0,0 +1,74 @@ +<?php + +namespace Behat\Mink\Exception; + +use Behat\Mink\Session; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink "element not found" exception. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ElementNotFoundException extends Exception +{ + /** + * Initializes exception. + * + * @param Session $session session instance + * @param string $type element type + * @param string $selector element selector type + * @param string $locator element locator + */ + public function __construct(Session $session, $type = null, $selector = null, $locator = null) + { + $message = ''; + + if (null !== $type) { + $message .= ucfirst($type); + } else { + $message .= 'Tag'; + } + + if (null !== $locator) { + if (null === $selector || in_array($selector, array('css', 'xpath'))) { + $selector = 'matching '.($selector ?: 'locator'); + } else { + $selector = 'with '.$selector; + } + $message .= ' '.$selector.' "' . $locator . '" '; + } + + $message .= 'not found.'; + + parent::__construct($message, $session); + } + + /** + * Returns exception message with additional context info. + * + * @return string + */ + public function __toString() + { + try { + $pageText = $this->trimBody($this->getSession()->getPage()->getContent()); + $string = sprintf("%s\n\n%s%s", + $this->getMessage(), + $this->getResponseInfo(), + $this->pipeString($pageText."\n") + ); + } catch (\Exception $e) { + return $this->getMessage(); + } + + return $string; + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementTextException.php b/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementTextException.php new file mode 100644 index 0000000..48e6183 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Exception/ElementTextException.php @@ -0,0 +1,43 @@ +<?php + +namespace Behat\Mink\Exception; + +use Behat\Mink\Session, + Behat\Mink\Element\Element; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink's element text exception. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ElementTextException extends ElementHtmlException +{ + /** + * Returns exception message with additional context info. + * + * @return string + */ + public function __toString() + { + try { + $pageText = $this->trimString($this->element->getText()); + $string = sprintf("%s\n\n%s%s", + $this->getMessage(), + $this->getResponseInfo(), + $this->pipeString($pageText."\n") + ); + } catch (\Exception $e) { + return $this->getMessage(); + } + + return $string; + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Exception/Exception.php b/core/vendor/behat/mink/src/Behat/Mink/Exception/Exception.php new file mode 100644 index 0000000..806428a --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Exception/Exception.php @@ -0,0 +1,113 @@ +<?php + +namespace Behat\Mink\Exception; + +use Behat\Mink\Session; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink base exception class. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +abstract class Exception extends \Exception +{ + private $session; + + /** + * Initializes Mink exception. + * + * @param string $message + * @param Session $session + * @param integer $code + * @param \Exception $previous + */ + public function __construct($message, Session $session = null, $code = 0, \Exception $previous = null) + { + $this->session = $session; + + parent::__construct($message, $code, $previous); + } + + /** + * Returns exception session. + * + * @return Session + */ + protected function getSession() + { + return $this->session; + } + + /** + * Prepends every line in a string with pipe (|). + * + * @param string $string + * + * @return string + */ + protected function pipeString($string) + { + return '| ' . strtr($string, array("\n" => "\n| ")); + } + + /** + * Removes response header/footer, letting only <body /> content and trim it. + * + * @param string $string response content + * @param integer $count trim count + * + * @return string + */ + protected function trimBody($string, $count = 1000) + { + $string = preg_replace(array('/^.*<body>/s', '/<\/body>.*$/s'), array('<body>', '</body>'), $string); + $string = $this->trimString($string, $count); + + return $string; + } + + /** + * Trims string to specified number of chars. + * + * @param string $string response content + * @param integer $count trim count + * + * @return string + */ + protected function trimString($string, $count = 1000) + { + $string = trim($string); + + if ($count < mb_strlen($string)) { + return mb_substr($string, 0, $count - 3) . '...'; + } + + return $string; + } + + /** + * Returns response information string. + * + * @return string + */ + protected function getResponseInfo() + { + $driver = basename(str_replace('\\', '/', get_class($this->session->getDriver()))); + + $info = '+--[ '; + if (!in_array($driver, array('SahiDriver', 'SeleniumDriver', 'Selenium2Driver'))) { + $info .= 'HTTP/1.1 '.$this->session->getStatusCode().' | '; + } + $info .= $this->session->getCurrentUrl().' | '.$driver." ]\n|\n"; + + return $info; + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Exception/ExpectationException.php b/core/vendor/behat/mink/src/Behat/Mink/Exception/ExpectationException.php new file mode 100644 index 0000000..41ada04 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Exception/ExpectationException.php @@ -0,0 +1,54 @@ +<?php + +namespace Behat\Mink\Exception; + +use Behat\Mink\Session; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink's expectation exception. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ExpectationException extends Exception +{ + /** + * Initializes exception. + * + * @param string $message optional message + * @param Session $session session instance + * @param \Exception $exception expectation exception + */ + public function __construct($message = null, Session $session, \Exception $exception = null) + { + parent::__construct($message ?: $exception->getMessage(), $session); + } + + /** + * Returns exception message with additional context info. + * + * @return string + */ + public function __toString() + { + try { + $pageText = $this->trimBody($this->getSession()->getPage()->getContent()); + $string = sprintf("%s\n\n%s%s", + $this->getMessage(), + $this->getResponseInfo(), + $this->pipeString($pageText."\n") + ); + } catch (\Exception $e) { + return $this->getMessage(); + } + + return $string; + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Exception/ResponseTextException.php b/core/vendor/behat/mink/src/Behat/Mink/Exception/ResponseTextException.php new file mode 100644 index 0000000..0535b06 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Exception/ResponseTextException.php @@ -0,0 +1,40 @@ +<?php + +namespace Behat\Mink\Exception; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink response's text exception. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ResponseTextException extends ExpectationException +{ + /** + * Returns exception message with additional context info. + * + * @return string + */ + public function __toString() + { + try { + $pageText = $this->trimString($this->getSession()->getPage()->getText()); + $string = sprintf("%s\n\n%s%s", + $this->getMessage(), + $this->getResponseInfo(), + $this->pipeString($pageText."\n") + ); + } catch (\Exception $e) { + return $this->getMessage(); + } + + return $string; + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Exception/UnsupportedDriverActionException.php b/core/vendor/behat/mink/src/Behat/Mink/Exception/UnsupportedDriverActionException.php new file mode 100644 index 0000000..f455d22 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Exception/UnsupportedDriverActionException.php @@ -0,0 +1,35 @@ +<?php + +namespace Behat\Mink\Exception; + +use Behat\Mink\Driver\DriverInterface; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink "element not found" exception. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class UnsupportedDriverActionException extends DriverException +{ + /** + * Initializes exception. + * + * @param string $template what is unsupported? + * @param DriverInterface $driver driver instance + * @param \Exception $previous previous exception + */ + public function __construct($template, DriverInterface $driver, \Exception $previous = null) + { + $message = sprintf($template, get_class($driver)); + + parent::__construct($message, 0, $previous); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Mink.php b/core/vendor/behat/mink/src/Behat/Mink/Mink.php new file mode 100644 index 0000000..9ce7df7 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Mink.php @@ -0,0 +1,210 @@ +<?php + +namespace Behat\Mink; + +use Behat\Mink\Driver\DriverInterface, + Behat\Mink\Selector\SelectorsHandler; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink sessions manager. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class Mink +{ + private $defaultSessionName; + private $sessions = array(); + + /** + * Initializes manager. + * + * @param array $sessions + */ + public function __construct(array $sessions = array()) + { + foreach ($sessions as $name => $session) { + $this->registerSession($name, $session); + } + } + + /** + * Stops all started sessions. + */ + public function __destruct() + { + $this->stopSessions(); + } + + /** + * Registers new session. + * + * @param string $name + * @param Session $session + */ + public function registerSession($name, Session $session) + { + $name = strtolower($name); + + $this->sessions[$name] = $session; + } + + /** + * Checks whether session with specified name is registered. + * + * @param string $name + * + * @return Boolean + */ + public function hasSession($name) + { + return isset($this->sessions[strtolower($name)]); + } + + /** + * Sets default session name to use. + * + * @param string $name name of the registered session + * + * @throws \InvalidArgumentException + */ + public function setDefaultSessionName($name) + { + $name = strtolower($name); + + if (!isset($this->sessions[$name])) { + throw new \InvalidArgumentException(sprintf('Session "%s" is not registered.', $name)); + } + + $this->defaultSessionName = $name; + } + + /** + * Returns default session name or null if none. + * + * @return null|string + */ + public function getDefaultSessionName() + { + return $this->defaultSessionName; + } + + /** + * Returns registered session by it's name or active one and automatically starts it if required. + * + * @param string $name session name + * + * @return Session + * + * @throws \InvalidArgumentException If the named session is not registered + */ + public function getSession($name = null) + { + $session = $this->locateSession($name); + + // start session if needed + if (!$session->isStarted()) { + $session->start(); + } + + return $session; + } + + /** + * Checks whether a named session (or the default session) has already been started + * + * @param string $name session name - if null then the default session will be checked + * + * @return bool whether the session has been started + * + * @throws \InvalidArgumentException If the named session is not registered + */ + public function isSessionStarted($name = null) + { + $session = $this->locateSession($name); + return $session->isStarted(); + } + + /** + * Returns session asserter. + * + * @param Session|string $session session object or name + * + * @return WebAssert + */ + public function assertSession($session = null) + { + if (!($session instanceof Session)) { + $session = $this->getSession($session); + } + return new WebAssert($session); + } + + /** + * Resets all started sessions. + */ + public function resetSessions() + { + foreach ($this->sessions as $name => $session) { + if ($session->isStarted()) { + $session->reset(); + } + } + } + + /** + * Restarts all started sessions. + */ + public function restartSessions() + { + foreach ($this->sessions as $name => $session) { + if ($session->isStarted()) { + $session->restart(); + } + } + } + + /** + * Stops all started sessions. + */ + public function stopSessions() + { + foreach ($this->sessions as $session) { + if ($session->isStarted()) { + $session->stop(); + } + } + } + + /** + * Returns the named or default session without starting it. + * + * @param string $name session name + * + * @return Session + * + * @throws \InvalidArgumentException If the named session is not registered + */ + protected function locateSession($name = null) + { + $name = strtolower($name) ?: $this->defaultSessionName; + + if (null === $name) { + throw new \InvalidArgumentException('Specify session name to get'); + } + + if (!isset($this->sessions[$name])) { + throw new \InvalidArgumentException(sprintf('Session "%s" is not registered.', $name)); + } + + $session = $this->sessions[$name]; + return $session; + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Selector/CssSelector.php b/core/vendor/behat/mink/src/Behat/Mink/Selector/CssSelector.php new file mode 100644 index 0000000..a02793c --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Selector/CssSelector.php @@ -0,0 +1,33 @@ +<?php + +namespace Behat\Mink\Selector; + +use Symfony\Component\CssSelector\CssSelector as CSS; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * CSS selector engine. Transforms CSS to XPath. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class CssSelector implements SelectorInterface +{ + /** + * Translates CSS into XPath. + * + * @param string $locator current selector locator + * + * @return string + */ + public function translateToXPath($locator) + { + return CSS::toXPath($locator); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Selector/NamedSelector.php b/core/vendor/behat/mink/src/Behat/Mink/Selector/NamedSelector.php new file mode 100644 index 0000000..0e9df06 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Selector/NamedSelector.php @@ -0,0 +1,112 @@ +<?php + +namespace Behat\Mink\Selector; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Named selectors engine. Uses registered XPath selectors to create new expressions. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class NamedSelector implements SelectorInterface +{ + private $selectors = array( + 'fieldset' => <<<XPATH +.//fieldset[(./@id = %locator% or .//legend[contains(normalize-space(string(.)), %locator%)])] +XPATH + ,'field' => <<<XPATH +.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@id = %locator% or ./@name = %locator%) or ./@id = //label[contains(normalize-space(string(.)), %locator%)]/@for) or ./@placeholder = %locator%)] | .//label[contains(normalize-space(string(.)), %locator%)]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')] +XPATH + ,'link' => <<<XPATH +.//a[./@href][(((./@id = %locator% or contains(normalize-space(string(.)), %locator%)) or contains(./@title, %locator%) or contains(./@rel, %locator%)) or .//img[contains(./@alt, %locator%)])] | .//*[./@role = 'link'][((./@id = %locator% or contains(./@value, %locator%)) or contains(./@title, %locator%) or contains(normalize-space(string(.)), %locator%))] +XPATH + ,'button' => <<<XPATH +.//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][(((./@id = %locator% or ./@name = %locator%) or contains(./@value, %locator%)) or contains(./@title, %locator%))] | .//input[./@type = 'image'][contains(./@alt, %locator%)] | .//button[((((./@id = %locator% or ./@name = %locator%) or contains(./@value, %locator%)) or contains(normalize-space(string(.)), %locator%)) or contains(./@title, %locator%))] | .//input[./@type = 'image'][contains(./@alt, %locator%)] | .//*[./@role = 'button'][(((./@id = %locator% or ./@name = %locator%) or contains(./@value, %locator%)) or contains(./@title, %locator%) or contains(normalize-space(string(.)), %locator%))] +XPATH + ,'link_or_button' => <<<XPATH +.//a[./@href][(((./@id = %locator% or contains(normalize-space(string(.)), %locator%)) or contains(./@title, %locator%) or contains(./@rel, %locator%)) or .//img[contains(./@alt, %locator%)])] | .//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][((./@id = %locator% or contains(./@value, %locator%)) or contains(./@title, %locator%))] | .//input[./@type = 'image'][contains(./@alt, %locator%)] | .//button[(((./@id = %locator% or contains(./@value, %locator%)) or contains(normalize-space(string(.)), %locator%)) or contains(./@title, %locator%))] | .//input[./@type = 'image'][contains(./@alt, %locator%)] | .//*[(./@role = 'button' or ./@role = 'link')][((./@id = %locator% or contains(./@value, %locator%)) or contains(./@title, %locator%) or contains(normalize-space(string(.)), %locator%))] +XPATH + ,'content' => <<<XPATH +./descendant-or-self::*[contains(normalize-space(.), %locator%)] +XPATH + ,'select' => <<<XPATH +.//select[(((./@id = %locator% or ./@name = %locator%) or ./@id = //label[contains(normalize-space(string(.)), %locator%)]/@for) or ./@placeholder = %locator%)] | .//label[contains(normalize-space(string(.)), %locator%)]//.//select +XPATH + ,'checkbox' => <<<XPATH +.//input[./@type = 'checkbox'][(((./@id = %locator% or ./@name = %locator%) or ./@id = //label[contains(normalize-space(string(.)), %locator%)]/@for) or ./@placeholder = %locator%)] | .//label[contains(normalize-space(string(.)), %locator%)]//.//input[./@type = 'checkbox'] +XPATH + ,'radio' => <<<XPATH +.//input[./@type = 'radio'][(((./@id = %locator% or ./@name = %locator%) or ./@id = //label[contains(normalize-space(string(.)), %locator%)]/@for) or ./@placeholder = %locator%)] | .//label[contains(normalize-space(string(.)), %locator%)]//.//input[./@type = 'radio'] +XPATH + ,'file' => <<<XPATH +.//input[./@type = 'file'][(((./@id = %locator% or ./@name = %locator%) or ./@id = //label[contains(normalize-space(string(.)), %locator%)]/@for) or ./@placeholder = %locator%)] | .//label[contains(normalize-space(string(.)), %locator%)]//.//input[./@type = 'file'] +XPATH + ,'optgroup' => <<<XPATH +.//optgroup[contains(./@label, %locator%)] +XPATH + ,'option' => <<<XPATH +.//option[(./@value = %locator% or contains(normalize-space(string(.)), %locator%))] +XPATH + ,'table' => <<<XPATH +.//table[(./@id = %locator% or contains(.//caption, %locator%))] +XPATH + ); + + /** + * Registers new XPath selector with specified name. + * + * @param string $name name for selector + * @param string $xpath xpath expression + */ + public function registerNamedXpath($name, $xpath) + { + $this->selectors[$name] = $xpath; + } + + /** + * Translates provided locator into XPath. + * + * @param string|array $locator selector name or array of (selector_name, locator) + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function translateToXPath($locator) + { + if (2 < count($locator)) { + throw new \InvalidArgumentException('NamedSelector expects array(name, locator) as argument'); + } + + if (2 == count($locator)) { + $selector = $locator[0]; + $locator = $locator[1]; + } else { + $selector = (string) $locator; + $locator = null; + } + + if (!isset($this->selectors[$selector])) { + throw new \InvalidArgumentException(sprintf( + 'Unknown named selector provided: "%s". Expected one of (%s)', + $selector, + implode(', ', array_keys($this->selectors)) + )); + } + + $xpath = $this->selectors[$selector]; + + if (null !== $locator) { + $xpath = strtr($xpath, array('%locator%' => $locator)); + } + + return $xpath; + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Selector/SelectorInterface.php b/core/vendor/behat/mink/src/Behat/Mink/Selector/SelectorInterface.php new file mode 100644 index 0000000..a261951 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Selector/SelectorInterface.php @@ -0,0 +1,28 @@ +<?php + +namespace Behat\Mink\Selector; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink selector engine interface. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +interface SelectorInterface +{ + /** + * Translates provided locator into XPath. + * + * @param string $locator current selector locator + * + * @return string + */ + public function translateToXPath($locator); +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Selector/SelectorsHandler.php b/core/vendor/behat/mink/src/Behat/Mink/Selector/SelectorsHandler.php new file mode 100644 index 0000000..38503b8 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Selector/SelectorsHandler.php @@ -0,0 +1,129 @@ +<?php + +namespace Behat\Mink\Selector; + +use Behat\Mink\Selector\SelectorInterface; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Selectors handler. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class SelectorsHandler +{ + private $selectors; + + /** + * Initializes selectors handler. + * + * @param array $selectors default selectors to register + */ + public function __construct(array $selectors = array()) + { + $this->registerSelector('named', new NamedSelector()); + $this->registerSelector('css', new CssSelector()); + + foreach ($selectors as $name => $selector) { + $this->registerSelector($name, $selector); + } + } + + /** + * Registers new selector engine with specified name. + * + * @param string $name selector engine name + * @param SelectorInterface $selector selector engine instance + */ + public function registerSelector($name, SelectorInterface $selector) + { + $this->selectors[$name] = $selector; + } + + /** + * Checks whether selector with specified name is registered on handler. + * + * @param string $name selector engine name + * + * @return Boolean + */ + public function isSelectorRegistered($name) + { + return isset($this->selectors[$name]); + } + + /** + * Returns selector engine with specified name. + * + * @param string $name selector engine name + * + * @return SelectorInterface + * + * @throws \InvalidArgumentException + */ + public function getSelector($name) + { + if (!$this->isSelectorRegistered($name)) { + throw new \InvalidArgumentException("Selector \"$name\" is not registered."); + } + + return $this->selectors[$name]; + } + + /** + * Translates selector with specified name to XPath. + * + * @param string $selector selector engine name (registered) + * @param string $locator selector locator + * + * @return string + */ + public function selectorToXpath($selector, $locator) + { + if ('xpath' === $selector) { + return $locator; + } + + return $this->getSelector($selector)->translateToXPath($locator); + } + + /** + * Translates string to XPath literal. + * + * @param string $s + * + * @return string + */ + public function xpathLiteral($s) + { + if (false === strpos($s, "'")) { + return sprintf("'%s'", $s); + } + + if (false === strpos($s, '"')) { + return sprintf('"%s"', $s); + } + + $string = $s; + $parts = array(); + while (true) { + if (false !== $pos = strpos($string, "'")) { + $parts[] = sprintf("'%s'", substr($string, 0, $pos)); + $parts[] = "\"'\""; + $string = substr($string, $pos + 1); + } else { + $parts[] = "'$string'"; + break; + } + } + + return sprintf("concat(%s)", implode($parts, ',')); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/Session.php b/core/vendor/behat/mink/src/Behat/Mink/Session.php new file mode 100644 index 0000000..dbc5571 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/Session.php @@ -0,0 +1,304 @@ +<?php + +namespace Behat\Mink; + +use Behat\Mink\Driver\DriverInterface, + Behat\Mink\Selector\SelectorsHandler, + Behat\Mink\Element\DocumentElement; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink session. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class Session +{ + private $driver; + private $page; + private $selectorsHandler; + + /** + * Initializes session. + * + * @param DriverInterface $driver + * @param SelectorsHandler $selectorsHandler + */ + public function __construct(DriverInterface $driver, SelectorsHandler $selectorsHandler = null) + { + $driver->setSession($this); + + if (null === $selectorsHandler) { + $selectorsHandler = new SelectorsHandler(); + } + + $this->driver = $driver; + $this->page = new DocumentElement($this); + $this->selectorsHandler = $selectorsHandler; + } + + /** + * Checks whether session (driver) was started. + * + * @return Boolean + */ + public function isStarted() + { + return $this->driver->isStarted(); + } + + /** + * Starts session driver. + */ + public function start() + { + $this->driver->start(); + } + + /** + * Stops session driver. + */ + public function stop() + { + $this->driver->stop(); + } + + /** + * Restart session driver. + */ + public function restart() + { + $this->driver->stop(); + $this->driver->start(); + } + + /** + * Reset session driver. + */ + public function reset() + { + $this->driver->reset(); + } + + /** + * Returns session driver. + * + * @return DriverInterface + */ + public function getDriver() + { + return $this->driver; + } + + /** + * Returns page element. + * + * @return DocumentElement + */ + public function getPage() + { + return $this->page; + } + + /** + * Returns selectors handler. + * + * @return SelectorsHandler + */ + public function getSelectorsHandler() + { + return $this->selectorsHandler; + } + + /** + * Visit specified URL. + * + * @param string $url url of the page + */ + public function visit($url) + { + $this->driver->visit($url); + } + + /** + * Sets HTTP Basic authentication parameters + * + * @param string|Boolean $user user name or false to disable authentication + * @param string $password password + */ + public function setBasicAuth($user, $password = '') + { + $this->driver->setBasicAuth($user, $password); + } + + /** + * Sets specific request header. + * + * @param string $name + * @param string $value + */ + public function setRequestHeader($name, $value) + { + $this->driver->setRequestHeader($name, $value); + } + + /** + * Returns all response headers. + * + * @return array + */ + public function getResponseHeaders() + { + return $this->driver->getResponseHeaders(); + } + + /** + * Sets cookie. + * + * @param string $name + * @param string $value + */ + public function setCookie($name, $value = null) + { + $this->driver->setCookie($name, $value); + } + + /** + * Returns cookie by name. + * + * @param string $name + * + * @return string|null + */ + public function getCookie($name) + { + return $this->driver->getCookie($name); + } + + /** + * Returns response status code. + * + * @return integer + */ + public function getStatusCode() + { + return $this->driver->getStatusCode(); + } + + /** + * Returns current URL address. + * + * @return string + */ + public function getCurrentUrl() + { + return $this->driver->getCurrentUrl(); + } + + /** + * Capture a screenshot of the current window. + * + * @return string screenshot of MIME type image/* depending + * on driver (e.g., image/png, image/jpeg) + */ + public function getScreenshot() + { + return $this->driver->getScreenshot(); + } + + /** + * Reloads current session page. + */ + public function reload() + { + $this->driver->reload(); + } + + /** + * Moves backward 1 page in history. + */ + public function back() + { + $this->driver->back(); + } + + /** + * Moves forward 1 page in history. + */ + public function forward() + { + $this->driver->forward(); + } + + /** + * Switches to specific browser window. + * + * @param string $name window name (null for switching back to main window) + */ + public function switchToWindow($name = null) + { + $this->driver->switchToWindow($name); + } + + /** + * Switches to specific iFrame. + * + * @param string $name iframe name (null for switching back) + */ + public function switchToIFrame($name = null) + { + $this->driver->switchToIFrame($name); + } + + /** + * Execute JS in browser. + * + * @param string $script javascript + */ + public function executeScript($script) + { + $this->driver->executeScript($script); + } + + /** + * Execute JS in browser and return it's response. + * + * @param string $script javascript + * + * @return string + */ + public function evaluateScript($script) + { + return $this->driver->evaluateScript($script); + } + + /** + * Waits some time or until JS condition turns true. + * + * @param integer $time time in milliseconds + * @param string $condition JS condition + */ + public function wait($time, $condition = 'false') + { + $this->driver->wait($time, $condition); + } + + /** + * Set the dimensions of the window. + * + * @param integer $width set the window width, measured in pixels + * @param integer $height set the window height, measured in pixels + * @param string $name window name (null for the main window) + */ + public function resizeWindow($width, $height, $name = null) + { + return $this->driver->resizeWindow($width, $height, $name); + } +} diff --git a/core/vendor/behat/mink/src/Behat/Mink/WebAssert.php b/core/vendor/behat/mink/src/Behat/Mink/WebAssert.php new file mode 100644 index 0000000..c92def3 --- /dev/null +++ b/core/vendor/behat/mink/src/Behat/Mink/WebAssert.php @@ -0,0 +1,593 @@ +<?php + +namespace Behat\Mink; + +use Behat\Mink\Element\Element, + Behat\Mink\Element\NodeElement, + Behat\Mink\Exception\ElementNotFoundException, + Behat\Mink\Exception\ExpectationException, + Behat\Mink\Exception\ResponseTextException, + Behat\Mink\Exception\ElementHtmlException, + Behat\Mink\Exception\ElementTextException; + +/* + * This file is part of the Behat\Mink. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Mink web assertions tool. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class WebAssert +{ + protected $session; + + /** + * Initializes assertion engine. + * + * @param Session $session + */ + public function __construct(Session $session) + { + $this->session = $session; + } + + /** + * Checks that current session address is equals to provided one. + * + * @param string $page + * + * @throws ExpectationException + */ + public function addressEquals($page) + { + $expected = $this->cleanScriptnameFromPath(parse_url($page, PHP_URL_PATH)); + $actual = $this->getCurrentUrlPath(); + + if ($actual !== $expected) { + $message = sprintf('Current page is "%s", but "%s" expected.', $actual, $expected); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that current session address is not equals to provided one. + * + * @param string $page + * + * @throws ExpectationException + */ + public function addressNotEquals($page) + { + $expected = $this->cleanScriptnameFromPath(parse_url($page, PHP_URL_PATH)); + $actual = $this->getCurrentUrlPath(); + + if ($actual === $expected) { + $message = sprintf('Current page is "%s", but should not be.', $actual); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that current session address matches regex. + * + * @param string $regex + * + * @throws ExpectationException + */ + public function addressMatches($regex) + { + $actual = $this->getCurrentUrlPath(); + + if (!preg_match($regex, $actual)) { + $message = sprintf('Current page "%s" does not match the regex "%s".', $actual, $regex); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that specified cookie exists and its value equals to a given one + * + * @param string $name cookie name + * @param string $value cookie value + * + * @throws Behat\Mink\Exception\ExpectationException + */ + public function cookieEquals($name, $value) + { + $this->cookieExists($name); + $actualValue = $this->session->getCookie($name); + if ($actualValue != $value) { + $message = sprintf('Cookie "%s" value is "%s", but should be "%s".', $name, + $actualValue, $value); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that specified cookie exists + * + * @param string $name cookie name + * + * @throws Behat\Mink\Exception\ExpectationException + */ + public function cookieExists($name) + { + if ($this->session->getCookie($name) === null) { + $message = sprintf('Cookie "%s" is not set, but should be.', $name); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that current response code equals to provided one. + * + * @param integer $code + * + * @throws ExpectationException + */ + public function statusCodeEquals($code) + { + $actual = $this->session->getStatusCode(); + + if (intval($code) !== intval($actual)) { + $message = sprintf('Current response status code is %d, but %d expected.', $actual, $code); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that current response code not equals to provided one. + * + * @param integer $code + * + * @throws ExpectationException + */ + public function statusCodeNotEquals($code) + { + $actual = $this->session->getStatusCode(); + + if (intval($code) === intval($actual)) { + $message = sprintf('Current response status code is %d, but should not be.', $actual); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that current page contains text. + * + * @param string $text + * + * @throws ResponseTextException + */ + public function pageTextContains($text) + { + $actual = $this->session->getPage()->getText(); + $actual = preg_replace('/\s+/u', ' ', $actual); + $regex = '/'.preg_quote($text, '/').'/ui'; + + if (!preg_match($regex, $actual)) { + $message = sprintf('The text "%s" was not found anywhere in the text of the current page.', $text); + throw new ResponseTextException($message, $this->session); + } + } + + /** + * Checks that current page does not contains text. + * + * @param string $text + * + * @throws ResponseTextException + */ + public function pageTextNotContains($text) + { + $actual = $this->session->getPage()->getText(); + $actual = preg_replace('/\s+/u', ' ', $actual); + $regex = '/'.preg_quote($text, '/').'/ui'; + + if (preg_match($regex, $actual)) { + $message = sprintf('The text "%s" appears in the text of this page, but it should not.', $text); + throw new ResponseTextException($message, $this->session); + } + } + + /** + * Checks that current page text matches regex. + * + * @param string $regex + * + * @throws ResponseTextException + */ + public function pageTextMatches($regex) + { + $actual = $this->session->getPage()->getText(); + + if (!preg_match($regex, $actual)) { + $message = sprintf('The pattern %s was not found anywhere in the text of the current page.', $regex); + throw new ResponseTextException($message, $this->session); + } + } + + /** + * Checks that current page text does not matches regex. + * + * @param string $regex + * + * @throws ResponseTextException + */ + public function pageTextNotMatches($regex) + { + $actual = $this->session->getPage()->getText(); + + if (preg_match($regex, $actual)) { + $message = sprintf('The pattern %s was found in the text of the current page, but it should not.', $regex); + throw new ResponseTextException($message, $this->session); + } + } + + /** + * Checks that page HTML (response content) contains text. + * + * @param string $text + * + * @throws ExpectationException + */ + public function responseContains($text) + { + $actual = $this->session->getPage()->getContent(); + $regex = '/'.preg_quote($text, '/').'/ui'; + + if (!preg_match($regex, $actual)) { + $message = sprintf('The string "%s" was not found anywhere in the HTML response of the current page.', $text); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that page HTML (response content) does not contains text. + * + * @param string $text + * + * @throws ExpectationException + */ + public function responseNotContains($text) + { + $actual = $this->session->getPage()->getContent(); + $regex = '/'.preg_quote($text, '/').'/ui'; + + if (preg_match($regex, $actual)) { + $message = sprintf('The string "%s" appears in the HTML response of this page, but it should not.', $text); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that page HTML (response content) matches regex. + * + * @param string $regex + * + * @throws ExpectationException + */ + public function responseMatches($regex) + { + $actual = $this->session->getPage()->getContent(); + + if (!preg_match($regex, $actual)) { + $message = sprintf('The pattern %s was not found anywhere in the HTML response of the page.', $regex); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that page HTML (response content) does not matches regex. + * + * @param $regex + * + * @throws ExpectationException + */ + public function responseNotMatches($regex) + { + $actual = $this->session->getPage()->getContent(); + + if (preg_match($regex, $actual)) { + $message = sprintf('The pattern %s was found in the HTML response of the page, but it should not.', $regex); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that there is specified number of specific elements on the page. + * + * @param string $selectorType element selector type (css, xpath) + * @param string $selector element selector + * @param integer $count expected count + * @param Element $container document to check against + * + * @throws ExpectationException + */ + public function elementsCount($selectorType, $selector, $count, Element $container = null) + { + $container = $container ?: $this->session->getPage(); + $nodes = $container->findAll($selectorType, $selector); + + if (intval($count) !== count($nodes)) { + $message = sprintf('%d elements matching %s "%s" found on the page, but should be %d.', count($nodes), $selectorType, $selector, $count); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that specific element exists on the current page. + * + * @param string $selectorType element selector type (css, xpath) + * @param string $selector element selector + * @param Element $container document to check against + * + * @return NodeElement + * + * @throws ElementNotFoundException + */ + public function elementExists($selectorType, $selector, Element $container = null) + { + $container = $container ?: $this->session->getPage(); + $node = $container->find($selectorType, $selector); + + if (null === $node) { + throw new ElementNotFoundException($this->session, 'element', $selectorType, $selector); + } + + return $node; + } + + /** + * Checks that specific element does not exists on the current page. + * + * @param string $selectorType element selector type (css, xpath) + * @param string $selector element selector + * @param Element $container document to check against + * + * @throws ExpectationException + */ + public function elementNotExists($selectorType, $selector, Element $container = null) + { + $container = $container ?: $this->session->getPage(); + $node = $container->find($selectorType, $selector); + + if (null !== $node) { + $message = sprintf('An element matching %s "%s" appears on this page, but it should not.', $selectorType, $selector); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that specific element contains text. + * + * @param string $selectorType element selector type (css, xpath) + * @param string $selector element selector + * @param string $text expected text + * + * @throws ElementTextException + */ + public function elementTextContains($selectorType, $selector, $text) + { + $element = $this->elementExists($selectorType, $selector); + $actual = $element->getText(); + $regex = '/'.preg_quote($text, '/').'/ui'; + + if (!preg_match($regex, $actual)) { + $message = sprintf('The text "%s" was not found in the text of the element matching %s "%s".', $text, $selectorType, $selector); + throw new ElementTextException($message, $this->session, $element); + } + } + + /** + * Checks that specific element does not contains text. + * + * @param string $selectorType element selector type (css, xpath) + * @param string $selector element selector + * @param string $text expected text + * + * @throws ElementTextException + */ + public function elementTextNotContains($selectorType, $selector, $text) + { + $element = $this->elementExists($selectorType, $selector); + $actual = $element->getText(); + $regex = '/'.preg_quote($text, '/').'/ui'; + + if (preg_match($regex, $actual)) { + $message = sprintf('The text "%s" appears in the text of the element matching %s "%s", but it should not.', $text, $selectorType, $selector); + throw new ElementTextException($message, $this->session, $element); + } + } + + /** + * Checks that specific element contains HTML. + * + * @param string $selectorType element selector type (css, xpath) + * @param string $selector element selector + * @param string $html expected text + * + * @throws ElementHtmlException + */ + public function elementContains($selectorType, $selector, $html) + { + $element = $this->elementExists($selectorType, $selector); + $actual = $element->getHtml(); + $regex = '/'.preg_quote($html, '/').'/ui'; + + if (!preg_match($regex, $actual)) { + $message = sprintf('The string "%s" was not found in the HTML of the element matching %s "%s".', $html, $selectorType, $selector); + throw new ElementHtmlException($message, $this->session, $element); + } + } + + /** + * Checks that specific element does not contains HTML. + * + * @param string $selectorType element selector type (css, xpath) + * @param string $selector element selector + * @param string $html expected text + * + * @throws ElementHtmlException + */ + public function elementNotContains($selectorType, $selector, $html) + { + $element = $this->elementExists($selectorType, $selector); + $actual = $element->getHtml(); + $regex = '/'.preg_quote($html, '/').'/ui'; + + if (preg_match($regex, $actual)) { + $message = sprintf('The string "%s" appears in the HTML of the element matching %s "%s", but it should not.', $html, $selectorType, $selector); + throw new ElementHtmlException($message, $this->session, $element); + } + } + + /** + * Checks that specific field exists on the current page. + * + * @param string $field field id|name|label|value + * @param Element $container document to check against + * + * @return NodeElement + * + * @throws ElementNotFoundException + */ + public function fieldExists($field, Element $container = null) + { + $container = $container ?: $this->session->getPage(); + $node = $container->findField($field); + + if (null === $node) { + throw new ElementNotFoundException($this->session, 'form field', 'id|name|label|value', $field); + } + + return $node; + } + + /** + * Checks that specific field does not exists on the current page. + * + * @param string $field field id|name|label|value + * @param Element $container document to check against + * + * @throws ExpectationException + */ + public function fieldNotExists($field, Element $container = null) + { + $container = $container ?: $this->session->getPage(); + $node = $container->findField($field); + + if (null !== $node) { + $message = sprintf('A field "%s" appears on this page, but it should not.', $field); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that specific field have provided value. + * + * @param string $field field id|name|label|value + * @param string $value field value + * @param Element $container document to check against + * + * @throws ExpectationException + */ + public function fieldValueEquals($field, $value, Element $container = null) + { + $node = $this->fieldExists($field, $container); + $actual = $node->getValue(); + $regex = '/^'.preg_quote($value, '/').'/ui'; + + if (!preg_match($regex, $actual)) { + $message = sprintf('The field "%s" value is "%s", but "%s" expected.', $field, $actual, $value); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that specific field have provided value. + * + * @param string $field field id|name|label|value + * @param string $value field value + * @param Element $container document to check against + * + * @throws ExpectationException + */ + public function fieldValueNotEquals($field, $value, Element $container = null) + { + $node = $this->fieldExists($field, $container); + $actual = $node->getValue(); + $regex = '/^'.preg_quote($value, '/').'/ui'; + + if (preg_match($regex, $actual)) { + $message = sprintf('The field "%s" value is "%s", but it should not be.', $field, $actual); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that specific checkbox is checked. + * + * @param string $field field id|name|label|value + * @param Element $container document to check against + * + * @throws ExpectationException + */ + public function checkboxChecked($field, Element $container = null) + { + $node = $this->fieldExists($field, $container); + + if (!$node->isChecked()) { + $message = sprintf('Checkbox "%s" is not checked, but it should be.', $field); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Checks that specific checkbox is unchecked. + * + * @param string $field field id|name|label|value + * @param Element $container document to check against + * + * @throws ExpectationException + */ + public function checkboxNotChecked($field, Element $container = null) + { + $node = $this->fieldExists($field, $container); + + if ($node->isChecked()) { + $message = sprintf('Checkbox "%s" is checked, but it should not be.', $field); + throw new ExpectationException($message, $this->session); + } + } + + /** + * Gets current url of the page. + * + * @return string + */ + protected function getCurrentUrlPath() + { + return $this->cleanScriptnameFromPath( + parse_url($this->session->getCurrentUrl(), PHP_URL_PATH) + ); + } + + /** + * Trims scriptname from the URL. + * + * @param string $path + * + * @return string + */ + protected function cleanScriptnameFromPath($path) + { + return preg_replace('/^\/[^\.\/]+\.php/', '', $path); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/GeneralDriverTest.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/GeneralDriverTest.php new file mode 100644 index 0000000..216d118 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/GeneralDriverTest.php @@ -0,0 +1,583 @@ +<?php + +namespace Tests\Behat\Mink\Driver; + +use Behat\Mink\Mink, + Behat\Mink\Session; + +require_once 'PHPUnit/Autoload.php'; +require_once 'PHPUnit/Framework/Assert/Functions.php'; + +abstract class GeneralDriverTest extends \PHPUnit_Framework_TestCase +{ + private static $mink; + + /** + * Initializes mink instance. + */ + public static function setUpBeforeClass() + { + self::$mink = new Mink(array('sess' => new Session(static::getDriver()))); + } + + public function getSession() + { + return self::$mink->getSession('sess'); + } + + protected function tearDown() + { + self::$mink->resetSessions(); + } + + public function testRedirect() + { + $this->getSession()->visit($this->pathTo('/redirector.php')); + $this->assertEquals($this->pathTo('/redirect_destination.php'), $this->getSession()->getCurrentUrl()); + } + + /** + * @group issue130 + */ + public function testIssue130() + { + $this->getSession()->visit($this->pathTo('/issue130.php?p=1')); + $page = $this->getSession()->getPage(); + + $page->clickLink('Go to 2'); + $this->assertEquals($this->pathTo('/issue130.php?p=1'), $page->getText()); + } + + /** + * @group issue131 + */ + public function testIssue131() + { + $this->getSession()->visit($this->pathTo('/issue131.php')); + $page = $this->getSession()->getPage(); + + $page->selectFieldOption('foobar', 'Gimme some accentués characters'); + + $this->assertEquals('1', $page->findField('foobar')->getValue()); + } + + /** + * @group issue140 + */ + public function testIssue140() + { + $this->getSession()->visit($this->pathTo('/issue140.php')); + + $this->getSession()->getPage()->fillField('cookie_value', 'some:value;'); + $this->getSession()->getPage()->pressButton('Set cookie'); + + $this->getSession()->visit($this->pathTo('/issue140.php?show_value')); + $this->assertEquals('some:value;', $this->getSession()->getCookie('tc')); + $this->assertEquals('some:value;', $this->getSession()->getPage()->getText()); + } + + /** + * @group issue162 + * TODO: fix goutte behavior + */ + public function _testIssue162() + { + $this->getSession()->visit($this->pathTo('/issue162.php')); + + $this->getSession()->getPage()->uncheckField('Checkbox 1'); + $this->getSession()->getPage()->pressButton('Submit'); + } + + /** + * @group issue211 + */ + public function testIssue211() + { + $this->getSession()->visit($this->pathTo('/issue211.php')); + $field = $this->getSession()->getPage()->findField('Téléphone'); + + $this->assertNotNull($field); + } + + public function testIssue212() + { + $session = $this->getSession(); + + $session->visit($this->pathTo('/issue212.php')); + $page = $session->getPage(); + + $field = $page->findById('poney-button'); + $this->assertEquals('poney', $field->getValue()); + } + + public function testCookie() + { + $this->getSession()->visit($this->pathTo('/cookie_page2.php')); + $this->assertContains('Previous cookie: NO', $this->getSession()->getPage()->getText()); + $this->assertNull($this->getSession()->getCookie('srvr_cookie')); + + $this->getSession()->setCookie('srvr_cookie', 'client cookie set'); + $this->getSession()->reload(); + $this->assertContains('Previous cookie: client cookie set', $this->getSession()->getPage()->getText()); + $this->assertEquals('client cookie set', $this->getSession()->getCookie('srvr_cookie')); + + $this->getSession()->setCookie('srvr_cookie', null); + $this->getSession()->reload(); + $this->assertContains('Previous cookie: NO', $this->getSession()->getPage()->getText()); + + $this->getSession()->visit($this->pathTo('/cookie_page1.php')); + $this->getSession()->visit($this->pathTo('/cookie_page2.php')); + + $this->assertContains('Previous cookie: srv_var_is_set', $this->getSession()->getPage()->getText()); + $this->getSession()->setCookie('srvr_cookie', null); + $this->getSession()->reload(); + $this->assertContains('Previous cookie: NO', $this->getSession()->getPage()->getText()); + } + + public function testReset() + { + $this->getSession()->visit($this->pathTo('/cookie_page1.php')); + $this->getSession()->visit($this->pathTo('/cookie_page2.php')); + $this->assertContains('Previous cookie: srv_var_is_set', $this->getSession()->getPage()->getText()); + + $this->getSession()->reset(); + $this->getSession()->visit($this->pathTo('/cookie_page2.php')); + + $this->assertContains('Previous cookie: NO', $this->getSession()->getPage()->getText()); + + $this->getSession()->setCookie('srvr_cookie', 'test_cookie'); + $this->getSession()->visit($this->pathTo('/cookie_page2.php')); + $this->assertContains('Previous cookie: test_cookie', $this->getSession()->getPage()->getText()); + $this->getSession()->reset(); + $this->getSession()->visit($this->pathTo('/cookie_page2.php')); + $this->assertContains('Previous cookie: NO', $this->getSession()->getPage()->getText()); + + $this->getSession()->setCookie('client_cookie1', 'some_val'); + $this->getSession()->setCookie('client_cookie2', 123); + $this->getSession()->visit($this->pathTo('/session_test.php')); + $this->getSession()->visit($this->pathTo('/cookie_page1.php')); + + $this->getSession()->visit($this->pathTo('/print_cookies.php')); + $this->assertContains( + "'client_cookie1' = 'some_val'", + $this->getSession()->getPage()->getText() + ); + $this->assertContains( + "'client_cookie2' = '123'", + $this->getSession()->getPage()->getText() + ); + $this->assertContains( + "_SESS' = ", + $this->getSession()->getPage()->getText() + ); + $this->assertContains( + " 'srvr_cookie' = 'srv_var_is_set', )", + $this->getSession()->getPage()->getText() + ); + + $this->getSession()->reset(); + $this->getSession()->visit($this->pathTo('/print_cookies.php')); + $this->assertContains( + 'array ( )', $this->getSession()->getPage()->getText() + ); + } + + public function testHttpOnlyCookieIsDeleted() + { + $this->getSession()->restart(); + $this->getSession()->visit($this->pathTo('/cookie_page3.php')); + $this->assertEquals('Has Cookie: false', $this->getSession()->getPage()->findById('cookie-status')->getText()); + + $this->getSession()->reload(); + $this->assertEquals('Has Cookie: true', $this->getSession()->getPage()->findById('cookie-status')->getText()); + + $this->getSession()->restart(); + $this->getSession()->visit($this->pathTo('/cookie_page3.php')); + $this->assertEquals('Has Cookie: false', $this->getSession()->getPage()->findById('cookie-status')->getText()); + } + + public function testSessionPersistsBetweenRequests() + { + $this->getSession()->visit($this->pathTo('/session_test.php')); + $this->assertNotNull($node = $this->getSession()->getPage()->find('css', '#session-id')); + $sessionId = $node->getText(); + + $this->getSession()->visit($this->pathTo('/session_test.php')); + $this->assertNotNull($node = $this->getSession()->getPage()->find('css', '#session-id')); + $this->assertEquals($sessionId, $node->getText()); + + $this->getSession()->visit($this->pathTo('/session_test.php?login')); + $this->assertNotNull($node = $this->getSession()->getPage()->find('css', '#session-id')); + $this->assertNotEquals($sessionId, $newSessionId = $node->getText()); + + $this->getSession()->visit($this->pathTo('/session_test.php')); + $this->assertNotNull($node = $this->getSession()->getPage()->find('css', '#session-id')); + $this->assertEquals($newSessionId, $node->getText()); + } + + public function testPageControlls() + { + $this->getSession()->visit($this->pathTo('/randomizer.php')); + $number1 = $this->getSession()->getPage()->find('css', '#number')->getText(); + + $this->getSession()->reload(); + $number2 = $this->getSession()->getPage()->find('css', '#number')->getText(); + + $this->assertNotEquals($number1, $number2); + + $this->getSession()->visit($this->pathTo('/links.php')); + $this->getSession()->getPage()->clickLink('Random number page'); + + $this->assertEquals($this->pathTo('/randomizer.php'), $this->getSession()->getCurrentUrl()); + + $this->getSession()->back(); + $this->assertEquals($this->pathTo('/links.php'), $this->getSession()->getCurrentUrl()); + + $this->getSession()->forward(); + $this->assertEquals($this->pathTo('/randomizer.php'), $this->getSession()->getCurrentUrl()); + } + + public function testElementsTraversing() + { + $this->getSession()->visit($this->pathTo('/index.php')); + + $page = $this->getSession()->getPage(); + + $this->assertNotNull($page->find('css', 'h1')); + $this->assertEquals('Extremely useless page', $page->find('css', 'h1')->getText()); + $this->assertEquals('h1', $page->find('css', 'h1')->getTagName()); + + $this->assertNotNull($page->find('xpath', '//div/strong[3]')); + $this->assertEquals('pariatur', $page->find('xpath', '//div/strong[3]')->getText()); + $this->assertEquals('super-duper', $page->find('xpath', '//div/strong[3]')->getAttribute('class')); + $this->assertTrue($page->find('xpath', '//div/strong[3]')->hasAttribute('class')); + + $this->assertNotNull($page->find('xpath', '//div/strong[2]')); + $this->assertEquals('veniam', $page->find('xpath', '//div/strong[2]')->getText()); + $this->assertEquals('strong', $page->find('xpath', '//div/strong[2]')->getTagName()); + $this->assertNull($page->find('xpath', '//div/strong[2]')->getAttribute('class')); + $this->assertFalse($page->find('xpath', '//div/strong[2]')->hasAttribute('class')); + + $strongs = $page->findAll('css', 'div#core > strong'); + $this->assertEquals(3, count($strongs)); + $this->assertEquals('Lorem', $strongs[0]->getText()); + $this->assertEquals('pariatur', $strongs[2]->getText()); + + $element = $page->find('css', '#some-element'); + + $this->assertEquals('some very interesting text', $element->getText()); + $this->assertEquals( + "\n some <div>very\n </div>\n". + "<em>interesting</em> text\n ", + $element->getHtml() + ); + + $this->assertTrue($element->hasAttribute('data-href')); + $this->assertFalse($element->hasAttribute('data-url')); + $this->assertEquals('http://mink.behat.org', $element->getAttribute('data-href')); + $this->assertNull($element->getAttribute('data-url')); + $this->assertEquals('div', $element->getTagName()); + } + + public function testVeryDeepElementsTraversing() + { + $this->getSession()->visit($this->pathTo('/index.php')); + + $page = $this->getSession()->getPage(); + + $footer = $page->find('css', 'footer'); + $this->assertNotNull($footer); + + $searchForm = $footer->find('css', 'form#search-form'); + $this->assertNotNull($searchForm); + $this->assertEquals('search-form', $searchForm->getAttribute('id')); + + $searchInput = $searchForm->findField('Search site...'); + $this->assertNotNull($searchInput); + $this->assertEquals('text', $searchInput->getAttribute('type')); + + $searchInput = $searchForm->findField('Search site...'); + $this->assertNotNull($searchInput); + $this->assertEquals('text', $searchInput->getAttribute('type')); + + $profileForm = $footer->find('css', '#profile'); + $this->assertNotNull($profileForm); + + $profileFormDiv = $profileForm->find('css', 'div'); + $this->assertNotNull($profileFormDiv); + + $profileFormDivLabel = $profileFormDiv->find('css', 'label'); + $this->assertNotNull($profileFormDivLabel); + + $profileFormDivParent = $profileFormDivLabel->getParent(); + $this->assertNotNull($profileFormDivParent); + + $profileFormDivParent = $profileFormDivLabel->getParent(); + $this->assertEquals('something', $profileFormDivParent->getAttribute('data-custom')); + + $profileFormInput = $profileFormDivLabel->findField('user-name'); + $this->assertNotNull($profileFormInput); + $this->assertEquals('username', $profileFormInput->getAttribute('name')); + } + + public function testDeepTraversing() + { + $this->getSession()->visit($this->pathTo('/index.php')); + + $traversDiv = $this->getSession()->getPage()->findAll('css', 'div.travers'); + + $this->assertEquals(1, count($traversDiv)); + $traversDiv = $traversDiv[0]; + + $subDivs = $traversDiv->findAll('css', 'div.sub'); + $this->assertEquals(3, count($subDivs)); + + $this->assertTrue($subDivs[2]->hasLink('some deep url')); + $this->assertFalse($subDivs[2]->hasLink('come deep url')); + $subUrl = $subDivs[2]->findLink('some deep url'); + $this->assertNotNull($subUrl); + + $this->assertRegExp('/some_url$/', $subUrl->getAttribute('href')); + $this->assertEquals('some deep url', $subUrl->getText()); + $this->assertEquals('some <strong>deep</strong> url', $subUrl->getHtml()); + + $this->assertTrue($subUrl->has('css', 'strong')); + $this->assertFalse($subUrl->has('css', 'em')); + $this->assertEquals('deep', $subUrl->find('css', 'strong')->getText()); + } + + public function testLinks() + { + $this->getSession()->visit($this->pathTo('/links.php')); + $page = $this->getSession()->getPage(); + $link = $page->findLink('Redirect me to'); + + $this->assertRegExp('/redirector\.php$/', $link->getAttribute('href')); + $link->click(); + + $this->assertEquals($this->pathTo('/redirect_destination.php'), $this->getSession()->getCurrentUrl()); + + $this->getSession()->visit($this->pathTo('/links.php')); + $page = $this->getSession()->getPage(); + $link = $page->findLink('basic form image'); + + $this->assertRegExp('/\/basic_form\.php$/', $link->getAttribute('href')); + $link->click(); + + $this->assertEquals($this->pathTo('/basic_form.php'), $this->getSession()->getCurrentUrl()); + + $this->getSession()->visit($this->pathTo('/links.php')); + $page = $this->getSession()->getPage(); + $link = $page->findLink("Link with a "); + + $this->assertNotNull($link); + $this->assertRegExp('/\/links\.php\?quoted$/', $link->getAttribute('href')); + $link->click(); + + $this->assertEquals($this->pathTo('/links.php?quoted'), $this->getSession()->getCurrentUrl()); + } + + public function testJson() + { + $this->getSession()->visit($this->pathTo('/json.php')); + $this->assertContains( + '{"key1":"val1","key2":234,"key3":[1,2,3]}', $this->getSession()->getPage()->getContent() + ); + } + + public function testBasicForm() + { + $this->getSession()->visit($this->pathTo('/basic_form.php')); + + $page = $this->getSession()->getPage(); + $this->assertEquals('Basic Form Page', $page->find('css', 'h1')->getText()); + + $firstname = $page->findField('first_name'); + $lastname = $page->findField('lastn'); + + $this->assertNotNull($firstname); + $this->assertNotNull($lastname); + + $this->assertEquals('Firstname', $firstname->getValue()); + $this->assertEquals('Lastname', $lastname->getValue()); + + $firstname->setValue('Konstantin'); + $page->fillField('last_name', 'Kudryashov'); + + $this->assertEquals('Konstantin', $firstname->getValue()); + $this->assertEquals('Kudryashov', $lastname->getValue()); + + $page->findButton('Save')->click(); + + $this->assertEquals('Anket for Konstantin', $page->find('css', 'h1')->getText()); + $this->assertEquals('Firstname: Konstantin', $page->find('css', '#first')->getText()); + $this->assertEquals('Lastname: Kudryashov', $page->find('css', '#last')->getText()); + } + + public function testBasicGetForm() + { + $this->getSession()->visit($this->pathTo('/basic_get_form.php')); + + $page = $this->getSession()->getPage(); + $this->assertEquals('Basic Get Form Page', $page->find('css', 'h1')->getText()); + + $search = $page->findField('q'); + $search->setValue('some#query'); + $page->pressButton('Find'); + + $this->assertNotNull($div = $page->find('css', 'div')); + $this->assertEquals('some#query', $div->getText()); + } + + public function testMultiselect() + { + $this->getSession()->visit($this->pathTo('/multiselect_form.php')); + $page = $this->getSession()->getPage(); + $this->assertEquals('Multiselect Test', $page->find('css', 'h1')->getText()); + + $select = $page->findField('select_number'); + $multiSelect = $page->findField('select_multiple_numbers[]'); + + $this->assertNotNull($select); + $this->assertNotNull($multiSelect); + + $this->assertEquals('20', $select->getValue()); + $this->assertSame(array(), $multiSelect->getValue()); + + $select->selectOption('thirty'); + $this->assertEquals('30', $select->getValue()); + + $multiSelect->selectOption('one', true); + + $this->assertSame(array('1'), $multiSelect->getValue()); + + $multiSelect->selectOption('three', true); + + $this->assertEquals(array('1', '3'), $multiSelect->getValue()); + + $button = $page->findButton('Register'); + $button->press(); + + $space = ' '; + $this->assertContains(<<<OUT + 'select_number' = '30', + 'select_multiple_numbers' =$space + array ( + 0 = '1', + 1 = '3', + ) +OUT + , $page->getContent() + ); + } + + public function testAdvancedForm() + { + $this->getSession()->visit($this->pathTo('/advanced_form.php')); + $page = $this->getSession()->getPage(); + + $page->fillField('first_name', 'ever'); + $page->fillField('last_name', 'zet'); + + $page->pressButton('Register'); + + $this->assertContains('no file', $page->getContent()); + + $this->getSession()->visit($this->pathTo('/advanced_form.php')); + + $page = $this->getSession()->getPage(); + $this->assertEquals('ADvanced Form Page', $page->find('css', 'h1')->getText()); + + $firstname = $page->findField('first_name'); + $lastname = $page->findField('lastn'); + $email = $page->findField('Your email:'); + $select = $page->findField('select_number'); + $sex = $page->findField('sex'); + $maillist = $page->findField('mail_list'); + $agreement = $page->findField('agreement'); + $about = $page->findField('about'); + + $this->assertNotNull($firstname); + $this->assertNotNull($lastname); + $this->assertNotNull($email); + $this->assertNotNull($select); + $this->assertNotNull($sex); + $this->assertNotNull($maillist); + $this->assertNotNull($agreement); + + $this->assertEquals('Firstname', $firstname->getValue()); + $this->assertEquals('Lastname', $lastname->getValue()); + $this->assertEquals('your@email.com', $email->getValue()); + $this->assertEquals('20', $select->getValue()); + $this->assertEquals('w', $sex->getValue()); + + $this->assertTrue($maillist->getValue()); + $this->assertFalse($agreement->getValue()); + + $this->assertTrue($maillist->isChecked()); + $this->assertFalse($agreement->isChecked()); + + $agreement->check(); + $this->assertTrue($agreement->isChecked()); + + $maillist->uncheck(); + $this->assertFalse($maillist->isChecked()); + + $select->selectOption('thirty'); + $this->assertEquals('30', $select->getValue()); + + $sex->selectOption('m'); + $this->assertEquals('m', $sex->getValue()); + $about->attachFile(__DIR__ . '/web-fixtures/some_file.txt'); + + $button = $page->findButton('Register'); + + $page->fillField('first_name', 'Foo "item"'); + $page->fillField('last_name', 'Bar'); + $page->fillField('Your email:', 'ever.zet@gmail.com'); + + $this->assertEquals('Foo "item"', $firstname->getValue()); + $this->assertEquals('Bar', $lastname->getValue()); + + $button->press(); + + $space = ' '; + $this->assertContains(<<<OUT +array ( + 'first_name' = 'Foo "item"', + 'last_name' = 'Bar', + 'email' = 'ever.zet@gmail.com', + 'select_number' = '30', + 'sex' = 'm', + 'agreement' = 'on', + 'submit' = 'Register', +) +1 uploaded file +OUT + , $page->getContent() + ); + } + + public function testAdvancedFormSecondSubmit() + { + $this->getSession()->visit($this->pathTo('/advanced_form.php')); + $page = $this->getSession()->getPage(); + + $button = $page->findButton('Login'); + $button->press(); + + $this->assertContains(<<<OUT + 'submit' = 'Login', + 'agreement' = 'off', +) +no file +OUT + , $page->getContent() + ); + } + + protected function pathTo($path) + { + return $_SERVER['WEB_FIXTURES_HOST'].$path; + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/HeadlessDriverTest.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/HeadlessDriverTest.php new file mode 100644 index 0000000..8d78470 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/HeadlessDriverTest.php @@ -0,0 +1,30 @@ +<?php + +namespace Tests\Behat\Mink\Driver; + +require_once 'GeneralDriverTest.php'; + +abstract class HeadlessDriverTest extends GeneralDriverTest +{ + public function testStatuses() + { + $this->getSession()->visit($this->pathTo('/index.php')); + + $this->assertEquals(200, $this->getSession()->getStatusCode()); + $this->assertEquals($this->pathTo('/index.php'), $this->getSession()->getCurrentUrl()); + + $this->getSession()->visit($this->pathTo('/404.php')); + + $this->assertEquals($this->pathTo('/404.php'), $this->getSession()->getCurrentUrl()); + $this->assertEquals(404, $this->getSession()->getStatusCode()); + $this->assertEquals('Sorry, page not found', $this->getSession()->getPage()->getContent()); + } + + public function testHeaders() + { + $this->getSession()->setRequestHeader('Accept-Language', 'fr'); + $this->getSession()->visit($this->pathTo('/headers.php')); + + $this->assertContains('[HTTP_ACCEPT_LANGUAGE] => fr', $this->getSession()->getPage()->getContent()); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/JavascriptDriverTest.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/JavascriptDriverTest.php new file mode 100644 index 0000000..aa1c1ad --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/JavascriptDriverTest.php @@ -0,0 +1,196 @@ +<?php + +namespace Tests\Behat\Mink\Driver; + +require_once 'GeneralDriverTest.php'; + +abstract class JavascriptDriverTest extends GeneralDriverTest +{ + public function testIFrame() + { + $this->getSession()->visit($this->pathTo('/iframe.php')); + $page = $this->getSession()->getPage(); + + $el = $page->find('css', '#text'); + $this->assertNotNull($el); + $this->assertSame('Main window div text', $el->getText()); + + $this->getSession()->switchToIFrame('subframe'); + + $el = $page->find('css', '#text'); + $this->assertNotNull($el); + $this->assertSame('iFrame div text', $el->getText()); + + $this->getSession()->switchToIFrame(); + + $el = $page->find('css', '#text'); + $this->assertNotNull($el); + $this->assertSame('Main window div text', $el->getText()); + } + + public function testWindow() + { + $this->getSession()->visit($this->pathTo('/window.php')); + $session = $this->getSession(); + $page = $session->getPage(); + + $page->clickLink('Popup #1'); + $page->clickLink('Popup #2'); + + $el = $page->find('css', '#text'); + $this->assertNotNull($el); + $this->assertSame('Main window div text', $el->getText()); + + $session->switchToWindow('popup_1'); + $el = $page->find('css', '#text'); + $this->assertNotNull($el); + $this->assertSame('Popup#1 div text', $el->getText()); + + $session->switchToWindow('popup_2'); + $el = $page->find('css', '#text'); + $this->assertNotNull($el); + $this->assertSame('Popup#2 div text', $el->getText()); + + $session->switchToWindow(null); + $el = $page->find('css', '#text'); + $this->assertNotNull($el); + $this->assertSame('Main window div text', $el->getText()); + } + + public function testAriaRoles() + { + $this->getSession()->visit($this->pathTo('/aria_roles.php')); + + $this->getSession()->wait(5000, '$("#hidden-element").is(":visible") === false'); + $this->getSession()->getPage()->pressButton('Toggle'); + $this->getSession()->wait(5000, '$("#hidden-element").is(":visible") === true'); + + $this->getSession()->getPage()->clickLink('Go to Index'); + $this->assertEquals($this->pathTo('/index.php'), $this->getSession()->getCurrentUrl()); + } + + public function testMouseEvents() + { + $this->getSession()->visit($this->pathTo('/js_test.php')); + + $clicker = $this->getSession()->getPage()->find('css', '.elements div#clicker'); + + $this->assertEquals('not clicked', $clicker->getText()); + + $clicker->click(); + $this->assertEquals('single clicked', $clicker->getText()); + + $clicker->doubleClick(); + $this->assertEquals('double clicked', $clicker->getText()); + + $clicker->rightClick(); + $this->assertEquals('right clicked', $clicker->getText()); + + $clicker->focus(); + $this->assertEquals('focused', $clicker->getText()); + + $clicker->blur(); + $this->assertEquals('blured', $clicker->getText()); + + $clicker->mouseOver(); + $this->assertEquals('mouse overed', $clicker->getText()); + } + + public function testKeyboardEvents() + { + $this->getSession()->visit($this->pathTo('/js_test.php')); + + $input1 = $this->getSession()->getPage()->find('css', '.elements input.input.first'); + $input2 = $this->getSession()->getPage()->find('css', '.elements input.input.second'); + $input3 = $this->getSession()->getPage()->find('css', '.elements input.input.third'); + $event = $this->getSession()->getPage()->find('css', '.elements .text-event'); + + $input1->keyDown('u'); + $this->assertEquals('key downed:0', $event->getText()); + + $input1->keyDown('u', 'alt'); + $this->assertEquals('key downed:1', $event->getText()); + + $input2->keyPress('r'); + $this->assertEquals('key pressed:114 / 0', $event->getText()); + + $input2->keyPress('r', 'alt'); + $this->assertEquals('key pressed:114 / 1', $event->getText()); + + $input3->keyUp(78); + $this->assertEquals('key upped:78 / 0', $event->getText()); + + $input3->keyUp(78, 'alt'); + $this->assertEquals('key upped:78 / 1', $event->getText()); + } + + public function testWait() + { + $this->getSession()->visit($this->pathTo('/js_test.php')); + + $this->getSession()->getPage()->findById('waitable')->click(); + $this->getSession()->wait(3000, '$("#waitable").has("div").length > 0'); + $this->assertEquals('arrived', $this->getSession()->getPage()->find('css', '#waitable > div')->getText()); + + $this->getSession()->getPage()->findById('waitable')->click(); + $this->getSession()->wait(3000, 'false'); + $this->assertEquals('timeout', $this->getSession()->getPage()->find('css', '#waitable > div')->getText()); + } + + public function testVisibility() + { + $this->getSession()->visit($this->pathTo('/js_test.php')); + + $clicker = $this->getSession()->getPage()->find('css', '.elements div#clicker'); + $invisible = $this->getSession()->getPage()->find('css', '#invisible'); + + $this->assertFalse($invisible->isVisible()); + $this->assertTrue($clicker->isVisible()); + } + + public function testDragDrop() + { + $this->getSession()->visit($this->pathTo('/js_test.php')); + + $draggable = $this->getSession()->getPage()->find('css', '#draggable'); + $droppable = $this->getSession()->getPage()->find('css', '#droppable'); + + $draggable->dragTo($droppable); + $this->assertEquals('Dropped!', $droppable->find('css', 'p')->getText()); + } + + public function testIssue193() + { + $session = $this->getSession(); + $session->visit($this->pathTo('/issue193.html')); + + $session->getPage()->selectFieldOption('options-without-values', 'Two'); + $this->assertEquals('Two', $session->getPage()->findById('options-without-values')->getValue()); + + $session->getPage()->selectFieldOption('options-with-values', 'two'); + $this->assertEquals('two', $session->getPage()->findById('options-with-values')->getValue()); + } + + public function testIssue225() + { + $this->getSession()->visit($this->pathTo('/issue225.php')); + $this->getSession()->getPage()->pressButton('Créer un compte'); + $this->getSession()->wait(5000, '$("#panel").text() != ""'); + + $this->assertContains('OH AIH!', $this->getSession()->getPage()->getText()); + } + + /** + * 'change' event should be fired after selecting an <option> in a <select> + */ + public function testIssue255() + { + $session = $this->getSession(); + $session->visit($this->pathTo('/issue255.php')); + + $session->getPage()->selectFieldOption('foo_select', 'Option 3'); + + $session->wait(2000, '$("#output_foo_select").text() != ""'); + $this->assertEquals('onChangeSelect', $session->getPage()->find('css', '#output_foo_select')->getText()); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/404.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/404.php new file mode 100644 index 0000000..dde7b26 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/404.php @@ -0,0 +1,2 @@ +<?php header("HTTP/1.0 404 Not Found") ?> +Sorry, page not found \ No newline at end of file diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/advanced_form.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/advanced_form.php new file mode 100644 index 0000000..1219178 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/advanced_form.php @@ -0,0 +1,38 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru"> +<head> + <title>ADvanced Form + + + +

ADvanced Form Page

+ +
+ + + + + + + + + + + + + + + +
+ + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/advanced_form_post.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/advanced_form_post.php new file mode 100644 index 0000000..3c94e36 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/advanced_form_post.php @@ -0,0 +1,25 @@ + + + + Advanced form save + + + +', '', var_export($_POST, true)) . "\n"; + if (file_exists($_FILES['about']['tmp_name'])) { + echo file_get_contents($_FILES['about']['tmp_name']); + } else { + echo "no file"; + } + +?> + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/aria_roles.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/aria_roles.php new file mode 100644 index 0000000..a5268f5 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/aria_roles.php @@ -0,0 +1,30 @@ + + + + ARIA roles test + + + + This page tests selected ARIA roles
+ (see http://www.w3.org/TR/wai-aria/) + +
Toggle
+ + + + + + + + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/basic_form.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/basic_form.php new file mode 100644 index 0000000..276d64b --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/basic_form.php @@ -0,0 +1,17 @@ + + + + Basic Form + + + +

Basic Form Page

+ +
+ + + + +
+ + \ No newline at end of file diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/basic_form_post.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/basic_form_post.php new file mode 100644 index 0000000..b203f44 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/basic_form_post.php @@ -0,0 +1,14 @@ + + + + + Basic Form Saving + + + +

Anket for

+ + Firstname: + Lastname: + + \ No newline at end of file diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/basic_get_form.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/basic_get_form.php new file mode 100644 index 0000000..c35f63c --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/basic_get_form.php @@ -0,0 +1,20 @@ + + + + Basic Get Form + + + +

Basic Get Form Page

+ +
+ +
+ +
+ + + +
+ + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/cookie_page1.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/cookie_page1.php new file mode 100644 index 0000000..cd586cf --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/cookie_page1.php @@ -0,0 +1,15 @@ + + + + + Basic Form + + + + + Basic Page With Cookie Set from Server Side + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/cookie_page2.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/cookie_page2.php new file mode 100644 index 0000000..6b2d798 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/cookie_page2.php @@ -0,0 +1,12 @@ + + + + Basic Form + + + + + Previous cookie: + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/cookie_page3.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/cookie_page3.php new file mode 100644 index 0000000..390e7b8 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/cookie_page3.php @@ -0,0 +1,18 @@ + + + + + HttpOnly Cookie Test + + + + + + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/headers.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/headers.php new file mode 100644 index 0000000..3f0cc2c --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/headers.php @@ -0,0 +1,10 @@ + + + + Headers page + + + + + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/iframe.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/iframe.php new file mode 100644 index 0000000..180c30c --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/iframe.php @@ -0,0 +1,11 @@ + + + + + +
+ Main window div text +
+ + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/iframe_inner.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/iframe_inner.php new file mode 100644 index 0000000..e603f1f --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/iframe_inner.php @@ -0,0 +1,9 @@ + + + +
+ iFrame div text +
+ + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/index.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/index.php new file mode 100644 index 0000000..cd46520 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/index.php @@ -0,0 +1,43 @@ + + + + Index page + + + +

Extremely useless page

+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +
+ some
very +
+interesting text +
+ +
+
el1
+
el2
+ +
+ +
el4
+ +
+ +
+
+ +
+
+
+ +
+
+
+ + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue130.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue130.php new file mode 100644 index 0000000..02159b6 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue130.php @@ -0,0 +1,14 @@ + + + + + + Go to 2'; + } else { + echo ''.$_SERVER['HTTP_REFERER'].''; + } + ?> + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue131.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue131.php new file mode 100644 index 0000000..93f42fb --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue131.php @@ -0,0 +1,17 @@ + + + + Issue 131 + + + +

There is a non breaking space

+
Some accentués characters
+
+ + +
+ + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue140.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue140.php new file mode 100644 index 0000000..821d0a9 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue140.php @@ -0,0 +1,18 @@ + + + + + + +
+ + +
+ diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue162.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue162.php new file mode 100644 index 0000000..5714975 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue162.php @@ -0,0 +1,23 @@ + + + + + +
+ + + + +
+ + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue178.html b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue178.html new file mode 100644 index 0000000..3efc743 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue178.html @@ -0,0 +1,19 @@ + + + + + Index page + + + +
+
+ +
+
+ +
+ + + \ No newline at end of file diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue193.html b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue193.html new file mode 100644 index 0000000..a68cab2 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue193.html @@ -0,0 +1,26 @@ + + + + Index page + + + + +
+ + + +
+ + + \ No newline at end of file diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue211.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue211.php new file mode 100644 index 0000000..550a115 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue211.php @@ -0,0 +1,25 @@ + + + + + Index page + + + + +
+

+ + +

+

+ + +

+ + +
+ + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue212.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue212.php new file mode 100644 index 0000000..d49417d --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue212.php @@ -0,0 +1,10 @@ + + + +
+ + +
+ + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue215.html b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue215.html new file mode 100644 index 0000000..adff3fb --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue215.html @@ -0,0 +1,17 @@ + + + + + Index page + + + +
+ +
+ + + \ No newline at end of file diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue225.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue225.php new file mode 100644 index 0000000..c237f74 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue225.php @@ -0,0 +1,21 @@ + + + + Index page + + + + + + + +
+ + + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue255.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue255.php new file mode 100644 index 0000000..5eb91a7 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/issue255.php @@ -0,0 +1,33 @@ + + + + Issue 255 + + + + +
+ + + + +

+

+
+ + + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/js/jquery-1.6.2-min.js b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/js/jquery-1.6.2-min.js new file mode 100644 index 0000000..8cdc80e --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/js/jquery-1.6.2-min.js @@ -0,0 +1,18 @@ +/*! + * jQuery JavaScript Library v1.6.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Jun 30 14:16:56 2011 -0400 + */ +(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. +shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j +)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); \ No newline at end of file diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/js/jquery-ui-1.8.14.custom.min.js b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/js/jquery-ui-1.8.14.custom.min.js new file mode 100644 index 0000000..1764e11 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/js/jquery-ui-1.8.14.custom.min.js @@ -0,0 +1,127 @@ +/*! + * jQuery UI 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */ +(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.14", +keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus(); +b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this, +"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection", +function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth, +outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b); +return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e= +0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted= +false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); +;/* + * jQuery UI Draggable 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Draggables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper== +"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b= +this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;d(b.iframeFix===true?"iframe":b.iframeFix).each(function(){d('
').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")});return true},_mouseStart:function(a){var b=this.options;this.helper= +this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}); +this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions();d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);d.ui.ddmanager&&d.ui.ddmanager.dragStart(this,a);return true}, +_mouseDrag:function(a,b){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b= +false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b=d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element,b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration, +10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop",a)!==false&&this._clear();return false},_mouseUp:function(a){this.options.iframeFix===true&&d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});d.ui.ddmanager&&d.ui.ddmanager.dragStop(this,a);return d.ui.mouse.prototype._mouseUp.call(this,a)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle|| +!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone().removeAttr("id"):this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&& +a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent= +this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"), +10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"), +10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[a.containment=="document"?0:d(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,a.containment=="document"?0:d(window).scrollTop()-this.offset.relative.top-this.offset.parent.top, +(a.containment=="document"?0:d(window).scrollLeft())+d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a.containment=="document"?0:d(window).scrollTop())+(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){a=d(a.containment);var b=a[0];if(b){a.offset();var c=d(b).css("overflow")!= +"hidden";this.containment=[(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0),(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0),(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"), +10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom];this.relative_container=a}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+ +this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&& +!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,h=a.pageY;if(this.originalPosition){var g;if(this.containment){if(this.relative_container){g=this.relative_container.offset();g=[this.containment[0]+g.left,this.containment[1]+g.top,this.containment[2]+g.left,this.containment[3]+g.top]}else g=this.containment;if(a.pageX-this.offset.click.leftg[2])e=g[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>g[3])h=g[3]+this.offset.click.top}if(b.grid){h=b.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/b.grid[1])*b.grid[1]:this.originalPageY;h=g?!(h-this.offset.click.topg[3])?h:!(h-this.offset.click.topg[2])?e:!(e-this.offset.click.left=0;i--){var j=c.snapElements[i].left,l=j+c.snapElements[i].width,k=c.snapElements[i].top,m=k+c.snapElements[i].height;if(j-e=j&&f<=l||h>=j&&h<=l||fl)&&(e>= +i&&e<=k||g>=i&&g<=k||ek);default:return false}};d.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(a,b){var c=d.ui.ddmanager.droppables[a.options.scope]||[],e=b?b.type:null,g=(a.currentItem||a.element).find(":data(droppable)").andSelf(),f=0;a:for(;f + + + JS elements test + + + + + +
+
not clicked
+ + + + +
+
+ +
+ +
+

Drop here

+
+ +
+ + + + + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/json.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/json.php new file mode 100644 index 0000000..173d358 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/json.php @@ -0,0 +1,7 @@ + 'val1', + 'key2' => 234, + 'key3' => array(1, 2, 3) +)); diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/links.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/links.php new file mode 100644 index 0000000..5a8646d --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/links.php @@ -0,0 +1,15 @@ + + + + Links page + + + + Redirect me to + Random number page + Link with a ' + + basic form image + + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/multiselect_form.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/multiselect_form.php new file mode 100644 index 0000000..852a2da --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/multiselect_form.php @@ -0,0 +1,26 @@ + + + + Multiselect Test + + + +

Multiselect Test

+ +
+ + + + + +
+ + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/popup1.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/popup1.php new file mode 100644 index 0000000..258176d --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/popup1.php @@ -0,0 +1,12 @@ + + + popup_1 + + + +
+ Popup#1 div text +
+ + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/popup2.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/popup2.php new file mode 100644 index 0000000..5b92d6f --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/popup2.php @@ -0,0 +1,12 @@ + + + popup_2 + + + +
+ Popup#2 div text +
+ + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/print_cookies.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/print_cookies.php new file mode 100644 index 0000000..3c4f714 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/print_cookies.php @@ -0,0 +1,10 @@ + + + + Cookies page + + + + ', '', var_export($_COOKIE, true)); ?> + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/randomizer.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/randomizer.php new file mode 100644 index 0000000..f800442 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/randomizer.php @@ -0,0 +1,12 @@ + + + + Index page + + + + +

+ + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/redirect_destination.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/redirect_destination.php new file mode 100644 index 0000000..bf9963e --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/redirect_destination.php @@ -0,0 +1,12 @@ + + + + Redirect destination + + + + + You were redirected! + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/redirector.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/redirector.php new file mode 100644 index 0000000..f24b7cf --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/redirector.php @@ -0,0 +1,3 @@ + + + + + Session Test + + + + +
+ + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/some_file.txt b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/some_file.txt new file mode 100644 index 0000000..d515a02 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/some_file.txt @@ -0,0 +1 @@ +1 uploaded file diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/window.php b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/window.php new file mode 100644 index 0000000..3c32526 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Driver/web-fixtures/window.php @@ -0,0 +1,25 @@ + + + + + + + Popup #1 + + + + Popup #2 + + +
+ Main window div text +
+ + + diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Element/DocumentElementTest.php b/core/vendor/behat/mink/tests/Behat/Mink/Element/DocumentElementTest.php new file mode 100644 index 0000000..84aa980 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Element/DocumentElementTest.php @@ -0,0 +1,469 @@ +session = $this->getSessionWithMockedDriver(); + $this->document = new DocumentElement($this->session); + } + + public function testGetSession() + { + $this->assertEquals($this->session, $this->document->getSession()); + } + + public function testFindAll() + { + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with('//html/h3[a]') + ->will($this->onConsecutiveCalls(array(2, 3, 4), array(1, 2), array())); + + $this->assertEquals(3, count($this->document->findAll('xpath', $xpath = 'h3[a]'))); + + $selector = $this->getMockBuilder('Behat\Mink\Selector\SelectorInterface')->getMock(); + $selector + ->expects($this->once()) + ->method('translateToXPath') + ->with($css = 'h3 > a') + ->will($this->returnValue($xpath)); + + $this->session->getSelectorsHandler()->registerSelector('css', $selector); + $this->assertEquals(2, count($this->document->findAll('css', $css))); + } + + public function testFind() + { + $this->session->getDriver() + ->expects($this->exactly(3)) + ->method('find') + ->with('//html/h3[a]') + ->will($this->onConsecutiveCalls(array(2, 3, 4), array(1, 2), array())); + + $this->assertEquals(2, $this->document->find('xpath', $xpath = 'h3[a]')); + + $selector = $this->getMockBuilder('Behat\Mink\Selector\SelectorInterface')->getMock(); + $selector + ->expects($this->once()) + ->method('translateToXPath') + ->with($css = 'h3 > a') + ->will($this->returnValue($xpath)); + + $this->session->getSelectorsHandler()->registerSelector('css', $selector); + $this->assertEquals(1, $this->document->find('css', $css)); + + $this->assertNull($this->document->find('xpath', $xpath)); + } + + public function testFindField() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('field1', 'field2', 'field3'), array())); + + $this->assertEquals('field1', $this->document->findField('some field')); + $this->assertEquals(null, $this->document->findField('some field')); + } + + public function testFindLink() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('link1', 'link2', 'link3'), array())); + + $this->assertEquals('link1', $this->document->findLink('some link')); + $this->assertEquals(null, $this->document->findLink('some link')); + } + + public function testFindButton() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('button1', 'button2', 'button3'), array())); + + $this->assertEquals('button1', $this->document->findButton('some button')); + $this->assertEquals(null, $this->document->findButton('some button')); + } + + public function testFindById() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('id2', 'id3'), array())); + + $this->assertEquals('id2', $this->document->findById('some-item-2')); + $this->assertEquals(null, $this->document->findById('some-item-2')); + } + + public function testHasSelector() + { + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with('//html/some xpath') + ->will($this->onConsecutiveCalls(array('id2', 'id3'), array())); + + $this->assertTrue($this->document->has('xpath', 'some xpath')); + $this->assertFalse($this->document->has('xpath', 'some xpath')); + } + + public function testHasContent() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('item1', 'item2'), array())); + + $this->assertTrue($this->document->hasContent('some content')); + $this->assertFalse($this->document->hasContent('some content')); + } + + public function testHasLink() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('link1', 'link2', 'link3'), array())); + + $this->assertTrue($this->document->hasLink('some link')); + $this->assertFalse($this->document->hasLink('some link')); + } + + public function testHasButton() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('button1', 'button2', 'button3'), array())); + + $this->assertTrue($this->document->hasButton('some button')); + $this->assertFalse($this->document->hasButton('some button')); + } + + public function testHasField() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('field1', 'field2', 'field3'), array())); + + $this->assertTrue($this->document->hasField('some field')); + $this->assertFalse($this->document->hasField('some field')); + } + + public function testHasCheckedField() + { + $xpath = <<getMockBuilder('Behat\Mink\Element\NodeElement') + ->disableOriginalConstructor() + ->getMock(); + $checkbox + ->expects($this->exactly(2)) + ->method('isChecked') + ->will($this->onConsecutiveCalls(true, false)); + + $this->session->getDriver() + ->expects($this->exactly(3)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array($checkbox), array(), array($checkbox))); + + $this->assertTrue($this->document->hasCheckedField('some checkbox')); + $this->assertFalse($this->document->hasCheckedField('some checkbox')); + $this->assertFalse($this->document->hasCheckedField('some checkbox')); + } + + public function testHasUncheckedField() + { + $xpath = <<getMockBuilder('Behat\Mink\Element\NodeElement') + ->disableOriginalConstructor() + ->getMock(); + $checkbox + ->expects($this->exactly(2)) + ->method('isChecked') + ->will($this->onConsecutiveCalls(true, false)); + + $this->session->getDriver() + ->expects($this->exactly(3)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array($checkbox), array(), array($checkbox))); + + $this->assertFalse($this->document->hasUncheckedField('some checkbox')); + $this->assertFalse($this->document->hasUncheckedField('some checkbox')); + $this->assertTrue($this->document->hasUncheckedField('some checkbox')); + } + + public function testHasSelect() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('select'), array())); + + $this->assertTrue($this->document->hasSelect('some select field')); + $this->assertFalse($this->document->hasSelect('some select field')); + } + + public function testHasTable() + { + $xpath = <<session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->with($xpath) + ->will($this->onConsecutiveCalls(array('table'), array())); + + $this->assertTrue($this->document->hasTable('some table')); + $this->assertFalse($this->document->hasTable('some table')); + } + + public function testClickLink() + { + $xpath = <<getMockBuilder('Behat\Mink\Element\NodeElement') + ->disableOriginalConstructor() + ->getMock(); + $node + ->expects($this->once()) + ->method('click'); + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->will($this->onConsecutiveCalls(array($node), array())); + + $this->document->clickLink('some link'); + $this->setExpectedException('Behat\Mink\Exception\ElementNotFoundException'); + $this->document->clickLink('some link'); + } + + public function testClickButton() + { + $xpath = <<getMockBuilder('Behat\Mink\Element\NodeElement') + ->disableOriginalConstructor() + ->getMock(); + $node + ->expects($this->once()) + ->method('press'); + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->will($this->onConsecutiveCalls(array($node), array())); + + $this->document->pressButton('some button'); + $this->setExpectedException('Behat\Mink\Exception\ElementNotFoundException'); + $this->document->pressButton('some button'); + } + + public function testFillField() + { + $xpath = <<getMockBuilder('Behat\Mink\Element\NodeElement') + ->disableOriginalConstructor() + ->getMock(); + $node + ->expects($this->once()) + ->method('setValue') + ->with('some val'); + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->will($this->onConsecutiveCalls(array($node), array())); + + $this->document->fillField('some field', 'some val'); + $this->setExpectedException('Behat\Mink\Exception\ElementNotFoundException'); + $this->document->fillField('some field', 'some val'); + } + + public function testCheckField() + { + $xpath = <<getMockBuilder('Behat\Mink\Element\NodeElement') + ->disableOriginalConstructor() + ->getMock(); + $node + ->expects($this->once()) + ->method('check'); + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->will($this->onConsecutiveCalls(array($node), array())); + + $this->document->checkField('some field'); + $this->setExpectedException('Behat\Mink\Exception\ElementNotFoundException'); + $this->document->checkField('some field'); + } + + public function testUncheckField() + { + $xpath = <<getMockBuilder('Behat\Mink\Element\NodeElement') + ->disableOriginalConstructor() + ->getMock(); + $node + ->expects($this->once()) + ->method('uncheck'); + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->will($this->onConsecutiveCalls(array($node), array())); + + $this->document->uncheckField('some field'); + $this->setExpectedException('Behat\Mink\Exception\ElementNotFoundException'); + $this->document->uncheckField('some field'); + } + + public function testSelectField() + { + $xpath = <<getMockBuilder('Behat\Mink\Element\NodeElement') + ->disableOriginalConstructor() + ->getMock(); + $node + ->expects($this->once()) + ->method('selectOption') + ->with('option2'); + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->will($this->onConsecutiveCalls(array($node), array())); + + $this->document->selectFieldOption('some field', 'option2'); + $this->setExpectedException('Behat\Mink\Exception\ElementNotFoundException'); + $this->document->selectFieldOption('some field', 'option2'); + } + + public function testAttachFileToField() + { + $xpath = <<getMockBuilder('Behat\Mink\Element\NodeElement') + ->disableOriginalConstructor() + ->getMock(); + $node + ->expects($this->once()) + ->method('attachFile') + ->with('/path/to/file'); + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('find') + ->will($this->onConsecutiveCalls(array($node), array())); + + $this->document->attachFileToField('some field', '/path/to/file'); + $this->setExpectedException('Behat\Mink\Exception\ElementNotFoundException'); + $this->document->attachFileToField('some field', '/path/to/file'); + } + + public function testGetContent() + { + $this->session->getDriver() + ->expects($this->once()) + ->method('getContent') + ->will($this->returnValue($ret = 'page content')); + + $this->assertEquals($ret, $this->document->getContent()); + } + + public function testGetText() + { + $this->session->getDriver() + ->expects($this->once()) + ->method('getText') + ->with('//html') + ->will($this->returnValue('val1')); + + $this->assertEquals('val1', $this->document->getText()); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Element/ElementTest.php b/core/vendor/behat/mink/tests/Behat/Mink/Element/ElementTest.php new file mode 100644 index 0000000..d290652 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Element/ElementTest.php @@ -0,0 +1,21 @@ +getMockBuilder('Behat\Mink\Driver\DriverInterface')->getMock(); + $selectors = new SelectorsHandler(); + $session = new Session($driver, $selectors); + + return $session; + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Element/NodeElementTest.php b/core/vendor/behat/mink/tests/Behat/Mink/Element/NodeElementTest.php new file mode 100644 index 0000000..2d23348 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Element/NodeElementTest.php @@ -0,0 +1,269 @@ +session = $this->getSessionWithMockedDriver(); + } + + public function testGetXpath() + { + $node = new NodeElement('some custom xpath', $this->session); + + $this->assertEquals('some custom xpath', $node->getXpath()); + $this->assertNotEquals('not some custom xpath', $node->getXpath()); + } + + public function testGetText() + { + $node = new NodeElement('text_tag', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('getText') + ->with('text_tag') + ->will($this->returnValue('val1')); + + $this->assertEquals('val1', $node->getText()); + } + + public function testHasAttribute() + { + $node = new NodeElement('input_tag', $this->session); + + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('getAttribute') + ->with('input_tag', 'href') + ->will($this->onConsecutiveCalls(null, 'http://...')); + + $this->assertFalse($node->hasAttribute('href')); + $this->assertTrue($node->hasAttribute('href')); + } + + public function testGetAttribute() + { + $node = new NodeElement('input_tag', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('getAttribute') + ->with('input_tag', 'href') + ->will($this->returnValue('http://...')); + + $this->assertEquals('http://...', $node->getAttribute('href')); + } + + public function testHasClass() + { + $node = new NodeElement('input_tag', $this->session); + + $this->session->getDriver() + ->expects($this->exactly(6)) + ->method('getAttribute') + ->with('input_tag', 'class') + ->will($this->returnValue('class1 class2')); + + $this->assertTrue($node->hasClass('class1')); + $this->assertTrue($node->hasClass('class2')); + $this->assertFalse($node->hasClass('class3')); + } + + public function testGetValue() + { + $node = new NodeElement('input_tag', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('getValue') + ->with('input_tag') + ->will($this->returnValue('val1')); + + $this->assertEquals('val1', $node->getValue()); + } + + public function testSetValue() + { + $node = new NodeElement('input_tag', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('setValue') + ->with('input_tag', 'new_val'); + + $node->setValue('new_val'); + } + + public function testClick() + { + $node = new NodeElement('link_or_button', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('click') + ->with('link_or_button'); + + $node->click(); + } + + public function testRightClick() + { + $node = new NodeElement('elem', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('rightClick') + ->with('elem'); + + $node->rightClick(); + } + + public function testDoubleClick() + { + $node = new NodeElement('elem', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('doubleClick') + ->with('elem'); + + $node->doubleClick(); + } + + public function testCheck() + { + $node = new NodeElement('checkbox_or_radio', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('check') + ->with('checkbox_or_radio'); + + $node->check(); + } + + public function testUncheck() + { + $node = new NodeElement('checkbox_or_radio', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('uncheck') + ->with('checkbox_or_radio'); + + $node->uncheck(); + } + + public function testSelectOption() + { + $node = new NodeElement('select', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('selectOption') + ->with('select', 'item1'); + + $node->selectOption('item1'); + } + + public function testGetTagName() + { + $node = new NodeElement('html//h3', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('getTagName') + ->with('html//h3') + ->will($this->returnValue('h3')); + + $this->assertEquals('h3', $node->getTagName()); + } + + public function testIsVisible() + { + $node = new NodeElement('some_xpath', $this->session); + + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('isVisible') + ->with('some_xpath') + ->will($this->onConsecutiveCalls(true, false)); + + $this->assertTrue($node->isVisible()); + $this->assertFalse($node->isVisible()); + } + + public function testIsChecked() + { + $node = new NodeElement('some_xpath', $this->session); + + $this->session->getDriver() + ->expects($this->exactly(2)) + ->method('isChecked') + ->with('some_xpath') + ->will($this->onConsecutiveCalls(true, false)); + + $this->assertTrue($node->isChecked()); + $this->assertFalse($node->isChecked()); + } + + public function testFocus() + { + $node = new NodeElement('some-element', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('focus') + ->with('some-element'); + + $node->focus(); + } + + public function testBlur() + { + $node = new NodeElement('some-element', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('blur') + ->with('some-element'); + + $node->blur(); + } + + public function testMouseOver() + { + $node = new NodeElement('some-element', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('mouseOver') + ->with('some-element'); + + $node->mouseOver(); + } + + public function dragTo() + { + $node = new NodeElement('some_tag1', $this->session); + + $this->session->getDriver() + ->expects($this->once()) + ->method('triggerEvent') + ->with('some_tag1', 'some_tag3'); + + $node->dragTo(new NodeElement('some_tag2', $this->session)); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/MinkTest.php b/core/vendor/behat/mink/tests/Behat/Mink/MinkTest.php new file mode 100644 index 0000000..6fddabe --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/MinkTest.php @@ -0,0 +1,179 @@ +mink = new Mink(); + } + + public function testRegisterSession() + { + $session = $this->getSessionMock(); + + $this->assertFalse($this->mink->hasSession('not_registered')); + $this->assertFalse($this->mink->hasSession('js')); + $this->assertFalse($this->mink->hasSession('my')); + + $this->mink->registerSession('my', $session); + + $this->assertTrue($this->mink->hasSession('my')); + $this->assertFalse($this->mink->hasSession('not_registered')); + $this->assertFalse($this->mink->hasSession('js')); + } + + public function testSessionAutostop() + { + $session1 = $this->getSessionMock(); + $session2 = $this->getSessionMock(); + $this->mink->registerSession('my1', $session1); + $this->mink->registerSession('my2', $session2); + + $session1 + ->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)); + $session1 + ->expects($this->once()) + ->method('stop'); + $session2 + ->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(false)); + $session2 + ->expects($this->never()) + ->method('stop'); + + unset($this->mink); + } + + public function testNotStartedSession() + { + $session = $this->getSessionMock(); + + $session + ->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(false)); + $session + ->expects($this->once()) + ->method('start'); + + $this->mink->registerSession('mock_session', $session); + $this->assertSame($session, $this->mink->getSession('mock_session')); + + $this->setExpectedException('InvalidArgumentException'); + + $this->mink->getSession('not_registered'); + } + + public function testGetAlreadyStartedSession() + { + $session = $this->getSessionMock(); + + $session + ->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)); + $session + ->expects($this->never()) + ->method('start'); + + $this->mink->registerSession('mock_session', $session); + $this->assertSame($session, $this->mink->getSession('mock_session')); + } + + public function testSetDefaultSessionName() + { + $this->assertNull($this->mink->getDefaultSessionName()); + + $session = $this->getSessionMock(); + $this->mink->registerSession('session_name', $session); + $this->mink->setDefaultSessionName('session_name'); + + $this->assertEquals('session_name', $this->mink->getDefaultSessionName()); + + $this->setExpectedException('InvalidArgumentException'); + + $this->mink->setDefaultSessionName('not_registered'); + } + + public function testGetDefaultSession() + { + $session1 = $this->getSessionMock(); + $session2 = $this->getSessionMock(); + + $this->assertNotSame($session1, $session2); + + $this->mink->registerSession('session_1', $session1); + $this->mink->registerSession('session_2', $session2); + $this->mink->setDefaultSessionName('session_2'); + + $this->assertSame($session1, $this->mink->getSession('session_1')); + $this->assertSame($session2, $this->mink->getSession('session_2')); + $this->assertSame($session2, $this->mink->getSession()); + + $this->mink->setDefaultSessionName('session_1'); + + $this->assertSame($session1, $this->mink->getSession()); + } + + public function testGetNoDefaultSession() + { + $session1 = $this->getSessionMock(); + + $this->mink->registerSession('session_1', $session1); + + $this->setExpectedException('InvalidArgumentException'); + + $this->mink->getSession(); + } + + public function testIsSessionStarted() + { + $session_1 = $this->getSessionMock(); + $session_2 = $this->getSessionMock(); + + $session_1 + ->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(false)); + $session_1 + ->expects($this->never()) + ->method('start'); + + $session_2 + ->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(true)); + $session_2 + ->expects($this->never()) + ->method('start'); + + $this->mink->registerSession('not_started', $session_1); + $this->assertFalse($this->mink->isSessionStarted('not_started')); + + $this->mink->registerSession('started', $session_2); + $this->assertTrue($this->mink->isSessionStarted('started')); + + $this->setExpectedException('InvalidArgumentException'); + + $this->mink->getSession('not_registered'); + } + + + + private function getSessionMock() + { + return $this->getMockBuilder('Behat\Mink\Session') + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Selector/CssSelectorTest.php b/core/vendor/behat/mink/tests/Behat/Mink/Selector/CssSelectorTest.php new file mode 100644 index 0000000..16bf2cf --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Selector/CssSelectorTest.php @@ -0,0 +1,31 @@ +markTestSkipped('Symfony2 CssSelector component not installed'); + } + + $selector = new CssSelector(); + + $this->assertEquals('descendant-or-self::h3', $selector->translateToXPath('h3')); + $this->assertEquals('descendant-or-self::h3/span', $selector->translateToXPath('h3 > span')); + + if (interface_exists('Symfony\Component\CssSelector\XPath\TranslatorInterface')) { + // The rewritten component of Symfony 2.3 checks for attribute existence first for the class. + $expectation = "descendant-or-self::h3/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' my_div ')]"; + } else { + $expectation = "descendant-or-self::h3/*[contains(concat(' ', normalize-space(@class), ' '), ' my_div ')]"; + } + $this->assertEquals($expectation, $selector->translateToXPath('h3 > .my_div')); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Selector/NamedSelectorTest.php b/core/vendor/behat/mink/tests/Behat/Mink/Selector/NamedSelectorTest.php new file mode 100644 index 0000000..4d49156 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Selector/NamedSelectorTest.php @@ -0,0 +1,23 @@ +registerNamedXpath('some', 'my_xpath'); + $this->assertEquals('my_xpath', $selector->translateToXPath('some')); + + $this->setExpectedException('InvalidArgumentException'); + + $selector->translateToXPath('custom'); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/Selector/SelectorsHandlerTest.php b/core/vendor/behat/mink/tests/Behat/Mink/Selector/SelectorsHandlerTest.php new file mode 100644 index 0000000..2ba17db --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/Selector/SelectorsHandlerTest.php @@ -0,0 +1,60 @@ +getMockBuilder('Behat\Mink\Selector\SelectorInterface')->getMock(); + $handler = new SelectorsHandler(); + + $this->assertFalse($handler->isSelectorRegistered('custom')); + + $handler->registerSelector('custom', $selector); + + $this->assertTrue($handler->isSelectorRegistered('custom')); + $this->assertSame($selector, $handler->getSelector('custom')); + } + + public function testSelectorToXpath() + { + $selector = $this->getMockBuilder('Behat\Mink\Selector\SelectorInterface')->getMock(); + $handler = new SelectorsHandler(); + + $handler->registerSelector('custom_selector', $selector); + + $selector + ->expects($this->once()) + ->method('translateToXPath') + ->with($locator = 'some[locator]') + ->will($this->returnValue($ret = '[]some[]locator')); + + $this->assertEquals($ret, $handler->selectorToXpath('custom_selector', $locator)); + + $this->setExpectedException('InvalidArgumentException'); + $handler->selectorToXpath('undefined', 'asd'); + } + + public function testXpathLiteral() + { + $handler = new SelectorsHandler(); + + $this->assertEquals("'some simple string'", $handler->xpathLiteral('some simple string')); + $this->assertEquals( + "'some \"d-brackets\" string'", $handler->xpathLiteral('some "d-brackets" string') + ); + $this->assertEquals( + "\"some 's-brackets' string\"", $handler->xpathLiteral('some \'s-brackets\' string') + ); + $this->assertEquals( + 'concat(\'some \',"\'",\'s-brackets\',"\'",\' and "d-brackets" string\')', + $handler->xpathLiteral('some \'s-brackets\' and "d-brackets" string') + ); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/SessionTest.php b/core/vendor/behat/mink/tests/Behat/Mink/SessionTest.php new file mode 100644 index 0000000..1160821 --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/SessionTest.php @@ -0,0 +1,117 @@ +driver = $this->getMockBuilder('Behat\Mink\Driver\DriverInterface')->getMock(); + $this->selectorsHandler = $this->getMockBuilder('Behat\Mink\Selector\SelectorsHandler')->getMock(); + $this->session = new Session($this->driver, $this->selectorsHandler); + } + + public function testGetDriver() + { + $this->assertSame($this->driver, $this->session->getDriver()); + } + + public function testGetPage() + { + $this->assertInstanceOf('Behat\Mink\Element\DocumentElement', $this->session->getPage()); + } + + public function testGetSelectorsHandler() + { + $this->assertSame($this->selectorsHandler, $this->session->getSelectorsHandler()); + } + + public function testVisit() + { + $this->driver + ->expects($this->once()) + ->method('visit') + ->with($url = 'some_url'); + + $this->session->visit($url); + } + + public function testReset() + { + $this->driver + ->expects($this->once()) + ->method('reset'); + + $this->session->reset(); + } + + public function testGetResponseHeaders() + { + $this->driver + ->expects($this->once()) + ->method('getResponseHeaders') + ->will($this->returnValue($ret = array(2, 3, 4))); + + $this->assertEquals($ret, $this->session->getResponseHeaders()); + } + + public function testGetStatusCode() + { + $this->driver + ->expects($this->once()) + ->method('getStatusCode') + ->will($this->returnValue($ret = 404)); + + $this->assertEquals($ret, $this->session->getStatusCode()); + } + + public function testGetCurrentUrl() + { + $this->driver + ->expects($this->once()) + ->method('getCurrentUrl') + ->will($this->returnValue($ret = 'http://some.url')); + + $this->assertEquals($ret, $this->session->getCurrentUrl()); + } + + public function testExecuteScript() + { + $this->driver + ->expects($this->once()) + ->method('executeScript') + ->with($arg = 'JS'); + + $this->session->executeScript($arg); + } + + public function testEvaluateScript() + { + $this->driver + ->expects($this->once()) + ->method('evaluateScript') + ->with($arg = 'JS func') + ->will($this->returnValue($ret = '23')); + + $this->assertEquals($ret, $this->session->evaluateScript($arg)); + } + + public function testWait() + { + $this->driver + ->expects($this->once()) + ->method('wait') + ->with(1000, 'function() {}'); + + $this->session->wait(1000, 'function() {}'); + } +} diff --git a/core/vendor/behat/mink/tests/Behat/Mink/WebAssertTest.php b/core/vendor/behat/mink/tests/Behat/Mink/WebAssertTest.php new file mode 100644 index 0000000..d66ee5c --- /dev/null +++ b/core/vendor/behat/mink/tests/Behat/Mink/WebAssertTest.php @@ -0,0 +1,851 @@ +session = $this->getMockBuilder('Behat\\Mink\\Session') + ->disableOriginalConstructor() + ->getMock(); + $this->assert = new WebAssert($this->session); + } + + public function testAddressEquals() + { + $this->session + ->expects($this->exactly(2)) + ->method('getCurrentUrl') + ->will($this->returnValue('http://example.com/script.php/sub/url')) + ; + + $this->assertCorrectAssertion('addressEquals', array('/sub/url')); + $this->assertWrongAssertion( + 'addressEquals', array('sub_url'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'Current page is "/sub/url", but "sub_url" expected.' + ); + } + + public function testAddressNotEquals() + { + $this->session + ->expects($this->exactly(2)) + ->method('getCurrentUrl') + ->will($this->returnValue('http://example.com/script.php/sub/url')) + ; + + $this->assertCorrectAssertion('addressNotEquals', array('sub_url')); + $this->assertWrongAssertion( + 'addressNotEquals', array('/sub/url'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'Current page is "/sub/url", but should not be.' + ); + } + + public function testAddressMatches() + { + $this->session + ->expects($this->exactly(2)) + ->method('getCurrentUrl') + ->will($this->returnValue('http://example.com/script.php/sub/url')) + ; + + $this->assertCorrectAssertion('addressMatches', array('/su.*rl/')); + $this->assertWrongAssertion( + 'addressMatches', array('/suburl/'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'Current page "/sub/url" does not match the regex "/suburl/".' + ); + } + + /** + * @covers Behat\Mink\WebAssert::cookieEquals + */ + public function testCookieEquals() + { + $this->session-> + expects($this->any())-> + method('getCookie')-> + will($this->returnValueMap( + array( + array('foo', 'bar'), + array('bar', 'baz'), + ) + )); + + $this->assertCorrectAssertion('cookieEquals', array('foo', 'bar')); + $this->assertWrongAssertion( + 'cookieEquals', array('bar', 'foo'), + 'Behat\Mink\Exception\ExpectationException', + 'Cookie "bar" value is "baz", but should be "foo".' + ); + } + + /** + * @covers Behat\Mink\WebAssert::cookieExists + */ + public function testCookieExists() + { + $this->session-> + expects($this->any())-> + method('getCookie')-> + will($this->returnValueMap( + array( + array('foo', '1'), + array('bar', null), + ) + )); + + $this->assertCorrectAssertion('cookieExists', array('foo')); + $this->assertWrongAssertion( + 'cookieExists', array('bar'), + 'Behat\Mink\Exception\ExpectationException', + 'Cookie "bar" is not set, but should be.' + ); + } + + public function testStatusCodeEquals() + { + $this->session + ->expects($this->exactly(2)) + ->method('getStatusCode') + ->will($this->returnValue(200)) + ; + + $this->assertCorrectAssertion('statusCodeEquals', array(200)); + $this->assertWrongAssertion( + 'statusCodeEquals', array(404), + 'Behat\\Mink\\Exception\\ExpectationException', + 'Current response status code is 200, but 404 expected.' + ); + } + + public function testPageTextContains() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('getText') + ->will($this->returnValue("Some page\n\ttext")) + ; + + $this->assertCorrectAssertion('pageTextContains', array('PAGE text')); + $this->assertWrongAssertion( + 'pageTextContains', array('html text'), + 'Behat\\Mink\\Exception\\ResponseTextException', + 'The text "html text" was not found anywhere in the text of the current page.' + ); + } + + public function testPageTextNotContains() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('getText') + ->will($this->returnValue("Some html\n\ttext")) + ; + + $this->assertCorrectAssertion('pageTextNotContains', array('PAGE text')); + $this->assertWrongAssertion( + 'pageTextNotContains', array('HTML text'), + 'Behat\\Mink\\Exception\\ResponseTextException', + 'The text "HTML text" appears in the text of this page, but it should not.' + ); + } + + public function testPageTextMatches() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('getText') + ->will($this->returnValue('Some page text')) + ; + + $this->assertCorrectAssertion('pageTextMatches', array('/PA.E/i')); + $this->assertWrongAssertion( + 'pageTextMatches', array('/html/'), + 'Behat\\Mink\\Exception\\ResponseTextException', + 'The pattern /html/ was not found anywhere in the text of the current page.' + ); + } + + public function testPageTextNotMatches() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('getText') + ->will($this->returnValue('Some html text')) + ; + + $this->assertCorrectAssertion('pageTextNotMatches', array('/PA.E/i')); + $this->assertWrongAssertion( + 'pageTextNotMatches', array('/HTML/i'), + 'Behat\\Mink\\Exception\\ResponseTextException', + 'The pattern /HTML/i was found in the text of the current page, but it should not.' + ); + } + + + public function testResponseContains() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('getContent') + ->will($this->returnValue('Some page text')) + ; + + $this->assertCorrectAssertion('responseContains', array('PAGE text')); + $this->assertWrongAssertion( + 'responseContains', array('html text'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The string "html text" was not found anywhere in the HTML response of the current page.' + ); + } + + public function testResponseNotContains() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('getContent') + ->will($this->returnValue('Some html text')) + ; + + $this->assertCorrectAssertion('responseNotContains', array('PAGE text')); + $this->assertWrongAssertion( + 'responseNotContains', array('HTML text'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The string "HTML text" appears in the HTML response of this page, but it should not.' + ); + } + + public function testResponseMatches() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('getContent') + ->will($this->returnValue('Some page text')) + ; + + $this->assertCorrectAssertion('responseMatches', array('/PA.E/i')); + $this->assertWrongAssertion( + 'responseMatches', array('/html/'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The pattern /html/ was not found anywhere in the HTML response of the page.' + ); + } + + public function testResponseNotMatches() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('getContent') + ->will($this->returnValue('Some html text')) + ; + + $this->assertCorrectAssertion('responseNotMatches', array('/PA.E/i')); + $this->assertWrongAssertion( + 'responseNotMatches', array('/HTML/i'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The pattern /HTML/i was found in the HTML response of the page, but it should not.' + ); + } + + public function testElementsCount() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('findAll') + ->with('css', 'h2 > span') + ->will($this->returnValue(array(1, 2))) + ; + + $this->assertCorrectAssertion('elementsCount', array('css', 'h2 > span', 2)); + $this->assertWrongAssertion( + 'elementsCount', array('css', 'h2 > span', 3), + 'Behat\\Mink\\Exception\\ExpectationException', + '2 elements matching css "h2 > span" found on the page, but should be 3.' + ); + } + + public function testElementExists() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(4)) + ->method('find') + ->with('css', 'h2 > span') + ->will($this->onConsecutiveCalls(1, null, 1, null)) + ; + + $this->assertCorrectAssertion('elementExists', array('css', 'h2 > span')); + $this->assertWrongAssertion( + 'elementExists', array('css', 'h2 > span'), + 'Behat\\Mink\\Exception\\ElementNotFoundException', + 'Element matching css "h2 > span" not found.' + ); + + $this->assertCorrectAssertion('elementExists', array('css', 'h2 > span', $page)); + $this->assertWrongAssertion( + 'elementExists', array('css', 'h2 > span', $page), + 'Behat\\Mink\\Exception\\ElementNotFoundException', + 'Element matching css "h2 > span" not found.' + ); + } + + public function testElementNotExists() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(4)) + ->method('find') + ->with('css', 'h2 > span') + ->will($this->onConsecutiveCalls(null, 1, null, 1)) + ; + + $this->assertCorrectAssertion('elementNotExists', array('css', 'h2 > span')); + $this->assertWrongAssertion( + 'elementNotExists', array('css', 'h2 > span'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'An element matching css "h2 > span" appears on this page, but it should not.' + ); + + $this->assertCorrectAssertion('elementNotExists', array('css', 'h2 > span', $page)); + $this->assertWrongAssertion( + 'elementNotExists', array('css', 'h2 > span', $page), + 'Behat\\Mink\\Exception\\ExpectationException', + 'An element matching css "h2 > span" appears on this page, but it should not.' + ); + } + + public function testElementTextContains() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('find') + ->with('css', 'h2 > span') + ->will($this->returnValue($element)) + ; + + $element + ->expects($this->exactly(2)) + ->method('getText') + ->will($this->returnValue('element text')) + ; + + $this->assertCorrectAssertion('elementTextContains', array('css', 'h2 > span', 'text')); + $this->assertWrongAssertion( + 'elementTextContains', array('css', 'h2 > span', 'html'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The text "html" was not found in the text of the element matching css "h2 > span".' + ); + } + + public function testElementTextNotContains() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('find') + ->with('css', 'h2 > span') + ->will($this->returnValue($element)) + ; + + $element + ->expects($this->exactly(2)) + ->method('getText') + ->will($this->returnValue('element text')) + ; + + $this->assertCorrectAssertion('elementTextNotContains', array('css', 'h2 > span', 'html')); + $this->assertWrongAssertion( + 'elementTextNotContains', array('css', 'h2 > span', 'text'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The text "text" appears in the text of the element matching css "h2 > span", but it should not.' + ); + } + + public function testElementContains() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('find') + ->with('css', 'h2 > span') + ->will($this->returnValue($element)) + ; + + $element + ->expects($this->exactly(2)) + ->method('getHtml') + ->will($this->returnValue('element html')) + ; + + $this->assertCorrectAssertion('elementContains', array('css', 'h2 > span', 'html')); + $this->assertWrongAssertion( + 'elementContains', array('css', 'h2 > span', 'text'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The string "text" was not found in the HTML of the element matching css "h2 > span".' + ); + } + + public function testElementNotContains() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('find') + ->with('css', 'h2 > span') + ->will($this->returnValue($element)) + ; + + $element + ->expects($this->exactly(2)) + ->method('getHtml') + ->will($this->returnValue('element html')) + ; + + $this->assertCorrectAssertion('elementNotContains', array('css', 'h2 > span', 'text')); + $this->assertWrongAssertion( + 'elementNotContains', array('css', 'h2 > span', 'html'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The string "html" appears in the HTML of the element matching css "h2 > span", but it should not.' + ); + } + + public function testFieldExists() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('findField') + ->with('username') + ->will($this->onConsecutiveCalls($element, null)) + ; + + $this->assertCorrectAssertion('fieldExists', array('username')); + $this->assertWrongAssertion( + 'fieldExists', array('username'), + 'Behat\\Mink\\Exception\\ElementNotFoundException', + 'Form field with id|name|label|value "username" not found.' + ); + } + + public function testFieldNotExists() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('findField') + ->with('username') + ->will($this->onConsecutiveCalls(null, $element)) + ; + + $this->assertCorrectAssertion('fieldNotExists', array('username')); + $this->assertWrongAssertion( + 'fieldNotExists', array('username'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'A field "username" appears on this page, but it should not.' + ); + } + + public function testFieldValueEquals() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('findField') + ->with('username') + ->will($this->returnValue($element)) + ; + + $element + ->expects($this->exactly(2)) + ->method('getValue') + ->will($this->returnValue(234)) + ; + + $this->assertCorrectAssertion('fieldValueEquals', array('username', 234)); + $this->assertWrongAssertion( + 'fieldValueEquals', array('username', 235), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The field "username" value is "234", but "235" expected.' + ); + } + + public function testFieldValueNotEquals() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('findField') + ->with('username') + ->will($this->returnValue($element)) + ; + + $element + ->expects($this->exactly(2)) + ->method('getValue') + ->will($this->returnValue(235)) + ; + + $this->assertCorrectAssertion('fieldValueNotEquals', array('username', 234)); + $this->assertWrongAssertion( + 'fieldValueNotEquals', array('username', 235), + 'Behat\\Mink\\Exception\\ExpectationException', + 'The field "username" value is "235", but it should not be.' + ); + } + + public function testCheckboxChecked() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('findField') + ->with('remember_me') + ->will($this->returnValue($element)) + ; + + $element + ->expects($this->exactly(2)) + ->method('isChecked') + ->will($this->onConsecutiveCalls(true, false)) + ; + + $this->assertCorrectAssertion('checkboxChecked', array('remember_me')); + $this->assertWrongAssertion( + 'checkboxChecked', array('remember_me'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'Checkbox "remember_me" is not checked, but it should be.' + ); + } + + public function testCheckboxNotChecked() + { + $page = $this->getMockBuilder('Behat\\Mink\\Element\\DocumentElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $element = $this->getMockBuilder('Behat\\Mink\\Element\\NodeElement') + ->disableOriginalConstructor() + ->getMock() + ; + + $this->session + ->expects($this->exactly(2)) + ->method('getPage') + ->will($this->returnValue($page)) + ; + + $page + ->expects($this->exactly(2)) + ->method('findField') + ->with('remember_me') + ->will($this->returnValue($element)) + ; + + $element + ->expects($this->exactly(2)) + ->method('isChecked') + ->will($this->onConsecutiveCalls(false, true)) + ; + + $this->assertCorrectAssertion('checkboxNotChecked', array('remember_me')); + $this->assertWrongAssertion( + 'checkboxNotChecked', array('remember_me'), + 'Behat\\Mink\\Exception\\ExpectationException', + 'Checkbox "remember_me" is checked, but it should not be.' + ); + } + + protected function assertCorrectAssertion($assertion, $arguments) + { + try { + call_user_func_array(array($this->assert, $assertion), $arguments); + } catch (\Exception $e) { + $this->fail('Correct assertion should not throw an exception: '.$e->getMessage()); + } + } + + protected function assertWrongAssertion($assertion, $arguments, $exceptionClass, $exceptionMessage) + { + try { + call_user_func_array(array($this->assert, $assertion), $arguments); + $this->fail('Wrong assertion should throw an exception'); + } catch (\Exception $e) { + $this->assertInstanceOf($exceptionClass, $e); + $this->assertSame($exceptionMessage, $e->getMessage()); + } + } +} \ No newline at end of file diff --git a/core/vendor/composer/autoload_files.php b/core/vendor/composer/autoload_files.php index 2ca5d44..ae1a45a 100644 --- a/core/vendor/composer/autoload_files.php +++ b/core/vendor/composer/autoload_files.php @@ -7,7 +7,7 @@ return array( $vendorDir . '/guzzlehttp/streams/src/functions.php', - $vendorDir . '/kriswallsmith/assetic/src/functions.php', $vendorDir . '/guzzlehttp/guzzle/src/functions.php', + $vendorDir . '/kriswallsmith/assetic/src/functions.php', $baseDir . '/core/lib/Drupal.php', ); diff --git a/core/vendor/composer/autoload_namespaces.php b/core/vendor/composer/autoload_namespaces.php index f49ab2c..68cc873 100644 --- a/core/vendor/composer/autoload_namespaces.php +++ b/core/vendor/composer/autoload_namespaces.php @@ -21,12 +21,15 @@ 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'), 'Symfony\\Component\\DependencyInjection\\' => array($vendorDir . '/symfony/dependency-injection'), 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), 'Symfony\\Component\\ClassLoader\\' => array($vendorDir . '/symfony/class-loader'), + 'Symfony\\Component\\BrowserKit\\' => array($vendorDir . '/symfony/browser-kit'), 'Symfony\\Cmf\\Component\\Routing' => array($vendorDir . '/symfony-cmf/routing'), 'Psr\\Log\\' => array($vendorDir . '/psr/log'), + 'Goutte' => array($vendorDir . '/fabpot/goutte'), 'Gliph' => array($vendorDir . '/sdboyer/gliph/src'), 'EasyRdf_' => array($vendorDir . '/easyrdf/easyrdf/lib'), 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib'), @@ -35,5 +38,7 @@ 'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib'), 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib'), 'Doctrine\\Common\\' => array($vendorDir . '/doctrine/common/lib'), + 'Behat\\Mink\\Driver' => array($vendorDir . '/behat/mink-browserkit-driver/src', $vendorDir . '/behat/mink-goutte-driver/src'), + 'Behat\\Mink' => array($vendorDir . '/behat/mink/src'), 'Assetic' => array($vendorDir . '/kriswallsmith/assetic/src'), ); diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json index c701f61..5fb6b25 100644 --- a/core/vendor/composer/installed.json +++ b/core/vendor/composer/installed.json @@ -2405,6 +2405,366 @@ ] }, { + "name": "mikey179/vfsStream", + "version": "v1.2.0", + "version_normalized": "1.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/mikey179/vfsStream.git", + "reference": "063fb10633f10c5ccbcac26227e94f46d9336f90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/063fb10633f10c5ccbcac26227e94f46d9336f90", + "reference": "063fb10633f10c5ccbcac26227e94f46d9336f90", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2013-04-01 10:41:02", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "org\\bovigo\\vfs\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "homepage": "http://vfs.bovigo.org/" + }, + { + "name": "symfony/dom-crawler", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "target-dir": "Symfony/Component/DomCrawler", + "source": { + "type": "git", + "url": "https://github.com/symfony/DomCrawler.git", + "reference": "b3d748f9d7ae77890d935bb97445c666b83dd59e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/b3d748f9d7ae77890d935bb97445c666b83dd59e", + "reference": "b3d748f9d7ae77890d935bb97445c666b83dd59e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/css-selector": "~2.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "time": "2014-07-09 09:05:48", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\DomCrawler\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/browser-kit", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "target-dir": "Symfony/Component/BrowserKit", + "source": { + "type": "git", + "url": "https://github.com/symfony/BrowserKit.git", + "reference": "b5e807a669333ac9e7def19ef39a6e542786010d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/b5e807a669333ac9e7def19ef39a6e542786010d", + "reference": "b5e807a669333ac9e7def19ef39a6e542786010d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/dom-crawler": "~2.0" + }, + "require-dev": { + "symfony/css-selector": "~2.0", + "symfony/process": "~2.0" + }, + "suggest": { + "symfony/process": "" + }, + "time": "2014-07-09 09:05:48", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\BrowserKit\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "http://symfony.com" + }, + { + "name": "fabpot/goutte", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/fabpot/Goutte.git", + "reference": "b12c3f7ec68d8814b50444cfe142fd0a056557f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fabpot/Goutte/zipball/b12c3f7ec68d8814b50444cfe142fd0a056557f9", + "reference": "b12c3f7ec68d8814b50444cfe142fd0a056557f9", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "4.*", + "php": ">=5.4.0", + "symfony/browser-kit": "~2.1", + "symfony/css-selector": "~2.1", + "symfony/dom-crawler": "~2.1" + }, + "time": "2014-07-22 13:24:11", + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Goutte": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A simple PHP Web Scraper", + "homepage": "https://github.com/fabpot/Goutte", + "keywords": [ + "scraper" + ] + }, + { + "name": "behat/mink", + "version": "v1.5.0", + "version_normalized": "1.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Mink.git", + "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Mink/zipball/0769e6d9726c140a54dbf827a438c0f9912749fe", + "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/css-selector": "~2.0" + }, + "suggest": { + "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", + "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", + "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", + "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)" + }, + "time": "2013-04-13 23:39:27", + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "1.5.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Behat\\Mink": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Web acceptance testing framework for PHP 5.3", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "testing", + "web" + ] + }, + { + "name": "behat/mink-browserkit-driver", + "version": "v1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/MinkBrowserKitDriver.git", + "reference": "63960c8fcad4529faad1ff33e950217980baa64c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/MinkBrowserKitDriver/zipball/63960c8fcad4529faad1ff33e950217980baa64c", + "reference": "63960c8fcad4529faad1ff33e950217980baa64c", + "shasum": "" + }, + "require": { + "behat/mink": "~1.5.0", + "php": ">=5.3.1", + "symfony/browser-kit": "~2.0", + "symfony/dom-crawler": "~2.0" + }, + "require-dev": { + "silex/silex": "@dev" + }, + "time": "2013-04-13 23:46:30", + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Behat\\Mink\\Driver": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Symfony2 BrowserKit driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "Mink", + "Symfony2", + "browser", + "testing" + ] + }, + { + "name": "behat/mink-goutte-driver", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/larowlan/MinkGoutteDriver.git", + "reference": "488f7f02b1e907888f4b156b635693daf51d760c" + }, + "require": { + "behat/mink": "~1.5@dev", + "behat/mink-browserkit-driver": "~1.1@dev", + "fabpot/goutte": "dev-master", + "php": ">=5.4" + }, + "time": "2014-06-04 04:29:46", + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "source", + "autoload": { + "psr-0": { + "Behat\\Mink\\Driver": "src/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Goutte driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "goutte", + "headless", + "testing" + ] + }, + { "name": "guzzlehttp/guzzle", "version": "4.1.3", "version_normalized": "4.1.3.0", @@ -2439,7 +2799,7 @@ "dev-master": "4.1.x-dev" } }, - "installation-source": "dist", + "installation-source": "source", "autoload": { "psr-4": { "GuzzleHttp\\": "src/" @@ -2470,37 +2830,5 @@ "rest", "web service" ] - }, - { - "name": "mikey179/vfsStream", - "version": "v1.2.0", - "version_normalized": "1.2.0.0", - "source": { - "type": "git", - "url": "https://github.com/mikey179/vfsStream.git", - "reference": "063fb10633f10c5ccbcac26227e94f46d9336f90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/063fb10633f10c5ccbcac26227e94f46d9336f90", - "reference": "063fb10633f10c5ccbcac26227e94f46d9336f90", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "time": "2013-04-01 10:41:02", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-0": { - "org\\bovigo\\vfs\\": "src/main/php" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD" - ], - "homepage": "http://vfs.bovigo.org/" } ] diff --git a/core/vendor/fabpot/goutte/.gitignore b/core/vendor/fabpot/goutte/.gitignore new file mode 100644 index 0000000..81b9258 --- /dev/null +++ b/core/vendor/fabpot/goutte/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor diff --git a/core/vendor/fabpot/goutte/.travis.yml b/core/vendor/fabpot/goutte/.travis.yml new file mode 100644 index 0000000..21a2aa0 --- /dev/null +++ b/core/vendor/fabpot/goutte/.travis.yml @@ -0,0 +1,17 @@ +language: php + +php: + - '5.6' + - '5.5' + - '5.4' + - hhvm + +before_script: + - composer install -n + +script: + - phpunit + +matrix: + allow_failures: + - php: hhvm diff --git a/core/vendor/fabpot/goutte/Goutte/Client.php b/core/vendor/fabpot/goutte/Goutte/Client.php new file mode 100644 index 0000000..8a25f19 --- /dev/null +++ b/core/vendor/fabpot/goutte/Goutte/Client.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Goutte; + +use GuzzleHttp\Client as GuzzleClient; +use GuzzleHttp\ClientInterface as GuzzleClientInterface; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Message\RequestInterface; +use GuzzleHttp\Message\Response as GuzzleResponse; +use GuzzleHttp\Post\PostFile; +use Symfony\Component\BrowserKit\Client as BaseClient; +use Symfony\Component\BrowserKit\Response; + +/** + * Client. + * + * @author Fabien Potencier + * @author Michael Dowling + */ +class Client extends BaseClient +{ + protected $client; + + private $headers = array(); + private $auth = null; + + public function setClient(GuzzleClientInterface $client) + { + $this->client = $client; + + return $this; + } + + public function getClient() + { + if (!$this->client) { + $this->client = new GuzzleClient(array('defaults' => array('allow_redirects' => false, 'cookies' => true))); + } + + return $this->client; + } + + public function setHeader($name, $value) + { + $this->headers[$name] = $value; + + return $this; + } + + public function removeHeader($name) + { + unset($this->headers[$name]); + } + + public function setAuth($user, $password = '', $type = 'basic') + { + $this->auth = array($user, $password, $type); + + return $this; + } + + public function resetAuth() + { + $this->auth = null; + + return $this; + } + + protected function doRequest($request) + { + $headers = array(); + foreach ($request->getServer() as $key => $val) { + $key = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $key))))); + $contentHeaders = array('Content-length' => true, 'Content-md5' => true, 'Content-type' => true); + if (0 === strpos($key, 'Http-')) { + $headers[substr($key, 5)] = $val; + } + // CONTENT_* are not prefixed with HTTP_ + elseif (isset($contentHeaders[$key])) { + $headers[$key] = $val; + } + } + + $body = null; + if (!in_array($request->getMethod(), array('GET','HEAD'))) { + if (null !== $request->getContent()) { + $body = $request->getContent(); + } else { + $body = $request->getParameters(); + } + } + + $this->getClient()->setDefaultOption('auth', $this->auth); + + $requestOptions = array( + 'body' => $body, + 'cookies' => $this->getCookieJar()->allRawValues($request->getUri()), + 'allow_redirects' => false, + 'timeout' => 30, + ); + + if (!empty($headers)) { + $requestOptions['headers'] = $headers; + } + + $guzzleRequest = $this->getClient()->createRequest( + $request->getMethod(), + $request->getUri(), + $requestOptions + ); + + foreach ($this->headers as $name => $value) { + $guzzleRequest->setHeader($name, $value); + } + + if ('POST' == $request->getMethod() || 'PUT' == $request->getMethod()) { + $this->addPostFiles($guzzleRequest, $request->getFiles()); + } + + // Let BrowserKit handle redirects + try { + $response = $this->getClient()->send($guzzleRequest); + } catch (RequestException $e) { + $response = $e->getResponse(); + if (null === $response) { + throw $e; + } + } + + return $this->createResponse($response); + } + + protected function addPostFiles(RequestInterface $request, array $files, $arrayName = '') + { + foreach ($files as $name => $info) { + if (!empty($arrayName)) { + $name = $arrayName.'['.$name.']'; + } + + if (is_array($info)) { + if (isset($info['tmp_name'])) { + if ('' !== $info['tmp_name']) { + $request->getBody()->addFile(new PostFile($name, fopen($info['tmp_name'], 'r'), isset($info['name']) ? $info['name'] : null)); + } else { + continue; + } + } else { + $this->addPostFiles($request, $info, $name); + } + } else { + $request->getBody()->addFile(new PostFile($name, fopen($info, 'r'))); + } + } + } + + protected function createResponse(GuzzleResponse $response) + { + $headers = $response->getHeaders(); + + return new Response($response->getBody(true), $response->getStatusCode(), $headers); + } +} diff --git a/core/vendor/fabpot/goutte/Goutte/Resources/phar-stub.php b/core/vendor/fabpot/goutte/Goutte/Resources/phar-stub.php new file mode 100644 index 0000000..b9c3b44 --- /dev/null +++ b/core/vendor/fabpot/goutte/Goutte/Resources/phar-stub.php @@ -0,0 +1,14 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +require_once 'phar://'.__FILE__.'/vendor/autoload.php'; + +__HALT_COMPILER(); diff --git a/core/vendor/fabpot/goutte/Goutte/Tests/ClientTest.php b/core/vendor/fabpot/goutte/Goutte/Tests/ClientTest.php new file mode 100644 index 0000000..b227e10 --- /dev/null +++ b/core/vendor/fabpot/goutte/Goutte/Tests/ClientTest.php @@ -0,0 +1,311 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Goutte\Tests; + +use Goutte\Client; +use GuzzleHttp\Client as GuzzleClient; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Message\Response as GuzzleResponse; +use GuzzleHttp\Stream\Stream; +use GuzzleHttp\Subscriber\History; +use GuzzleHttp\Subscriber\Mock; +use GuzzleHttp\Post\PostFile; +use Symfony\Component\BrowserKit\Cookie; + +/** + * Goutte Client Test + * + * @author Michael Dowling + */ +class ClientTest extends \PHPUnit_Framework_TestCase +{ + protected $history; + protected $mock; + + protected function getGuzzle() + { + $this->history = new History(); + $this->mock = new Mock(); + $this->mock->addResponse(new GuzzleResponse(200, array(), Stream::factory('

Hi

'))); + $guzzle = new GuzzleClient(array('redirect.disable' => true, 'base_url' => '')); + $guzzle->getEmitter()->attach($this->mock); + $guzzle->getEmitter()->attach($this->history); + + return $guzzle; + } + + public function testCreatesDefaultClient() + { + $client = new Client(); + $this->assertInstanceOf('GuzzleHttp\\ClientInterface', $client->getClient()); + } + + public function testUsesCustomClient() + { + $guzzle = new GuzzleClient(); + $client = new Client(); + $this->assertSame($client, $client->setClient($guzzle)); + $this->assertSame($guzzle, $client->getClient()); + } + + public function testUsesCustomHeaders() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $client->setHeader('X-Test', 'test'); + $crawler = $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('test', $this->history->getLastRequest()->getHeader('X-Test')); + } + + public function testCustomUserAgent() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $client->setHeader('User-Agent', 'foo'); + $crawler = $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('foo', $this->history->getLastRequest()->getHeader('User-Agent')); + } + + public function testUsesAuth() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $client->setAuth('me', '**'); + $crawler = $client->request('GET', 'http://www.example.com/'); + $request = $this->history->getLastRequest(); + $this->assertEquals('me', $request->getConfig()->get('auth')[0]); + $this->assertEquals('**', $request->getConfig()->get('auth')[1]); + $this->assertEquals('basic', $request->getConfig()->get('auth')[2]); + } + + public function testResetsAuth() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $client->setAuth('me', '**'); + $client->resetAuth(); + $crawler = $client->request('GET', 'http://www.example.com/'); + $request = $this->history->getLastRequest(); + $this->assertNull($request->getConfig()->get('auth')[0]); + $this->assertNull($request->getConfig()->get('auth')[1]); + } + + public function testUsesCookies() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $client->getCookieJar()->set(new Cookie('test', '123')); + $crawler = $client->request('GET', 'http://www.example.com/'); + $request = $this->history->getLastRequest(); + $this->assertEquals('test=123', $request->getHeader('Cookie')); + } + + public function testUsesPostFiles() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $files = array( + 'test' => array( + 'name' => 'test.txt', + 'tmp_name' => __FILE__ + ) + ); + + $crawler = $client->request('POST', 'http://www.example.com/', array(), $files); + $request = $this->history->getLastRequest(); + + $files = $request->getBody()->getFiles(); + $this->assertFile(reset($files), 'test', 'test.txt', array( + 'Content-Type' => 'text/plain', + 'Content-Disposition' => 'form-data; filename="test.txt"; name="test"', + )); + } + + public function testUsesPostNamedFiles() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $files = array( + 'test' => __FILE__ + ); + + $crawler = $client->request('POST', 'http://www.example.com/', array(), $files); + $request = $this->history->getLastRequest(); + $files = $request->getBody()->getFiles(); + $this->assertFile(reset($files), 'test', __FILE__, array( + 'Content-Type' => 'text/x-php', + 'Content-Disposition' => 'form-data; filename="ClientTest.php"; name="test"', + )); + } + + public function testUsesPostFilesNestedFields() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $files = array( + 'form' => array( + 'test' => array( + 'name' => 'test.txt', + 'tmp_name' => __FILE__ + ), + ), + ); + + $crawler = $client->request('POST', 'http://www.example.com/', array(), $files); + $request = $this->history->getLastRequest(); + $files = $request->getBody()->getFiles(); + $this->assertFile(reset($files), 'form[test]', 'test.txt', array( + 'Content-Type' => 'text/plain', + 'Content-Disposition' => 'form-data; filename="test.txt"; name="form[test]"', + )); + } + + public function testUsesPostFilesOnClientSide() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $files = array( + 'test' => __FILE__, + ); + + $crawler = $client->request('POST', 'http://www.example.com/', array(), $files); + $request = $this->history->getLastRequest(); + $files = $request->getBody()->getFiles(); + $this->assertFile(reset($files), 'test', __FILE__, array( + 'Content-Type' => 'text/x-php', + 'Content-Disposition' => 'form-data; filename="ClientTest.php"; name="test"', + )); + } + + public function testUsesPostFilesUploadError() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $files = array( + 'test' => array( + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => 4, + 'size' => 0, + ), + ); + + $crawler = $client->request('POST', 'http://www.example.com/', array(), $files); + $request = $this->history->getLastRequest(); + + $this->assertEquals(array(), $request->getBody()->getFiles()); + } + + public function testCreatesResponse() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $crawler = $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('Hi', $crawler->filter('p')->text()); + } + + public function testHandlesRedirectsCorrectly() + { + $guzzle = $this->getGuzzle(); + + $this->mock->clearQueue(); + $this->mock->addResponse(new GuzzleResponse(301, array( + 'Location' => 'http://www.example.com/' + ))); + $this->mock->addResponse(new GuzzleResponse(200, [], Stream::factory('

Test

'))); + + $client = new Client(); + $client->setClient($guzzle); + + $crawler = $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('Test', $crawler->filter('p')->text()); + + // Ensure that two requests were sent + $this->assertEquals(2, count($this->history)); + } + + public function testConvertsGuzzleHeadersToArrays() + { + $guzzle = $this->getGuzzle(); + + $this->mock->clearQueue(); + $this->mock->addResponse(new GuzzleResponse(200, array( + 'Date' => 'Tue, 04 Jun 2013 13:22:41 GMT', + ))); + + $client = new Client(); + $client->setClient($guzzle); + $client->request('GET', 'http://www.example.com/'); + $response = $client->getResponse(); + $headers = $response->getHeaders(); + + $this->assertInternalType("array", array_shift($headers), "Header not converted from Guzzle\Http\Message\Header to array"); + } + + public function testNullResponseException() + { + $this->setExpectedException('GuzzleHttp\Exception\RequestException'); + $guzzle = $this->getGuzzle(); + $this->mock->clearQueue(); + $exception = new RequestException('', $this->getMock('GuzzleHttp\Message\RequestInterface')); + $this->mock->addException($exception); + $client = new Client(); + $client->setClient($guzzle); + $client->request('GET', 'http://www.example.com/'); + $response = $client->getResponse(); + } + + protected function assertFile(PostFile $postFile, $fieldName, $fileName, $headers) + { + $this->assertEquals($postFile->getName(), $fieldName); + $this->assertEquals($postFile->getFilename(), $fileName); + $this->assertEquals($postFile->getHeaders(), $headers); + } + + public function testHttps() + { + $guzzle = $this->getGuzzle(); + + $this->mock->clearQueue(); + $this->mock->addResponse(new GuzzleResponse(200, [], Stream::factory('

Test

'))); + $client = new Client(); + $client->setClient($guzzle); + $crawler = $client->request('GET', 'https://www.example.com/'); + $this->assertEquals('https', $this->history->getLastRequest()->getScheme()); + $this->assertEquals('Test', $crawler->filter('p')->text()); + } + + public function testCustomUserAgentConstructor() + { + $guzzle = $this->getGuzzle(); + $client = new Client([ + 'HTTP_HOST' => '1.2.3.4', + 'HTTP_USER_AGENT' => 'SomeHost' + ]); + $client->setClient($guzzle); + $crawler = $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('SomeHost', $this->history->getLastRequest()->getHeader('User-Agent')); + } + +} diff --git a/core/vendor/fabpot/goutte/LICENSE b/core/vendor/fabpot/goutte/LICENSE new file mode 100644 index 0000000..ff727f4 --- /dev/null +++ b/core/vendor/fabpot/goutte/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/core/vendor/fabpot/goutte/README.rst b/core/vendor/fabpot/goutte/README.rst new file mode 100644 index 0000000..58eecdb --- /dev/null +++ b/core/vendor/fabpot/goutte/README.rst @@ -0,0 +1,114 @@ +Goutte, a simple PHP Web Scraper +================================ + +Goutte is a screen scraping and web crawling library for PHP. + +Goutte provides a nice API to crawl websites and extract data from the HTML/XML +responses. + +Requirements +------------ + +Goutte depends on PHP 5.4+ and Guzzle 4+. + +.. tip:: + + If you need support for PHP 5.3 or Guzzle 3, use Goutte 1.0.6. + +Installation +------------ + +Add ``fabpot/goutte`` as a require dependency in your ``composer.json`` file: + +.. code-block:: bash + + php composer.phar require fabpot/goutte:~2.0 + +.. tip:: + + You can also download the `Goutte.phar`_ file: + + .. code-block:: php + + require_once '/path/to/goutte.phar'; + +Usage +----- + +Create a Goutte Client instance (which extends +``Symfony\Component\BrowserKit\Client``): + +.. code-block:: php + + use Goutte\Client; + + $client = new Client(); + +Make requests with the ``request()`` method: + +.. code-block:: php + + // Go to the symfony.com website + $crawler = $client->request('GET', 'http://www.symfony.com/blog/'); + +The method returns a ``Crawler`` object +(``Symfony\Component\DomCrawler\Crawler``). + +Fine-tune cURL options: + +.. code-block:: php + + $client->getClient()->setDefaultOption('config/curl/'.CURLOPT_TIMEOUT, 60); + +Click on links: + +.. code-block:: php + + // Click on the "Security Advisories" link + $link = $crawler->selectLink('Security Advisories')->link(); + $crawler = $client->click($link); + +Extract data: + +.. code-block:: php + + // Get the latest post in this category and display the titles + $crawler->filter('h2.post > a')->each(function ($node) { + print $node->text()."\n"; + }); + +Submit forms: + +.. code-block:: php + + $crawler = $client->request('GET', 'http://github.com/'); + $crawler = $client->click($crawler->selectLink('Sign in')->link()); + $form = $crawler->selectButton('Sign in')->form(); + $crawler = $client->submit($form, array('login' => 'fabpot', 'password' => 'xxxxxx')); + $crawler->filter('.flash-error')->each(function ($node) { + print $node->text()."\n"; + }); + +More Information +---------------- + +Read the documentation of the BrowserKit and DomCrawler Symfony Components for +more information about what you can do with Goutte. + +Technical Information +--------------------- + +Goutte is a thin wrapper around the following fine PHP libraries: + +* Symfony Components: BrowserKit, CssSelector and DomCrawler; + +* `Guzzle`_ HTTP Component. + +License +------- + +Goutte is licensed under the MIT license. + +.. _`Composer`: http://getcomposer.org +.. _`Goutte.phar`: http://get.sensiolabs.org/goutte.phar +.. _`Guzzle`: http://docs.guzzlephp.org diff --git a/core/vendor/fabpot/goutte/box.json b/core/vendor/fabpot/goutte/box.json new file mode 100644 index 0000000..943c61e --- /dev/null +++ b/core/vendor/fabpot/goutte/box.json @@ -0,0 +1,21 @@ +{ + "output": "goutte.phar", + "chmod": "0755", + "compactors": [ + "Herrera\\Box\\Compactor\\Php" + ], + "extract": false, + "files": [ + "LICENSE", + "Goutte/Client.php" + ], + "finder": [ + { + "name": ["*.php", "*.pem*"], + "exclude": ["Tests", "tests"], + "in": "vendor" + } + ], + "stub": "Goutte/Resources/phar-stub.php", + "web": false +} diff --git a/core/vendor/fabpot/goutte/composer.json b/core/vendor/fabpot/goutte/composer.json new file mode 100644 index 0000000..e4059ed --- /dev/null +++ b/core/vendor/fabpot/goutte/composer.json @@ -0,0 +1,29 @@ +{ + "name": "fabpot/goutte", + "type": "application", + "description": "A simple PHP Web Scraper", + "keywords": ["scraper"], + "homepage": "https://github.com/fabpot/Goutte", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "require": { + "php": ">=5.4.0", + "symfony/browser-kit": "~2.1", + "symfony/css-selector": "~2.1", + "symfony/dom-crawler": "~2.1", + "guzzlehttp/guzzle": "4.*" + }, + "autoload": { + "psr-0": { "Goutte": "." } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/core/vendor/fabpot/goutte/phpunit.xml.dist b/core/vendor/fabpot/goutte/phpunit.xml.dist new file mode 100644 index 0000000..9e7ff98 --- /dev/null +++ b/core/vendor/fabpot/goutte/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + + + + ./Goutte/Tests + + + diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/.gitignore b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CHANGELOG.md b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CHANGELOG.md new file mode 100644 index 0000000..d2b1074 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CHANGELOG.md @@ -0,0 +1,18 @@ +CHANGELOG +========= + +2.3.0 +----- + + * [BC BREAK] `Client::followRedirect()` won't redirect responses with + a non-3xx Status Code and `Location` header anymore, as per + http://tools.ietf.org/html/rfc2616#section-14.30 + + * added `Client::getInternalRequest()` and `Client::getInternalResponse()` to + have access to the BrowserKit internal request and response objects + +2.1.0 +----- + + * [BC BREAK] The CookieJar internals have changed to allow cookies with the + same name on different sub-domains/sub-paths diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php new file mode 100644 index 0000000..a74d907 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php @@ -0,0 +1,613 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\DomCrawler\Link; +use Symfony\Component\DomCrawler\Form; +use Symfony\Component\Process\PhpProcess; + +/** + * Client simulates a browser. + * + * To make the actual request, you need to implement the doRequest() method. + * + * If you want to be able to run requests in their own process (insulated flag), + * you need to also implement the getScript() method. + * + * @author Fabien Potencier + * + * @api + */ +abstract class Client +{ + protected $history; + protected $cookieJar; + protected $server = array(); + protected $internalRequest; + protected $request; + protected $internalResponse; + protected $response; + protected $crawler; + protected $insulated = false; + protected $redirect; + protected $followRedirects = true; + + private $maxRedirects = -1; + private $redirectCount = 0; + private $isMainRequest = true; + + /** + * Constructor. + * + * @param array $server The server parameters (equivalent of $_SERVER) + * @param History $history A History instance to store the browser history + * @param CookieJar $cookieJar A CookieJar instance to store the cookies + * + * @api + */ + public function __construct(array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + $this->setServerParameters($server); + $this->history = $history ?: new History(); + $this->cookieJar = $cookieJar ?: new CookieJar(); + } + + /** + * Sets whether to automatically follow redirects or not. + * + * @param bool $followRedirect Whether to follow redirects + * + * @api + */ + public function followRedirects($followRedirect = true) + { + $this->followRedirects = (bool) $followRedirect; + } + + /** + * Sets the maximum number of requests that crawler can follow. + * + * @param int $maxRedirects + */ + public function setMaxRedirects($maxRedirects) + { + $this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects; + $this->followRedirects = -1 != $this->maxRedirects; + } + + /** + * Sets the insulated flag. + * + * @param bool $insulated Whether to insulate the requests or not + * + * @throws \RuntimeException When Symfony Process Component is not installed + * + * @api + */ + public function insulate($insulated = true) + { + if ($insulated && !class_exists('Symfony\\Component\\Process\\Process')) { + throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.'); + } + + $this->insulated = (bool) $insulated; + } + + /** + * Sets server parameters. + * + * @param array $server An array of server parameters + * + * @api + */ + public function setServerParameters(array $server) + { + $this->server = array_merge(array( + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony2 BrowserKit', + ), $server); + } + + /** + * Sets single server parameter. + * + * @param string $key A key of the parameter + * @param string $value A value of the parameter + */ + public function setServerParameter($key, $value) + { + $this->server[$key] = $value; + } + + /** + * Gets single server parameter for specified key. + * + * @param string $key A key of the parameter to get + * @param string $default A default value when key is undefined + * + * @return string A value of the parameter + */ + public function getServerParameter($key, $default = '') + { + return (isset($this->server[$key])) ? $this->server[$key] : $default; + } + + /** + * Returns the History instance. + * + * @return History A History instance + * + * @api + */ + public function getHistory() + { + return $this->history; + } + + /** + * Returns the CookieJar instance. + * + * @return CookieJar A CookieJar instance + * + * @api + */ + public function getCookieJar() + { + return $this->cookieJar; + } + + /** + * Returns the current Crawler instance. + * + * @return Crawler|null A Crawler instance + * + * @api + */ + public function getCrawler() + { + return $this->crawler; + } + + /** + * Returns the current BrowserKit Response instance. + * + * @return Response|null A BrowserKit Response instance + * + * @api + */ + public function getInternalResponse() + { + return $this->internalResponse; + } + + /** + * Returns the current origin response instance. + * + * The origin response is the response instance that is returned + * by the code that handles requests. + * + * @return object|null A response instance + * + * @see doRequest + * + * @api + */ + public function getResponse() + { + return $this->response; + } + + /** + * Returns the current BrowserKit Request instance. + * + * @return Request|null A BrowserKit Request instance + * + * @api + */ + public function getInternalRequest() + { + return $this->internalRequest; + } + + /** + * Returns the current origin Request instance. + * + * The origin request is the request instance that is sent + * to the code that handles requests. + * + * @return object|null A Request instance + * + * @see doRequest + * + * @api + */ + public function getRequest() + { + return $this->request; + } + + /** + * Clicks on a given link. + * + * @param Link $link A Link instance + * + * @return Crawler + * + * @api + */ + public function click(Link $link) + { + if ($link instanceof Form) { + return $this->submit($link); + } + + return $this->request($link->getMethod(), $link->getUri()); + } + + /** + * Submits a form. + * + * @param Form $form A Form instance + * @param array $values An array of form field values + * + * @return Crawler + * + * @api + */ + public function submit(Form $form, array $values = array()) + { + $form->setValues($values); + + return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles()); + } + + /** + * Calls a URI. + * + * @param string $method The request method + * @param string $uri The URI to fetch + * @param array $parameters The Request parameters + * @param array $files The files + * @param array $server The server parameters (HTTP headers are referenced with a HTTP_ prefix as PHP does) + * @param string $content The raw body data + * @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload()) + * + * @return Crawler + * + * @api + */ + public function request($method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true) + { + if ($this->isMainRequest) { + $this->redirectCount = 0; + } else { + ++$this->redirectCount; + } + + $uri = $this->getAbsoluteUri($uri); + + if (isset($server['HTTP_HOST'])) { + $uri = preg_replace('{^(https?\://)'.parse_url($uri, PHP_URL_HOST).'}', '${1}'.$server['HTTP_HOST'], $uri); + } + + if (isset($server['HTTPS'])) { + $uri = preg_replace('{^'.parse_url($uri, PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri); + } + + $server = array_merge($this->server, $server); + + if (!$this->history->isEmpty()) { + $server['HTTP_REFERER'] = $this->history->current()->getUri(); + } + + $server['HTTP_HOST'] = parse_url($uri, PHP_URL_HOST); + + if ($port = parse_url($uri, PHP_URL_PORT)) { + $server['HTTP_HOST'] .= ':'.$port; + } + + $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME); + + $this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content); + + $this->request = $this->filterRequest($this->internalRequest); + + if (true === $changeHistory) { + $this->history->add($this->internalRequest); + } + + if ($this->insulated) { + $this->response = $this->doRequestInProcess($this->request); + } else { + $this->response = $this->doRequest($this->request); + } + + $this->internalResponse = $this->filterResponse($this->response); + + $this->cookieJar->updateFromResponse($this->internalResponse, $uri); + + $status = $this->internalResponse->getStatus(); + + if ($status >= 300 && $status < 400) { + $this->redirect = $this->internalResponse->getHeader('Location'); + } else { + $this->redirect = null; + } + + if ($this->followRedirects && $this->redirect) { + return $this->crawler = $this->followRedirect(); + } + + return $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type')); + } + + /** + * Makes a request in another process. + * + * @param object $request An origin request instance + * + * @return object An origin response instance + * + * @throws \RuntimeException When processing returns exit code + */ + protected function doRequestInProcess($request) + { + // We set the TMPDIR (for Macs) and TEMP (for Windows), because on these platforms the temp directory changes based on the user. + $process = new PhpProcess($this->getScript($request), null, array('TMPDIR' => sys_get_temp_dir(), 'TEMP' => sys_get_temp_dir())); + $process->run(); + + if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) { + throw new \RuntimeException(sprintf('OUTPUT: %s ERROR OUTPUT: %s', $process->getOutput(), $process->getErrorOutput())); + } + + return unserialize($process->getOutput()); + } + + /** + * Makes a request. + * + * @param object $request An origin request instance + * + * @return object An origin response instance + */ + abstract protected function doRequest($request); + + /** + * Returns the script to execute when the request must be insulated. + * + * @param object $request An origin request instance + * + * @throws \LogicException When this abstract class is not implemented + */ + protected function getScript($request) + { + throw new \LogicException('To insulate requests, you need to override the getScript() method.'); + } + + /** + * Filters the BrowserKit request to the origin one. + * + * @param Request $request The BrowserKit Request to filter + * + * @return object An origin request instance + */ + protected function filterRequest(Request $request) + { + return $request; + } + + /** + * Filters the origin response to the BrowserKit one. + * + * @param object $response The origin response to filter + * + * @return Response An BrowserKit Response instance + */ + protected function filterResponse($response) + { + return $response; + } + + /** + * Creates a crawler. + * + * This method returns null if the DomCrawler component is not available. + * + * @param string $uri A URI + * @param string $content Content for the crawler to use + * @param string $type Content type + * + * @return Crawler|null + */ + protected function createCrawlerFromContent($uri, $content, $type) + { + if (!class_exists('Symfony\Component\DomCrawler\Crawler')) { + return; + } + + $crawler = new Crawler(null, $uri); + $crawler->addContent($content, $type); + + return $crawler; + } + + /** + * Goes back in the browser history. + * + * @return Crawler + * + * @api + */ + public function back() + { + return $this->requestFromRequest($this->history->back(), false); + } + + /** + * Goes forward in the browser history. + * + * @return Crawler + * + * @api + */ + public function forward() + { + return $this->requestFromRequest($this->history->forward(), false); + } + + /** + * Reloads the current browser. + * + * @return Crawler + * + * @api + */ + public function reload() + { + return $this->requestFromRequest($this->history->current(), false); + } + + /** + * Follow redirects? + * + * @return Crawler + * + * @throws \LogicException If request was not a redirect + * + * @api + */ + public function followRedirect() + { + if (empty($this->redirect)) { + throw new \LogicException('The request was not redirected.'); + } + + if (-1 !== $this->maxRedirects) { + if ($this->redirectCount > $this->maxRedirects) { + throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects)); + } + } + + $request = $this->internalRequest; + + if (in_array($this->internalResponse->getStatus(), array(302, 303))) { + $method = 'get'; + $files = array(); + $content = null; + } else { + $method = $request->getMethod(); + $files = $request->getFiles(); + $content = $request->getContent(); + } + + if ('get' === strtolower($method)) { + // Don't forward parameters for GET request as it should reach the redirection URI + $parameters = array(); + } else { + $parameters = $request->getParameters(); + } + + $server = $request->getServer(); + $server = $this->updateServerFromUri($server, $this->redirect); + + $this->isMainRequest = false; + + $response = $this->request($method, $this->redirect, $parameters, $files, $server, $content); + + $this->isMainRequest = true; + + return $response; + } + + /** + * Restarts the client. + * + * It flushes history and all cookies. + * + * @api + */ + public function restart() + { + $this->cookieJar->clear(); + $this->history->clear(); + } + + /** + * Takes a URI and converts it to absolute if it is not already absolute. + * + * @param string $uri A URI + * + * @return string An absolute URI + */ + protected function getAbsoluteUri($uri) + { + // already absolute? + if (0 === strpos($uri, 'http')) { + return $uri; + } + + if (!$this->history->isEmpty()) { + $currentUri = $this->history->current()->getUri(); + } else { + $currentUri = sprintf('http%s://%s/', + isset($this->server['HTTPS']) ? 's' : '', + isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost' + ); + } + + // protocol relative URL + if (0 === strpos($uri, '//')) { + return parse_url($currentUri, PHP_URL_SCHEME).':'.$uri; + } + + // anchor? + if (!$uri || '#' == $uri[0]) { + return preg_replace('/#.*?$/', '', $currentUri).$uri; + } + + if ('/' !== $uri[0]) { + $path = parse_url($currentUri, PHP_URL_PATH); + + if ('/' !== substr($path, -1)) { + $path = substr($path, 0, strrpos($path, '/') + 1); + } + + $uri = $path.$uri; + } + + return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri; + } + + /** + * Makes a request from a Request object directly. + * + * @param Request $request A Request instance + * @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload()) + * + * @return Crawler + */ + protected function requestFromRequest(Request $request, $changeHistory = true) + { + return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory); + } + + private function updateServerFromUri($server, $uri) + { + $server['HTTP_HOST'] = parse_url($uri, PHP_URL_HOST); + $scheme = parse_url($uri, PHP_URL_SCHEME); + $server['HTTPS'] = null === $scheme ? $server['HTTPS'] : 'https' == $scheme; + unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']); + + return $server; + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Cookie.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Cookie.php new file mode 100644 index 0000000..90636e9 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Cookie.php @@ -0,0 +1,332 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * Cookie represents an HTTP cookie. + * + * @author Fabien Potencier + * + * @api + */ +class Cookie +{ + /** + * Handles dates as defined by RFC 2616 section 3.3.1, and also some other + * non-standard, but common formats. + * + * @var array + */ + private static $dateFormats = array( + 'D, d M Y H:i:s T', + 'D, d-M-y H:i:s T', + 'D, d-M-Y H:i:s T', + 'D, d-m-y H:i:s T', + 'D, d-m-Y H:i:s T', + 'D M j G:i:s Y', + 'D M d H:i:s Y T', + ); + + protected $name; + protected $value; + protected $expires; + protected $path; + protected $domain; + protected $secure; + protected $httponly; + protected $rawValue; + + /** + * Sets a cookie. + * + * @param string $name The cookie name + * @param string $value The value of the cookie + * @param string $expires The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available + * @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client + * @param bool $httponly The cookie httponly flag + * @param bool $encodedValue Whether the value is encoded or not + * + * @api + */ + public function __construct($name, $value, $expires = null, $path = null, $domain = '', $secure = false, $httponly = true, $encodedValue = false) + { + if ($encodedValue) { + $this->value = urldecode($value); + $this->rawValue = $value; + } else { + $this->value = $value; + $this->rawValue = urlencode($value); + } + $this->name = $name; + $this->expires = null === $expires ? null : (int) $expires; + $this->path = empty($path) ? '/' : $path; + $this->domain = $domain; + $this->secure = (bool) $secure; + $this->httponly = (bool) $httponly; + } + + /** + * Returns the HTTP representation of the Cookie. + * + * @return string The HTTP representation of the Cookie + * + * @throws \UnexpectedValueException + * + * @api + */ + public function __toString() + { + $cookie = sprintf('%s=%s', $this->name, $this->rawValue); + + if (null !== $this->expires) { + $dateTime = \DateTime::createFromFormat('U', $this->expires, new \DateTimeZone('GMT')); + + if ($dateTime === false) { + throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.'), $this->expires); + } + + $cookie .= '; expires='.str_replace('+0000', '', $dateTime->format(self::$dateFormats[0])); + } + + if ('' !== $this->domain) { + $cookie .= '; domain='.$this->domain; + } + + if ($this->path) { + $cookie .= '; path='.$this->path; + } + + if ($this->secure) { + $cookie .= '; secure'; + } + + if ($this->httponly) { + $cookie .= '; httponly'; + } + + return $cookie; + } + + /** + * Creates a Cookie instance from a Set-Cookie header value. + * + * @param string $cookie A Set-Cookie header value + * @param string $url The base URL + * + * @return Cookie A Cookie instance + * + * @throws \InvalidArgumentException + * + * @api + */ + public static function fromString($cookie, $url = null) + { + $parts = explode(';', $cookie); + + if (false === strpos($parts[0], '=')) { + throw new \InvalidArgumentException(sprintf('The cookie string "%s" is not valid.', $parts[0])); + } + + list($name, $value) = explode('=', array_shift($parts), 2); + + $values = array( + 'name' => trim($name), + 'value' => trim($value), + 'expires' => null, + 'path' => '/', + 'domain' => '', + 'secure' => false, + 'httponly' => false, + 'passedRawValue' => true, + ); + + if (null !== $url) { + if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host'])) { + throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url)); + } + + $values['domain'] = $urlParts['host']; + $values['path'] = isset($urlParts['path']) ? substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) : ''; + } + + foreach ($parts as $part) { + $part = trim($part); + + if ('secure' === strtolower($part)) { + // Ignore the secure flag if the original URI is not given or is not HTTPS + if (!$url || !isset($urlParts['scheme']) || 'https' != $urlParts['scheme']) { + continue; + } + + $values['secure'] = true; + + continue; + } + + if ('httponly' === strtolower($part)) { + $values['httponly'] = true; + + continue; + } + + if (2 === count($elements = explode('=', $part, 2))) { + if ('expires' === strtolower($elements[0])) { + $elements[1] = self::parseDate($elements[1]); + } + + $values[strtolower($elements[0])] = $elements[1]; + } + } + + return new static( + $values['name'], + $values['value'], + $values['expires'], + $values['path'], + $values['domain'], + $values['secure'], + $values['httponly'], + $values['passedRawValue'] + ); + } + + private static function parseDate($dateValue) + { + // trim single quotes around date if present + if (($length = strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length-1]) { + $dateValue = substr($dateValue, 1, -1); + } + + foreach (self::$dateFormats as $dateFormat) { + if (false !== $date = \DateTime::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) { + return $date->getTimestamp(); + } + } + + // attempt a fallback for unusual formatting + if (false !== $date = date_create($dateValue, new \DateTimeZone('GMT'))) { + return $date->getTimestamp(); + } + + throw new \InvalidArgumentException(sprintf('Could not parse date "%s".', $dateValue)); + } + + /** + * Gets the name of the cookie. + * + * @return string The cookie name + * + * @api + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string The cookie value + * + * @api + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the raw value of the cookie. + * + * @return string The cookie value + * + * @api + */ + public function getRawValue() + { + return $this->rawValue; + } + + /** + * Gets the expires time of the cookie. + * + * @return string The cookie expires time + * + * @api + */ + public function getExpiresTime() + { + return $this->expires; + } + + /** + * Gets the path of the cookie. + * + * @return string The cookie path + * + * @api + */ + public function getPath() + { + return $this->path; + } + + /** + * Gets the domain of the cookie. + * + * @return string The cookie domain + * + * @api + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Returns the secure flag of the cookie. + * + * @return bool The cookie secure flag + * + * @api + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Returns the httponly flag of the cookie. + * + * @return bool The cookie httponly flag + * + * @api + */ + public function isHttpOnly() + { + return $this->httponly; + } + + /** + * Returns true if the cookie has expired. + * + * @return bool true if the cookie has expired, false otherwise + * + * @api + */ + public function isExpired() + { + return null !== $this->expires && 0 !== $this->expires && $this->expires < time(); + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CookieJar.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CookieJar.php new file mode 100644 index 0000000..a3b0e24 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CookieJar.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * CookieJar. + * + * @author Fabien Potencier + * + * @api + */ +class CookieJar +{ + protected $cookieJar = array(); + + /** + * Sets a cookie. + * + * @param Cookie $cookie A Cookie instance + * + * @api + */ + public function set(Cookie $cookie) + { + $this->cookieJar[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + } + + /** + * Gets a cookie by name. + * + * You should never use an empty domain, but if you do so, + * this method returns the first cookie for the given name/path + * (this behavior ensures a BC behavior with previous versions of + * Symfony). + * + * @param string $name The cookie name + * @param string $path The cookie path + * @param string $domain The cookie domain + * + * @return Cookie|null A Cookie instance or null if the cookie does not exist + * + * @api + */ + public function get($name, $path = '/', $domain = null) + { + $this->flushExpiredCookies(); + + if (!empty($domain)) { + foreach ($this->cookieJar as $cookieDomain => $pathCookies) { + if ($cookieDomain) { + $cookieDomain = '.'.ltrim($cookieDomain, '.'); + if ($cookieDomain != substr('.'.$domain, -strlen($cookieDomain))) { + continue; + } + } + + foreach ($pathCookies as $cookiePath => $namedCookies) { + if ($cookiePath != substr($path, 0, strlen($cookiePath))) { + continue; + } + if (isset($namedCookies[$name])) { + return $namedCookies[$name]; + } + } + } + + return; + } + + // avoid relying on this behavior that is mainly here for BC reasons + foreach ($this->cookieJar as $cookies) { + if (isset($cookies[$path][$name])) { + return $cookies[$path][$name]; + } + } + } + + /** + * Removes a cookie by name. + * + * You should never use an empty domain, but if you do so, + * all cookies for the given name/path expire (this behavior + * ensures a BC behavior with previous versions of Symfony). + * + * @param string $name The cookie name + * @param string $path The cookie path + * @param string $domain The cookie domain + * + * @api + */ + public function expire($name, $path = '/', $domain = null) + { + if (null === $path) { + $path = '/'; + } + + if (empty($domain)) { + // an empty domain means any domain + // this should never happen but it allows for a better BC + $domains = array_keys($this->cookieJar); + } else { + $domains = array($domain); + } + + foreach ($domains as $domain) { + unset($this->cookieJar[$domain][$path][$name]); + + if (empty($this->cookieJar[$domain][$path])) { + unset($this->cookieJar[$domain][$path]); + + if (empty($this->cookieJar[$domain])) { + unset($this->cookieJar[$domain]); + } + } + } + } + + /** + * Removes all the cookies from the jar. + * + * @api + */ + public function clear() + { + $this->cookieJar = array(); + } + + /** + * Updates the cookie jar from a response Set-Cookie headers. + * + * @param array $setCookies Set-Cookie headers from an HTTP response + * @param string $uri The base URL + */ + public function updateFromSetCookie(array $setCookies, $uri = null) + { + $cookies = array(); + + foreach ($setCookies as $cookie) { + foreach (explode(',', $cookie) as $i => $part) { + if (0 === $i || preg_match('/^(?P\s*[0-9A-Za-z!#\$%\&\'\*\+\-\.^_`\|~]+)=/', $part)) { + $cookies[] = ltrim($part); + } else { + $cookies[count($cookies) - 1] .= ','.$part; + } + } + } + + foreach ($cookies as $cookie) { + try { + $this->set(Cookie::fromString($cookie, $uri)); + } catch (\InvalidArgumentException $e) { + // invalid cookies are just ignored + } + } + } + + /** + * Updates the cookie jar from a Response object. + * + * @param Response $response A Response object + * @param string $uri The base URL + */ + public function updateFromResponse(Response $response, $uri = null) + { + $this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri); + } + + /** + * Returns not yet expired cookies. + * + * @return Cookie[] An array of cookies + */ + public function all() + { + $this->flushExpiredCookies(); + + $flattenedCookies = array(); + foreach ($this->cookieJar as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Returns not yet expired cookie values for the given URI. + * + * @param string $uri A URI + * @param bool $returnsRawValue Returns raw value or urldecoded value + * + * @return array An array of cookie values + */ + public function allValues($uri, $returnsRawValue = false) + { + $this->flushExpiredCookies(); + + $parts = array_replace(array('path' => '/'), parse_url($uri)); + $cookies = array(); + foreach ($this->cookieJar as $domain => $pathCookies) { + if ($domain) { + $domain = '.'.ltrim($domain, '.'); + if ($domain != substr('.'.$parts['host'], -strlen($domain))) { + continue; + } + } + + foreach ($pathCookies as $path => $namedCookies) { + if ($path != substr($parts['path'], 0, strlen($path))) { + continue; + } + + foreach ($namedCookies as $cookie) { + if ($cookie->isSecure() && 'https' != $parts['scheme']) { + continue; + } + + $cookies[$cookie->getName()] = $returnsRawValue ? $cookie->getRawValue() : $cookie->getValue(); + } + } + } + + return $cookies; + } + + /** + * Returns not yet expired raw cookie values for the given URI. + * + * @param string $uri A URI + * + * @return array An array of cookie values + */ + public function allRawValues($uri) + { + return $this->allValues($uri, true); + } + + /** + * Removes all expired cookies. + */ + public function flushExpiredCookies() + { + foreach ($this->cookieJar as $domain => $pathCookies) { + foreach ($pathCookies as $path => $namedCookies) { + foreach ($namedCookies as $name => $cookie) { + if ($cookie->isExpired()) { + unset($this->cookieJar[$domain][$path][$name]); + } + } + } + } + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/History.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/History.php new file mode 100644 index 0000000..d76d79b --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/History.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * History. + * + * @author Fabien Potencier + */ +class History +{ + protected $stack = array(); + protected $position = -1; + + /** + * Clears the history. + */ + public function clear() + { + $this->stack = array(); + $this->position = -1; + } + + /** + * Adds a Request to the history. + * + * @param Request $request A Request instance + */ + public function add(Request $request) + { + $this->stack = array_slice($this->stack, 0, $this->position + 1); + $this->stack[] = clone $request; + $this->position = count($this->stack) - 1; + } + + /** + * Returns true if the history is empty. + * + * @return bool true if the history is empty, false otherwise + */ + public function isEmpty() + { + return count($this->stack) == 0; + } + + /** + * Goes back in the history. + * + * @return Request A Request instance + * + * @throws \LogicException if the stack is already on the first page + */ + public function back() + { + if ($this->position < 1) { + throw new \LogicException('You are already on the first page.'); + } + + return clone $this->stack[--$this->position]; + } + + /** + * Goes forward in the history. + * + * @return Request A Request instance + * + * @throws \LogicException if the stack is already on the last page + */ + public function forward() + { + if ($this->position > count($this->stack) - 2) { + throw new \LogicException('You are already on the last page.'); + } + + return clone $this->stack[++$this->position]; + } + + /** + * Returns the current element in the history. + * + * @return Request A Request instance + * + * @throws \LogicException if the stack is empty + */ + public function current() + { + if (-1 == $this->position) { + throw new \LogicException('The page history is empty.'); + } + + return clone $this->stack[$this->position]; + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/LICENSE b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/LICENSE new file mode 100644 index 0000000..0b3292c --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2014 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/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/README.md b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/README.md new file mode 100644 index 0000000..1b40c9f --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/README.md @@ -0,0 +1,23 @@ +BrowserKit Component +==================== + +BrowserKit simulates the behavior of a web browser. + +The component only provide an abstract client and does not provide any +"default" backend for the HTTP layer. + +Resources +--------- + +For a simple implementation of a browser based on an HTTP layer, have a look +at [Goutte](https://github.com/fabpot/Goutte). + +For an implementation based on HttpKernelInterface, have a look at the +[Client](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Client.php) +provided by the HttpKernel component. + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/BrowserKit/ + $ composer.phar install + $ phpunit diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Request.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Request.php new file mode 100644 index 0000000..6d381d2 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Request.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * Request object. + * + * @author Fabien Potencier + * + * @api + */ +class Request +{ + protected $uri; + protected $method; + protected $parameters; + protected $files; + protected $cookies; + protected $server; + protected $content; + + /** + * Constructor. + * + * @param string $uri The request URI + * @param string $method The HTTP method request + * @param array $parameters The request parameters + * @param array $files An array of uploaded files + * @param array $cookies An array of cookies + * @param array $server An array of server parameters + * @param string $content The raw body data + * + * @api + */ + public function __construct($uri, $method, array $parameters = array(), array $files = array(), array $cookies = array(), array $server = array(), $content = null) + { + $this->uri = $uri; + $this->method = $method; + $this->parameters = $parameters; + $this->files = $files; + $this->cookies = $cookies; + $this->server = $server; + $this->content = $content; + } + + /** + * Gets the request URI. + * + * @return string The request URI + * + * @api + */ + public function getUri() + { + return $this->uri; + } + + /** + * Gets the request HTTP method. + * + * @return string The request HTTP method + * + * @api + */ + public function getMethod() + { + return $this->method; + } + + /** + * Gets the request parameters. + * + * @return array The request parameters + * + * @api + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Gets the request server files. + * + * @return array The request files + * + * @api + */ + public function getFiles() + { + return $this->files; + } + + /** + * Gets the request cookies. + * + * @return array The request cookies + * + * @api + */ + public function getCookies() + { + return $this->cookies; + } + + /** + * Gets the request server parameters. + * + * @return array The request server parameters + * + * @api + */ + public function getServer() + { + return $this->server; + } + + /** + * Gets the request raw body data. + * + * @return string The request raw body data. + * + * @api + */ + public function getContent() + { + return $this->content; + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Response.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Response.php new file mode 100644 index 0000000..77aad83 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Response.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * Response object. + * + * @author Fabien Potencier + * + * @api + */ +class Response +{ + protected $content; + protected $status; + protected $headers; + + /** + * Constructor. + * + * The headers array is a set of key/value pairs. If a header is present multiple times + * then the value is an array of all the values. + * + * @param string $content The content of the response + * @param int $status The response status code + * @param array $headers An array of headers + * + * @api + */ + public function __construct($content = '', $status = 200, array $headers = array()) + { + $this->content = $content; + $this->status = $status; + $this->headers = $headers; + } + + /** + * Converts the response object to string containing all headers and the response content. + * + * @return string The response with headers and content + */ + public function __toString() + { + $headers = ''; + foreach ($this->headers as $name => $value) { + if (is_string($value)) { + $headers .= $this->buildHeader($name, $value); + } else { + foreach ($value as $headerValue) { + $headers .= $this->buildHeader($name, $headerValue); + } + } + } + + return $headers."\n".$this->content; + } + + /** + * Returns the build header line. + * + * @param string $name The header name + * @param string $value The header value + * + * @return string The built header line + */ + protected function buildHeader($name, $value) + { + return sprintf("%s: %s\n", $name, $value); + } + + /** + * Gets the response content. + * + * @return string The response content + * + * @api + */ + public function getContent() + { + return $this->content; + } + + /** + * Gets the response status code. + * + * @return int The response status code + * + * @api + */ + public function getStatus() + { + return $this->status; + } + + /** + * Gets the response headers. + * + * @return array The response headers + * + * @api + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Gets a response header. + * + * @param string $header The header name + * @param bool $first Whether to return the first value or all header values + * + * @return string|array The first header value if $first is true, an array of values otherwise + */ + public function getHeader($header, $first = true) + { + foreach ($this->headers as $key => $value) { + if (str_replace('-', '_', strtolower($key)) == str_replace('-', '_', strtolower($header))) { + if ($first) { + return is_array($value) ? (count($value) ? $value[0] : '') : $value; + } + + return is_array($value) ? $value : array($value); + } + } + + return $first ? null : array(); + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ClientTest.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ClientTest.php new file mode 100644 index 0000000..ca6898a --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -0,0 +1,594 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\Client; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\Request; +use Symfony\Component\BrowserKit\Response; + +class SpecialResponse extends Response +{ +} + +class TestClient extends Client +{ + protected $nextResponse = null; + protected $nextScript = null; + + public function setNextResponse(Response $response) + { + $this->nextResponse = $response; + } + + public function setNextScript($script) + { + $this->nextScript = $script; + } + + protected function doRequest($request) + { + if (null === $this->nextResponse) { + return new Response(); + } + + $response = $this->nextResponse; + $this->nextResponse = null; + + return $response; + } + + protected function filterResponse($response) + { + if ($response instanceof SpecialResponse) { + return new Response($response->getContent(), $response->getStatus(), $response->getHeaders()); + } + + return $response; + } + + protected function getScript($request) + { + $r = new \ReflectionClass('Symfony\Component\BrowserKit\Response'); + $path = $r->getFileName(); + + return <<nextScript); +EOF; + } +} + +class ClientTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\BrowserKit\Client::getHistory + */ + public function testGetHistory() + { + $client = new TestClient(array(), $history = new History()); + $this->assertSame($history, $client->getHistory(), '->getHistory() returns the History'); + } + + /** + * @covers Symfony\Component\BrowserKit\Client::getCookieJar + */ + public function testGetCookieJar() + { + $client = new TestClient(array(), null, $cookieJar = new CookieJar()); + $this->assertSame($cookieJar, $client->getCookieJar(), '->getCookieJar() returns the CookieJar'); + } + + /** + * @covers Symfony\Component\BrowserKit\Client::getRequest + */ + public function testGetRequest() + { + $client = new TestClient(); + $client->request('GET', 'http://example.com/'); + + $this->assertEquals('http://example.com/', $client->getRequest()->getUri(), '->getCrawler() returns the Request of the last request'); + } + + public function testGetRequestWithIpAsHost() + { + $client = new TestClient(); + $client->request('GET', 'https://example.com/foo', array(), array(), array('HTTP_HOST' => '127.0.0.1')); + + $this->assertEquals('https://127.0.0.1/foo', $client->getRequest()->getUri()); + } + + public function testGetResponse() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $client->request('GET', 'http://example.com/'); + + $this->assertEquals('foo', $client->getResponse()->getContent(), '->getCrawler() returns the Response of the last request'); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getResponse(), '->getCrawler() returns the Response of the last request'); + } + + public function testGetInternalResponse() + { + $client = new TestClient(); + $client->setNextResponse(new SpecialResponse('foo')); + $client->request('GET', 'http://example.com/'); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getInternalResponse()); + $this->assertNotInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialResponse', $client->getInternalResponse()); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialResponse', $client->getResponse()); + } + + public function testGetContent() + { + $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; + + $client = new TestClient(); + $client->request('POST', 'http://example.com/jsonrpc', array(), array(), array(), $json); + $this->assertEquals($json, $client->getRequest()->getContent()); + } + + /** + * @covers Symfony\Component\BrowserKit\Client::getCrawler + */ + public function testGetCrawler() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $crawler = $client->request('GET', 'http://example.com/'); + + $this->assertSame($crawler, $client->getCrawler(), '->getCrawler() returns the Crawler of the last request'); + } + + public function testRequestHttpHeaders() + { + $client = new TestClient(); + $client->request('GET', '/'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('localhost', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('www.example.com', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header'); + + $client->request('GET', 'https://www.example.com'); + $headers = $client->getRequest()->getServer(); + $this->assertTrue($headers['HTTPS'], '->request() sets the HTTPS header'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com:8080'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('www.example.com:8080', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header with port'); + } + + public function testRequestURIConversion() + { + $client = new TestClient(); + $client->request('GET', '/foo'); + $this->assertEquals('http://localhost/foo', $client->getRequest()->getUri(), '->request() converts the URI to an absolute one'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com'); + $this->assertEquals('http://www.example.com', $client->getRequest()->getUri(), '->request() does not change absolute URIs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/'); + $client->request('GET', '/foo'); + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo'); + $client->request('GET', '#'); + $this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + $client->request('GET', '#'); + $this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + $client->request('GET', '#foo'); + $this->assertEquals('http://www.example.com/foo#foo', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/'); + $client->request('GET', 'bar'); + $this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + $this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + } + + public function testRequestReferer() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + $server = $client->getRequest()->getServer(); + $this->assertEquals('http://www.example.com/foo/foobar', $server['HTTP_REFERER'], '->request() sets the referer'); + } + + public function testRequestHistory() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + + $this->assertEquals('http://www.example.com/foo/bar', $client->getHistory()->current()->getUri(), '->request() updates the History'); + $this->assertEquals('http://www.example.com/foo/foobar', $client->getHistory()->back()->getUri(), '->request() updates the History'); + } + + public function testRequestCookies() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo', 200, array('Set-Cookie' => 'foo=bar'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals(array('foo' => 'bar'), $client->getCookieJar()->allValues('http://www.example.com/foo/foobar'), '->request() updates the CookieJar'); + + $client->request('GET', 'bar'); + $this->assertEquals(array('foo' => 'bar'), $client->getCookieJar()->allValues('http://www.example.com/foo/foobar'), '->request() updates the CookieJar'); + } + + public function testRequestSecureCookies() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo', 200, array('Set-Cookie' => 'foo=bar; path=/; secure'))); + $client->request('GET', 'https://www.example.com/foo/foobar'); + + $this->assertTrue($client->getCookieJar()->get('foo', '/', 'www.example.com')->isSecure()); + } + + public function testClick() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->click($crawler->filter('a')->link()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() clicks on links'); + } + + public function testClickForm() + { + $client = new TestClient(); + $client->setNextResponse(new Response('
')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->click($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() Form submit forms'); + } + + public function testSubmit() + { + $client = new TestClient(); + $client->setNextResponse(new Response('
')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->submit($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->submit() submit forms'); + } + + public function testSubmitPreserveAuth() + { + $client = new TestClient(array('PHP_AUTH_USER' => 'foo', 'PHP_AUTH_PW' => 'bar')); + $client->setNextResponse(new Response('
')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $server = $client->getRequest()->getServer(); + $this->assertArrayHasKey('PHP_AUTH_USER', $server); + $this->assertEquals('foo', $server['PHP_AUTH_USER']); + $this->assertArrayHasKey('PHP_AUTH_PW', $server); + $this->assertEquals('bar', $server['PHP_AUTH_PW']); + + $client->submit($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->submit() submit forms'); + + $server = $client->getRequest()->getServer(); + $this->assertArrayHasKey('PHP_AUTH_USER', $server); + $this->assertEquals('foo', $server['PHP_AUTH_USER']); + $this->assertArrayHasKey('PHP_AUTH_PW', $server); + $this->assertEquals('bar', $server['PHP_AUTH_PW']); + } + + public function testFollowRedirect() + { + $client = new TestClient(); + $client->followRedirects(false); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + try { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request was not redirected'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->followRedirect() throws a \LogicException if the request was not redirected'); + } + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->followRedirect(); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() automatically follows redirects if followRedirects is true'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 201, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->followRedirect() does not follow redirect if HTTP Code is not 30x'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 201, array('Location' => 'http://www.example.com/redirected'))); + $client->followRedirects(false); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + try { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request did not respond with 30x HTTP Code'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->followRedirect() throws a \LogicException if the request did not respond with 30x HTTP Code'); + } + } + + public function testFollowRedirectWithMaxRedirects() + { + $client = new TestClient(); + $client->setMaxRedirects(1); + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected2'))); + try { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request was redirected and limit of redirections was reached'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->followRedirect() throws a \LogicException if the request was redirected and limit of redirections was reached'); + } + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client->setNextResponse(new Response('', 302, array('Location' => '/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows relative URLs'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array('Location' => '//www.example.org/'))); + $client->request('GET', 'https://www.example.com/'); + + $this->assertEquals('https://www.example.org/', $client->getRequest()->getUri(), '->followRedirect() follows protocol-relative URLs'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('POST', 'http://www.example.com/foo/foobar', array('name' => 'bar')); + + $this->assertEquals('get', $client->getRequest()->getMethod(), '->followRedirect() uses a get for 302'); + $this->assertEquals(array(), $client->getRequest()->getParameters(), '->followRedirect() does not submit parameters when changing the method'); + } + + public function testFollowRedirectWithCookies() + { + $client = new TestClient(); + $client->followRedirects(false); + $client->setNextResponse(new Response('', 302, array( + 'Location' => 'http://www.example.com/redirected', + 'Set-Cookie' => 'foo=bar', + ))); + $client->request('GET', 'http://www.example.com/'); + $this->assertEquals(array(), $client->getRequest()->getCookies()); + $client->followRedirect(); + $this->assertEquals(array('foo' => 'bar'), $client->getRequest()->getCookies()); + } + + public function testFollowRedirectWithHeaders() + { + $headers = array( + 'HTTP_HOST' => 'www.example.com', + 'HTTP_USER_AGENT' => 'Symfony2 BrowserKit', + 'CONTENT_TYPE' => 'application/vnd.custom+xml', + 'HTTPS' => false, + ); + + $client = new TestClient(); + $client->followRedirects(false); + $client->setNextResponse(new Response('', 302, array( + 'Location' => 'http://www.example.com/redirected', + ))); + $client->request('GET', 'http://www.example.com/', array(), array(), array( + 'CONTENT_TYPE' => 'application/vnd.custom+xml', + )); + + $this->assertEquals($headers, $client->getRequest()->getServer()); + + $client->followRedirect(); + + $headers['HTTP_REFERER'] = 'http://www.example.com/'; + + $this->assertEquals($headers, $client->getRequest()->getServer()); + } + + public function testFollowRedirectWithPort() + { + $headers = array( + 'HTTP_HOST' => 'www.example.com:8080', + 'HTTP_USER_AGENT' => 'Symfony2 BrowserKit', + 'HTTPS' => false + ); + + $client = new TestClient(); + $client->followRedirects(false); + $client->setNextResponse(new Response('', 302, array( + 'Location' => 'http://www.example.com:8080/redirected', + ))); + $client->request('GET', 'http://www.example.com:8080/'); + + $this->assertEquals($headers, $client->getRequest()->getServer()); + } + + public function testBack() + { + $client = new TestClient(); + + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client->request('GET', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content); + $client->request('GET', 'http://www.example.com/foo'); + $client->back(); + + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->back() goes back in the history'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->back() keeps parameters'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->back() keeps files'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->back() keeps $_SERVER'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->back() keeps content'); + } + + public function testForward() + { + $client = new TestClient(); + + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'http://www.example.com/foo', $parameters, $files, $server, $content); + $client->back(); + $client->forward(); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->forward() goes forward in the history'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->forward() keeps parameters'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->forward() keeps files'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->forward() keeps $_SERVER'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->forward() keeps content'); + } + + public function testReload() + { + $client = new TestClient(); + + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client->request('GET', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content); + $client->reload(); + + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->reload() reloads the current page'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->reload() keeps parameters'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->reload() keeps files'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->reload() keeps $_SERVER'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->reload() keeps content'); + } + + public function testRestart() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->restart(); + + $this->assertTrue($client->getHistory()->isEmpty(), '->restart() clears the history'); + $this->assertEquals(array(), $client->getCookieJar()->all(), '->restart() clears the cookies'); + } + + public function testInsulatedRequests() + { + $client = new TestClient(); + $client->insulate(); + $client->setNextScript("new Symfony\Component\BrowserKit\Response('foobar')"); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('foobar', $client->getResponse()->getContent(), '->insulate() process the request in a forked process'); + + $client->setNextScript("new Symfony\Component\BrowserKit\Response('foobar)"); + + try { + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->fail('->request() throws a \RuntimeException if the script has an error'); + } catch (\Exception $e) { + $this->assertInstanceof('RuntimeException', $e, '->request() throws a \RuntimeException if the script has an error'); + } + } + + public function testGetServerParameter() + { + $client = new TestClient(); + $this->assertEquals('localhost', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony2 BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + $this->assertEquals('testvalue', $client->getServerParameter('testkey', 'testvalue')); + } + + public function testSetServerParameter() + { + $client = new TestClient(); + + $this->assertEquals('localhost', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony2 BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + + $client->setServerParameter('HTTP_HOST', 'testhost'); + $this->assertEquals('testhost', $client->getServerParameter('HTTP_HOST')); + + $client->setServerParameter('HTTP_USER_AGENT', 'testua'); + $this->assertEquals('testua', $client->getServerParameter('HTTP_USER_AGENT')); + } + + public function testSetServerParameterInRequest() + { + $client = new TestClient(); + + $this->assertEquals('localhost', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony2 BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + + $client->request('GET', 'https://www.example.com/https/www.example.com', array(), array(), array( + 'HTTP_HOST' => 'testhost', + 'HTTP_USER_AGENT' => 'testua', + 'HTTPS' => false, + 'NEW_SERVER_KEY' => 'new-server-key-value' + )); + + $this->assertEquals('localhost', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony2 BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + + $this->assertEquals('http://testhost/https/www.example.com', $client->getRequest()->getUri()); + + $server = $client->getRequest()->getServer(); + + $this->assertArrayHasKey('HTTP_USER_AGENT', $server); + $this->assertEquals('testua', $server['HTTP_USER_AGENT']); + + $this->assertArrayHasKey('HTTP_HOST', $server); + $this->assertEquals('testhost', $server['HTTP_HOST']); + + $this->assertArrayHasKey('NEW_SERVER_KEY', $server); + $this->assertEquals('new-server-key-value', $server['NEW_SERVER_KEY']); + + $this->assertArrayHasKey('HTTPS', $server); + $this->assertFalse($server['HTTPS']); + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieJarTest.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieJarTest.php new file mode 100644 index 0000000..3deef66 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieJarTest.php @@ -0,0 +1,231 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\Cookie; +use Symfony\Component\BrowserKit\Response; + +class CookieJarTest extends \PHPUnit_Framework_TestCase +{ + public function testSetGet() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar')); + + $this->assertEquals($cookie, $cookieJar->get('foo'), '->set() sets a cookie'); + + $this->assertNull($cookieJar->get('foobar'), '->get() returns null if the cookie does not exist'); + + $cookieJar->set($cookie = new Cookie('foo', 'bar', time() - 86400)); + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + } + + public function testExpire() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar')); + $cookieJar->expire('foo'); + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + } + + public function testAll() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar')); + $cookieJar->set($cookie2 = new Cookie('bar', 'foo')); + + $this->assertEquals(array($cookie1, $cookie2), $cookieJar->all(), '->all() returns all cookies in the jar'); + } + + public function testClear() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar')); + $cookieJar->set($cookie2 = new Cookie('bar', 'foo')); + + $cookieJar->clear(); + + $this->assertEquals(array(), $cookieJar->all(), '->clear() expires all cookies'); + } + + public function testUpdateFromResponse() + { + $response = new Response('', 200, array('Set-Cookie' => 'foo=foo')); + + $cookieJar = new CookieJar(); + $cookieJar->updateFromResponse($response); + + $this->assertEquals('foo', $cookieJar->get('foo')->getValue(), '->updateFromResponse() updates cookies from a Response objects'); + } + + public function testUpdateFromSetCookie() + { + $setCookies = array('foo=foo'); + + $cookieJar = new CookieJar(); + $cookieJar->set(new Cookie('bar', 'bar')); + $cookieJar->updateFromSetCookie($setCookies); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $cookieJar->get('foo')); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $cookieJar->get('bar')); + $this->assertEquals('foo', $cookieJar->get('foo')->getValue(), '->updateFromSetCookie() updates cookies from a Set-Cookie header'); + $this->assertEquals('bar', $cookieJar->get('bar')->getValue(), '->updateFromSetCookie() keeps existing cookies'); + } + + public function testUpdateFromEmptySetCookie() + { + $cookieJar = new CookieJar(); + $cookieJar->updateFromSetCookie(array('')); + $this->assertEquals(array(), $cookieJar->all()); + } + + public function testUpdateFromSetCookieWithMultipleCookies() + { + $timestamp = time() + 3600; + $date = gmdate('D, d M Y H:i:s \G\M\T', $timestamp); + $setCookies = array(sprintf('foo=foo; expires=%s; domain=.symfony.com; path=/, bar=bar; domain=.blog.symfony.com, PHPSESSID=id; expires=%s', $date, $date)); + + $cookieJar = new CookieJar(); + $cookieJar->updateFromSetCookie($setCookies); + + $fooCookie = $cookieJar->get('foo', '/', '.symfony.com'); + $barCookie = $cookieJar->get('bar', '/', '.blog.symfony.com'); + $phpCookie = $cookieJar->get('PHPSESSID'); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $fooCookie); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $barCookie); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $phpCookie); + $this->assertEquals('foo', $fooCookie->getValue()); + $this->assertEquals('bar', $barCookie->getValue()); + $this->assertEquals('id', $phpCookie->getValue()); + $this->assertEquals($timestamp, $fooCookie->getExpiresTime()); + $this->assertNull($barCookie->getExpiresTime()); + $this->assertEquals($timestamp, $phpCookie->getExpiresTime()); + } + + /** + * @dataProvider provideAllValuesValues + */ + public function testAllValues($uri, $values) + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo_nothing', 'foo')); + $cookieJar->set($cookie2 = new Cookie('foo_expired', 'foo', time() - 86400)); + $cookieJar->set($cookie3 = new Cookie('foo_path', 'foo', null, '/foo')); + $cookieJar->set($cookie4 = new Cookie('foo_domain', 'foo', null, '/', '.example.com')); + $cookieJar->set($cookie4 = new Cookie('foo_strict_domain', 'foo', null, '/', '.www4.example.com')); + $cookieJar->set($cookie5 = new Cookie('foo_secure', 'foo', null, '/', '', true)); + + $this->assertEquals($values, array_keys($cookieJar->allValues($uri)), '->allValues() returns the cookie for a given URI'); + } + + public function provideAllValuesValues() + { + return array( + array('http://www.example.com', array('foo_nothing', 'foo_domain')), + array('http://www.example.com/', array('foo_nothing', 'foo_domain')), + array('http://foo.example.com/', array('foo_nothing', 'foo_domain')), + array('http://foo.example1.com/', array('foo_nothing')), + array('https://foo.example.com/', array('foo_nothing', 'foo_secure', 'foo_domain')), + array('http://www.example.com/foo/bar', array('foo_nothing', 'foo_path', 'foo_domain')), + array('http://www4.example.com/', array('foo_nothing', 'foo_domain', 'foo_strict_domain')), + ); + } + + public function testEncodedValues() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true)); + + $this->assertEquals(array('foo' => 'bar=baz'), $cookieJar->allValues('/')); + $this->assertEquals(array('foo' => 'bar%3Dbaz'), $cookieJar->allRawValues('/')); + } + + public function testCookieExpireWithSameNameButDifferentPaths() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo')); + $cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/bar')); + $cookieJar->expire('foo', '/foo'); + + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + $this->assertEquals(array(), $cookieJar->allValues('http://example.com/foo')); + $this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://example.com/bar')); + } + + public function testCookieExpireWithNullPaths() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/')); + $cookieJar->expire('foo', null); + + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + } + + public function testCookieWithSameNameButDifferentPaths() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo')); + $cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/bar')); + + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + $this->assertEquals(array('foo' => 'bar1'), $cookieJar->allValues('http://example.com/foo')); + $this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://example.com/bar')); + } + + public function testCookieWithSameNameButDifferentDomains() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/', 'foo.example.com')); + $cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/', 'bar.example.com')); + + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + $this->assertEquals(array('foo' => 'bar1'), $cookieJar->allValues('http://foo.example.com/')); + $this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://bar.example.com/')); + } + + public function testCookieGetWithSubdomain() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar', null, '/', '.example.com')); + $cookieJar->set($cookie2 = new Cookie('foo1', 'bar', null, '/', 'test.example.com')); + + $this->assertEquals($cookie1, $cookieJar->get('foo','/','foo.example.com')); + $this->assertEquals($cookie1, $cookieJar->get('foo','/','example.com')); + $this->assertEquals($cookie2, $cookieJar->get('foo1','/','test.example.com')); + } + + public function testCookieGetWithSubdirectory() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar', null, '/test', '.example.com')); + $cookieJar->set($cookie2 = new Cookie('foo1', 'bar1', null, '/', '.example.com')); + + $this->assertNull($cookieJar->get('foo','/','.example.com')); + $this->assertNull($cookieJar->get('foo','/bar','.example.com')); + $this->assertEquals($cookie1, $cookieJar->get('foo','/test','example.com')); + $this->assertEquals($cookie2, $cookieJar->get('foo1','/','example.com')); + $this->assertEquals($cookie2, $cookieJar->get('foo1','/bar','example.com')); + } + + public function testCookieWithWildcardDomain() + { + $cookieJar = new CookieJar(); + $cookieJar->set(new Cookie('foo', 'bar', null, '/', '.example.com')); + + $this->assertEquals(array('foo' => 'bar'), $cookieJar->allValues('http://www.example.com')); + $this->assertEmpty($cookieJar->allValues('http://wwwexample.com')); + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieTest.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieTest.php new file mode 100644 index 0000000..8e3578a --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieTest.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\Cookie; + +class CookieTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTestsForToFromString + */ + public function testToFromString($cookie, $url = null) + { + $this->assertEquals($cookie, (string) Cookie::fromString($cookie, $url)); + } + + public function getTestsForToFromString() + { + return array( + array('foo=bar; path=/'), + array('foo=bar; path=/foo'), + array('foo=bar; domain=google.com; path=/'), + array('foo=bar; domain=example.com; path=/; secure', 'https://example.com/'), + array('foo=bar; path=/; httponly'), + array('foo=bar; domain=google.com; path=/foo; secure; httponly', 'https://google.com/'), + array('foo=bar=baz; path=/'), + array('foo=bar%3Dbaz; path=/'), + ); + } + + public function testFromStringIgnoreSecureFlag() + { + $this->assertFalse(Cookie::fromString('foo=bar; secure')->isSecure()); + $this->assertFalse(Cookie::fromString('foo=bar; secure', 'http://example.com/')->isSecure()); + } + + /** + * @dataProvider getExpireCookieStrings + */ + public function testFromStringAcceptsSeveralExpiresDateFormats($cookie) + { + $this->assertEquals(1596185377, Cookie::fromString($cookie)->getExpiresTime()); + } + + public function getExpireCookieStrings() + { + return array( + array('foo=bar; expires=Fri, 31-Jul-2020 08:49:37 GMT'), + array('foo=bar; expires=Fri, 31 Jul 2020 08:49:37 GMT'), + array('foo=bar; expires=Fri, 31-07-2020 08:49:37 GMT'), + array('foo=bar; expires=Fri, 31-07-20 08:49:37 GMT'), + array('foo=bar; expires=Friday, 31-Jul-20 08:49:37 GMT'), + array('foo=bar; expires=Fri Jul 31 08:49:37 2020'), + array('foo=bar; expires=\'Fri Jul 31 08:49:37 2020\''), + array('foo=bar; expires=Friday July 31st 2020, 08:49:37 GMT'), + ); + } + + public function testFromStringWithCapitalization() + { + $this->assertEquals('Foo=Bar; path=/', (string) Cookie::fromString('Foo=Bar')); + $this->assertEquals('foo=bar; expires=Fri, 31 Dec 2010 23:59:59 GMT; path=/', (string) Cookie::fromString('foo=bar; Expires=Fri, 31 Dec 2010 23:59:59 GMT')); + $this->assertEquals('foo=bar; domain=www.example.org; path=/; httponly', (string) Cookie::fromString('foo=bar; DOMAIN=www.example.org; HttpOnly')); + } + + public function testFromStringWithUrl() + { + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar', 'http://www.example.com/')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar', 'http://www.example.com')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar', 'http://www.example.com?foo')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/foo', (string) Cookie::FromString('foo=bar', 'http://www.example.com/foo/bar')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar; path=/', 'http://www.example.com/foo/bar')); + $this->assertEquals('foo=bar; domain=www.myotherexample.com; path=/', (string) Cookie::FromString('foo=bar; domain=www.myotherexample.com', 'http://www.example.com/')); + } + + public function testFromStringThrowsAnExceptionIfCookieIsNotValid() + { + $this->setExpectedException('InvalidArgumentException'); + Cookie::FromString('foo'); + } + + public function testFromStringThrowsAnExceptionIfCookieDateIsNotValid() + { + $this->setExpectedException('InvalidArgumentException'); + Cookie::FromString('foo=bar; expires=Flursday July 31st 2020, 08:49:37 GMT'); + } + + public function testFromStringThrowsAnExceptionIfUrlIsNotValid() + { + $this->setExpectedException('InvalidArgumentException'); + Cookie::FromString('foo=bar', 'foobar'); + } + + public function testGetName() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals('foo', $cookie->getName(), '->getName() returns the cookie name'); + } + + public function testGetValue() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals('bar', $cookie->getValue(), '->getValue() returns the cookie value'); + + $cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true); // raw value + $this->assertEquals('bar=baz', $cookie->getValue(), '->getValue() returns the urldecoded cookie value'); + } + + public function testGetRawValue() + { + $cookie = new Cookie('foo', 'bar=baz'); // decoded value + $this->assertEquals('bar%3Dbaz', $cookie->getRawValue(), '->getRawValue() returns the urlencoded cookie value'); + $cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true); // raw value + $this->assertEquals('bar%3Dbaz', $cookie->getRawValue(), '->getRawValue() returns the non-urldecoded cookie value'); + } + + public function testGetPath() + { + $cookie = new Cookie('foo', 'bar', 0); + $this->assertEquals('/', $cookie->getPath(), '->getPath() returns / is no path is defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/foo'); + $this->assertEquals('/foo', $cookie->getPath(), '->getPath() returns the cookie path'); + } + + public function testGetDomain() + { + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com'); + $this->assertEquals('foo.com', $cookie->getDomain(), '->getDomain() returns the cookie domain'); + } + + public function testIsSecure() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertFalse($cookie->isSecure(), '->isSecure() returns false if not defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', true); + $this->assertTrue($cookie->isSecure(), '->isSecure() returns the cookie secure flag'); + } + + public function testIsHttponly() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns false if not defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', false, true); + $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns the cookie httponly flag'); + } + + public function testGetExpiresTime() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertNull($cookie->getExpiresTime(), '->getExpiresTime() returns the expires time'); + + $cookie = new Cookie('foo', 'bar', $time = time() - 86400); + $this->assertEquals($time, $cookie->getExpiresTime(), '->getExpiresTime() returns the expires time'); + } + + public function testIsExpired() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertFalse($cookie->isExpired(), '->isExpired() returns false when the cookie never expires (null as expires time)'); + + $cookie = new Cookie('foo', 'bar', time() - 86400); + $this->assertTrue($cookie->isExpired(), '->isExpired() returns true when the cookie is expired'); + + $cookie = new Cookie('foo', 'bar', 0); + $this->assertFalse($cookie->isExpired()); + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/HistoryTest.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/HistoryTest.php new file mode 100644 index 0000000..882b730 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/HistoryTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\Request; + +class HistoryTest extends \PHPUnit_Framework_TestCase +{ + public function testAdd() + { + $history = new History(); + $history->add(new Request('http://www.example1.com/', 'get')); + $this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->add(new Request('http://www.example2.com/', 'get')); + $this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->add(new Request('http://www.example3.com/', 'get')); + $history->back(); + $history->add(new Request('http://www.example4.com/', 'get')); + $this->assertSame('http://www.example4.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->back(); + $this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history'); + } + + public function testClearIsEmpty() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + + $this->assertFalse($history->isEmpty(), '->isEmpty() returns false if the history is not empty'); + + $history->clear(); + + $this->assertTrue($history->isEmpty(), '->isEmpty() true if the history is empty'); + } + + public function testCurrent() + { + $history = new History(); + + try { + $history->current(); + $this->fail('->current() throws a \LogicException if the history is empty'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->current() throws a \LogicException if the history is empty'); + } + + $history->add(new Request('http://www.example.com/', 'get')); + + $this->assertSame('http://www.example.com/', $history->current()->getUri(), '->current() returns the current request in the history'); + } + + public function testBack() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + + try { + $history->back(); + $this->fail('->back() throws a \LogicException if the history is already on the first page'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->current() throws a \LogicException if the history is already on the first page'); + } + + $history->add(new Request('http://www.example1.com/', 'get')); + $history->back(); + + $this->assertSame('http://www.example.com/', $history->current()->getUri(), '->back() returns the previous request in the history'); + } + + public function testForward() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + $history->add(new Request('http://www.example1.com/', 'get')); + + try { + $history->forward(); + $this->fail('->forward() throws a \LogicException if the history is already on the last page'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->forward() throws a \LogicException if the history is already on the last page'); + } + + $history->back(); + $history->forward(); + + $this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->forward() returns the next request in the history'); + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/RequestTest.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/RequestTest.php new file mode 100644 index 0000000..b75b5fb --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/RequestTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\Request; + +class RequestTest extends \PHPUnit_Framework_TestCase +{ + public function testGetUri() + { + $request = new Request('http://www.example.com/', 'get'); + $this->assertEquals('http://www.example.com/', $request->getUri(), '->getUri() returns the URI of the request'); + } + + public function testGetMethod() + { + $request = new Request('http://www.example.com/', 'get'); + $this->assertEquals('get', $request->getMethod(), '->getMethod() returns the method of the request'); + } + + public function testGetParameters() + { + $request = new Request('http://www.example.com/', 'get', array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getParameters(), '->getParameters() returns the parameters of the request'); + } + + public function testGetFiles() + { + $request = new Request('http://www.example.com/', 'get', array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getFiles(), '->getFiles() returns the uploaded files of the request'); + } + + public function testGetCookies() + { + $request = new Request('http://www.example.com/', 'get', array(), array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getCookies(), '->getCookies() returns the cookies of the request'); + } + + public function testGetServer() + { + $request = new Request('http://www.example.com/', 'get', array(), array(), array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getServer(), '->getServer() returns the server parameters of the request'); + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ResponseTest.php b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ResponseTest.php new file mode 100644 index 0000000..878752c --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ResponseTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\Response; + +class ResponseTest extends \PHPUnit_Framework_TestCase +{ + public function testGetUri() + { + $response = new Response('foo'); + $this->assertEquals('foo', $response->getContent(), '->getContent() returns the content of the response'); + } + + public function testGetStatus() + { + $response = new Response('foo', 304); + $this->assertEquals('304', $response->getStatus(), '->getStatus() returns the status of the response'); + } + + public function testGetHeaders() + { + $response = new Response('foo', 200, array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $response->getHeaders(), '->getHeaders() returns the headers of the response'); + } + + public function testGetHeader() + { + $response = new Response('foo', 200, array( + 'Content-Type' => 'text/html', + 'Set-Cookie' => array('foo=bar', 'bar=foo'), + )); + + $this->assertEquals('text/html', $response->getHeader('Content-Type'), '->getHeader() returns a header of the response'); + $this->assertEquals('text/html', $response->getHeader('content-type'), '->getHeader() returns a header of the response'); + $this->assertEquals('text/html', $response->getHeader('content_type'), '->getHeader() returns a header of the response'); + $this->assertEquals('foo=bar', $response->getHeader('Set-Cookie'), '->getHeader() returns the first header value'); + $this->assertEquals(array('foo=bar', 'bar=foo'), $response->getHeader('Set-Cookie', false), '->getHeader() returns all header values if first is false'); + + $this->assertNull($response->getHeader('foo'), '->getHeader() returns null if the header is not defined'); + $this->assertEquals(array(), $response->getHeader('foo', false), '->getHeader() returns an empty array if the header is not defined and first is set to false'); + } + + public function testMagicToString() + { + $response = new Response('foo', 304, array('foo' => 'bar')); + + $this->assertEquals("foo: bar\n\nfoo", $response->__toString(), '->__toString() returns the headers and the content as a string'); + } + + public function testMagicToStringWithMultipleSetCookieHeader() + { + $headers = array( + 'content-type' => 'text/html; charset=utf-8', + 'set-cookie' => array('foo=bar', 'bar=foo') + ); + + $expected = 'content-type: text/html; charset=utf-8'."\n"; + $expected.= 'set-cookie: foo=bar'."\n"; + $expected.= 'set-cookie: bar=foo'."\n\n"; + $expected.= 'foo'; + + $response = new Response('foo', 304, $headers); + + $this->assertEquals($expected, $response->__toString(), '->__toString() returns the headers and the content as a string'); + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/composer.json b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/composer.json new file mode 100644 index 0000000..ed914dd --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/browser-kit", + "type": "library", + "description": "Symfony BrowserKit Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "symfony/dom-crawler": "~2.0" + }, + "require-dev": { + "symfony/process": "~2.0", + "symfony/css-selector": "~2.0" + }, + "suggest": { + "symfony/process": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\BrowserKit\\": "" } + }, + "target-dir": "Symfony/Component/BrowserKit", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + } +} diff --git a/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/phpunit.xml.dist b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/phpunit.xml.dist new file mode 100644 index 0000000..51a0f31 --- /dev/null +++ b/core/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/.gitignore b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/CHANGELOG.md b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/CHANGELOG.md new file mode 100644 index 0000000..48fd323 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/CHANGELOG.md @@ -0,0 +1,45 @@ +CHANGELOG +========= + +2.5.0 +----- + +* [BC BREAK] The default value for checkbox and radio inputs without a value attribute have changed + from '1' to 'on' to match the HTML specification. +* [BC BREAK] The typehints on the `Link`, `Form` and `FormField` classes have been changed from + `\DOMNode` to `DOMElement`. Using any other type of `DOMNode` was triggering fatal errors in previous + versions. Code extending these classes will need to update the typehints when overwriting these methods. + +2.4.0 +----- + + * `Crawler::addXmlContent()` removes the default document namespace again if it's an only namespace. + * added support for automatic discovery and explicit registration of document + namespaces for `Crawler::filterXPath()` and `Crawler::filter()` + * improved content type guessing in `Crawler::addContent()` + * [BC BREAK] `Crawler::addXmlContent()` no longer removes the default document + namespace + +2.3.0 +----- + + * added Crawler::html() + * [BC BREAK] Crawler::each() and Crawler::reduce() now return Crawler instances instead of DomElement instances + * added schema relative URL support to links + * added support for HTML5 'form' attribute + +2.2.0 +----- + + * added a way to set raw path to the file in FileFormField - necessary for + simulating HTTP requests + +2.1.0 +----- + + * added support for the HTTP PATCH method + * refactored the Form class internals to support multi-dimensional fields + (the public API is backward compatible) + * added a way to get parsing errors for Crawler::addHtmlContent() and + Crawler::addXmlContent() via libxml functions + * added support for submitting a form without a submit button diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Crawler.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Crawler.php new file mode 100644 index 0000000..0b7200a --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Crawler.php @@ -0,0 +1,991 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\CssSelector\CssSelector; + +/** + * Crawler eases navigation of a list of \DOMElement objects. + * + * @author Fabien Potencier + * + * @api + */ +class Crawler extends \SplObjectStorage +{ + /** + * @var string The current URI or the base href value + */ + protected $uri; + + /** + * @var string The default namespace prefix to be used with XPath and CSS expressions + */ + private $defaultNamespacePrefix = 'default'; + + /** + * @var array A map of manually registered namespaces + */ + private $namespaces = array(); + + /** + * Constructor. + * + * @param mixed $node A Node to use as the base for the crawling + * @param string $uri The current URI or the base href value + * + * @api + */ + public function __construct($node = null, $uri = null) + { + $this->uri = $uri; + + $this->add($node); + } + + /** + * Removes all the nodes. + * + * @api + */ + public function clear() + { + $this->removeAll($this); + } + + /** + * Adds a node to the current list of nodes. + * + * This method uses the appropriate specialized add*() method based + * on the type of the argument. + * + * @param \DOMNodeList|\DOMNode|array|string|null $node A node + * + * @throws \InvalidArgumentException When node is not the expected type. + * + * @api + */ + public function add($node) + { + if ($node instanceof \DOMNodeList) { + $this->addNodeList($node); + } elseif ($node instanceof \DOMNode) { + $this->addNode($node); + } elseif (is_array($node)) { + $this->addNodes($node); + } elseif (is_string($node)) { + $this->addContent($node); + } elseif (null !== $node) { + throw new \InvalidArgumentException(sprintf('Expecting a DOMNodeList or DOMNode instance, an array, a string, or null, but got "%s".', is_object($node) ? get_class($node) : gettype($node))); + } + } + + /** + * Adds HTML/XML content. + * + * If the charset is not set via the content type, it is assumed + * to be ISO-8859-1, which is the default charset defined by the + * HTTP 1.1 specification. + * + * @param string $content A string to parse as HTML/XML + * @param null|string $type The content type of the string + */ + public function addContent($content, $type = null) + { + if (empty($type)) { + $type = 0 === strpos($content, ']+charset *= *["\']?([a-zA-Z\-0-9_:.]+)/i', $content, $matches)) { + $charset = $matches[1]; + } + + if (null === $charset) { + $charset = 'ISO-8859-1'; + } + + if ('x' === $xmlMatches[1]) { + $this->addXmlContent($content, $charset); + } else { + $this->addHtmlContent($content, $charset); + } + } + + /** + * Adds an HTML content to the list of nodes. + * + * The libxml errors are disabled when the content is parsed. + * + * If you want to get parsing errors, be sure to enable + * internal errors via libxml_use_internal_errors(true) + * and then, get the errors via libxml_get_errors(). Be + * sure to clear errors with libxml_clear_errors() afterward. + * + * @param string $content The HTML content + * @param string $charset The charset + * + * @api + */ + public function addHtmlContent($content, $charset = 'UTF-8') + { + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + + $dom = new \DOMDocument('1.0', $charset); + $dom->validateOnParse = true; + + if (function_exists('mb_convert_encoding')) { + $hasError = false; + set_error_handler(function () use (&$hasError) { + $hasError = true; + }); + $tmpContent = @mb_convert_encoding($content, 'HTML-ENTITIES', $charset); + + restore_error_handler(); + + if (!$hasError) { + $content = $tmpContent; + } + } + + if ('' !== trim($content)) { + @$dom->loadHTML($content); + } + + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + $this->addDocument($dom); + + $base = $this->filterRelativeXPath('descendant-or-self::base')->extract(array('href')); + + $baseHref = current($base); + if (count($base) && !empty($baseHref)) { + if ($this->uri) { + $linkNode = $dom->createElement('a'); + $linkNode->setAttribute('href', $baseHref); + $link = new Link($linkNode, $this->uri); + $this->uri = $link->getUri(); + } else { + $this->uri = $baseHref; + } + } + } + + /** + * Adds an XML content to the list of nodes. + * + * The libxml errors are disabled when the content is parsed. + * + * If you want to get parsing errors, be sure to enable + * internal errors via libxml_use_internal_errors(true) + * and then, get the errors via libxml_get_errors(). Be + * sure to clear errors with libxml_clear_errors() afterward. + * + * @param string $content The XML content + * @param string $charset The charset + * + * @api + */ + public function addXmlContent($content, $charset = 'UTF-8') + { + // remove the default namespace if it's the only namespace to make XPath expressions simpler + if (!preg_match('/xmlns:/', $content)) { + $content = str_replace('xmlns', 'ns', $content); + } + + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + + $dom = new \DOMDocument('1.0', $charset); + $dom->validateOnParse = true; + + if ('' !== trim($content)) { + @$dom->loadXML($content, LIBXML_NONET); + } + + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + $this->addDocument($dom); + } + + /** + * Adds a \DOMDocument to the list of nodes. + * + * @param \DOMDocument $dom A \DOMDocument instance + * + * @api + */ + public function addDocument(\DOMDocument $dom) + { + if ($dom->documentElement) { + $this->addNode($dom->documentElement); + } + } + + /** + * Adds a \DOMNodeList to the list of nodes. + * + * @param \DOMNodeList $nodes A \DOMNodeList instance + * + * @api + */ + public function addNodeList(\DOMNodeList $nodes) + { + foreach ($nodes as $node) { + $this->addNode($node); + } + } + + /** + * Adds an array of \DOMNode instances to the list of nodes. + * + * @param \DOMNode[] $nodes An array of \DOMNode instances + * + * @api + */ + public function addNodes(array $nodes) + { + foreach ($nodes as $node) { + $this->add($node); + } + } + + /** + * Adds a \DOMNode instance to the list of nodes. + * + * @param \DOMNode $node A \DOMNode instance + * + * @api + */ + public function addNode(\DOMNode $node) + { + if ($node instanceof \DOMDocument) { + $this->attach($node->documentElement); + } else { + $this->attach($node); + } + } + + /** + * Returns a node given its position in the node list. + * + * @param int $position The position + * + * @return Crawler A new instance of the Crawler with the selected node, or an empty Crawler if it does not exist. + * + * @api + */ + public function eq($position) + { + foreach ($this as $i => $node) { + if ($i == $position) { + return new static($node, $this->uri); + } + } + + return new static(null, $this->uri); + } + + /** + * Calls an anonymous function on each node of the list. + * + * The anonymous function receives the position and the node wrapped + * in a Crawler instance as arguments. + * + * Example: + * + * $crawler->filter('h1')->each(function ($node, $i) { + * return $node->text(); + * }); + * + * @param \Closure $closure An anonymous function + * + * @return array An array of values returned by the anonymous function + * + * @api + */ + public function each(\Closure $closure) + { + $data = array(); + foreach ($this as $i => $node) { + $data[] = $closure(new static($node, $this->uri), $i); + } + + return $data; + } + + /** + * Reduces the list of nodes by calling an anonymous function. + * + * To remove a node from the list, the anonymous function must return false. + * + * @param \Closure $closure An anonymous function + * + * @return Crawler A Crawler instance with the selected nodes. + * + * @api + */ + public function reduce(\Closure $closure) + { + $nodes = array(); + foreach ($this as $i => $node) { + if (false !== $closure(new static($node, $this->uri), $i)) { + $nodes[] = $node; + } + } + + return new static($nodes, $this->uri); + } + + /** + * Returns the first node of the current selection + * + * @return Crawler A Crawler instance with the first selected node + * + * @api + */ + public function first() + { + return $this->eq(0); + } + + /** + * Returns the last node of the current selection + * + * @return Crawler A Crawler instance with the last selected node + * + * @api + */ + public function last() + { + return $this->eq(count($this) - 1); + } + + /** + * Returns the siblings nodes of the current selection + * + * @return Crawler A Crawler instance with the sibling nodes + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function siblings() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0)->parentNode->firstChild), $this->uri); + } + + /** + * Returns the next siblings nodes of the current selection + * + * @return Crawler A Crawler instance with the next sibling nodes + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function nextAll() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0)), $this->uri); + } + + /** + * Returns the previous sibling nodes of the current selection + * + * @return Crawler A Crawler instance with the previous sibling nodes + * + * @throws \InvalidArgumentException + * + * @api + */ + public function previousAll() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0), 'previousSibling'), $this->uri); + } + + /** + * Returns the parents nodes of the current selection + * + * @return Crawler A Crawler instance with the parents nodes of the current selection + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function parents() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + $nodes = array(); + + while ($node = $node->parentNode) { + if (1 === $node->nodeType) { + $nodes[] = $node; + } + } + + return new static($nodes, $this->uri); + } + + /** + * Returns the children nodes of the current selection + * + * @return Crawler A Crawler instance with the children nodes + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function children() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0)->firstChild; + + return new static($node ? $this->sibling($node) : array(), $this->uri); + } + + /** + * Returns the attribute value of the first node of the list. + * + * @param string $attribute The attribute name + * + * @return string|null The attribute value or null if the attribute does not exist + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function attr($attribute) + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + + return $node->hasAttribute($attribute) ? $node->getAttribute($attribute) : null; + } + + /** + * Returns the node value of the first node of the list. + * + * @return string The node value + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function text() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->getNode(0)->nodeValue; + } + + /** + * Returns the first node of the list as HTML. + * + * @return string The node html + * + * @throws \InvalidArgumentException When current node is empty + */ + public function html() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $html = ''; + foreach ($this->getNode(0)->childNodes as $child) { + if (version_compare(PHP_VERSION, '5.3.6', '>=')) { + // node parameter was added to the saveHTML() method in PHP 5.3.6 + // @see http://php.net/manual/en/domdocument.savehtml.php + $html .= $child->ownerDocument->saveHTML($child); + } else { + $document = new \DOMDocument('1.0', 'UTF-8'); + $document->appendChild($document->importNode($child, true)); + $html .= rtrim($document->saveHTML()); + } + } + + return $html; + } + + /** + * Extracts information from the list of nodes. + * + * You can extract attributes or/and the node value (_text). + * + * Example: + * + * $crawler->filter('h1 a')->extract(array('_text', 'href')); + * + * @param array $attributes An array of attributes + * + * @return array An array of extracted values + * + * @api + */ + public function extract($attributes) + { + $attributes = (array) $attributes; + $count = count($attributes); + + $data = array(); + foreach ($this as $node) { + $elements = array(); + foreach ($attributes as $attribute) { + if ('_text' === $attribute) { + $elements[] = $node->nodeValue; + } else { + $elements[] = $node->getAttribute($attribute); + } + } + + $data[] = $count > 1 ? $elements : $elements[0]; + } + + return $data; + } + + /** + * Filters the list of nodes with an XPath expression. + * + * The XPath expression is evaluated in the context of the crawler, which + * is considered as a fake parent of the elements inside it. + * This means that a child selector "div" or "./div" will match only + * the div elements of the current crawler, not their children. + * + * @param string $xpath An XPath expression + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @api + */ + public function filterXPath($xpath) + { + $xpath = $this->relativize($xpath); + + // If we dropped all expressions in the XPath while preparing it, there would be no match + if ('' === $xpath) { + return new static(null, $this->uri); + } + + return $this->filterRelativeXPath($xpath); + } + + /** + * Filters the list of nodes with a CSS selector. + * + * This method only works if you have installed the CssSelector Symfony Component. + * + * @param string $selector A CSS selector + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @throws \RuntimeException if the CssSelector Component is not available + * + * @api + */ + public function filter($selector) + { + if (!class_exists('Symfony\\Component\\CssSelector\\CssSelector')) { + throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector is not installed (you can use filterXPath instead).'); + } + + // The CssSelector already prefixes the selector with descendant-or-self:: + return $this->filterRelativeXPath(CssSelector::toXPath($selector)); + } + + /** + * Selects links by name or alt value for clickable images. + * + * @param string $value The link text + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @api + */ + public function selectLink($value) + { + $xpath = sprintf('descendant-or-self::a[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) ', static::xpathLiteral(' '.$value.' ')). + sprintf('or ./img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)]]', static::xpathLiteral(' '.$value.' ')); + + return $this->filterRelativeXPath($xpath); + } + + /** + * Selects a button by name or alt value for images. + * + * @param string $value The button text + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @api + */ + public function selectButton($value) + { + $translate = 'translate(@type, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")'; + $xpath = sprintf('descendant-or-self::input[((contains(%s, "submit") or contains(%s, "button")) and contains(concat(\' \', normalize-space(string(@value)), \' \'), %s)) ', $translate, $translate, static::xpathLiteral(' '.$value.' ')). + sprintf('or (contains(%s, "image") and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)) or @id=%s or @name=%s] ', $translate, static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value), static::xpathLiteral($value)). + sprintf('| descendant-or-self::button[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) or @id=%s or @name=%s]', static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value), static::xpathLiteral($value)); + + return $this->filterRelativeXPath($xpath); + } + + /** + * Returns a Link object for the first node in the list. + * + * @param string $method The method for the link (get by default) + * + * @return Link A Link instance + * + * @throws \InvalidArgumentException If the current node list is empty + * + * @api + */ + public function link($method = 'get') + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + + return new Link($node, $this->uri, $method); + } + + /** + * Returns an array of Link objects for the nodes in the list. + * + * @return Link[] An array of Link instances + * + * @api + */ + public function links() + { + $links = array(); + foreach ($this as $node) { + $links[] = new Link($node, $this->uri, 'get'); + } + + return $links; + } + + /** + * Returns a Form object for the first node in the list. + * + * @param array $values An array of values for the form fields + * @param string $method The method for the form + * + * @return Form A Form instance + * + * @throws \InvalidArgumentException If the current node list is empty + * + * @api + */ + public function form(array $values = null, $method = null) + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $form = new Form($this->getNode(0), $this->uri, $method); + + if (null !== $values) { + $form->setValues($values); + } + + return $form; + } + + /** + * Overloads a default namespace prefix to be used with XPath and CSS expressions. + * + * @param string $prefix + */ + public function setDefaultNamespacePrefix($prefix) + { + $this->defaultNamespacePrefix = $prefix; + } + + /** + * @param string $prefix + * @param string $namespace + */ + public function registerNamespace($prefix, $namespace) + { + $this->namespaces[$prefix] = $namespace; + } + + /** + * Converts string for XPath expressions. + * + * Escaped characters are: quotes (") and apostrophe ('). + * + * Examples: + * + * echo Crawler::xpathLiteral('foo " bar'); + * //prints 'foo " bar' + * + * echo Crawler::xpathLiteral("foo ' bar"); + * //prints "foo ' bar" + * + * echo Crawler::xpathLiteral('a\'b"c'); + * //prints concat('a', "'", 'b"c') + * + * + * @param string $s String to be escaped + * + * @return string Converted string + */ + public static function xpathLiteral($s) + { + if (false === strpos($s, "'")) { + return sprintf("'%s'", $s); + } + + if (false === strpos($s, '"')) { + return sprintf('"%s"', $s); + } + + $string = $s; + $parts = array(); + while (true) { + if (false !== $pos = strpos($string, "'")) { + $parts[] = sprintf("'%s'", substr($string, 0, $pos)); + $parts[] = "\"'\""; + $string = substr($string, $pos + 1); + } else { + $parts[] = "'$string'"; + break; + } + } + + return sprintf("concat(%s)", implode($parts, ', ')); + } + + /** + * Filters the list of nodes with an XPath expression. + * + * The XPath expression should already be processed to apply it in the context of each node. + * + * @param string $xpath + * + * @return Crawler + */ + private function filterRelativeXPath($xpath) + { + $prefixes = $this->findNamespacePrefixes($xpath); + + $crawler = new static(null, $this->uri); + + foreach ($this as $node) { + $domxpath = $this->createDOMXPath($node->ownerDocument, $prefixes); + $crawler->add($domxpath->query($xpath, $node)); + } + + return $crawler; + } + + /** + * Make the XPath relative to the current context. + * + * The returned XPath will match elements matching the XPath inside the current crawler + * when running in the context of a node of the crawler. + * + * @param string $xpath + * + * @return string + */ + private function relativize($xpath) + { + $expressions = array(); + + $unionPattern = '/\|(?![^\[]*\])/'; + // An expression which will never match to replace expressions which cannot match in the crawler + // We cannot simply drop + $nonMatchingExpression = 'a[name() = "b"]'; + + // Split any unions into individual expressions. + foreach (preg_split($unionPattern, $xpath) as $expression) { + $expression = trim($expression); + $parenthesis = ''; + + // If the union is inside some braces, we need to preserve the opening braces and apply + // the change only inside it. + if (preg_match('/^[\(\s*]+/', $expression, $matches)) { + $parenthesis = $matches[0]; + $expression = substr($expression, strlen($parenthesis)); + } + + // BC for Symfony 2.4 and lower were elements were adding in a fake _root parent + if (0 === strpos($expression, '/_root/')) { + $expression = './'.substr($expression, 7); + } + + // add prefix before absolute element selector + if (empty($expression)) { + $expression = $nonMatchingExpression; + } elseif (0 === strpos($expression, '//')) { + $expression = 'descendant-or-self::' . substr($expression, 2); + } elseif (0 === strpos($expression, './/')) { + $expression = 'descendant-or-self::' . substr($expression, 3); + } elseif (0 === strpos($expression, './')) { + $expression = 'self::' . substr($expression, 2); + } elseif ('/' === $expression[0]) { + // the only direct child in Symfony 2.4 and lower is _root, which is already handled previously + // so let's drop the expression entirely + $expression = $nonMatchingExpression; + } elseif ('.' === $expression[0]) { + // '.' is the fake root element in Symfony 2.4 and lower, which is excluded from results + $expression = $nonMatchingExpression; + } elseif (0 === strpos($expression, 'descendant::')) { + $expression = 'descendant-or-self::' . substr($expression, strlen('descendant::')); + } elseif (0 !== strpos($expression, 'descendant-or-self::')) { + $expression = 'self::' .$expression; + } + $expressions[] = $parenthesis.$expression; + } + + return implode(' | ', $expressions); + } + + /** + * @param int $position + * + * @return \DOMElement|null + */ + public function getNode($position) + { + foreach ($this as $i => $node) { + if ($i == $position) { + return $node; + } + } + } + + /** + * @param \DOMElement $node + * @param string $siblingDir + * + * @return array + */ + protected function sibling($node, $siblingDir = 'nextSibling') + { + $nodes = array(); + + do { + if ($node !== $this->getNode(0) && $node->nodeType === 1) { + $nodes[] = $node; + } + } while ($node = $node->$siblingDir); + + return $nodes; + } + + /** + * @param \DOMDocument $document + * @param array $prefixes + * + * @return \DOMXPath + * + * @throws \InvalidArgumentException + */ + private function createDOMXPath(\DOMDocument $document, array $prefixes = array()) + { + $domxpath = new \DOMXPath($document); + + foreach ($prefixes as $prefix) { + $namespace = $this->discoverNamespace($domxpath, $prefix); + if (null !== $namespace) { + $domxpath->registerNamespace($prefix, $namespace); + } + } + + return $domxpath; + } + + /** + * @param \DOMXPath $domxpath + * @param string $prefix + * + * @return string + * + * @throws \InvalidArgumentException + */ + private function discoverNamespace(\DOMXPath $domxpath, $prefix) + { + if (isset($this->namespaces[$prefix])) { + return $this->namespaces[$prefix]; + } + + // ask for one namespace, otherwise we'd get a collection with an item for each node + $namespaces = $domxpath->query(sprintf('(//namespace::*[name()="%s"])[last()]', $this->defaultNamespacePrefix === $prefix ? '' : $prefix)); + + if ($node = $namespaces->item(0)) { + return $node->nodeValue; + } + } + + /** + * @param $xpath + * + * @return array + */ + private function findNamespacePrefixes($xpath) + { + if (preg_match_all('/(?P[a-z_][a-z_0-9\-\.]*):[^"\/]/i', $xpath, $matches)) { + return array_unique($matches['prefix']); + } + + return array(); + } +} diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/ChoiceFormField.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/ChoiceFormField.php new file mode 100644 index 0000000..56e74dc --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/ChoiceFormField.php @@ -0,0 +1,327 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * ChoiceFormField represents a choice form field. + * + * It is constructed from a HTML select tag, or a HTML checkbox, or radio inputs. + * + * @author Fabien Potencier + * + * @api + */ +class ChoiceFormField extends FormField +{ + /** + * @var string + */ + private $type; + /** + * @var bool + */ + private $multiple; + /** + * @var array + */ + private $options; + /** + * @var bool + */ + private $validationDisabled = false; + + /** + * Returns true if the field should be included in the submitted values. + * + * @return bool true if the field should be included in the submitted values, false otherwise + */ + public function hasValue() + { + // don't send a value for unchecked checkboxes + if (in_array($this->type, array('checkbox', 'radio')) && null === $this->value) { + return false; + } + + return true; + } + + /** + * Check if the current selected option is disabled + * + * @return bool + */ + public function isDisabled() + { + foreach ($this->options as $option) { + if ($option['value'] == $this->value && $option['disabled']) { + return true; + } + } + + return false; + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + * + * @api + */ + public function select($value) + { + $this->setValue($value); + } + + /** + * Ticks a checkbox. + * + * @throws \LogicException When the type provided is not correct + * + * @api + */ + public function tick() + { + if ('checkbox' !== $this->type) { + throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); + } + + $this->setValue(true); + } + + /** + * Ticks a checkbox. + * + * @throws \LogicException When the type provided is not correct + * + * @api + */ + public function untick() + { + if ('checkbox' !== $this->type) { + throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); + } + + $this->setValue(false); + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + * + * @throws \InvalidArgumentException When value type provided is not correct + */ + public function setValue($value) + { + if ('checkbox' === $this->type && false === $value) { + // uncheck + $this->value = null; + } elseif ('checkbox' === $this->type && true === $value) { + // check + $this->value = $this->options[0]['value']; + } else { + if (is_array($value)) { + if (!$this->multiple) { + throw new \InvalidArgumentException(sprintf('The value for "%s" cannot be an array.', $this->name)); + } + + foreach ($value as $v) { + if (!$this->containsOption($v, $this->options)) { + throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $v, implode(', ', $this->availableOptionValues()))); + } + } + } elseif (!$this->containsOption($value, $this->options)) { + throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $value, implode(', ', $this->availableOptionValues()))); + } + + if ($this->multiple) { + $value = (array) $value; + } + + if (is_array($value)) { + $this->value = $value; + } else { + parent::setValue($value); + } + } + } + + /** + * Adds a choice to the current ones. + * + * This method should only be used internally. + * + * @param \DOMElement $node + * + * @throws \LogicException When choice provided is not multiple nor radio + */ + public function addChoice(\DOMElement $node) + { + if (!$this->multiple && 'radio' !== $this->type) { + throw new \LogicException(sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name)); + } + + $option = $this->buildOptionValue($node); + $this->options[] = $option; + + if ($node->hasAttribute('checked')) { + $this->value = $option['value']; + } + } + + /** + * Returns the type of the choice field (radio, select, or checkbox). + * + * @return string The type + */ + public function getType() + { + return $this->type; + } + + /** + * Returns true if the field accepts multiple values. + * + * @return bool true if the field accepts multiple values, false otherwise + */ + public function isMultiple() + { + return $this->multiple; + } + + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('input' !== $this->node->nodeName && 'select' !== $this->node->nodeName) { + throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName)); + } + + if ('input' === $this->node->nodeName && 'checkbox' !== strtolower($this->node->getAttribute('type')) && 'radio' !== strtolower($this->node->getAttribute('type'))) { + throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is %s).', $this->node->getAttribute('type'))); + } + + $this->value = null; + $this->options = array(); + $this->multiple = false; + + if ('input' == $this->node->nodeName) { + $this->type = strtolower($this->node->getAttribute('type')); + $optionValue = $this->buildOptionValue($this->node); + $this->options[] = $optionValue; + + if ($this->node->hasAttribute('checked')) { + $this->value = $optionValue['value']; + } + } else { + $this->type = 'select'; + if ($this->node->hasAttribute('multiple')) { + $this->multiple = true; + $this->value = array(); + $this->name = str_replace('[]', '', $this->name); + } + + $found = false; + foreach ($this->xpath->query('descendant::option', $this->node) as $option) { + $optionValue = $this->buildOptionValue($option); + $this->options[] = $optionValue; + + if ($option->hasAttribute('selected')) { + $found = true; + if ($this->multiple) { + $this->value[] = $optionValue['value']; + } else { + $this->value = $optionValue['value']; + } + } + } + + // if no option is selected and if it is a simple select box, take the first option as the value + if (!$found && !$this->multiple && !empty($this->options)) { + $this->value = $this->options[0]['value']; + } + } + } + + /** + * Returns option value with associated disabled flag + * + * @param \DOMElement $node + * + * @return array + */ + private function buildOptionValue(\DOMElement $node) + { + $option = array(); + + $defaultValue = (isset($node->nodeValue) && !empty($node->nodeValue)) ? $node->nodeValue : 'on'; + $option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue; + $option['disabled'] = $node->hasAttribute('disabled'); + + return $option; + } + + /** + * Checks whether given value is in the existing options + * + * @param string $optionValue + * @param array $options + * + * @return bool + */ + public function containsOption($optionValue, $options) + { + if ($this->validationDisabled) { + return true; + } + + foreach ($options as $option) { + if ($option['value'] == $optionValue) { + return true; + } + } + + return false; + } + + /** + * Returns list of available field options + * + * @return array + */ + public function availableOptionValues() + { + $values = array(); + + foreach ($this->options as $option) { + $values[] = $option['value']; + } + + return $values; + } + + /** + * Disables the internal validation of the field. + * + * @return self + */ + public function disableValidation() + { + $this->validationDisabled = true; + + return $this; + } +} diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FileFormField.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FileFormField.php new file mode 100644 index 0000000..bbdf467 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FileFormField.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * FileFormField represents a file form field (an HTML file input tag). + * + * @author Fabien Potencier + * + * @api + */ +class FileFormField extends FormField +{ + /** + * Sets the PHP error code associated with the field. + * + * @param int $error The error code (one of UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, or UPLOAD_ERR_EXTENSION) + * + * @throws \InvalidArgumentException When error code doesn't exist + */ + public function setErrorCode($error) + { + $codes = array(UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION); + if (!in_array($error, $codes)) { + throw new \InvalidArgumentException(sprintf('The error code %s is not valid.', $error)); + } + + $this->value = array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0); + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + * + * @api + */ + public function upload($value) + { + $this->setValue($value); + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + */ + public function setValue($value) + { + if (null !== $value && is_readable($value)) { + $error = UPLOAD_ERR_OK; + $size = filesize($value); + $info = pathinfo($value); + $name = $info['basename']; + + // copy to a tmp location + $tmp = sys_get_temp_dir().'/'.sha1(uniqid(mt_rand(), true)); + if (array_key_exists('extension', $info)) { + $tmp .= '.'.$info['extension']; + } + if (is_file($tmp)) { + unlink($tmp); + } + copy($value, $tmp); + $value = $tmp; + } else { + $error = UPLOAD_ERR_NO_FILE; + $size = 0; + $name = ''; + $value = ''; + } + + $this->value = array('name' => $name, 'type' => '', 'tmp_name' => $value, 'error' => $error, 'size' => $size); + } + + /** + * Sets path to the file as string for simulating HTTP request + * + * @param string $path The path to the file + */ + public function setFilePath($path) + { + parent::setValue($path); + } + + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('input' !== $this->node->nodeName) { + throw new \LogicException(sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName)); + } + + if ('file' !== strtolower($this->node->getAttribute('type'))) { + throw new \LogicException(sprintf('A FileFormField can only be created from an input tag with a type of file (given type is %s).', $this->node->getAttribute('type'))); + } + + $this->setValue(null); + } +} diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FormField.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FormField.php new file mode 100644 index 0000000..785e301 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FormField.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * FormField is the abstract class for all form fields. + * + * @author Fabien Potencier + */ +abstract class FormField +{ + /** + * @var \DOMElement + */ + protected $node; + /** + * @var string + */ + protected $name; + /** + * @var string + */ + protected $value; + /** + * @var \DOMDocument + */ + protected $document; + /** + * @var \DOMXPath + */ + protected $xpath; + /** + * @var bool + */ + protected $disabled; + + /** + * Constructor. + * + * @param \DOMElement $node The node associated with this field + */ + public function __construct(\DOMElement $node) + { + $this->node = $node; + $this->name = $node->getAttribute('name'); + $this->xpath = new \DOMXPath($node->ownerDocument); + + $this->initialize(); + } + + /** + * Returns the name of the field. + * + * @return string The name of the field + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the field. + * + * @return string|array The value of the field + */ + public function getValue() + { + return $this->value; + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + * + * @api + */ + public function setValue($value) + { + $this->value = (string) $value; + } + + /** + * Returns true if the field should be included in the submitted values. + * + * @return bool true if the field should be included in the submitted values, false otherwise + */ + public function hasValue() + { + return true; + } + + /** + * Check if the current field is disabled + * + * @return bool + */ + public function isDisabled() + { + return $this->node->hasAttribute('disabled'); + } + + /** + * Initializes the form field. + */ + abstract protected function initialize(); +} diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/InputFormField.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/InputFormField.php new file mode 100644 index 0000000..b9bd0a4 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/InputFormField.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * InputFormField represents an input form field (an HTML input tag). + * + * For inputs with type of file, checkbox, or radio, there are other more + * specialized classes (cf. FileFormField and ChoiceFormField). + * + * @author Fabien Potencier + * + * @api + */ +class InputFormField extends FormField +{ + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('input' !== $this->node->nodeName && 'button' !== $this->node->nodeName) { + throw new \LogicException(sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName)); + } + + if ('checkbox' === strtolower($this->node->getAttribute('type'))) { + throw new \LogicException('Checkboxes should be instances of ChoiceFormField.'); + } + + if ('file' === strtolower($this->node->getAttribute('type'))) { + throw new \LogicException('File inputs should be instances of FileFormField.'); + } + + $this->value = $this->node->getAttribute('value'); + } +} diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/TextareaFormField.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/TextareaFormField.php new file mode 100644 index 0000000..a14e707 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/TextareaFormField.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * TextareaFormField represents a textarea form field (an HTML textarea tag). + * + * @author Fabien Potencier + * + * @api + */ +class TextareaFormField extends FormField +{ + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('textarea' !== $this->node->nodeName) { + throw new \LogicException(sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName)); + } + + $this->value = ''; + foreach ($this->node->childNodes as $node) { + $this->value .= $node->wholeText; + } + } +} diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Form.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Form.php new file mode 100644 index 0000000..955b243 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Form.php @@ -0,0 +1,476 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\DomCrawler\Field\FormField; + +/** + * Form represents an HTML form. + * + * @author Fabien Potencier + * + * @api + */ +class Form extends Link implements \ArrayAccess +{ + /** + * @var \DOMElement + */ + private $button; + + /** + * @var FormFieldRegistry + */ + private $fields; + + /** + * Constructor. + * + * @param \DOMElement $node A \DOMElement instance + * @param string $currentUri The URI of the page where the form is embedded + * @param string $method The method to use for the link (if null, it defaults to the method defined by the form) + * + * @throws \LogicException if the node is not a button inside a form tag + * + * @api + */ + public function __construct(\DOMElement $node, $currentUri, $method = null) + { + parent::__construct($node, $currentUri, $method); + + $this->initialize(); + } + + /** + * Gets the form node associated with this form. + * + * @return \DOMElement A \DOMElement instance + */ + public function getFormNode() + { + return $this->node; + } + + /** + * Sets the value of the fields. + * + * @param array $values An array of field values + * + * @return Form + * + * @api + */ + public function setValues(array $values) + { + foreach ($values as $name => $value) { + $this->fields->set($name, $value); + } + + return $this; + } + + /** + * Gets the field values. + * + * The returned array does not include file fields (@see getFiles). + * + * @return array An array of field values. + * + * @api + */ + public function getValues() + { + $values = array(); + foreach ($this->fields->all() as $name => $field) { + if ($field->isDisabled()) { + continue; + } + + if (!$field instanceof Field\FileFormField && $field->hasValue()) { + $values[$name] = $field->getValue(); + } + } + + return $values; + } + + /** + * Gets the file field values. + * + * @return array An array of file field values. + * + * @api + */ + public function getFiles() + { + if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { + return array(); + } + + $files = array(); + + foreach ($this->fields->all() as $name => $field) { + if ($field->isDisabled()) { + continue; + } + + if ($field instanceof Field\FileFormField) { + $files[$name] = $field->getValue(); + } + } + + return $files; + } + + /** + * Gets the field values as PHP. + * + * This method converts fields with the array notation + * (like foo[bar] to arrays) like PHP does. + * + * @return array An array of field values. + * + * @api + */ + public function getPhpValues() + { + $values = array(); + foreach ($this->getValues() as $name => $value) { + $qs = http_build_query(array($name => $value), '', '&'); + if (!empty($qs)) { + parse_str($qs, $expandedValue); + $varName = substr($name, 0, strlen(key($expandedValue))); + $values = array_replace_recursive($values, array($varName => current($expandedValue))); + } + } + + return $values; + } + + /** + * Gets the file field values as PHP. + * + * This method converts fields with the array notation + * (like foo[bar] to arrays) like PHP does. + * + * @return array An array of field values. + * + * @api + */ + public function getPhpFiles() + { + $values = array(); + foreach ($this->getFiles() as $name => $value) { + $qs = http_build_query(array($name => $value), '', '&'); + if (!empty($qs)) { + parse_str($qs, $expandedValue); + $varName = substr($name, 0, strlen(key($expandedValue))); + $values = array_replace_recursive($values, array($varName => current($expandedValue))); + } + } + + return $values; + } + + /** + * Gets the URI of the form. + * + * The returned URI is not the same as the form "action" attribute. + * This method merges the value if the method is GET to mimics + * browser behavior. + * + * @return string The URI + * + * @api + */ + public function getUri() + { + $uri = parent::getUri(); + + if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH')) && $queryString = http_build_query($this->getValues(), null, '&')) { + $sep = false === strpos($uri, '?') ? '?' : '&'; + $uri .= $sep.$queryString; + } + + return $uri; + } + + protected function getRawUri() + { + return $this->node->getAttribute('action'); + } + + /** + * Gets the form method. + * + * If no method is defined in the form, GET is returned. + * + * @return string The method + * + * @api + */ + public function getMethod() + { + if (null !== $this->method) { + return $this->method; + } + + return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET'; + } + + /** + * Returns true if the named field exists. + * + * @param string $name The field name + * + * @return bool true if the field exists, false otherwise + * + * @api + */ + public function has($name) + { + return $this->fields->has($name); + } + + /** + * Removes a field from the form. + * + * @param string $name The field name + * + * @throws \InvalidArgumentException when the name is malformed + * + * @api + */ + public function remove($name) + { + $this->fields->remove($name); + } + + /** + * Gets a named field. + * + * @param string $name The field name + * + * @return FormField The field instance + * + * @throws \InvalidArgumentException When field is not present in this form + * + * @api + */ + public function get($name) + { + return $this->fields->get($name); + } + + /** + * Sets a named field. + * + * @param FormField $field The field + * + * @api + */ + public function set(FormField $field) + { + $this->fields->add($field); + } + + /** + * Gets all fields. + * + * @return FormField[] An array of fields + * + * @api + */ + public function all() + { + return $this->fields->all(); + } + + /** + * Returns true if the named field exists. + * + * @param string $name The field name + * + * @return bool true if the field exists, false otherwise + */ + public function offsetExists($name) + { + return $this->has($name); + } + + /** + * Gets the value of a field. + * + * @param string $name The field name + * + * @return FormField The associated Field instance + * + * @throws \InvalidArgumentException if the field does not exist + */ + public function offsetGet($name) + { + return $this->fields->get($name); + } + + /** + * Sets the value of a field. + * + * @param string $name The field name + * @param string|array $value The value of the field + * + * @throws \InvalidArgumentException if the field does not exist + */ + public function offsetSet($name, $value) + { + $this->fields->set($name, $value); + } + + /** + * Removes a field from the form. + * + * @param string $name The field name + */ + public function offsetUnset($name) + { + $this->fields->remove($name); + } + + /** + * Disables validation + * + * @return self + */ + public function disableValidation() + { + foreach ($this->fields->all() as $field) { + if ($field instanceof Field\ChoiceFormField) { + $field->disableValidation(); + } + } + + return $this; + } + + /** + * Sets the node for the form. + * + * Expects a 'submit' button \DOMElement and finds the corresponding form element, or the form element itself. + * + * @param \DOMElement $node A \DOMElement instance + * + * @throws \LogicException If given node is not a button or input or does not have a form ancestor + */ + protected function setNode(\DOMElement $node) + { + $this->button = $node; + if ('button' === $node->nodeName || ('input' === $node->nodeName && in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image')))) { + if ($node->hasAttribute('form')) { + // if the node has the HTML5-compliant 'form' attribute, use it + $formId = $node->getAttribute('form'); + $form = $node->ownerDocument->getElementById($formId); + if (null === $form) { + throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId)); + } + $this->node = $form; + + return; + } + // we loop until we find a form ancestor + do { + if (null === $node = $node->parentNode) { + throw new \LogicException('The selected node does not have a form ancestor.'); + } + } while ('form' !== $node->nodeName); + } elseif ('form' !== $node->nodeName) { + throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName)); + } + + $this->node = $node; + } + + /** + * Adds form elements related to this form. + * + * Creates an internal copy of the submitted 'button' element and + * the form node or the entire document depending on whether we need + * to find non-descendant elements through HTML5 'form' attribute. + */ + private function initialize() + { + $this->fields = new FormFieldRegistry(); + + $xpath = new \DOMXPath($this->node->ownerDocument); + + // add submitted button if it has a valid name + if ('form' !== $this->button->nodeName && $this->button->hasAttribute('name') && $this->button->getAttribute('name')) { + if ('input' == $this->button->nodeName && 'image' == strtolower($this->button->getAttribute('type'))) { + $name = $this->button->getAttribute('name'); + $this->button->setAttribute('value', '0'); + + // temporarily change the name of the input node for the x coordinate + $this->button->setAttribute('name', $name.'.x'); + $this->set(new Field\InputFormField($this->button)); + + // temporarily change the name of the input node for the y coordinate + $this->button->setAttribute('name', $name.'.y'); + $this->set(new Field\InputFormField($this->button)); + + // restore the original name of the input node + $this->button->setAttribute('name', $name); + } else { + $this->set(new Field\InputFormField($this->button)); + } + } + + // find form elements corresponding to the current form + if ($this->node->hasAttribute('id')) { + // corresponding elements are either descendants or have a matching HTML5 form attribute + $formId = Crawler::xpathLiteral($this->node->getAttribute('id')); + + $fieldNodes = $xpath->query(sprintf('descendant::input[@form=%s] | descendant::button[@form=%s] | descendant::textarea[@form=%s] | descendant::select[@form=%s] | //form[@id=%s]//input[not(@form)] | //form[@id=%s]//button[not(@form)] | //form[@id=%s]//textarea[not(@form)] | //form[@id=%s]//select[not(@form)]', $formId, $formId, $formId, $formId, $formId, $formId, $formId, $formId)); + foreach ($fieldNodes as $node) { + $this->addField($node); + } + } else { + // do the xpath query with $this->node as the context node, to only find descendant elements + // however, descendant elements with form attribute are not part of this form + $fieldNodes = $xpath->query('descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)]', $this->node); + foreach ($fieldNodes as $node) { + $this->addField($node); + } + } + } + + private function addField(\DOMElement $node) + { + if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { + return; + } + + $nodeName = $node->nodeName; + if ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == strtolower($node->getAttribute('type'))) { + $this->set(new Field\ChoiceFormField($node)); + } elseif ('input' == $nodeName && 'radio' == strtolower($node->getAttribute('type'))) { + if ($this->has($node->getAttribute('name'))) { + $this->get($node->getAttribute('name'))->addChoice($node); + } else { + $this->set(new Field\ChoiceFormField($node)); + } + } elseif ('input' == $nodeName && 'file' == strtolower($node->getAttribute('type'))) { + $this->set(new Field\FileFormField($node)); + } elseif ('input' == $nodeName && !in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image'))) { + $this->set(new Field\InputFormField($node)); + } elseif ('textarea' == $nodeName) { + $this->set(new Field\TextareaFormField($node)); + } + } +} diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/FormFieldRegistry.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/FormFieldRegistry.php new file mode 100644 index 0000000..150f94d --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/FormFieldRegistry.php @@ -0,0 +1,220 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\DomCrawler\Field\FormField; + +/** + * This is an internal class that must not be used directly. + */ +class FormFieldRegistry +{ + private $fields = array(); + + private $base; + + /** + * Adds a field to the registry. + * + * @param FormField $field The field + * + * @throws \InvalidArgumentException when the name is malformed + */ + public function add(FormField $field) + { + $segments = $this->getSegments($field->getName()); + + $target =& $this->fields; + while ($segments) { + if (!is_array($target)) { + $target = array(); + } + $path = array_shift($segments); + if ('' === $path) { + $target =& $target[]; + } else { + $target =& $target[$path]; + } + } + $target = $field; + } + + /** + * Removes a field and its children from the registry. + * + * @param string $name The fully qualified name of the base field + * + * @throws \InvalidArgumentException when the name is malformed + */ + public function remove($name) + { + $segments = $this->getSegments($name); + $target =& $this->fields; + while (count($segments) > 1) { + $path = array_shift($segments); + if (!array_key_exists($path, $target)) { + return; + } + $target =& $target[$path]; + } + unset($target[array_shift($segments)]); + } + + /** + * Returns the value of the field and its children. + * + * @param string $name The fully qualified name of the field + * + * @return mixed The value of the field + * + * @throws \InvalidArgumentException when the name is malformed + * @throws \InvalidArgumentException if the field does not exist + */ + public function &get($name) + { + $segments = $this->getSegments($name); + $target =& $this->fields; + while ($segments) { + $path = array_shift($segments); + if (!array_key_exists($path, $target)) { + throw new \InvalidArgumentException(sprintf('Unreachable field "%s"', $path)); + } + $target =& $target[$path]; + } + + return $target; + } + + /** + * Tests whether the form has the given field. + * + * @param string $name The fully qualified name of the field + * + * @return bool Whether the form has the given field + */ + public function has($name) + { + try { + $this->get($name); + + return true; + } catch (\InvalidArgumentException $e) { + return false; + } + } + + /** + * Set the value of a field and its children. + * + * @param string $name The fully qualified name of the field + * @param mixed $value The value + * + * @throws \InvalidArgumentException when the name is malformed + * @throws \InvalidArgumentException if the field does not exist + */ + public function set($name, $value) + { + $target =& $this->get($name); + if (!is_array($value) || $target instanceof Field\ChoiceFormField) { + $target->setValue($value); + } else { + $fields = self::create($name, $value); + foreach ($fields->all() as $k => $v) { + $this->set($k, $v); + } + } + } + + /** + * Returns the list of field with their value. + * + * @return FormField[] The list of fields as array((string) Fully qualified name => (mixed) value) + */ + public function all() + { + return $this->walk($this->fields, $this->base); + } + + /** + * Creates an instance of the class. + * + * This function is made private because it allows overriding the $base and + * the $values properties without any type checking. + * + * @param string $base The fully qualified name of the base field + * @param array $values The values of the fields + * + * @return FormFieldRegistry + */ + private static function create($base, array $values) + { + $registry = new static(); + $registry->base = $base; + $registry->fields = $values; + + return $registry; + } + + /** + * Transforms a PHP array in a list of fully qualified name / value. + * + * @param array $array The PHP array + * @param string $base The name of the base field + * @param array $output The initial values + * + * @return array The list of fields as array((string) Fully qualified name => (mixed) value) + */ + private function walk(array $array, $base = '', array &$output = array()) + { + foreach ($array as $k => $v) { + $path = empty($base) ? $k : sprintf("%s[%s]", $base, $k); + if (is_array($v)) { + $this->walk($v, $path, $output); + } else { + $output[$path] = $v; + } + } + + return $output; + } + + /** + * Splits a field name into segments as a web browser would do. + * + * + * getSegments('base[foo][3][]') = array('base', 'foo, '3', ''); + * + * + * @param string $name The name of the field + * + * @return string[] The list of segments + * + * @throws \InvalidArgumentException when the name is malformed + */ + private function getSegments($name) + { + if (preg_match('/^(?P[^[]+)(?P(\[.*)|$)/', $name, $m)) { + $segments = array($m['base']); + while (!empty($m['extra'])) { + if (preg_match('/^\[(?P.*?)\](?P.*)$/', $m['extra'], $m)) { + $segments[] = $m['segment']; + } else { + throw new \InvalidArgumentException(sprintf('Malformed field path "%s"', $name)); + } + } + + return $segments; + } + + throw new \InvalidArgumentException(sprintf('Malformed field path "%s"', $name)); + } +} diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/LICENSE b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/LICENSE new file mode 100644 index 0000000..0b3292c --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2014 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/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Link.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Link.php new file mode 100644 index 0000000..bb2302d --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Link.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +/** + * Link represents an HTML link (an HTML a or area tag). + * + * @author Fabien Potencier + * + * @api + */ +class Link +{ + /** + * @var \DOMElement + */ + protected $node; + + /** + * @var string The method to use for the link + */ + protected $method; + + /** + * @var string The URI of the page where the link is embedded (or the base href) + */ + protected $currentUri; + + /** + * Constructor. + * + * @param \DOMElement $node A \DOMElement instance + * @param string $currentUri The URI of the page where the link is embedded (or the base href) + * @param string $method The method to use for the link (get by default) + * + * @throws \InvalidArgumentException if the node is not a link + * + * @api + */ + public function __construct(\DOMElement $node, $currentUri, $method = 'GET') + { + if (!in_array(strtolower(substr($currentUri, 0, 4)), array('http', 'file'))) { + throw new \InvalidArgumentException(sprintf('Current URI must be an absolute URL ("%s").', $currentUri)); + } + + $this->setNode($node); + $this->method = $method ? strtoupper($method) : null; + $this->currentUri = $currentUri; + } + + /** + * Gets the node associated with this link. + * + * @return \DOMElement A \DOMElement instance + */ + public function getNode() + { + return $this->node; + } + + /** + * Gets the method associated with this link. + * + * @return string The method + * + * @api + */ + public function getMethod() + { + return $this->method; + } + + /** + * Gets the URI associated with this link. + * + * @return string The URI + * + * @api + */ + public function getUri() + { + $uri = trim($this->getRawUri()); + + // absolute URL? + if (null !== parse_url($uri, PHP_URL_SCHEME)) { + return $uri; + } + + // empty URI + if (!$uri) { + return $this->currentUri; + } + + // an anchor + if ('#' === $uri[0]) { + return $this->cleanupAnchor($this->currentUri).$uri; + } + + $baseUri = $this->cleanupUri($this->currentUri); + + if ('?' === $uri[0]) { + return $baseUri.$uri; + } + + // absolute URL with relative schema + if (0 === strpos($uri, '//')) { + return preg_replace('#^([^/]*)//.*$#', '$1', $baseUri).$uri; + } + + $baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUri); + + // absolute path + if ('/' === $uri[0]) { + return $baseUri.$uri; + } + + // relative path + $path = parse_url(substr($this->currentUri, strlen($baseUri)), PHP_URL_PATH); + $path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri); + + return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path; + } + + /** + * Returns raw URI data. + * + * @return string + */ + protected function getRawUri() + { + return $this->node->getAttribute('href'); + } + + /** + * Returns the canonicalized URI path (see RFC 3986, section 5.2.4) + * + * @param string $path URI path + * + * @return string + */ + protected function canonicalizePath($path) + { + if ('' === $path || '/' === $path) { + return $path; + } + + if ('.' === substr($path, -1)) { + $path = $path.'/'; + } + + $output = array(); + + foreach (explode('/', $path) as $segment) { + if ('..' === $segment) { + array_pop($output); + } elseif ('.' !== $segment) { + array_push($output, $segment); + } + } + + return implode('/', $output); + } + + /** + * Sets current \DOMElement instance. + * + * @param \DOMElement $node A \DOMElement instance + * + * @throws \LogicException If given node is not an anchor + */ + protected function setNode(\DOMElement $node) + { + if ('a' !== $node->nodeName && 'area' !== $node->nodeName) { + throw new \LogicException(sprintf('Unable to click on a "%s" tag.', $node->nodeName)); + } + + $this->node = $node; + } + + /** + * Removes the query string and the anchor from the given uri. + * + * @param string $uri The uri to clean + * + * @return string + */ + private function cleanupUri($uri) + { + return $this->cleanupQuery($this->cleanupAnchor($uri)); + } + + /** + * Remove the query string from the uri. + * + * @param string $uri + * + * @return string + */ + private function cleanupQuery($uri) + { + if (false !== $pos = strpos($uri, '?')) { + return substr($uri, 0, $pos); + } + + return $uri; + } + + /** + * Remove the anchor from the uri. + * + * @param string $uri + * + * @return string + */ + private function cleanupAnchor($uri) + { + if (false !== $pos = strpos($uri, '#')) { + return substr($uri, 0, $pos); + } + + return $uri; + } +} diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/README.md b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/README.md new file mode 100644 index 0000000..489e468 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/README.md @@ -0,0 +1,32 @@ +DomCrawler Component +==================== + +DomCrawler eases DOM navigation for HTML and XML documents. + +If you are familiar with jQuery, DomCrawler is a PHP equivalent: + + use Symfony\Component\DomCrawler\Crawler; + + $crawler = new Crawler(); + $crawler->addContent('

Hello World!

'); + + print $crawler->filterXPath('descendant-or-self::body/p')->text(); + +If you are also using the CssSelector component, you can use CSS Selectors +instead of XPath expressions: + + use Symfony\Component\DomCrawler\Crawler; + + $crawler = new Crawler(); + $crawler->addContent('

Hello World!

'); + + print $crawler->filter('body > p')->text(); + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/DomCrawler/ + $ composer.phar install + $ phpunit diff --git a/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/CrawlerTest.php b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/CrawlerTest.php new file mode 100644 index 0000000..37b8d83 --- /dev/null +++ b/core/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/CrawlerTest.php @@ -0,0 +1,924 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Tests; + +use Symfony\Component\CssSelector\CssSelector; +use Symfony\Component\DomCrawler\Crawler; + +class CrawlerTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $crawler = new Crawler(); + $this->assertCount(0, $crawler, '__construct() returns an empty crawler'); + + $crawler = new Crawler(new \DOMNode()); + $this->assertCount(1, $crawler, '__construct() takes a node as a first argument'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::add + */ + public function testAdd() + { + $crawler = new Crawler(); + $crawler->add($this->createDomDocument()); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMDocument'); + + $crawler = new Crawler(); + $crawler->add($this->createNodeList()); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMNodeList'); + + foreach ($this->createNodeList() as $node) { + $list[] = $node; + } + $crawler = new Crawler(); + $crawler->add($list); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from an array of nodes'); + + $crawler = new Crawler(); + $crawler->add($this->createNodeList()->item(0)); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMElement'); + + $crawler = new Crawler(); + $crawler->add('Foo'); + $this->assertEquals('Foo', $crawler->filterXPath('//body')->text(), '->add() adds nodes from a string'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testAddInvalidNode() + { + $crawler = new Crawler(); + $crawler->add(1); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContent() + { + $crawler = new Crawler(); + $crawler->addHtmlContent('
', 'UTF-8'); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addHtmlContent() adds nodes from an HTML string'); + + $crawler->addHtmlContent('', 'UTF-8'); + + $this->assertEquals('http://symfony.com', $crawler->filterXPath('//base')->attr('href'), '->addHtmlContent() adds nodes from an HTML string'); + $this->assertEquals('http://symfony.com/contact', $crawler->filterXPath('//a')->link()->getUri(), '->addHtmlContent() adds nodes from an HTML string'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContentCharset() + { + $crawler = new Crawler(); + $crawler->addHtmlContent('
Tiếng Việt', 'UTF-8'); + + $this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text()); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContentInvalidBaseTag() + { + $crawler = new Crawler(null, 'http://symfony.com'); + + $crawler->addHtmlContent('', 'UTF-8'); + + $this->assertEquals('http://symfony.com/contact', current($crawler->filterXPath('//a')->links())->getUri(), '->addHtmlContent() correctly handles a non-existent base tag href attribute'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContentUnsupportedCharset() + { + $crawler = new Crawler(); + $crawler->addHtmlContent(file_get_contents(__DIR__.'/Fixtures/windows-1250.html'), 'Windows-1250'); + + $this->assertEquals('ŽťÄýů', $crawler->filterXPath('//p')->text()); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContentCharsetGbk() + { + $crawler = new Crawler(); + //gbk encode of

中文

+ $crawler->addHtmlContent(base64_decode('PGh0bWw+PHA+1tDOxDwvcD48L2h0bWw+'), 'gbk'); + + $this->assertEquals('中文', $crawler->filterXPath('//p')->text()); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContentWithErrors() + { + $internalErrors = libxml_use_internal_errors(true); + + $crawler = new Crawler(); + $crawler->addHtmlContent(<< + + + + + + + +EOF + , 'UTF-8'); + + $errors = libxml_get_errors(); + $this->assertCount(1, $errors); + $this->assertEquals("Tag nav invalid\n", $errors[0]->message); + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addXmlContent + */ + public function testAddXmlContent() + { + $crawler = new Crawler(); + $crawler->addXmlContent('
', 'UTF-8'); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addXmlContent() adds nodes from an XML string'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addXmlContent + */ + public function testAddXmlContentCharset() + { + $crawler = new Crawler(); + $crawler->addXmlContent('
Tiếng Việt
', 'UTF-8'); + + $this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text()); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addXmlContent + */ + public function testAddXmlContentWithErrors() + { + $internalErrors = libxml_use_internal_errors(true); + + $crawler = new Crawler(); + $crawler->addXmlContent(<< + + + + +
+ + +EOF + , 'UTF-8'); + + $this->assertTrue(count(libxml_get_errors()) > 1); + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addContent + */ + public function testAddContent() + { + $crawler = new Crawler(); + $crawler->addContent('
', 'text/html; charset=UTF-8'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string'); + + $crawler = new Crawler(); + $crawler->addContent('
', 'text/html; charset=UTF-8; dir=RTL'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string with extended content type'); + + $crawler = new Crawler(); + $crawler->addContent('
'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() uses text/html as the default type'); + + $crawler = new Crawler(); + $crawler->addContent('
', 'text/xml; charset=UTF-8'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string'); + + $crawler = new Crawler(); + $crawler->addContent('
', 'text/xml'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string'); + + $crawler = new Crawler(); + $crawler->addContent('foo bar', 'text/plain'); + $this->assertCount(0, $crawler, '->addContent() does nothing if the type is not (x|ht)ml'); + + $crawler = new Crawler(); + $crawler->addContent('中文'); + $this->assertEquals('中文', $crawler->filterXPath('//span')->text(), '->addContent() guess wrong charset'); + + $crawler = new Crawler(); + $crawler->addContent(mb_convert_encoding('日本語', 'SJIS', 'UTF-8')); + $this->assertEquals('日本語', $crawler->filterXPath('//body')->text(), '->addContent() can recognize "Shift_JIS" in html5 meta charset tag'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addDocument + */ + public function testAddDocument() + { + $crawler = new Crawler(); + $crawler->addDocument($this->createDomDocument()); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addDocument() adds nodes from a \DOMDocument'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addNodeList + */ + public function testAddNodeList() + { + $crawler = new Crawler(); + $crawler->addNodeList($this->createNodeList()); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodeList() adds nodes from a \DOMNodeList'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addNodes + */ + public function testAddNodes() + { + foreach ($this->createNodeList() as $node) { + $list[] = $node; + } + + $crawler = new Crawler(); + $crawler->addNodes($list); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodes() adds nodes from an array of nodes'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addNode + */ + public function testAddNode() + { + $crawler = new Crawler(); + $crawler->addNode($this->createNodeList()->item(0)); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNode() adds nodes from a \DOMElement'); + } + + public function testClear() + { + $crawler = new Crawler(new \DOMNode()); + $crawler->clear(); + $this->assertCount(0, $crawler, '->clear() removes all the nodes from the crawler'); + } + + public function testEq() + { + $crawler = $this->createTestCrawler()->filterXPath('//li'); + $this->assertNotSame($crawler, $crawler->eq(0), '->eq() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->eq() returns a new instance of a crawler'); + + $this->assertEquals('Two', $crawler->eq(1)->text(), '->eq() returns the nth node of the list'); + $this->assertCount(0, $crawler->eq(100), '->eq() returns an empty crawler if the nth node does not exist'); + } + + public function testEach() + { + $data = $this->createTestCrawler()->filterXPath('//ul[1]/li')->each(function ($node, $i) { + return $i.'-'.$node->text(); + }); + + $this->assertEquals(array('0-One', '1-Two', '2-Three'), $data, '->each() executes an anonymous function on each node of the list'); + } + + public function testReduce() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + $nodes = $crawler->reduce(function ($node, $i) { + return $i == 1 ? false : true; + }); + $this->assertNotSame($nodes, $crawler, '->reduce() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $nodes, '->reduce() returns a new instance of a crawler'); + + $this->assertCount(2, $nodes, '->reduce() filters the nodes in the list'); + } + + public function testAttr() + { + $this->assertEquals('first', $this->createTestCrawler()->filterXPath('//li')->attr('class'), '->attr() returns the attribute of the first element of the node list'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->attr('class'); + $this->fail('->attr() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->attr() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testMissingAttrValueIsNull() + { + $crawler = new Crawler(); + $crawler->addContent('
', 'text/html; charset=UTF-8'); + $div = $crawler->filterXPath('//div'); + + $this->assertEquals('sample value', $div->attr('non-empty-attr'), '->attr() reads non-empty attributes correctly'); + $this->assertEquals('', $div->attr('empty-attr'), '->attr() reads empty attributes correctly'); + $this->assertNull($div->attr('missing-attr'), '->attr() reads missing attributes correctly'); + } + + public function testText() + { + $this->assertEquals('One', $this->createTestCrawler()->filterXPath('//li')->text(), '->text() returns the node value of the first element of the node list'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->text(); + $this->fail('->text() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->text() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testHtml() + { + $this->assertEquals('Bar', $this->createTestCrawler()->filterXPath('//a[5]')->html()); + $this->assertEquals('' + , trim($this->createTestCrawler()->filterXPath('//form[@id="FooFormId"]')->html())); + + try { + $this->createTestCrawler()->filterXPath('//ol')->html(); + $this->fail('->html() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->html() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testExtract() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + + $this->assertEquals(array('One', 'Two', 'Three'), $crawler->extract('_text'), '->extract() returns an array of extracted data from the node list'); + $this->assertEquals(array(array('One', 'first'), array('Two', ''), array('Three', '')), $crawler->extract(array('_text', 'class')), '->extract() returns an array of extracted data from the node list'); + + $this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->extract('_text'), '->extract() returns an empty array if the node list is empty'); + } + + public function testFilterXpathComplexQueries() + { + $crawler = $this->createTestCrawler()->filterXPath('//body'); + + $this->assertCount(0, $crawler->filterXPath('/input')); + $this->assertCount(0, $crawler->filterXPath('/body')); + $this->assertCount(1, $crawler->filterXPath('/_root/body')); + $this->assertCount(1, $crawler->filterXPath('./body')); + $this->assertCount(1, $crawler->filterXPath('.//body')); + $this->assertCount(5, $crawler->filterXPath('.//input')); + $this->assertCount(4, $crawler->filterXPath('//form')->filterXPath('//button | //input')); + $this->assertCount(1, $crawler->filterXPath('body')); + $this->assertCount(6, $crawler->filterXPath('//button | //input')); + $this->assertCount(1, $crawler->filterXPath('//body')); + $this->assertCount(1, $crawler->filterXPath('descendant-or-self::body')); + $this->assertCount(1, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('./div'), 'A child selection finds only the current div'); + $this->assertCount(2, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('descendant::div'), 'A descendant selector matches the current div and its child'); + $this->assertCount(2, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('//div'), 'A descendant selector matches the current div and its child'); + $this->assertCount(5, $crawler->filterXPath('(//a | //div)//img')); + $this->assertCount(7, $crawler->filterXPath('((//a | //div)//img | //ul)')); + $this->assertCount(7, $crawler->filterXPath('( ( //a | //div )//img | //ul )')); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::filterXPath + */ + public function testFilterXPath() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->filterXPath('//li'), '->filterXPath() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filterXPath() returns a new instance of a crawler'); + + $crawler = $this->createTestCrawler()->filterXPath('//ul'); + $this->assertCount(6, $crawler->filterXPath('//li'), '->filterXPath() filters the node list with the XPath expression'); + + $crawler = $this->createTestCrawler(); + $this->assertCount(3, $crawler->filterXPath('//body')->filterXPath('//button')->parents(), '->filterXpath() preserves parents when chained'); + } + + public function testFilterXPathWithDefaultNamespace() + { + $crawler = $this->createTestXmlCrawler()->filterXPath('//default:entry/default:id'); + $this->assertCount(1, $crawler, '->filterXPath() automatically registers a namespace'); + $this->assertSame('tag:youtube.com,2008:video:kgZRZmEc9j4', $crawler->text()); + } + + public function testFilterXPathWithCustomDefaultNamespace() + { + $crawler = $this->createTestXmlCrawler(); + $crawler->setDefaultNamespacePrefix('x'); + $crawler = $crawler->filterXPath('//x:entry/x:id'); + + $this->assertCount(1, $crawler, '->filterXPath() lets to override the default namespace prefix'); + $this->assertSame('tag:youtube.com,2008:video:kgZRZmEc9j4', $crawler->text()); + } + + public function testFilterXPathWithNamespace() + { + $crawler = $this->createTestXmlCrawler()->filterXPath('//yt:accessControl'); + $this->assertCount(2, $crawler, '->filterXPath() automatically registers a namespace'); + } + + public function testFilterXPathWithMultipleNamespaces() + { + $crawler = $this->createTestXmlCrawler()->filterXPath('//media:group/yt:aspectRatio'); + $this->assertCount(1, $crawler, '->filterXPath() automatically registers multiple namespaces'); + $this->assertSame('widescreen', $crawler->text()); + } + + public function testFilterXPathWithManuallyRegisteredNamespace() + { + $crawler = $this->createTestXmlCrawler(); + $crawler->registerNamespace('m', 'http://search.yahoo.com/mrss/'); + + $crawler = $crawler->filterXPath('//m:group/yt:aspectRatio'); + $this->assertCount(1, $crawler, '->filterXPath() uses manually registered namespace'); + $this->assertSame('widescreen', $crawler->text()); + } + + public function testFilterXPathWithAnUrl() + { + $crawler = $this->createTestXmlCrawler(); + + $crawler = $crawler->filterXPath('//media:category[@scheme="http://gdata.youtube.com/schemas/2007/categories.cat"]'); + $this->assertCount(1, $crawler); + $this->assertSame('Music', $crawler->text()); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::filter + */ + public function testFilter() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->filter('li'), '->filter() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filter() returns a new instance of a crawler'); + + $crawler = $this->createTestCrawler()->filter('ul'); + + $this->assertCount(6, $crawler->filter('li'), '->filter() filters the node list with the CSS selector'); + } + + public function testFilterWithDefaultNamespace() + { + $crawler = $this->createTestXmlCrawler()->filter('default|entry default|id'); + $this->assertCount(1, $crawler, '->filter() automatically registers namespaces'); + $this->assertSame('tag:youtube.com,2008:video:kgZRZmEc9j4', $crawler->text()); + } + + public function testFilterWithNamespace() + { + CssSelector::disableHtmlExtension(); + + $crawler = $this->createTestXmlCrawler()->filter('yt|accessControl'); + $this->assertCount(2, $crawler, '->filter() automatically registers namespaces'); + } + + public function testFilterWithMultipleNamespaces() + { + CssSelector::disableHtmlExtension(); + + $crawler = $this->createTestXmlCrawler()->filter('media|group yt|aspectRatio'); + $this->assertCount(1, $crawler, '->filter() automatically registers namespaces'); + $this->assertSame('widescreen', $crawler->text()); + } + + public function testFilterWithDefaultNamespaceOnly() + { + $crawler = new Crawler(' + + + http://localhost/foo + weekly + 0.5 + 2012-11-16 + + + http://localhost/bar + weekly + 0.5 + 2012-11-16 + + + '); + + $this->assertEquals(2, $crawler->filter('url')->count()); + } + + public function testSelectLink() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->selectLink('Foo'), '->selectLink() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectLink() returns a new instance of a crawler'); + + $this->assertCount(1, $crawler->selectLink('Fabien\'s Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(1, $crawler->selectLink('Fabien\'s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); + + $this->assertCount(2, $crawler->selectLink('Fabien"s Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(2, $crawler->selectLink('Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); + + $this->assertCount(1, $crawler->selectLink('\' Fabien"s Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(1, $crawler->selectLink('\' Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); + + $this->assertCount(4, $crawler->selectLink('Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(4, $crawler->selectLink('Bar'), '->selectLink() selects links by the node values'); + } + + public function testSelectButton() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->selectButton('FooValue'), '->selectButton() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectButton() returns a new instance of a crawler'); + + $this->assertEquals(1, $crawler->selectButton('FooValue')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('FooName')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('FooId')->count(), '->selectButton() selects buttons'); + + $this->assertEquals(1, $crawler->selectButton('BarValue')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('BarName')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('BarId')->count(), '->selectButton() selects buttons'); + + $this->assertEquals(1, $crawler->selectButton('FooBarValue')->count(), '->selectButton() selects buttons with form attribute too'); + $this->assertEquals(1, $crawler->selectButton('FooBarName')->count(), '->selectButton() selects buttons with form attribute too'); + } + + public function testSelectButtonWithSingleQuotesInNameAttribute() + { + $html = << + + +
+
+ +
+ + +HTML; + + $crawler = new Crawler($html); + + $this->assertCount(1, $crawler->selectButton('Click \'Here\'')); + } + + public function testSelectButtonWithDoubleQuotesInNameAttribute() + { + $html = << + + +
+ Login +
+
+ +
+ + +HTML; + + $crawler = new Crawler($html); + + $this->assertCount(1, $crawler->selectButton('Click "Here"')); + } + + public function testLink() + { + $crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $crawler->link(), '->link() returns a Link instance'); + + $this->assertEquals('POST', $crawler->link('post')->getMethod(), '->link() takes a method as its argument'); + + $crawler = $this->createTestCrawler('http://example.com/bar')->selectLink('GetLink'); + $this->assertEquals('http://example.com/bar?get=param', $crawler->link()->getUri(), '->link() returns a Link instance'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->link(); + $this->fail('->link() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->link() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testSelectLinkAndLinkFiltered() + { + $html = << + + +
+ Login +
+
+ +
+ + +HTML; + + $crawler = new Crawler($html); + $filtered = $crawler->filterXPath("descendant-or-self::*[@id = 'login-form']"); + + $this->assertCount(0, $filtered->selectLink('Login')); + $this->assertCount(1, $filtered->selectButton('Submit')); + + $filtered = $crawler->filterXPath("descendant-or-self::*[@id = 'action']"); + + $this->assertCount(1, $filtered->selectLink('Login')); + $this->assertCount(0, $filtered->selectButton('Submit')); + + $this->assertCount(1, $crawler->selectLink('Login')->selectLink('Login')); + $this->assertCount(1, $crawler->selectButton('Submit')->selectButton('Submit')); + } + + public function testChaining() + { + $crawler = new Crawler('
'); + + $this->assertEquals('a', $crawler->filterXPath('//div')->filterXPath('div')->filterXPath('div')->attr('name')); + } + + public function testLinks() + { + $crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo'); + $this->assertInternalType('array', $crawler->links(), '->links() returns an array'); + + $this->assertCount(4, $crawler->links(), '->links() returns an array'); + $links = $crawler->links(); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $links[0], '->links() returns an array of Link instances'); + + $this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->links(), '->links() returns an empty array if the node selection is empty'); + } + + public function testForm() + { + $testCrawler = $this->createTestCrawler('http://example.com/bar/'); + $crawler = $testCrawler->selectButton('FooValue'); + $crawler2 = $testCrawler->selectButton('FooBarValue'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler->form(), '->form() returns a Form instance'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler2->form(), '->form() returns a Form instance'); + + $this->assertEquals($crawler->form()->getFormNode()->getAttribute('id'), $crawler2->form()->getFormNode()->getAttribute('id'), '->form() works on elements with form attribute'); + + $this->assertEquals(array('FooName' => 'FooBar', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form(array('FooName' => 'FooBar'))->getValues(), '->form() takes an array of values to submit as its first argument'); + $this->assertEquals(array('FooName' => 'FooValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form()->getValues(), '->getValues() returns correct form values'); + $this->assertEquals(array('FooBarName' => 'FooBarValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler2->form()->getValues(), '->getValues() returns correct form values'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->form(); + $this->fail('->form() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->form() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testLast() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + $this->assertNotSame($crawler, $crawler->last(), '->last() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->last() returns a new instance of a crawler'); + + $this->assertEquals('Three', $crawler->last()->text()); + } + + public function testFirst() + { + $crawler = $this->createTestCrawler()->filterXPath('//li'); + $this->assertNotSame($crawler, $crawler->first(), '->first() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->first() returns a new instance of a crawler'); + + $this->assertEquals('One', $crawler->first()->text()); + } + + public function testSiblings() + { + $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1); + $this->assertNotSame($crawler, $crawler->siblings(), '->siblings() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->siblings() returns a new instance of a crawler'); + + $nodes = $crawler->siblings(); + $this->assertEquals(2, $nodes->count()); + $this->assertEquals('One', $nodes->eq(0)->text()); + $this->assertEquals('Three', $nodes->eq(1)->text()); + + $nodes = $this->createTestCrawler()->filterXPath('//li')->eq(0)->siblings(); + $this->assertEquals(2, $nodes->count()); + $this->assertEquals('Two', $nodes->eq(0)->text()); + $this->assertEquals('Three', $nodes->eq(1)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->siblings(); + $this->fail('->siblings() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->siblings() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testNextAll() + { + $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1); + $this->assertNotSame($crawler, $crawler->nextAll(), '->nextAll() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->nextAll() returns a new instance of a crawler'); + + $nodes = $crawler->nextAll(); + $this->assertEquals(1, $nodes->count()); + $this->assertEquals('Three', $nodes->eq(0)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->nextAll(); + $this->fail('->nextAll() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->nextAll() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testPreviousAll() + { + $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(2); + $this->assertNotSame($crawler, $crawler->previousAll(), '->previousAll() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->previousAll() returns a new instance of a crawler'); + + $nodes = $crawler->previousAll(); + $this->assertEquals(2, $nodes->count()); + $this->assertEquals('Two', $nodes->eq(0)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->previousAll(); + $this->fail('->previousAll() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->previousAll() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testChildren() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul'); + $this->assertNotSame($crawler, $crawler->children(), '->children() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->children() returns a new instance of a crawler'); + + $nodes = $crawler->children(); + $this->assertEquals(3, $nodes->count()); + $this->assertEquals('One', $nodes->eq(0)->text()); + $this->assertEquals('Two', $nodes->eq(1)->text()); + $this->assertEquals('Three', $nodes->eq(2)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->children(); + $this->fail('->children() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->children() throws an \InvalidArgumentException if the node list is empty'); + } + + try { + $crawler = new Crawler('

'); + $crawler->filter('p')->children(); + $this->assertTrue(true, '->children() does not trigger a notice if the node has no children'); + } catch (\PHPUnit_Framework_Error_Notice $e) { + $this->fail('->children() does not trigger a notice if the node has no children'); + } + } + + public function testParents() + { + $crawler = $this->createTestCrawler()->filterXPath('//li[1]'); + $this->assertNotSame($crawler, $crawler->parents(), '->parents() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->parents() returns a new instance of a crawler'); + + $nodes = $crawler->parents(); + $this->assertEquals(3, $nodes->count()); + + $nodes = $this->createTestCrawler()->filterXPath('//html')->parents(); + $this->assertEquals(0, $nodes->count()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->parents(); + $this->fail('->parents() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->parents() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testBaseTag() + { + $crawler = new Crawler(''); + $this->assertEquals('http://base.com/link', $crawler->filterXPath('//a')->link()->getUri()); + + $crawler = new Crawler('', 'https://domain.com'); + $this->assertEquals('https://base.com/link', $crawler->filterXPath('//a')->link()->getUri(), ' tag can use a schema-less URL'); + + $crawler = new Crawler('', 'https://domain.com'); + $this->assertEquals('https://domain.com/path/link', $crawler->filterXPath('//a')->link()->getUri(), ' tag can set a path'); + } + + public function createTestCrawler($uri = null) + { + $dom = new \DOMDocument(); + $dom->loadHTML(' + + + Foo + Fabien\'s Foo + Fabien"s Foo + \' Fabien"s Foo + + Bar +    Fabien\'s Bar   + Fabien"s Bar + \' Fabien"s Bar + + GetLink + +
+ + + +
', + array('bar' => array('InputFormField', 'bar')), + ), + array( + 'appends the submitted button value but not other submit buttons', + ' + ', + array('foobar' => array('InputFormField', 'foobar')), + ), + array( + 'turns an image input into x and y fields', + '', + array('bar.x' => array('InputFormField', '0'), 'bar.y' => array('InputFormField', '0')), + ), + array( + 'returns textareas', + ' + ', + array('foo' => array('TextareaFormField', 'foo')), + ), + array( + 'returns inputs', + ' + ', + array('foo' => array('InputFormField', 'foo')), + ), + array( + 'returns checkboxes', + ' + ', + array('foo' => array('ChoiceFormField', 'foo')), + ), + array( + 'returns not-checked checkboxes', + ' + ', + array('foo' => array('ChoiceFormField', false)), + ), + array( + 'returns radio buttons', + ' + + ', + array('foo' => array('ChoiceFormField', 'bar')), + ), + array( + 'returns file inputs', + ' + ', + array('foo' => array('FileFormField', array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), + ), + ); + } + + public function testGetFormNode() + { + $dom = new \DOMDocument(); + $dom->loadHTML('
'); + + $form = new Form($dom->getElementsByTagName('input')->item(0), 'http://example.com'); + + $this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); + } + + public function testGetFormNodeFromNamedForm() + { + $dom = new \DOMDocument(); + $dom->loadHTML('
'); + + $form = new Form($dom->getElementsByTagName('form')->item(0), 'http://example.com'); + + $this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); + } + + public function testGetMethod() + { + $form = $this->createForm('
'); + $this->assertEquals('GET', $form->getMethod(), '->getMethod() returns get if no method is defined'); + + $form = $this->createForm('
'); + $this->assertEquals('POST', $form->getMethod(), '->getMethod() returns the method attribute value of the form'); + + $form = $this->createForm('
', 'put'); + $this->assertEquals('PUT', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); + + $form = $this->createForm('
', 'delete'); + $this->assertEquals('DELETE', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); + + $form = $this->createForm('
', 'patch'); + $this->assertEquals('PATCH', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); + } + + public function testGetSetValue() + { + $form = $this->createForm('
'); + + $this->assertEquals('foo', $form['foo']->getValue(), '->offsetGet() returns the value of a form field'); + + $form['foo'] = 'bar'; + + $this->assertEquals('bar', $form['foo']->getValue(), '->offsetSet() changes the value of a form field'); + + try { + $form['foobar'] = 'bar'; + $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } + + try { + $form['foobar']; + $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } + } + + public function testSetValueOnMultiValuedFieldsWithMalformedName() + { + $form = $this->createForm('
'); + + try { + $form['foo[bar'] = 'bar'; + $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the name is malformed.'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the name is malformed.'); + } + } + + public function testDisableValidation() + { + $form = $this->createForm('
+ + + +
'); + + $form->disableValidation(); + + $form['foo[bar]']->select('foo'); + $form['foo[baz]']->select('bar'); + $this->assertEquals('foo', $form['foo[bar]']->getValue(), '->disableValidation() disables validation of all ChoiceFormField.'); + $this->assertEquals('bar', $form['foo[baz]']->getValue(), '->disableValidation() disables validation of all ChoiceFormField.'); + } + + public function testOffsetUnset() + { + $form = $this->createForm('
'); + unset($form['foo']); + $this->assertFalse(isset($form['foo']), '->offsetUnset() removes a field'); + } + + public function testOffsetExists() + { + $form = $this->createForm('
'); + + $this->assertTrue(isset($form['foo']), '->offsetExists() return true if the field exists'); + $this->assertFalse(isset($form['bar']), '->offsetExists() return false if the field does not exist'); + } + + public function testGetValues() + { + $form = $this->createForm('
'); + $this->assertEquals(array('foo[bar]' => 'foo', 'bar' => 'bar', 'baz' => array()), $form->getValues(), '->getValues() returns all form field values'); + + $form = $this->createForm('
'); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include not-checked checkboxes'); + + $form = $this->createForm('
'); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include file input fields'); + + $form = $this->createForm('
'); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include disabled fields'); + } + + public function testSetValues() + { + $form = $this->createForm('
'); + $form->setValues(array('foo' => false, 'bar' => 'foo')); + $this->assertEquals(array('bar' => 'foo'), $form->getValues(), '->setValues() sets the values of fields'); + } + + public function testMultiselectSetValues() + { + $form = $this->createForm('
'); + $form->setValues(array('multi' => array("foo", "bar"))); + $this->assertEquals(array('multi' => array('foo', 'bar')), $form->getValues(), '->setValue() sets the values of select'); + } + + public function testGetPhpValues() + { + $form = $this->createForm('
'); + $this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), '->getPhpValues() converts keys with [] to arrays'); + + $form = $this->createForm('
'); + $this->assertEquals(array('fo.o' => array('ba.r' => 'foo'), 'ba r' => 'bar'), $form->getPhpValues(), '->getPhpValues() preserves periods and spaces in names'); + + $form = $this->createForm('
'); + $this->assertEquals(array('fo.o' => array('ba.r' => array('foo', 'ba.z' => 'bar'))), $form->getPhpValues(), '->getPhpValues() preserves periods and spaces in names recursively'); + + $form = $this->createForm('
'); + $this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), "->getPhpValues() doesn't return empty values"); + + } + + public function testGetFiles() + { + $form = $this->createForm('
'); + $this->assertEquals(array(), $form->getFiles(), '->getFiles() returns an empty array if method is get'); + + $form = $this->createForm('
'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for POST'); + + $form = $this->createForm('
', 'put'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PUT'); + + $form = $this->createForm('
', 'delete'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for DELETE'); + + $form = $this->createForm('
', 'patch'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PATCH'); + + $form = $this->createForm('
'); + $this->assertEquals(array(), $form->getFiles(), '->getFiles() does not include disabled file fields'); + } + + public function testGetPhpFiles() + { + $form = $this->createForm('
'); + $this->assertEquals(array('foo' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() converts keys with [] to arrays'); + + $form = $this->createForm('
'); + $this->assertEquals(array('f.o o' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() preserves periods and spaces in names'); + + $form = $this->createForm('
'); + $this->assertEquals(array('f.o o' => array('bar' => array('ba.z' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0), array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)))), $form->getPhpFiles(), '->getPhpFiles() preserves periods and spaces in names recursively'); + } + + /** + * @dataProvider provideGetUriValues + */ + public function testGetUri($message, $form, $values, $uri, $method = null) + { + $form = $this->createForm($form, $method); + $form->setValues($values); + + $this->assertEquals('http://example.com'.$uri, $form->getUri(), '->getUri() '.$message); + } + + public function testGetBaseUri() + { + $dom = new \DOMDocument(); + $dom->loadHTML('
'); + + $nodes = $dom->getElementsByTagName('input'); + $form = new Form($nodes->item($nodes->length - 1), 'http://www.foo.com/'); + $this->assertEquals('http://www.foo.com/foo.php', $form->getUri()); + } + + public function testGetUriWithAnchor() + { + $form = $this->createForm('
', null, 'http://example.com/id/123'); + + $this->assertEquals('http://example.com/id/123#foo', $form->getUri()); + } + + public function testGetUriActionAbsolute() + { + $formHtml='
'; + + $form = $this->createForm($formHtml); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + $form = $this->createForm($formHtml, null, 'https://login.foo.com'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + $form = $this->createForm($formHtml, null, 'https://login.foo.com/bar/'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + // The action URI haven't the same domain Host have an another domain as Host + $form = $this->createForm($formHtml, null, 'https://www.foo.com'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + $form = $this->createForm($formHtml, null, 'https://www.foo.com/bar/'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + } + + public function testGetUriAbsolute() + { + $form = $this->createForm('
', null, 'http://localhost/foo/'); + $this->assertEquals('http://localhost/foo/foo', $form->getUri(), '->getUri() returns absolute URIs'); + + $form = $this->createForm('
', null, 'http://localhost/foo/'); + $this->assertEquals('http://localhost/foo', $form->getUri(), '->getUri() returns absolute URIs'); + } + + public function testGetUriWithOnlyQueryString() + { + $form = $this->createForm('
', null, 'http://localhost/foo/bar'); + $this->assertEquals('http://localhost/foo/bar?get=param', $form->getUri(), '->getUri() returns absolute URIs only if the host has been defined in the constructor'); + } + + public function testGetUriWithoutAction() + { + $form = $this->createForm('
', null, 'http://localhost/foo/bar'); + $this->assertEquals('http://localhost/foo/bar', $form->getUri(), '->getUri() returns path if no action defined'); + } + + public function provideGetUriValues() + { + return array( + array( + 'returns the URI of the form', + '
', + array(), + '/foo' + ), + array( + 'appends the form values if the method is get', + '
', + array(), + '/foo?foo=foo' + ), + array( + 'appends the form values and merges the submitted values', + '
', + array('foo' => 'bar'), + '/foo?foo=bar' + ), + array( + 'does not append values if the method is post', + '
', + array(), + '/foo' + ), + array( + 'does not append values if the method is patch', + '
', + array(), + '/foo', + 'PUT' + ), + array( + 'does not append values if the method is delete', + '
', + array(), + '/foo', + 'DELETE' + ), + array( + 'does not append values if the method is put', + '
', + array(), + '/foo', + 'PATCH' + ), + array( + 'appends the form values to an existing query string', + '
', + array(), + '/foo?bar=bar&foo=foo' + ), + array( + 'returns an empty URI if the action is empty', + '
', + array(), + '/', + ), + array( + 'appends the form values even if the action is empty', + '
', + array(), + '/?foo=foo', + ), + array( + 'chooses the path if the action attribute value is a sharp (#)', + '
', + array(), + '/#', + ), + ); + } + + public function testHas() + { + $form = $this->createForm('
'); + + $this->assertFalse($form->has('foo'), '->has() returns false if a field is not in the form'); + $this->assertTrue($form->has('bar'), '->has() returns true if a field is in the form'); + } + + public function testRemove() + { + $form = $this->createForm('
'); + $form->remove('bar'); + $this->assertFalse($form->has('bar'), '->remove() removes a field'); + } + + public function testGet() + { + $form = $this->createForm('
'); + + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Field\\InputFormField', $form->get('bar'), '->get() returns the field object associated with the given name'); + + try { + $form->get('foo'); + $this->fail('->get() throws an \InvalidArgumentException if the field does not exist'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->get() throws an \InvalidArgumentException if the field does not exist'); + } + } + + public function testAll() + { + $form = $this->createForm('
'); + + $fields = $form->all(); + $this->assertCount(1, $fields, '->all() return an array of form field objects'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Field\\InputFormField', $fields['bar'], '->all() return an array of form field objects'); + } + + public function testSubmitWithoutAFormButton() + { + $dom = new \DOMDocument(); + $dom->loadHTML(' + +
+ +
+ + '); + + $nodes = $dom->getElementsByTagName('form'); + $form = new Form($nodes->item(0), 'http://example.com'); + $this->assertSame($nodes->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); + } + + public function testTypeAttributeIsCaseInsensitive() + { + $form = $this->createForm('
'); + $this->assertTrue($form->has('example.x'), '->has() returns true if the image input was correctly turned into an x and a y fields'); + $this->assertTrue($form->has('example.y'), '->has() returns true if the image input was correctly turned into an x and a y fields'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistryAddThrowAnExceptionWhenTheNameIsMalformed() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('[foo]')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistryRemoveThrowAnExceptionWhenTheNameIsMalformed() + { + $registry = new FormFieldRegistry(); + $registry->remove('[foo]'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistryGetThrowAnExceptionWhenTheNameIsMalformed() + { + $registry = new FormFieldRegistry(); + $registry->get('[foo]'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistryGetThrowAnExceptionWhenTheFieldDoesNotExist() + { + $registry = new FormFieldRegistry(); + $registry->get('foo'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistrySetThrowAnExceptionWhenTheNameIsMalformed() + { + $registry = new FormFieldRegistry(); + $registry->set('[foo]', null); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistrySetThrowAnExceptionWhenTheFieldDoesNotExist() + { + $registry = new FormFieldRegistry(); + $registry->set('foo', null); + } + + public function testFormFieldRegistryHasReturnsTrueWhenTheFQNExists() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo[bar]')); + + $this->assertTrue($registry->has('foo')); + $this->assertTrue($registry->has('foo[bar]')); + $this->assertFalse($registry->has('bar')); + $this->assertFalse($registry->has('foo[foo]')); + } + + public function testFormRegistryFieldsCanBeRemoved() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo')); + $registry->remove('foo'); + $this->assertFalse($registry->has('foo')); + } + + public function testFormRegistrySupportsMultivaluedFields() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo[]')); + $registry->add($this->getFormFieldMock('foo[]')); + $registry->add($this->getFormFieldMock('bar[5]')); + $registry->add($this->getFormFieldMock('bar[]')); + $registry->add($this->getFormFieldMock('bar[baz]')); + + $this->assertEquals( + array('foo[0]', 'foo[1]', 'bar[5]', 'bar[6]', 'bar[baz]'), + array_keys($registry->all()) + ); + } + + public function testFormRegistrySetValues() + { + $registry = new FormFieldRegistry(); + $registry->add($f2 = $this->getFormFieldMock('foo[2]')); + $registry->add($f3 = $this->getFormFieldMock('foo[3]')); + $registry->add($fbb = $this->getFormFieldMock('foo[bar][baz]')); + + $f2 + ->expects($this->exactly(2)) + ->method('setValue') + ->with(2) + ; + + $f3 + ->expects($this->exactly(2)) + ->method('setValue') + ->with(3) + ; + + $fbb + ->expects($this->exactly(2)) + ->method('setValue') + ->with('fbb') + ; + + $registry->set('foo[2]', 2); + $registry->set('foo[3]', 3); + $registry->set('foo[bar][baz]', 'fbb'); + + $registry->set('foo', array( + 2 => 2, + 3 => 3, + 'bar' => array( + 'baz' => 'fbb' + ) + )); + } + + protected function getFormFieldMock($name, $value = null) + { + $field = $this + ->getMockBuilder('Symfony\\Component\\DomCrawler\\Field\\FormField') + ->setMethods(array('getName', 'getValue', 'setValue', 'initialize')) + ->disableOriginalConstructor() + ->getMock() + ; + + $field + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)) + ; + + $field + ->expects($this->any()) + ->method('getValue') + ->will($this->returnValue($value)) + ; + + return $field; + } + + protected function createForm($form, $method = null, $currentUri = null) + { + $dom = new \DOMDocument(); + $dom->loadHTML(''.$form.''); + + $xPath = new \DOMXPath($dom); + $nodes = $xPath->query('//input | //button'); + + if (null === $currentUri) { + $currentUri = 'http://example.com/'; + } + + return new Form($nodes->item($nodes->length - 1), $currentUri, $method); + } + + protected function createTestHtml5Form() + { + $dom = new \DOMDocument(); + $dom->loadHTML(' + +

Hello form

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