2
0
فهرست منبع

ZF-10991: allow ReCaptcha form elements to co-exist in array-based forms

- Allows contact[captcha] notation
- Adds hidden fields for capturing ReCaptcha challenge and response
- Adds JS on document load that will fill above hidden fields from ReCaptcha
  values
- Write <noscript> elements in namespaced fashion always

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@24223 44c647ce-9c0f-0410-b52a-842ac1e357ba
matthew 14 سال پیش
والد
کامیت
0a23e1cd40

+ 10 - 0
library/Zend/Captcha/ReCaptcha.php

@@ -270,4 +270,14 @@ echo "    Context " . var_export($context, 1) . "\n";
         }
         return $this->getService()->getHTML($name);
     }
+
+    /**
+     * Get captcha decorator
+     *
+     * @return string
+     */
+    public function getDecorator()
+    {
+        return "Captcha_ReCaptcha";
+    }
 }

+ 115 - 0
library/Zend/Form/Decorator/Captcha/ReCaptcha.php

@@ -0,0 +1,115 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Form
+ * @subpackage Decorator
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** @see Zend_Form_Decorator_Abstract */
+require_once 'Zend/Form/Decorator/Abstract.php';
+
+/**
+ * ReCaptcha-based captcha decorator
+ *
+ * Adds hidden fields for challenge and response input, and JS for populating 
+ * from known recaptcha IDs
+ *
+ * @category   Zend
+ * @package    Zend_Form
+ * @subpackage Element
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Form_Decorator_Captcha_ReCaptcha extends Zend_Form_Decorator_Abstract
+{
+    /**
+     * Render captcha
+     *
+     * @param  string $content
+     * @return string
+     */
+    public function render($content)
+    {
+        $element = $this->getElement();
+        if (!$element instanceof Zend_Form_Element_Captcha) {
+            return $content;
+        }
+
+        $view    = $element->getView();
+        if (null === $view) {
+            return $content;
+        }
+
+        $id            = $element->getId();
+        $name          = $element->getBelongsTo();
+        $placement     = $this->getPlacement();
+        $separator     = $this->getSeparator();
+        $challengeName = empty($name) ? 'recaptcha_challenge_field' : $name . '[recaptcha_challenge_field]';
+        $responseName  = empty($name) ? 'recaptcha_response_field'  : $name . '[recaptcha_response_field]';
+        $challengeId   = $id . '-challenge';
+        $responseId    = $id . '-response';
+        $captcha       = $element->getCaptcha();
+        $markup        = $captcha->render($view, $element);
+
+        // Create hidden fields for holding the final recaptcha values
+        // Placing "id" in "attribs" to ensure it is not overwritten with the name
+        $hidden = $view->formHidden(array(
+            'name'    => $challengeName,
+            'attribs' => array('id' => $challengeId),
+        ));
+        $hidden .= $view->formHidden(array(
+            'name'    => $responseName,
+            'attribs' => array('id'   => $responseId),
+        ));
+
+        // Create a window.onload event so that we can bind to the form.
+        // Once bound, add an onsubmit event that will replace the hidden field 
+        // values with those produced by ReCaptcha
+        $js =<<<EOJ
+<script type="text/javascript" language="JavaScript">
+function windowOnLoad(fn) {
+    var old = window.onload;
+    window.onload = function() {
+        if (old) {
+            old();
+        }
+        fn();
+    };
+}
+windowOnLoad(function(){
+    document.getElementById("$challengeId").form.addEventListener("submit", function(e) {
+        document.getElementById("$challengeId").value = document.getElementById("recaptcha_challenge_field").value;
+        document.getElementById("$responseId").value = document.getElementById("recaptcha_response_field").value;
+    });
+});
+</script>
+EOJ;
+
+        // Always place the hidden fields before the captcha markup, and follow 
+        // with the JS from above
+        switch ($placement) {
+            case 'PREPEND':
+                $content = $hidden . $markup . $js . $separator . $content;
+                break;
+            case 'APPEND':
+            default:
+                $content = $content . $separator . $hidden . $markup . $js;
+        }
+        return $content;
+    }
+}
+

+ 3 - 3
library/Zend/Form/Element/Captcha.php

@@ -183,11 +183,11 @@ class Zend_Form_Element_Captcha extends Zend_Form_Element_Xhtml
         $decorator  = $captcha->getDecorator();
         if (!empty($decorator)) {
             array_unshift($decorators, $decorator);
+        } else {
+            $decorator = array('Captcha', array('captcha' => $captcha));
+            array_unshift($decorators, $decorator);
         }
 
-        $decorator = array('Captcha', array('captcha' => $captcha));
-        array_unshift($decorators, $decorator);
-
         $this->setDecorators($decorators);
 
         $this->setValue($this->getCaptcha()->generate());

+ 7 - 0
tests/Zend/Captcha/ReCaptchaTest.php

@@ -178,6 +178,13 @@ class Zend_Captcha_ReCaptchaTest extends PHPUnit_Framework_TestCase
         $this->assertContains('contact[recaptcha_challenge_field]', $html);
         $this->assertContains('contact[recaptcha_response_field]', $html);
     }
+
+    /** @group ZF-10991 */
+    public function testUsesReCaptchaSpecificDecorator()
+    {
+        $captcha = new Zend_Captcha_ReCaptcha;
+        $this->assertEquals('Captcha_ReCaptcha', $captcha->getDecorator());
+    }
 }
 
 class Zend_Captcha_ReCaptchaTest_SessionContainer

+ 2 - 0
tests/Zend/Form/Decorator/AllTests.php

