• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / recaptcha / 25285885407

03 May 2026 05:28PM UTC coverage: 94.737% (-5.3%) from 100.0%
25285885407

Pull #629

github

web-flow
Merge 989f6e4fc into 6721ab8bf
Pull Request #629: Restore 1.x API compatibility while keeping 1.5 hardening

89 of 101 new or added lines in 8 files covered. (88.12%)

216 of 228 relevant lines covered (94.74%)

25.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.74
/src/ReCaptcha/Response.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This is a PHP library that handles calling reCAPTCHA.
7
 *
8
 * BSD 3-Clause License
9
 *
10
 * @copyright (c) 2019, Google Inc.
11
 *
12
 * @see https://www.google.com/recaptcha
13
 * All rights reserved.
14
 *
15
 * Redistribution and use in source and binary forms, with or without
16
 * modification, are permitted provided that the following conditions are met:
17
 * 1. Redistributions of source code must retain the above copyright notice, this
18
 *    list of conditions and the following disclaimer.
19
 *
20
 * 2. Redistributions in binary form must reproduce the above copyright notice,
21
 *    this list of conditions and the following disclaimer in the documentation
22
 *    and/or other materials provided with the distribution.
23
 *
24
 * 3. Neither the name of the copyright holder nor the names of its
25
 *    contributors may be used to endorse or promote products derived from
26
 *    this software without specific prior written permission.
27
 *
28
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
29
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
32
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
36
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
 */
39

40
namespace ReCaptcha;
41

42
/**
43
 * The response returned from the service.
44
 */
45
class Response
46
{
47
    private bool $success = false;
48

49
    /**
50
     * @var array<string>
51
     */
52
    private array $errorCodes = [];
53

54
    private string $hostname = '';
55

56
    private string $challengeTs = '';
57

58
    private string $apkPackageName = '';
59

60
    private ?float $score = null;
61

62
    private string $action = '';
63

64
    /**
65
     * Constructor.
66
     *
67
     * @param bool          $success        success or failure
68
     * @param array<string> $errorCodes     error code strings
69
     * @param string        $hostname       the hostname of the site where the reCAPTCHA was solved
70
     * @param string        $challengeTs    timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
71
     * @param string        $apkPackageName APK package name
72
     * @param ?float        $score          score assigned to the request
73
     * @param string        $action         action as specified by the page
74
     */
75
    public function __construct($success, array $errorCodes = [], $hostname = '', $challengeTs = '', $apkPackageName = '', $score = null, $action = '')
76
    {
77
        $this->success = (bool) $success;
96✔
78
        $this->errorCodes = $errorCodes;
96✔
79
        $this->hostname = self::stringValue($hostname);
96✔
80
        $this->challengeTs = self::stringValue($challengeTs);
96✔
81
        $this->apkPackageName = self::stringValue($apkPackageName);
96✔
82
        $this->score = self::nullableFloatValue($score);
96✔
83
        $this->action = self::stringValue($action);
96✔
84
    }
85

86
    /**
87
     * Build the response from the expected JSON returned by the service.
88
     *
89
     * @param mixed $json
90
     *
91
     * @return Response
92
     */
93
    public static function fromJson($json)
94
    {
95
        if (!is_string($json)) {
74✔
96
            return new Response(false, [ReCaptcha::E_INVALID_JSON]);
2✔
97
        }
98

99
        $responseData = json_decode($json, true);
72✔
100

101
        if (!$responseData || !is_array($responseData)) {
72✔
102
            return new Response(false, [ReCaptcha::E_INVALID_JSON]);
4✔
103
        }
104

105
        $hostname = self::stringValue($responseData['hostname'] ?? '');
68✔
106
        $challengeTs = self::stringValue($responseData['challenge_ts'] ?? '');
68✔
107
        $apkPackageName = self::stringValue($responseData['apk_package_name'] ?? '');
68✔
108
        $score = self::nullableFloatValue($responseData['score'] ?? null);
68✔
109
        $action = self::stringValue($responseData['action'] ?? '');
68✔
110

111
        if (isset($responseData['success']) && true == $responseData['success']) {
68✔
112
            return new Response(true, [], $hostname, $challengeTs, $apkPackageName, $score, $action);
50✔
113
        }
114

115
        if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) {
18✔
116
            /** @var array<string> $errorCodes */
117
            $errorCodes = $responseData['error-codes'];
8✔
118

119
            return new Response(false, $errorCodes, $hostname, $challengeTs, $apkPackageName, $score, $action);
8✔
120
        }
121

122
        return new Response(false, [ReCaptcha::E_UNKNOWN_ERROR], $hostname, $challengeTs, $apkPackageName, $score, $action);
10✔
123
    }
124

125
    /**
126
     * Is success?
127
     *
128
     * @return bool
129
     */
130
    public function isSuccess()
131
    {
132
        return $this->success;
82✔
133
    }
134

135
    /**
136
     * Get error codes.
137
     *
138
     * @return array<string>
139
     */
140
    public function getErrorCodes()
141
    {
142
        return $this->errorCodes;
56✔
143
    }
144

145
    /**
146
     * Get hostname.
147
     *
148
     * @return string
149
     */
150
    public function getHostname()
151
    {
152
        return $this->hostname;
58✔
153
    }
154

155
    /**
156
     * Get challenge timestamp.
157
     *
158
     * @return string
159
     */
160
    public function getChallengeTs()
161
    {
162
        return $this->challengeTs;
54✔
163
    }
164

165
    /**
166
     * Get APK package name.
167
     *
168
     * @return string
169
     */
170
    public function getApkPackageName()
171
    {
172
        return $this->apkPackageName;
52✔
173
    }
174

175
    /**
176
     * Get score.
177
     *
178
     * @return null|float
179
     */
180
    public function getScore()
181
    {
182
        return $this->score;
54✔
183
    }
184

185
    /**
186
     * Get action.
187
     *
188
     * @return string
189
     */
190
    public function getAction()
191
    {
192
        return $this->action;
52✔
193
    }
194

195
    /**
196
     * Array representation.
197
     *
198
     * @return array{
199
     *     success: bool,
200
     *     hostname: string,
201
     *     challenge_ts: string,
202
     *     apk_package_name: string,
203
     *     score: null|float,
204
     *     action: string,
205
     *     error-codes: string[]
206
     * }
207
     */
208
    public function toArray()
209
    {
210
        return [
2✔
211
            'success' => $this->isSuccess(),
2✔
212
            'hostname' => $this->getHostname(),
2✔
213
            'challenge_ts' => $this->getChallengeTs(),
2✔
214
            'apk_package_name' => $this->getApkPackageName(),
2✔
215
            'score' => $this->getScore(),
2✔
216
            'action' => $this->getAction(),
2✔
217
            'error-codes' => $this->getErrorCodes(),
2✔
218
        ];
2✔
219
    }
220

221
    private static function stringValue(mixed $value): string
222
    {
223
        if (is_scalar($value) || $value instanceof \Stringable) {
96✔
224
            return (string) $value;
96✔
225
        }
226

NEW
227
        return '';
×
228
    }
229

230
    private static function nullableFloatValue(mixed $value): ?float
231
    {
232
        if (is_null($value)) {
96✔
233
            return null;
80✔
234
        }
235

236
        if (is_scalar($value)) {
16✔
237
            return floatval($value);
16✔
238
        }
239

NEW
240
        return null;
×
241
    }
242
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc