diff --git a/.gitignore b/.gitignore index e17b0bc274afa..e107b1e221218 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,14 @@ Desktop.ini # Extra files installed by Composer not needed in the CMS environment # This should only ignore files like unit testing or READMEs, production # code must remain to ensure all libraries properly function +/libraries/vendor/google/recaptcha/examples +/libraries/vendor/google/recaptcha/tests +/libraries/vendor/google/recaptcha/.gitignore +/libraries/vendor/google/recaptcha/.travis.yml +/libraries/vendor/google/recaptcha/composer.json +/libraries/vendor/google/recaptcha/CONTRIBUTING.md +/libraries/vendor/google/recaptcha/phpunit.xml.dist +/libraries/vendor/google/recaptcha/README.md /libraries/vendor/ircmaxell/password-compat/test /libraries/vendor/ircmaxell/password-compat/.travis.yml /libraries/vendor/ircmaxell/password-compat/composer.json diff --git a/administrator/components/com_admin/sql/updates/mysql/3.8.11-2018-07-18.sql b/administrator/components/com_admin/sql/updates/mysql/3.8.11-2018-07-18.sql new file mode 100644 index 0000000000000..5a80bb12ad108 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/mysql/3.8.11-2018-07-18.sql @@ -0,0 +1,2 @@ +INSERT INTO `#__extensions` (`extension_id`, `package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `custom_data`, `system_data`, `checked_out`, `checked_out_time`, `ordering`, `state`) VALUES +(481, 0, 'plg_captcha_recaptcha_invisible', 'plugin', 'recaptcha_invisible', 'captcha', 0, 0, 1, 0, '', '{"public_key":"","private_key":"","theme":"clean"}', '', '', 0, '0000-00-00 00:00:00', 0, 0); diff --git a/administrator/components/com_admin/sql/updates/postgresql/3.8.11-2018-07-18.sql b/administrator/components/com_admin/sql/updates/postgresql/3.8.11-2018-07-18.sql new file mode 100644 index 0000000000000..421c09f94b830 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/postgresql/3.8.11-2018-07-18.sql @@ -0,0 +1,2 @@ +INSERT INTO "#__extensions" ("extension_id", "package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "manifest_cache", "params", "custom_data", "system_data", "checked_out", "checked_out_time", "ordering", "state") VALUES +(481, 0, 'plg_captcha_recaptcha_invisible', 'plugin', 'recaptcha_invisible', 'captcha', 0, 0, 1, 0, '', '{"public_key":"","private_key":"","theme":"clean"}', '', '', 0, '1970-01-01 00:00:00', 0, 0); diff --git a/administrator/components/com_admin/sql/updates/sqlazure/3.8.11-2018-07-18.sql b/administrator/components/com_admin/sql/updates/sqlazure/3.8.11-2018-07-18.sql new file mode 100644 index 0000000000000..04b0cfc23ff70 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/sqlazure/3.8.11-2018-07-18.sql @@ -0,0 +1,2 @@ +INSERT INTO "#__extensions" ("extension_id", "package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "manifest_cache", "params", "custom_data", "system_data", "checked_out", "checked_out_time", "ordering", "state") VALUES +(481, 0, 'plg_captcha_recaptcha_invisible', 'plugin', 'recaptcha_invisible', 'captcha', 0, 0, 1, 0, '', '{"public_key":"","private_key":"","theme":"clean"}', '', '', 0, '1900-01-01 00:00:00', 0, 0); diff --git a/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.ini b/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.ini index 8a501818501b4..0eff9e05d1438 100644 --- a/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.ini +++ b/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.ini @@ -35,6 +35,14 @@ PLG_RECAPTCHA_SIZE_LABEL="Size" PLG_RECAPTCHA_SIZE_DESC="Select the size for the reCAPTCHA field." PLG_RECAPTCHA_THEME_NORMAL="Normal" PLG_RECAPTCHA_THEME_COMPACT="Compact" +PLG_RECAPTCHA_TABINDEX_LABEL="Tabindex" +PLG_RECAPTCHA_TABINDEX_DESC="The tabindex of the reCAPTCHA widget" +PLG_RECAPTCHA_CALLBACK_LABEL="Callback" +PLG_RECAPTCHA_CALLBACK_DESC="(Optional) JavaScript callback, executed after successful reCAPTCHA response" +PLG_RECAPTCHA_EXPIRED_CALLBACK_LABEL="Expired callback" +PLG_RECAPTCHA_EXPIRED_CALLBACK_DESC="(Optional) JavaScript callback, executed when the reCAPTCHA expired" +PLG_RECAPTCHA_EXPIRED_CALLBACK_LABEL="Error callback" +PLG_RECAPTCHA_EXPIRED_CALLBACK_DESC="(Optional) JavaScript callback, executed when the reCAPTCHA encounters an error" ; Error messages PLG_RECAPTCHA_ERROR_NO_PRIVATE_KEY="reCAPTCHA plugin needs a secret key to be set in its parameters. Please contact a site administrator." diff --git a/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.sys.ini b/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.sys.ini index 929448dc46b01..b914a40e586d0 100644 --- a/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.sys.ini +++ b/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.sys.ini @@ -3,5 +3,5 @@ ; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php ; Note : All ini files need to be saved as UTF-8 -PLG_CAPTCHA_RECAPTCHA_XML_DESCRIPTION="This CAPTCHA plugin uses the reCAPTCHA service to prevent spammers while it helps to digitize books, newspapers and old radio shows. To get a site and secret key for your domain, go to https://www.google.com/recaptcha. To use this for new account registration, go to Options in the User Manager and select Captcha - reCaptcha as the Captcha." -PLG_CAPTCHA_RECAPTCHA="Captcha - ReCaptcha" +PLG_CAPTCHA_RECAPTCHA_XML_DESCRIPTION="This CAPTCHA plugin uses the reCAPTCHA service to prevent spammers while it helps to digitize books, newspapers and old radio shows. To get a site and secret key for your domain, go to https://www.google.com/recaptcha. To use this for new account registration, go to Options in the User Manager and select CAPTCHA - reCAPTCHA as the CAPTCHA." +PLG_CAPTCHA_RECAPTCHA="CAPTCHA - reCAPTCHA" diff --git a/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.ini b/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.ini new file mode 100644 index 0000000000000..d2a556fab0501 --- /dev/null +++ b/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.ini @@ -0,0 +1,32 @@ +; Joomla! Project +; Copyright (C) 2005 - 2017 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_CAPTCHA_RECAPTCHA_INVISIBLE_XML_DESCRIPTION="This CAPTCHA plugin uses the Invisible reCAPTCHA service. To get a site and secret key for your domain, go to https://www.google.com/recaptcha." +PLG_CAPTCHA_RECAPTCHA_INVISIBLE="CAPTCHA - Invisible reCAPTCHA " + +; Params +PLG_RECAPTCHA_INVISIBLE_PUBLIC_KEY_LABEL="Site Key" +PLG_RECAPTCHA_INVISIBLE_PUBLIC_KEY_DESC="Used in the JavaScript code that is served to your users." +PLG_RECAPTCHA_INVISIBLE_PRIVATE_KEY_LABEL="Secret Key" +PLG_RECAPTCHA_INVISIBLE_PRIVATE_KEY_DESC="Used in the communication between your server and the reCAPTCHA server. Be sure to keep it a secret." +PLG_RECAPTCHA_INVISIBLE_BADGE_LABEL="Badge" +PLG_RECAPTCHA_INVISIBLE_BADGE_DESC="Positioning of the reCAPTCHA badge." +PLG_RECAPTCHA_INVISIBLE_BADGE_BOTTOMRIGHT="Bottom right" +PLG_RECAPTCHA_INVISIBLE_BADGE_BOTTOMLEFT="Bottom left" +PLG_RECAPTCHA_INVISIBLE_BADGE_INLINE="Inline" +PLG_RECAPTCHA_INVISIBLE_TABINDEX_LABEL="Tabindex" +PLG_RECAPTCHA_INVISIBLE_TABINDEX_DESC="The tabindex of the challenge." +PLG_RECAPTCHA_INVISIBLE_CALLBACK_LABEL="Callback" +PLG_RECAPTCHA_INVISIBLE_CALLBACK_DESC="(Optional) JavaScript callback, executed after successful reCAPTCHA response" +PLG_RECAPTCHA_INVISIBLE_EXPIRED_CALLBACK_LABEL="Expired callback" +PLG_RECAPTCHA_INVISIBLE_EXPIRED_CALLBACK_DESC="(Optional) JavaScript callback, executed when the reCAPTCHA expired" +PLG_RECAPTCHA_INVISIBLE_ERROR_CALLBACK_LABEL="Error callback" +PLG_RECAPTCHA_INVISIBLE_ERROR_CALLBACK_DESC="(Optional) JavaScript callback, executed when the reCAPTCHA encounters an error" + +; Error messages +PLG_RECAPTCHA_INVISIBLE_ERROR_NO_PRIVATE_KEY="reCAPTCHA plugin needs a secret key to be set in its parameters. Please contact a site administrator." +PLG_RECAPTCHA_INVISIBLE_ERROR_NO_PUBLIC_KEY="reCAPTCHA plugin needs a site key to be set in its parameters. Please contact a site administrator." +PLG_RECAPTCHA_INVISIBLE_ERROR_EMPTY_SOLUTION="Empty solution not allowed." +PLG_RECAPTCHA_INVISIBLE_ERROR_NO_IP="For security reasons, you must pass the remote IP address to reCAPTCHA." diff --git a/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.sys.ini b/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.sys.ini new file mode 100644 index 0000000000000..227d2e9d79348 --- /dev/null +++ b/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.sys.ini @@ -0,0 +1,7 @@ +; Joomla! Project +; Copyright (C) 2005 - 2017 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_CAPTCHA_RECAPTCHA_INVISIBLE_XML_DESCRIPTION="This CAPTCHA plugin uses the Invisible reCAPTCHA service. To get a site and secret key for your domain, go to https://www.google.com/recaptcha." +PLG_CAPTCHA_RECAPTCHA_INVISIBLE="CAPTCHA - Invisible reCAPTCHA " diff --git a/components/com_users/layouts/joomla/form/renderfield.php b/components/com_users/layouts/joomla/form/renderfield.php new file mode 100644 index 0000000000000..d86dded0df3ef --- /dev/null +++ b/components/com_users/layouts/joomla/form/renderfield.php @@ -0,0 +1,60 @@ + 'auto', 'relative' => true)); +} + +$class = empty($options['class']) ? '' : ' ' . $options['class']; +$rel = empty($options['rel']) ? '' : ' ' . $options['rel']; + +/** + * @TODO: + * + * As mentioned in #8473 (https://github.com/joomla/joomla-cms/pull/8473), ... + * as long as we cannot access the field properties properly, this seems to + * be the way to go for now. + * + * On a side note: Parsing html is seldom a good idea. + * https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454 + */ +preg_match('/class=\"([^\"]+)\"/i', $input, $match); + +$required = (strpos($input, 'aria-required="true"') !== false || (!empty($match[1]) && strpos($match[1], 'required') !== false)); +$typeOfSpacer = (strpos($label, 'spacer-lbl') !== false); + +?> + +
> + +
+ + + + +
+ +
+ +
+
diff --git a/components/com_users/views/login/tmpl/default_login.php b/components/com_users/views/login/tmpl/default_login.php index d80a49f0256f5..0ee7d4e3d08d6 100644 --- a/components/com_users/views/login/tmpl/default_login.php +++ b/components/com_users/views/login/tmpl/default_login.php @@ -35,27 +35,9 @@
- form->getFieldset('credentials') as $field) : ?> - hidden) : ?> -
-
- label; ?> -
-
- input; ?> -
-
- - + form->renderFieldset('credentials'); ?> tfa) : ?> -
-
- form->getField('secretkey')->label; ?> -
-
- form->getField('secretkey')->input; ?> -
-
+ form->renderField('secretkey'); ?>
diff --git a/components/com_users/views/registration/tmpl/default.php b/components/com_users/views/registration/tmpl/default.php index 12ce2c87d203f..fc964310389cf 100644 --- a/components/com_users/views/registration/tmpl/default.php +++ b/components/com_users/views/registration/tmpl/default.php @@ -29,25 +29,7 @@ label)) : ?> label); ?> - - - - hidden) : ?> - input; ?> - -
-
- label; ?> - required && $field->type !== 'Spacer') : ?> - - -
-
- input; ?> -
-
- - + form->renderFieldset($fieldset->name); ?>
diff --git a/components/com_users/views/remind/tmpl/default.php b/components/com_users/views/remind/tmpl/default.php index 7a98814539fe2..7543f97111247 100644 --- a/components/com_users/views/remind/tmpl/default.php +++ b/components/com_users/views/remind/tmpl/default.php @@ -24,19 +24,10 @@ form->getFieldsets() as $fieldset) : ?>
-