@@ -35,6 +35,7 @@ require_once 'Zend/Form/Decorator/FormTest.php';
 require_once 'Zend/Form/Decorator/HtmlTagTest.php';
 require_once 'Zend/Form/Decorator/ImageTest.php';
 require_once 'Zend/Form/Decorator/LabelTest.php';
+require_once 'Zend/Form/Decorator/ReCaptchaTest.php';
 require_once 'Zend/Form/Decorator/ViewHelperTest.php';
 require_once 'Zend/Form/Decorator/ViewScriptTest.php';
 
@@ -68,6 +69,7 @@ class Zend_Form_Decorator_AllTests
         $suite->addTestSuite('Zend_Form_Decorator_HtmlTagTest');
         $suite->addTestSuite('Zend_Form_Decorator_ImageTest');
         $suite->addTestSuite('Zend_Form_Decorator_LabelTest');
+        $suite->addTestSuite('Zend_Form_Decorator_ReCaptchaTest');
         $suite->addTestSuite('Zend_Form_Decorator_ViewHelperTest');
         $suite->addTestSuite('Zend_Form_Decorator_ViewScriptTest');
 

+ 130 - 0
tests/Zend/Form/Decorator/ReCaptchaTest.php

@@ -0,0 +1,130 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Form
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+// Call Zend_Form_Decorator_ReCaptchaTest::main() if this source file is executed directly.
+if (!defined("PHPUnit_MAIN_METHOD")) {
+    define("PHPUnit_MAIN_METHOD", "Zend_Form_Decorator_ReCaptchaTest::main");
+}
+
+require_once 'Zend/Form/Decorator/Captcha/ReCaptcha.php';
+require_once 'Zend/Form/Element/Captcha.php';
+require_once 'Zend/View.php';
+
+/**
+ * Test class for Zend_Form_Decorator_Captcha_ReCaptcha
+ *
+ * @category   Zend
+ * @package    Zend_Form
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Form
+ */
+class Zend_Form_Decorator_ReCaptchaTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Runs the test methods of this class.
+     *
+     * @return void
+     */
+    public static function main()
+    {
+
+        $suite  = new PHPUnit_Framework_TestSuite("Zend_Form_Decorator_ReCaptchaTest");
+        $result = PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    /**
+     * Sets up the fixture, for example, open a network connection.
+     * This method is called before a test is executed.
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        $view = new Zend_View();
+        $this->element   = new Zend_Form_Element_Captcha('captcha', array(
+            'captcha' => array(
+                'captcha' => 'ReCaptcha',
+                'privKey' => 'private',
+                'pubKey'  => 'public',
+            ),
+        ));
+        $this->element->setView($view);
+        $this->decorator = new Zend_Form_Decorator_Captcha_ReCaptcha();
+        $this->decorator->setElement($this->element);
+    }
+
+    /** @group ZF-10991 */
+    public function testDecoratorCreatesHiddenFieldsForChallengeAndResponse()
+    {
+        $html = $this->decorator->render('');
+        $this->assertContains('type="hidden" name="recaptcha_challenge_field" value="" id="captcha-challenge"', $html, $html);
+        $this->assertContains('type="hidden" name="recaptcha_response_field" value="" id="captcha-response"', $html, $html);
+    }
+
+    /** @group ZF-10991 */
+    public function testDecoratorCreatesJavascriptOnSubmitHandler()
+    {
+        $html = $this->decorator->render('');
+        // Test that we have script tags
+        $this->assertContains('<script type="text/javascript" language="JavaScript">', $html);
+        $this->assertContains('</script>', $html);
+
+        // Test that we create a DOM window.onload event, and trigger any previous
+        $this->assertContains('function windowOnLoad', $html);
+        $this->assertContains('old = window.onload', $html);
+        $this->assertContains('if (old)', $html);
+
+        // Test that we add an event listener for the form submit event
+        $this->assertContains('document.getElementById("captcha-challenge").form.addEventListener("submit", function(e)', $html);
+
+        // Test that we reset the hidden fields with the global recaptcha values
+        $this->assertContains('document.getElementById("captcha-challenge").value = document.getElementById("recaptcha_challenge_field").value', $html);
+        $this->assertContains('document.getElementById("captcha-response").value = document.getElementById("recaptcha_response_field").value', $html);
+    }
+
+    /** @group ZF-10991 */
+    public function testDecoratorCreatesHiddenFieldsWithNestedIdsAndNamesWhenElementBelongsToArray()
+    {
+        $this->element->setBelongsTo('contact');
+        $html = $this->decorator->render('');
+        $this->assertContains('type="hidden" name="contact[recaptcha_challenge_field]" value="" id="contact-captcha-challenge"', $html, $html);
+        $this->assertContains('type="hidden" name="contact[recaptcha_response_field]" value="" id="contact-captcha-response"', $html, $html);
+    }
+
+    /** @group ZF-10991 */
+    public function testDecoratorUsesNamespacedIdsInJavascriptOnSubmitHandler()
+    {
+        $this->element->setBelongsTo('contact');
+        $html = $this->decorator->render('');
+        $this->assertContains('document.getElementById("contact-captcha-challenge").form.addEventListener("submit", function(e)', $html);
+        $this->assertContains('document.getElementById("contact-captcha-challenge").value = document.getElementById("recaptcha_challenge_field").value', $html);
+        $this->assertContains('document.getElementById("contact-captcha-response").value = document.getElementById("recaptcha_response_field").value', $html);
+    }
+}
+
+// Call Zend_Form_Decorator_ReCaptchaTest::main() if this source file is executed directly.
+if (PHPUnit_MAIN_METHOD == "Zend_Form_Decorator_ReCaptchaTest::main") {
+    Zend_Form_Decorator_ReCaptchaTest::main();
+}
+