Completed
Push — 2.1 ( f59067...a01579 )
by
unknown
21:13 queued 09:54
created

CaptchaAction::run()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 10
cts 10
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 11
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\captcha;
9
10
use Yii;
11
use yii\base\Action;
12
use yii\base\InvalidConfigException;
13
use yii\di\Instance;
14
use yii\helpers\Url;
15
use yii\web\Response;
16
17
/**
18
 * CaptchaAction renders a CAPTCHA image.
19
 *
20
 * CaptchaAction is used together with [[Captcha]] and [[\yii\captcha\CaptchaValidator]]
21
 * to provide the [CAPTCHA](http://en.wikipedia.org/wiki/Captcha) feature.
22
 *
23
 * You should configure [[driver]] with the actual CAPTCHA rendering driver to be used.
24
 * Note that different drivers may require different libraries or PHP extension installed.
25
 * Please refer to the particular driver class for details.
26
 *
27
 * Using CAPTCHA involves the following steps:
28
 *
29
 * 1. Override [[\yii\web\Controller::actions()]] and register an action of class CaptchaAction with ID 'captcha'
30
 * 2. In the form model, declare an attribute to store user-entered verification code, and declare the attribute
31
 *    to be validated by the 'captcha' validator.
32
 * 3. In the controller view, insert a [[Captcha]] widget in the form.
33
 *
34
 * @property string $verifyCode The verification code. This property is read-only.
35
 *
36
 * @author Qiang Xue <[email protected]>
37
 * @since 2.0
38
 */
39
class CaptchaAction extends Action
40
{
41
    /**
42
     * The name of the GET parameter indicating whether the CAPTCHA image should be regenerated.
43
     */
44
    const REFRESH_GET_VAR = 'refresh';
45
46
    /**
47
     * @var int how many times should the same CAPTCHA be displayed. Defaults to 3.
48
     * A value less than or equal to 0 means the test is unlimited.
49
     */
50
    public $testLimit = 3;
51
    /**
52
     * @var string the fixed verification code. When this property is set,
53
     * [[getVerifyCode()]] will always return the value of this property.
54
     * This is mainly used in automated tests where we want to be able to reproduce
55
     * the same verification code each time we run the tests.
56
     * If not set, it means the verification code will be randomly generated.
57
     */
58
    public $fixedVerifyCode;
59
    /**
60
     * @var DriverInterface|array|string the driver to be used for CAPTCHA rendering. It could be either an instance
61
     * of [[DriverInterface]] or its DI compatible configuration.
62
     * For example:
63
     *
64
     * ```php
65
     * [
66
     *     'class' => \yii\captcha\ImagickDriver::class,
67
     *     // 'backColor' => 0xFFFFFF,
68
     *     // 'foreColor' => 0x2040A0,
69
     * ]
70
     * ```
71
     *
72
     * After the action object is created, if you want to change this property, you should assign it
73
     * with a [[DriverInterface]] object only.
74
     * @since 2.1.0
75
     */
76
    public $driver;
77
78
79
    /**
80
     * Initializes the action.
81
     * @throws InvalidConfigException if the font file does not exist.
82
     */
83 2
    public function init()
84
    {
85 2
        parent::init();
86 2
        $this->driver = Instance::ensure($this->driver, DriverInterface::class);
87 2
    }
88
89
    /**
90
     * Runs the action.
91
     */
92 2
    public function run()
93
    {
94 2
        if (Yii::$app->request->getQueryParam(self::REFRESH_GET_VAR) !== null) {
95
            // AJAX request for regenerating code
96 1
            $code = $this->getVerifyCode(true);
97 1
            Yii::$app->response->format = Response::FORMAT_JSON;
98
            return [
99 1
                'hash1' => $this->generateValidationHash($code),
100 1
                'hash2' => $this->generateValidationHash(strtolower($code)),
101
                // we add a random 'v' parameter so that FireFox can refresh the image
102
                // when src attribute of image tag is changed
103 1
                'url' => Url::to([$this->id, 'v' => uniqid()]),
104
            ];
105
        }
106
107 1
        $this->setHttpHeaders();
108 1
        Yii::$app->response->format = Response::FORMAT_RAW;
109
110 1
        return $this->driver->renderImage($this->getVerifyCode());
111
    }
112
113
    /**
114
     * Generates a hash code that can be used for client-side validation.
115
     * @param string $code the CAPTCHA code
116
     * @return string a hash code generated from the CAPTCHA code
117
     */
118 1
    public function generateValidationHash($code)
119
    {
120 1
        for ($h = 0, $i = strlen($code) - 1; $i >= 0; --$i) {
121
            $h += ord($code[$i]);
122
        }
123
124 1
        return $h;
125
    }
126
127
    /**
128
     * Gets the verification code.
129
     * @param bool $regenerate whether the verification code should be regenerated.
130
     * @return string the verification code.
131
     */
132 2
    public function getVerifyCode($regenerate = false)
133
    {
134 2
        if ($this->fixedVerifyCode !== null) {
135
            return $this->fixedVerifyCode;
136
        }
137
138 2
        $session = Yii::$app->getSession();
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
139 2
        $session->open();
140 2
        $name = $this->getSessionKey();
141 2
        if ($session->get($name) === null || $regenerate) {
142 2
            $session->set($name, $this->driver->generateVerifyCode());
143 2
            $session->set($name . 'count', 1);
144
        }
145
146 2
        return $session->get($name);
147
    }
148
149
    /**
150
     * Validates the input to see if it matches the generated code.
151
     * @param string $input user input
152
     * @param bool $caseSensitive whether the comparison should be case-sensitive
153
     * @return bool whether the input is valid
154
     */
155
    public function validate($input, $caseSensitive)
156
    {
157
        $code = $this->getVerifyCode();
158
        $valid = $caseSensitive ? ($input === $code) : strcasecmp($input, $code) === 0;
159
        $session = Yii::$app->getSession();
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
160
        $session->open();
161
        $name = $this->getSessionKey() . 'count';
162
        $session[$name] = $session[$name] + 1;
163
        if ($valid || $session[$name] > $this->testLimit && $this->testLimit > 0) {
164
            $this->getVerifyCode(true);
165
        }
166
167
        return $valid;
168
    }
169
170
    /**
171
     * Returns the session variable name used to store verification code.
172
     * @return string the session variable name
173
     */
174 2
    protected function getSessionKey()
175
    {
176 2
        return '__captcha/' . $this->getUniqueId();
177
    }
178
179
    /**
180
     * Sets the HTTP headers needed by image response.
181
     */
182 1
    protected function setHttpHeaders()
183
    {
184 1
        Yii::$app->getResponse()->getHeaders()
185 1
            ->set('Pragma', 'public')
186 1
            ->set('Expires', '0')
187 1
            ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
188 1
            ->set('Content-Transfer-Encoding', 'binary')
189 1
            ->set('Content-type', $this->driver->getImageMimeType());
190 1
    }
191
}
192