label); ?>

- form->getFieldset($fieldset->name) as $name => $field) : ?> - hidden === false) : ?> -
-
- label; ?> -
-
- input; ?> -
-
- - + label)) : ?> +

label); ?>

+ + form->renderFieldset($fieldset->name); ?>
diff --git a/components/com_users/views/reset/tmpl/complete.php b/components/com_users/views/reset/tmpl/complete.php index 57729e886f552..d9f9db20ddf4c 100644 --- a/components/com_users/views/reset/tmpl/complete.php +++ b/components/com_users/views/reset/tmpl/complete.php @@ -24,17 +24,10 @@ form->getFieldsets() as $fieldset) : ?>
-

label); ?>

- form->getFieldset($fieldset->name) as $name => $field) : ?> -
-
- label; ?> -
-
- input; ?> -
-
- + label)) : ?> +

label); ?>

+ + form->renderFieldset($fieldset->name); ?>
diff --git a/components/com_users/views/reset/tmpl/confirm.php b/components/com_users/views/reset/tmpl/confirm.php index 14abd9de0b15d..f5848d2588f99 100644 --- a/components/com_users/views/reset/tmpl/confirm.php +++ b/components/com_users/views/reset/tmpl/confirm.php @@ -24,17 +24,10 @@ form->getFieldsets() as $fieldset) : ?>
-

