Skip to content

Commit 76342b8

Browse files
committed
Add reCaptcha plugin
1 parent f7e14cc commit 76342b8

7 files changed

Lines changed: 279 additions & 2 deletions

File tree

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"symfony/mailer": "^7.3",
2727
"ramsey/uuid": "^4.7",
2828
"avalon/framework": "dev-master",
29-
"league/commonmark": "^2.7"
29+
"league/commonmark": "^2.7",
30+
"google/recaptcha": "^1.3"
3031
},
3132
"autoload": {
3233
"psr-4": {

composer.lock

Lines changed: 53 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Traq/Locale/enus.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,13 @@ public static function locale()
610610
'errors.label_blank' => "Label cannot be blank",
611611
'errors.url_empty' => "URL cannot be empty",
612612

613+
// ----------------------------------------------------------------------------------------------------
614+
// ReCaptcha
615+
'recaptcha' => "reCaptcha",
616+
'site_key' => "Site Key",
617+
'secret_key' => "Secret Key",
618+
'errors.recaptcha.failed' => "The reCaptcha verification failed, please try again.",
619+
613620
// ----------------------------------------------------------------------------------------------------
614621
// Notifications
615622

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
/*!
3+
* Traq
4+
* Copyright (C) 2009-2025 Jack Polgar
5+
* Copyright (C) 2012-2025 Traq.io
6+
* https://github.com/nirix
7+
* http://traq.io
8+
*
9+
* This file is part of Traq.
10+
*
11+
* Traq is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU General Public License as published by
13+
* the Free Software Foundation; version 3 only.
14+
*
15+
* Traq is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU General Public License
21+
* along with Traq. If not, see <http://www.gnu.org/licenses/>.
22+
*/
23+
24+
namespace ReCaptcha\Controllers;
25+
26+
use Avalon\Database;
27+
use Avalon\Http\Request;
28+
29+
/**
30+
* reCaptcha controller.
31+
*
32+
* @author Jack P.
33+
* @since 3.9
34+
* @package SecurityQuestions
35+
* @subpackage Controllers
36+
*/
37+
class ReCaptchaController extends \Traq\Controllers\Admin\AppController
38+
{
39+
/**
40+
* Question management page.
41+
*/
42+
public function index()
43+
{
44+
if (Request::method() == 'POST') {
45+
$site_key = Request::$post['site_key'];
46+
$secret_key = Request::$post['secret_key'];
47+
48+
Database::connection()->update('settings')->set(['value' => $site_key])->where('setting', 'recaptcha_site_key')->exec();
49+
Database::connection()->update('settings')->set(['value' => $secret_key])->where('setting', 'recaptcha_secret_key')->exec();
50+
51+
return $this->redirectTo('/admin/settings/recaptcha');
52+
}
53+
54+
// Set page title
55+
$this->title(l('recaptcha'));
56+
57+
$data = [
58+
'site_key' => settings('recaptcha_site_key'),
59+
'secret_key' => settings('recaptcha_secret_key'),
60+
];
61+
62+
return $this->render('recaptcha/index.phtml', $data);
63+
}
64+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
/*!
3+
* Traq
4+
* Copyright (C) 2009-2025 Jack Polgar
5+
* Copyright (C) 2012-2025 Traq.io
6+
* https://github.com/nirix
7+
* http://traq.io
8+
*
9+
* This file is part of Traq.
10+
*
11+
* Traq is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU General Public License as published by
13+
* the Free Software Foundation; version 3 only.
14+
*
15+
* Traq is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU General Public License
21+
* along with Traq. If not, see <http://www.gnu.org/licenses/>.
22+
*/
23+
24+
namespace Traq\Plugins;
25+
26+
use Avalon\Database;
27+
use \FishHook;
28+
use ReCaptcha\Controllers\ReCaptchaController;
29+
use Request;
30+
use Router;
31+
use Traq\Libraries\Plugin;
32+
use View;
33+
34+
/**
35+
* Security Questions Plugin.
36+
*
37+
* @package Traq
38+
* @subpackage Plugins
39+
* @author Jack P.
40+
* @copyright (c) Jack P.
41+
*/
42+
class ReCaptcha extends Plugin
43+
{
44+
protected static $info = [
45+
'name' => 'reCaptcha',
46+
'version' => '1.0',
47+
'author' => 'Jack P.'
48+
];
49+
50+
public static function init()
51+
{
52+
// Add routes
53+
Router::register('security_questions.index', '/admin/settings/recaptcha', [ReCaptchaController::class, 'index']);
54+
55+
// Register the view path
56+
View::$searchPaths[] = dirname(__FILE__) . '/views';
57+
58+
// Hook into the settings navbar
59+
FishHook::add('template:admin/settings/_nav', array(get_called_class(), 'adminNav'));
60+
61+
if (settings('recaptcha_site_key') && settings('recaptcha_secret_key')) {
62+
// Hook into register form
63+
FishHook::add('template:users/register', array(get_called_class(), 'recaptchaField'));
64+
65+
// Hook into the register action
66+
FishHook::add('controller:users.register', array(get_called_class(), 'verifyRecaptcha'));
67+
}
68+
}
69+
70+
/**
71+
* Adds the link to the settings navbar.
72+
*/
73+
public static function adminNav()
74+
{
75+
echo sprintf('<li%s><a href="%s">%s</a></li>', iif(active_nav('/admin/settings/recaptcha'), ' class="active"', ''), Request::base('/admin/settings/recaptcha'), l('recaptcha'));
76+
}
77+
78+
/**
79+
* Adds the question field to the register form.
80+
*/
81+
public static function recaptchaField()
82+
{
83+
echo View::render('users/recaptcha_field', ['siteKey' => settings('recaptcha_site_key')]);
84+
}
85+
86+
/**
87+
* Checks the submitted answer.
88+
*
89+
* @param object $model
90+
*/
91+
public static function verifyRecaptcha(&$model)
92+
{
93+
$recaptcha = new \ReCaptcha\ReCaptcha(settings('recaptcha_secret_key'));
94+
$resp = $recaptcha->setExpectedHostname($_SERVER['SERVER_NAME'])
95+
->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
96+
97+
if (!$resp->isSuccess()) {
98+
$model->_add_error('recaptcha', l('errors.recaptcha.failed'));
99+
}
100+
}
101+
102+
/**
103+
* Creates the setting row.
104+
*/
105+
public static function __install()
106+
{
107+
Database::connection()->insert(array('setting' => 'recaptcha_site_key', 'value' => ''))->into('settings')->exec();
108+
Database::connection()->insert(array('setting' => 'recaptcha_secret_key', 'value' => ''))->into('settings')->exec();
109+
110+
return true;
111+
}
112+
113+
/**
114+
* Deletes the setting row.
115+
*/
116+
public static function __uninstall()
117+
{
118+
Database::connection()->delete()->from('settings')->where('setting', 'recaptcha_site_key')->exec();
119+
Database::connection()->delete()->from('settings')->where('setting', 'recaptcha_secret_key')->exec();
120+
121+
return true;
122+
}
123+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php extendView('layouts/admin.phtml'); ?>
2+
3+
<div class="traq_settings title content">
4+
<h2 class="page-title"><?= l('recaptcha'); ?></h2>
5+
</div>
6+
<?= View::render('admin/settings/_nav'); ?>
7+
<div class="traq_settings content">
8+
<form action="<?= Request::requestUri(); ?>" method="post">
9+
<div class="panel">
10+
<div class="panel-content">
11+
<div class="form-group-row">
12+
<label><?= l('site_key'); ?></label>
13+
<input type="text" name="site_key" value="<?= $siteKey; ?>" autocomplete="off" />
14+
</div>
15+
16+
<div class="form-group-row">
17+
<label><?= l('secret_key'); ?></label>
18+
<input type="text" name="secret_key" value="<?= $secretKey; ?>" autocomplete="off" />
19+
</div>
20+
</div>
21+
</div>
22+
<div class="actions">
23+
<input type="submit" value="<?= l('save'); ?>" class="btn-primary" />
24+
</div>
25+
</form>
26+
</div>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div class="p-4 flex justify-center">
2+
<div class="g-recaptcha" data-sitekey="<?= $siteKey ?>" data-action="LOGIN"></div>
3+
<script src="https://www.google.com/recaptcha/enterprise.js" async defer></script>
4+
</div>

0 commit comments

Comments
 (0)