label); ?>

- form->getFieldset($fieldset->name) as $name => $field) : ?> -
-
- label; ?> -
-
- input; ?> -
-
- + label)) : ?> +

label); ?>

+ + form->renderFieldset($fieldset->name); ?>
diff --git a/components/com_users/views/reset/tmpl/default.php b/components/com_users/views/reset/tmpl/default.php index 895f50eaa7b3a..1c4c2f7df177e 100644 --- a/components/com_users/views/reset/tmpl/default.php +++ b/components/com_users/views/reset/tmpl/default.php @@ -24,19 +24,10 @@ form->getFieldsets() as $fieldset) : ?>
-

label); ?>

- form->getFieldset($fieldset->name) as $name => $field) : ?> - hidden === false) : ?> -
-
- label; ?> -
-
- input; ?> -
-
- - + label)) : ?> +

label); ?>

+ + form->renderFieldset($fieldset->name); ?>
diff --git a/composer.json b/composer.json index 5a748f9b2e6a0..e2419041038a1 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,8 @@ "symfony/polyfill-php56": "~1.0", "symfony/polyfill-php73": "~1.8", "symfony/yaml": "2.*", - "simplepie/simplepie": "1.3.1" + "simplepie/simplepie": "1.3.1", + "google/recaptcha": "^1.1" }, "require-dev": { "phpunit/phpunit": "^4.8.35", diff --git a/composer.lock b/composer.lock index 819e73ffc7b99..4a5b222a3f24d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,53 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "df5cd1e4b9c48fd4beea1f87832cd250", + "content-hash": "c3f089d81a6bd685e0b37b4483597f20", "packages": [ + { + "name": "google/recaptcha", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/google/recaptcha.git", + "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/recaptcha/zipball/2b7e00566afca82a38a1d3adb8e42c118006296e", + "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.5.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "ReCaptcha\\": "src/ReCaptcha" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Client library for reCAPTCHA, a free service that protect websites from spam and abuse.", + "homepage": "http://www.google.com/recaptcha/", + "keywords": [ + "Abuse", + "captcha", + "recaptcha", + "spam" + ], + "time": "2015-09-02T17:23:59+00:00" + }, { "name": "ircmaxell/password-compat", "version": "v1.0.4", diff --git a/installation/sql/mysql/joomla.sql b/installation/sql/mysql/joomla.sql index 9599b09ae3234..0f7a2d2beb7d0 100644 --- a/installation/sql/mysql/joomla.sql +++ b/installation/sql/mysql/joomla.sql @@ -666,6 +666,7 @@ INSERT INTO `#__extensions` (`extension_id`, `package_id`, `name`, `type`, `elem (491, 0, 'plg_privacy_content', 'plugin', 'content', 'privacy', 0, 1, 1, 0, '', '{}', '', '', 0, '0000-00-00 00:00:00', 0, 0), (492, 0, 'plg_privacy_message', 'plugin', 'message', 'privacy', 0, 1, 1, 0, '', '{}', '', '', 0, '0000-00-00 00:00:00', 0, 0), (493, 0, 'plg_privacy_actionlogs', 'plugin', 'actionlogs', 'privacy', 0, 0, 1, 0, '', '{}', '', '', 0, '0000-00-00 00:00:00', 0, 0), +(494, 0, 'plg_captcha_recaptcha_invisible', 'plugin', 'recaptcha_invisible', 'captcha', 0, 0, 1, 0, '', '{"public_key":"","private_key":"","theme":"clean"}', '', '', 0, '0000-00-00 00:00:00', 0, 0), (503, 0, 'beez3', 'template', 'beez3', '', 0, 1, 1, 0, '', '{"wrapperSmall":"53","wrapperLarge":"72","sitetitle":"","sitedescription":"","navposition":"center","templatecolor":"nature"}', '', '', 0, '0000-00-00 00:00:00', 0, 0), (504, 0, 'hathor', 'template', 'hathor', '', 1, 1, 1, 0, '', '{"showSiteName":"0","colourChoice":"0","boldText":"0"}', '', '', 0, '0000-00-00 00:00:00', 0, 0), (506, 0, 'protostar', 'template', 'protostar', '', 0, 1, 1, 0, '', '{"templateColor":"","logoFile":"","googleFont":"1","googleFontName":"Open+Sans","fluidContainer":"0"}', '', '', 0, '0000-00-00 00:00:00', 0, 0), diff --git a/installation/sql/postgresql/joomla.sql b/installation/sql/postgresql/joomla.sql index 4c86112162db1..08e2c85cc593e 100644 --- a/installation/sql/postgresql/joomla.sql +++ b/installation/sql/postgresql/joomla.sql @@ -679,6 +679,7 @@ INSERT INTO "#__extensions" ("extension_id", "package_id", "name", "type", "elem (491, 0, 'plg_privacy_content', 'plugin', 'content', 'privacy', 0, 1, 1, 0, '', '{}', '', '', 0, '1970-01-01 00:00:00', 0, 0), (492, 0, 'plg_privacy_message', 'plugin', 'message', 'privacy', 0, 1, 1, 0, '', '{}', '', '', 0, '1970-01-01 00:00:00', 0, 0), (493, 0, 'plg_privacy_actionlogs', 'plugin', 'actionlogs', 'privacy', 0, 0, 1, 0, '', '{}', '', '', 0, '1970-01-01 00:00:00', 0, 0), +(494, 0, 'plg_captcha_recaptcha_invisible', 'plugin', 'recaptcha_invisible', 'captcha', 0, 0, 1, 0, '', '{"public_key":"","private_key":"","theme":"clean"}', '', '', 0, '1970-01-01 00:00:00', 0, 0), (503, 0, 'beez3', 'template', 'beez3', '', 0, 1, 1, 0, '', '{"wrapperSmall":"53","wrapperLarge":"72","sitetitle":"","sitedescription":"","navposition":"center","templatecolor":"nature"}', '', '', 0, '1970-01-01 00:00:00', 0, 0), (504, 0, 'hathor', 'template', 'hathor', '', 1, 1, 1, 0, '', '{"showSiteName":"0","colourChoice":"0","boldText":"0"}', '', '', 0, '1970-01-01 00:00:00', 0, 0), (506, 0, 'protostar', 'template', 'protostar', '', 0, 1, 1, 0, '', '{"templateColor":"","logoFile":"","googleFont":"1","googleFontName":"Open+Sans","fluidContainer":"0"}', '', '', 0, '1970-01-01 00:00:00', 0, 0), diff --git a/installation/sql/sqlazure/joomla.sql b/installation/sql/sqlazure/joomla.sql index 60bae0aeebbc6..833e070fc49aa 100644 --- a/installation/sql/sqlazure/joomla.sql +++ b/installation/sql/sqlazure/joomla.sql @@ -894,6 +894,7 @@ INSERT INTO "#__extensions" ("extension_id", "package_id", "name", "type", "elem (491, 0, 'plg_privacy_content', 'plugin', 'user', 'content', 0, 1, 1, 0, '', '{}', '', '', 0, '1900-01-01 00:00:00', 0, 0), (492, 0, 'plg_privacy_message', 'plugin', 'user', 'message', 0, 1, 1, 0, '', '{}', '', '', 0, '1900-01-01 00:00:00', 0, 0), (493, 0, 'plg_privacy_actionlogs', 'plugin', 'actionlogs', 'privacy', 0, 0, 1, 0, '', '{}', '', '', 0, '1900-01-01 00:00:00', 0, 0), +(494, 0, 'plg_captcha_recaptcha_invisible', 'plugin', 'recaptcha_invisible', 'captcha', 0, 0, 1, 0, '', '{"public_key":"","private_key":"","theme":"clean"}', '', '', 0, '1900-01-01 00:00:00', 0, 0), (503, 0, 'beez3', 'template', 'beez3', '', 0, 1, 1, 0, '', '{"wrapperSmall":"53","wrapperLarge":"72","sitetitle":"","sitedescription":"","navposition":"center","templatecolor":"nature"}', '', '', 0, '1900-01-01 00:00:00', 0, 0), (504, 0, 'hathor', 'template', 'hathor', '', 1, 1, 1, 0, '', '{"showSiteName":"0","colourChoice":"0","boldText":"0"}', '', '', 0, '1900-01-01 00:00:00', 0, 0), (506, 0, 'protostar', 'template', 'protostar', '', 0, 1, 1, 0, '', '{"templateColor":"","logoFile":"","googleFont":"1","googleFontName":"Open+Sans","fluidContainer":"0"}', '', '', 0, '1900-01-01 00:00:00', 0, 0), diff --git a/libraries/src/Captcha/Captcha.php b/libraries/src/Captcha/Captcha.php index 396efad21be5d..293e071af97b8 100644 --- a/libraries/src/Captcha/Captcha.php +++ b/libraries/src/Captcha/Captcha.php @@ -76,10 +76,11 @@ class Captcha extends \JObject /** * Class constructor. * - * @param string $captcha The editor to use. + * @param string $captcha The plugin to use. * @param array $options Associative array of options. * * @since 2.5 + * @throws \RuntimeException */ public function __construct($captcha, $options) { @@ -97,6 +98,7 @@ public function __construct($captcha, $options) * @return Captcha|null Instance of this class. * * @since 2.5 + * @throws \RuntimeException */ public static function getInstance($captcha, array $options = array()) { @@ -104,16 +106,7 @@ public static function getInstance($captcha, array $options = array()) if (empty(self::$_instances[$signature])) { - try - { - self::$_instances[$signature] = new Captcha($captcha, $options); - } - catch (\RuntimeException $e) - { - \JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); - - return; - } + self::$_instances[$signature] = new Captcha($captcha, $options); } return self::$_instances[$signature]; @@ -127,22 +120,14 @@ public static function getInstance($captcha, array $options = array()) * @return boolean True on success * * @since 2.5 + * @throws \RuntimeException */ public function initialise($id) { $args['id'] = $id; $args['event'] = 'onInit'; - try - { - $this->_captcha->update($args); - } - catch (\Exception $e) - { - \JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); - - return false; - } + $this->_captcha->update($args); return true; } @@ -157,6 +142,7 @@ public function initialise($id) * @return mixed The return value of the function "onDisplay" of the selected Plugin. * * @since 2.5 + * @throws \RuntimeException */ public function display($name, $id, $class = '') { @@ -174,7 +160,7 @@ public function display($name, $id, $class = '') $args['name'] = $name; $args['id'] = $id ?: $name; - $args['class'] = $class ? 'class="' . $class . '"' : ''; + $args['class'] = $class; $args['event'] = 'onDisplay'; return $this->_captcha->update($args); @@ -185,9 +171,10 @@ public function display($name, $id, $class = '') * * @param string $code The answer. * - * @return mixed The return value of the function "onCheckAnswer" of the selected Plugin. + * @return bool Whether the provided answer was correct * * @since 2.5 + * @throws \RuntimeException */ public function checkAnswer($code) { @@ -203,6 +190,32 @@ public function checkAnswer($code) return $this->_captcha->update($args); } + /** + * Method to react on the setup of a captcha field. Gives the possibility + * to change the field and/or the XML element for the field. + * + * @param \Joomla\CMS\Form\Field\CaptchaField $field Captcha field instance + * @param \SimpleXMLElement $element XML form definition + * + * @return void + */ + public function setupField(\Joomla\CMS\Form\Field\CaptchaField $field, \SimpleXMLElement $element) + { + if ($this->_captcha === null) + { + return; + } + + $args = array( + 'event' => 'onSetupField', + 'field' => $field, + 'element' => $element, + ); + + // Forward to the captcha plugin + return $this->_captcha->update($args); + } + /** * Load the Captcha plugin. * diff --git a/libraries/src/Captcha/Google/HttpBridgePostRequestMethod.php b/libraries/src/Captcha/Google/HttpBridgePostRequestMethod.php new file mode 100644 index 0000000000000..057c7666c0046 --- /dev/null +++ b/libraries/src/Captcha/Google/HttpBridgePostRequestMethod.php @@ -0,0 +1,77 @@ +http = $http ?: HttpFactory::getHttp(); + } + + /** + * Submit the request with the specified parameters. + * + * @param RequestParameters $params Request parameters + * + * @return string Body of the reCAPTCHA response + * + * @since __DEPLOY_VERSION__ + */ + public function submit(RequestParameters $params) + { + try + { + $response = $this->http->post(self::SITE_VERIFY_URL, $params->toArray()); + + return (string) $response->body; + } + catch (InvalidResponseCodeException $exception) + { + return ''; + } + } +} + diff --git a/libraries/src/Form/Field/CaptchaField.php b/libraries/src/Form/Field/CaptchaField.php index 9e0646c18a4a2..45bc06eea712a 100644 --- a/libraries/src/Form/Field/CaptchaField.php +++ b/libraries/src/Form/Field/CaptchaField.php @@ -29,6 +29,13 @@ class CaptchaField extends FormField */ protected $type = 'Captcha'; + /** + * The captcha base instance of our type. + * + * @var Captcha + */ + protected $_captcha; + /** * Method to get certain otherwise inaccessible properties from the form field object. * @@ -124,6 +131,25 @@ public function setup(\SimpleXMLElement $element, $value, $group = null) $this->namespace = $this->element['namespace'] ? (string) $this->element['namespace'] : $this->form->getName(); + try + { + // Get an instance of the captcha class that we are using + $this->_captcha = Captcha::getInstance($this->plugin, array('namespace' => $this->namespace)); + + /** + * Give the captcha instance a possibility to react on the setup-process, + * e.g. by altering the XML structure of the field, for example hiding the label + * when using invisible captchas. + */ + $this->_captcha->setupField($this, $element); + } + catch (\RuntimeException $e) + { + $this->_captcha = null; + \JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); + return false; + } + return $result; } @@ -136,18 +162,19 @@ public function setup(\SimpleXMLElement $element, $value, $group = null) */ protected function getInput() { - if ($this->hidden) + if ($this->hidden || $this->_captcha == null) { return ''; } - else + + try { - if (($captcha = Captcha::getInstance($this->plugin, array('namespace' => $this->namespace))) == null) - { - return ''; - } + return $this->_captcha->display($this->name, $this->id, $this->class); } - - return $captcha->display($this->name, $this->id, $this->class); + catch (\RuntimeException $e) + { + \JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); + } + return ''; } } diff --git a/libraries/src/Form/Rule/CaptchaRule.php b/libraries/src/Form/Rule/CaptchaRule.php index 8de64ab3d282b..65d0f40577770 100644 --- a/libraries/src/Form/Rule/CaptchaRule.php +++ b/libraries/src/Form/Rule/CaptchaRule.php @@ -54,26 +54,16 @@ public function test(\SimpleXMLElement $element, $value, $group = null, Registry { return true; } - else + + try { $captcha = Captcha::getInstance((string) $plugin, array('namespace' => (string) $namespace)); + return $captcha->checkAnswer($value); } - - // Test the value. - if (!$captcha->checkAnswer($value)) + catch (\RuntimeException $e) { - $error = $captcha->getError(); - - if ($error instanceof \Exception) - { - return $error; - } - else - { - return new \JException($error); - } + \JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); } - - return true; + return false; } } diff --git a/libraries/vendor/composer/autoload_classmap.php b/libraries/vendor/composer/autoload_classmap.php index 23fd5b4d8497f..ac744dee23b42 100644 --- a/libraries/vendor/composer/autoload_classmap.php +++ b/libraries/vendor/composer/autoload_classmap.php @@ -123,6 +123,15 @@ 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', + 'ReCaptcha\\ReCaptcha' => $vendorDir . '/google/recaptcha/src/ReCaptcha/ReCaptcha.php', + 'ReCaptcha\\RequestMethod' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod.php', + 'ReCaptcha\\RequestMethod\\Curl' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/Curl.php', + 'ReCaptcha\\RequestMethod\\CurlPost' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php', + 'ReCaptcha\\RequestMethod\\Post' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php', + 'ReCaptcha\\RequestMethod\\Socket' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php', + 'ReCaptcha\\RequestMethod\\SocketPost' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php', + 'ReCaptcha\\RequestParameters' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestParameters.php', + 'ReCaptcha\\Response' => $vendorDir . '/google/recaptcha/src/ReCaptcha/Response.php', 'SMTP' => $vendorDir . '/phpmailer/phpmailer/class.smtp.php', 'SimplePie' => $vendorDir . '/simplepie/simplepie/library/SimplePie.php', 'SimplePie_Author' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Author.php', diff --git a/libraries/vendor/composer/autoload_psr4.php b/libraries/vendor/composer/autoload_psr4.php index a08fa71cb6bd6..c967fb06f6766 100644 --- a/libraries/vendor/composer/autoload_psr4.php +++ b/libraries/vendor/composer/autoload_psr4.php @@ -12,6 +12,7 @@ 'Symfony\\Polyfill\\Php55\\' => array($vendorDir . '/symfony/polyfill-php55'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), + 'ReCaptcha\\' => array($vendorDir . '/google/recaptcha/src/ReCaptcha'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Joomla\\Utilities\\' => array($vendorDir . '/joomla/utilities/src'), diff --git a/libraries/vendor/composer/autoload_static.php b/libraries/vendor/composer/autoload_static.php index 0f1dda4928e3f..ed03841ab3933 100644 --- a/libraries/vendor/composer/autoload_static.php +++ b/libraries/vendor/composer/autoload_static.php @@ -41,6 +41,10 @@ class ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe 'Symfony\\Polyfill\\Ctype\\' => 23, 'Symfony\\Component\\Yaml\\' => 23, ), + 'R' => + array ( + 'ReCaptcha\\' => 10, + ), 'P' => array ( 'Psr\\Log\\' => 8, @@ -93,6 +97,10 @@ class ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe array ( 0 => __DIR__ . '/..' . '/symfony/yaml', ), + 'ReCaptcha\\' => + array ( + 0 => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha', + ), 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', @@ -306,6 +314,15 @@ class ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php', 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php', 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php', + 'ReCaptcha\\ReCaptcha' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/ReCaptcha.php', + 'ReCaptcha\\RequestMethod' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod.php', + 'ReCaptcha\\RequestMethod\\Curl' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/Curl.php', + 'ReCaptcha\\RequestMethod\\CurlPost' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php', + 'ReCaptcha\\RequestMethod\\Post' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php', + 'ReCaptcha\\RequestMethod\\Socket' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php', + 'ReCaptcha\\RequestMethod\\SocketPost' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php', + 'ReCaptcha\\RequestParameters' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestParameters.php', + 'ReCaptcha\\Response' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/Response.php', 'SMTP' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.smtp.php', 'SimplePie' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie.php', 'SimplePie_Author' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Author.php', diff --git a/libraries/vendor/composer/installed.json b/libraries/vendor/composer/installed.json index 9a581b0f7c7ac..3bd4b035ac38a 100644 --- a/libraries/vendor/composer/installed.json +++ b/libraries/vendor/composer/installed.json @@ -1,4 +1,51 @@ [ + { + "name": "google/recaptcha", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/google/recaptcha.git", + "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/recaptcha/zipball/2b7e00566afca82a38a1d3adb8e42c118006296e", + "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.5.*" + }, + "time": "2015-09-02T17:23:59+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "ReCaptcha\\": "src/ReCaptcha" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Client library for reCAPTCHA, a free service that protect websites from spam and abuse.", + "homepage": "http://www.google.com/recaptcha/", + "keywords": [ + "Abuse", + "captcha", + "recaptcha", + "spam" + ] + }, { "name": "ircmaxell/password-compat", "version": "v1.0.4", diff --git a/libraries/vendor/google/recaptcha/LICENSE b/libraries/vendor/google/recaptcha/LICENSE new file mode 100644 index 0000000000000..f6412328f4c81 --- /dev/null +++ b/libraries/vendor/google/recaptcha/LICENSE @@ -0,0 +1,29 @@ +Copyright 2014, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/libraries/vendor/google/recaptcha/src/ReCaptcha/ReCaptcha.php b/libraries/vendor/google/recaptcha/src/ReCaptcha/ReCaptcha.php new file mode 100644 index 0000000000000..e2f7c347ebfc4 --- /dev/null +++ b/libraries/vendor/google/recaptcha/src/ReCaptcha/ReCaptcha.php @@ -0,0 +1,97 @@ +secret = $secret; + + if (!is_null($requestMethod)) { + $this->requestMethod = $requestMethod; + } else { + $this->requestMethod = new RequestMethod\Post(); + } + } + + /** + * Calls the reCAPTCHA siteverify API to verify whether the user passes + * CAPTCHA test. + * + * @param string $response The value of 'g-recaptcha-response' in the submitted form. + * @param string $remoteIp The end user's IP address. + * @return Response Response from the service. + */ + public function verify($response, $remoteIp = null) + { + // Discard empty solution submissions + if (empty($response)) { + $recaptchaResponse = new Response(false, array('missing-input-response')); + return $recaptchaResponse; + } + + $params = new RequestParameters($this->secret, $response, $remoteIp, self::VERSION); + $rawResponse = $this->requestMethod->submit($params); + return Response::fromJson($rawResponse); + } +} diff --git a/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod.php b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod.php new file mode 100644 index 0000000000000..fc4dde59c0fce --- /dev/null +++ b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod.php @@ -0,0 +1,42 @@ +curl = $curl; + } else { + $this->curl = new Curl(); + } + } + + /** + * Submit the cURL request with the specified parameters. + * + * @param RequestParameters $params Request parameters + * @return string Body of the reCAPTCHA response + */ + public function submit(RequestParameters $params) + { + $handle = $this->curl->init(self::SITE_VERIFY_URL); + + $options = array( + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $params->toQueryString(), + CURLOPT_HTTPHEADER => array( + 'Content-Type: application/x-www-form-urlencoded' + ), + CURLINFO_HEADER_OUT => false, + CURLOPT_HEADER => false, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => true + ); + $this->curl->setoptArray($handle, $options); + + $response = $this->curl->exec($handle); + $this->curl->close($handle); + + return $response; + } +} diff --git a/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php new file mode 100644 index 0000000000000..7770d9081414a --- /dev/null +++ b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php @@ -0,0 +1,70 @@ + array( + 'header' => "Content-type: application/x-www-form-urlencoded\r\n", + 'method' => 'POST', + 'content' => $params->toQueryString(), + // Force the peer to validate (not needed in 5.6.0+, but still works + 'verify_peer' => true, + // Force the peer validation to use www.google.com + $peer_key => 'www.google.com', + ), + ); + $context = stream_context_create($options); + return file_get_contents(self::SITE_VERIFY_URL, false, $context); + } +} diff --git a/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php new file mode 100644 index 0000000000000..d3c87922d224e --- /dev/null +++ b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php @@ -0,0 +1,105 @@ +handle = fsockopen($hostname, $port, $errno, $errstr, (is_null($timeout) ? ini_get("default_socket_timeout") : $timeout)); + + if ($this->handle != false && $errno === 0 && $errstr === '') { + return $this->handle; + } else { + return false; + } + } + + /** + * fwrite + * + * @see http://php.net/fwrite + * @param string $string + * @param int $length + * @return int | bool + */ + public function fwrite($string, $length = null) + { + return fwrite($this->handle, $string, (is_null($length) ? strlen($string) : $length)); + } + + /** + * fgets + * + * @see http://php.net/fgets + * @param int $length + * @return string + */ + public function fgets($length = null) + { + return fgets($this->handle, $length); + } + + /** + * feof + * + * @see http://php.net/feof + * @return bool + */ + public function feof() + { + return feof($this->handle); + } + + /** + * fclose + * + * @see http://php.net/fclose + * @return bool + */ + public function fclose() + { + return fclose($this->handle); + } +} diff --git a/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php new file mode 100644 index 0000000000000..47541215f046f --- /dev/null +++ b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php @@ -0,0 +1,121 @@ +socket = $socket; + } else { + $this->socket = new Socket(); + } + } + + /** + * Submit the POST request with the specified parameters. + * + * @param RequestParameters $params Request parameters + * @return string Body of the reCAPTCHA response + */ + public function submit(RequestParameters $params) + { + $errno = 0; + $errstr = ''; + + if (false === $this->socket->fsockopen('ssl://' . self::RECAPTCHA_HOST, 443, $errno, $errstr, 30)) { + return self::BAD_REQUEST; + } + + $content = $params->toQueryString(); + + $request = "POST " . self::SITE_VERIFY_PATH . " HTTP/1.1\r\n"; + $request .= "Host: " . self::RECAPTCHA_HOST . "\r\n"; + $request .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $request .= "Content-length: " . strlen($content) . "\r\n"; + $request .= "Connection: close\r\n\r\n"; + $request .= $content . "\r\n\r\n"; + + $this->socket->fwrite($request); + $response = ''; + + while (!$this->socket->feof()) { + $response .= $this->socket->fgets(4096); + } + + $this->socket->fclose(); + + if (0 !== strpos($response, 'HTTP/1.1 200 OK')) { + return self::BAD_RESPONSE; + } + + $parts = preg_split("#\n\s*\n#Uis", $response); + + return $parts[1]; + } +} diff --git a/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestParameters.php b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestParameters.php new file mode 100644 index 0000000000000..cb66f26cf4b6c --- /dev/null +++ b/libraries/vendor/google/recaptcha/src/ReCaptcha/RequestParameters.php @@ -0,0 +1,103 @@ +secret = $secret; + $this->response = $response; + $this->remoteIp = $remoteIp; + $this->version = $version; + } + + /** + * Array representation. + * + * @return array Array formatted parameters. + */ + public function toArray() + { + $params = array('secret' => $this->secret, 'response' => $this->response); + + if (!is_null($this->remoteIp)) { + $params['remoteip'] = $this->remoteIp; + } + + if (!is_null($this->version)) { + $params['version'] = $this->version; + } + + return $params; + } + + /** + * Query string representation for HTTP request. + * + * @return string Query string formatted parameters. + */ + public function toQueryString() + { + return http_build_query($this->toArray(), '', '&'); + } +} diff --git a/libraries/vendor/google/recaptcha/src/ReCaptcha/Response.php b/libraries/vendor/google/recaptcha/src/ReCaptcha/Response.php new file mode 100644 index 0000000000000..d2d8a8bf77720 --- /dev/null +++ b/libraries/vendor/google/recaptcha/src/ReCaptcha/Response.php @@ -0,0 +1,102 @@ +success = $success; + $this->errorCodes = $errorCodes; + } + + /** + * Is success? + * + * @return boolean + */ + public function isSuccess() + { + return $this->success; + } + + /** + * Get error codes. + * + * @return array + */ + public function getErrorCodes() + { + return $this->errorCodes; + } +} diff --git a/libraries/vendor/google/recaptcha/src/autoload.php b/libraries/vendor/google/recaptcha/src/autoload.php new file mode 100644 index 0000000000000..a53cbd78bf25c --- /dev/null +++ b/libraries/vendor/google/recaptcha/src/autoload.php @@ -0,0 +1,38 @@ +params->get('version', '1.0') === '1.0') { JHtml::_('jquery.framework'); - $theme = $this->params->get('theme', 'clean'); - $file = 'https://www.google.com/recaptcha/api/js/recaptcha_ajax.js'; + $theme = $this->params->get('theme', 'clean'); + $file = 'https://www.google.com/recaptcha/api/js/recaptcha_ajax.js'; JHtml::_('script', $file); JFactory::getDocument()->addScriptDeclaration('jQuery( document ).ready(function() @@ -91,8 +92,7 @@ public function onInit($id = 'dynamic_recaptcha_1') * * @param string $name The name of the field. Not Used. * @param string $id The id of the field. - * @param string $class The class of the field. This should be passed as - * e.g. 'class="required"'. + * @param string $class The class of the field. * * @return string The HTML to be embedded in the form. * @@ -100,18 +100,28 @@ public function onInit($id = 'dynamic_recaptcha_1') */ public function onDisplay($name = null, $id = 'dynamic_recaptcha_1', $class = '') { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $ele = $dom->createElement('div'); + $ele->setAttribute('id', $id); + if ($this->params->get('version', '1.0') === '1.0') { - return '
'; + $ele->setAttribute('class', $class); } else { - return '
'; + $ele->setAttribute('class', ((trim($class) == '') ? 'g-recaptcha' : ($class . ' g-recaptcha'))); + $ele->setAttribute('data-sitekey', $this->params->get('public_key', '')); + $ele->setAttribute('data-theme', $this->params->get('theme2', 'light')); + $ele->setAttribute('data-size', $this->params->get('size', 'normal')); + $ele->setAttribute('data-tabindex', $this->params->get('tabindex', '0')); + $ele->setAttribute('data-callback', $this->params->get('callback', '')); + $ele->setAttribute('data-expired-callback', $this->params->get('expired_callback', '')); + $ele->setAttribute('data-error-callback', $this->params->get('error_callback', '')); } + + $dom->appendChild($ele); + return $dom->saveXML($ele); } /** @@ -121,11 +131,12 @@ public function onDisplay($name = null, $id = 'dynamic_recaptcha_1', $class = '' * * @return True if the answer is correct, false otherwise * - * @since 2.5 + * @since 2.5 + * @throws \RuntimeException */ public function onCheckAnswer($code = null) { - $input = JFactory::getApplication()->input; + $input = \JFactory::getApplication()->input; $privatekey = $this->params->get('private_key'); $version = $this->params->get('version', '1.0'); $remoteip = $input->server->get('REMOTE_ADDR', '', 'string'); @@ -148,25 +159,19 @@ public function onCheckAnswer($code = null) // Check for Private Key if (empty($privatekey)) { - $this->_subject->setError(JText::_('PLG_RECAPTCHA_ERROR_NO_PRIVATE_KEY')); - - return false; + throw new \RuntimeException(JText::_('PLG_RECAPTCHA_ERROR_NO_PRIVATE_KEY')); } // Check for IP if (empty($remoteip)) { - $this->_subject->setError(JText::_('PLG_RECAPTCHA_ERROR_NO_IP')); - - return false; + throw new \RuntimeException(JText::_('PLG_RECAPTCHA_ERROR_NO_IP')); } // Discard spam submissions if ($spam) { - $this->_subject->setError(JText::_('PLG_RECAPTCHA_ERROR_EMPTY_SOLUTION')); - - return false; + throw new \RuntimeException(JText::_('PLG_RECAPTCHA_ERROR_EMPTY_SOLUTION')); } return $this->getResponse($privatekey, $remoteip, $response, $challenge); @@ -178,11 +183,12 @@ public function onCheckAnswer($code = null) * @param string $privatekey The private key for authentication. * @param string $remoteip The remote IP of the visitor. * @param string $response The response received from Google. - * @param string $challenge The challenge field from the reCaptcha. Only for 1.0. + * @param string $challenge The challenge field from the reCaptcha. Only for 1.0 * * @return bool True if response is good | False if response is bad. * * @since 3.4 + * @throws \RuntimeException */ private function getResponse($privatekey, $remoteip, $response, $challenge = null) { @@ -212,20 +218,14 @@ private function getResponse($privatekey, $remoteip, $response, $challenge = nul } break; case '2.0': - require_once 'recaptchalib.php'; + $reCaptcha = new \ReCaptcha\ReCaptcha($privatekey, new HttpBridgePostRequestMethod); + $response = $reCaptcha->verify($response, $remoteip); - $reCaptcha = new JReCaptcha($privatekey); - $response = $reCaptcha->verifyResponse($remoteip, $response); - - if (!isset($response->success) || !$response->success) + if (!$response->isSuccess()) { - // @todo use exceptions here - if (is_array($response->errorCodes)) + foreach ($response->getErrorCodes() as $error) { - foreach ($response->errorCodes as $error) - { - $this->_subject->setError($error); - } + throw new \RuntimeException($error); } return false; diff --git a/plugins/captcha/recaptcha/recaptcha.xml b/plugins/captcha/recaptcha/recaptcha.xml index 1325c6f4efe68..0c321434677ff 100644 --- a/plugins/captcha/recaptcha/recaptcha.xml +++ b/plugins/captcha/recaptcha/recaptcha.xml @@ -11,7 +11,6 @@ PLG_CAPTCHA_RECAPTCHA_XML_DESCRIPTION recaptcha.php - recaptchalib.php @@ -43,7 +42,8 @@ default="" required="true" filter="string" - size="50" + size="100" + class="input-xxlarge" /> PLG_RECAPTCHA_THEME_NORMAL + + + + + + + + diff --git a/plugins/captcha/recaptcha/recaptchalib.php b/plugins/captcha/recaptcha/recaptchalib.php deleted file mode 100644 index ee969b79ae9aa..0000000000000 --- a/plugins/captcha/recaptcha/recaptchalib.php +++ /dev/null @@ -1,162 +0,0 @@ -" . self::$_signupUrl . ""); - } - - $this->_secret = $secret; - } - - /** - * Encodes the given data into a query string format. - * - * @param array $data array of string elements to be encoded. - * - * @return string - encoded request. - */ - private function _encodeQS($data) - { - $req = ''; - - foreach ($data as $key => $value) - { - $req .= $key . '=' . urlencode(stripslashes($value)) . '&'; - } - - // Cut the last '&' - return substr($req, 0, strlen($req) - 1); - } - - /** - * Submits an HTTP GET to a reCAPTCHA server. - * - * @param string $path URL path to recaptcha server. - * @param array $data array of parameters to be sent. - * - * @return mixed JSON string or false on error - */ - private function _submitHTTPGet($path, $data) - { - $req = $this->_encodeQS($data); - - try - { - $http = JHttpFactory::getHttp(); - $result = $http->get($path . '?' . $req)->body; - } - catch (RuntimeException $e) - { - return false; - } - - return $result; - } - - /** - * Calls the reCAPTCHA siteverify API to verify whether the user passes - * CAPTCHA test. - * - * @param string $remoteIp IP address of end user. - * @param string $response response string from recaptcha verification. - * - * @return JReCaptchaResponse - */ - public function verifyResponse($remoteIp, $response) - { - // Discard empty solution submissions - if ($response === null || $response === '') - { - $recaptchaResponse = new JReCaptchaResponse(); - $recaptchaResponse->success = false; - $recaptchaResponse->errorCodes = 'missing-input'; - - return $recaptchaResponse; - } - - $getResponse = $this->_submitHttpGet( - self::$_siteVerifyUrl, - array( - 'secret' => $this->_secret, - 'remoteip' => $remoteIp, - 'v' => self::$_version, - 'response' => $response - ) - ); - - // Something is broken in submiting the http get request - if ($getResponse === false) - { - $recaptchaResponse->success = false; - } - - $answers = json_decode($getResponse, true); - $recaptchaResponse = new JReCaptchaResponse(); - - if (trim($answers['success']) !== '') - { - $recaptchaResponse->success = true; - } - else - { - $recaptchaResponse->success = false; - $recaptchaResponse->errorCodes = isset($answers['error-codes']) ? $answers['error-codes'] : ''; - } - - return $recaptchaResponse; - } -} \ No newline at end of file diff --git a/plugins/captcha/recaptcha_invisible/recaptcha_invisible.php b/plugins/captcha/recaptcha_invisible/recaptcha_invisible.php new file mode 100644 index 0000000000000..a46f7c3598885 --- /dev/null +++ b/plugins/captcha/recaptcha_invisible/recaptcha_invisible.php @@ -0,0 +1,182 @@ +params->get('public_key', ''); + + if ($pubkey === '') + { + throw new \RuntimeException(JText::_('PLG_RECAPTCHA_INVISIBLE_ERROR_NO_PUBLIC_KEY')); + } + + + // Load callback first for browser compatibility + \JHtml::_( + 'script', + 'plg_captcha_recaptcha_invisible/recaptcha.min.js', + array('version' => 'auto', 'relative' => true), + array('async' => 'async', 'defer' => 'defer') + ); + + // Load Google reCAPTCHA api js + $file = 'https://www.google.com/recaptcha/api.js' + . '?onload=JoomlaInitReCaptchaInvisible' + . '&render=explicit' + . '&hl=' . \JFactory::getLanguage()->getTag(); + \JHtml::_( + 'script', + $file, + array(), + array('async' => 'async', 'defer' => 'defer') + ); + } + + /** + * Gets the challenge HTML + * + * @param string $name The name of the field. Not Used. + * @param string $id The id of the field. + * @param string $class The class of the field. + * + * @return string The HTML to be embedded in the form. + * + * @since __DEPLOY_VERSION__ + */ + public function onDisplay($name = null, $id = 'dynamic_recaptcha_invisible_1', $class = '') + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $ele = $dom->createElement('div'); + $ele->setAttribute('id', $id); + $ele->setAttribute('class', ((trim($class) == '') ? 'g-recaptcha' : ($class . ' g-recaptcha'))); + $ele->setAttribute('data-sitekey', $this->params->get('public_key', '')); + $ele->setAttribute('data-badge', $this->params->get('badge', 'bottomright')); + $ele->setAttribute('data-size', 'invisible'); + $ele->setAttribute('data-tabindex', $this->params->get('tabindex', '0')); + $ele->setAttribute('data-callback', $this->params->get('callback', '')); + $ele->setAttribute('data-expired-callback', $this->params->get('expired_callback', '')); + $ele->setAttribute('data-error-callback', $this->params->get('error_callback', '')); + $dom->appendChild($ele); + + return $dom->saveHTML($ele); + } + + /** + * Calls an HTTP POST function to verify if the user's guess was correct + * + * @param string $code Answer provided by user. Not needed for the Recaptcha implementation + * + * @return True if the answer is correct, false otherwise + * + * @since __DEPLOY_VERSION__ + * @throws \RuntimeException + */ + public function onCheckAnswer($code = null) + { + $input = \JFactory::getApplication()->input; + $privatekey = $this->params->get('private_key'); + $remoteip = $input->server->get('REMOTE_ADDR', '', 'string'); + + $response = $input->get('g-recaptcha-response', '', 'string'); + + // Check for Private Key + if (empty($privatekey)) + { + throw new \RuntimeException(JText::_('PLG_RECAPTCHA_INVISIBLE_ERROR_NO_PRIVATE_KEY')); + } + + // Check for IP + if (empty($remoteip)) + { + throw new \RuntimeException(JText::_('PLG_RECAPTCHA_INVISIBLE_ERROR_NO_IP')); + } + + // Discard spam submissions + if (trim($response) == '') + { + throw new \RuntimeException(JText::_('PLG_RECAPTCHA_INVISIBLE_ERROR_EMPTY_SOLUTION')); + } + + return $this->getResponse($privatekey, $remoteip, $response); + } + + /** + * Method to react on the setup of a captcha field. Gives the possibility + * to change the field and/or the XML element for the field. + * + * @param \Joomla\CMS\Form\Field\CaptchaField $field Captcha field instance + * @param \SimpleXMLElement $element XML form definition + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onSetupField(\Joomla\CMS\Form\Field\CaptchaField $field, \SimpleXMLElement $element) + { + // Hide the label for the invisible recaptcha type + $element['hiddenLabel'] = true; + } + + /** + * Get the reCaptcha response. + * + * @param string $privatekey The private key for authentication. + * @param string $remoteip The remote IP of the visitor. + * @param string $response The response received from Google. + * + * @return bool True if response is good | False if response is bad. + * + * @since __DEPLOY_VERSION__ + * @throws \RuntimeException + */ + private function getResponse($privatekey, $remoteip, $response) + { + $reCaptcha = new \ReCaptcha\ReCaptcha($privatekey, new HttpBridgePostRequestMethod); + $response = $reCaptcha->verify($response, $remoteip); + + if (!$response->isSuccess()) + { + foreach ($response->getErrorCodes() as $error) + { + throw new \RuntimeException($error); + } + + return false; + } + } +} diff --git a/plugins/captcha/recaptcha_invisible/recaptcha_invisible.xml b/plugins/captcha/recaptcha_invisible/recaptcha_invisible.xml new file mode 100644 index 0000000000000..b5dc7f606b5bb --- /dev/null +++ b/plugins/captcha/recaptcha_invisible/recaptcha_invisible.xml @@ -0,0 +1,93 @@ + + + plg_captcha_recaptcha_invisible + 3.8 + November 2017 + Joomla! Project + admin@joomla.org + www.joomla.org + Copyright (C) 2005 - 2017 Open Source Matters. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + PLG_CAPTCHA_RECAPTCHA_INVISIBLE_XML_DESCRIPTION + + recaptcha_invisible.php + + + +
+ + + + + + + + + + + + + + + + + + +
+
+
+