PassValidator::validateBarcodeKeys()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 16
ccs 8
cts 9
cp 0.8889
rs 10
cc 4
nc 5
nop 1
crap 4.0218
1
<?php
2
3
namespace Passbook;
4
5
use Passbook\Pass\Barcode;
6
use Passbook\Pass\Beacon;
7
use Passbook\Pass\Nfc;
8
use Passbook\Pass\Location;
9
10
/**
11
 * Validates the contents of a pass
12
 *
13
 * This attempts to find errors with a pass that would prevent it from working
14
 * properly with Apple Wallet. Passes with errors often just fail to load and
15
 * do not provide any feedback as to the error. Additionally, some issues (such
16
 * as a pass not having an icon) are not well documented and potentially
17
 * difficult to identify. This class aims to help identify and prevent these
18
 * issues.
19
 */
20
class PassValidator implements PassValidatorInterface
21
{
22
    private $errors;
23
24
    public const DESCRIPTION_REQUIRED = 'description is required and cannot be blank';
25
    public const FORMAT_VERSION_REQUIRED = 'formatVersion is required and must be 1';
26
    public const ORGANIZATION_NAME_REQUIRED = 'organizationName is required and cannot be blank';
27
    public const PASS_TYPE_IDENTIFIER_REQUIRED = 'passTypeIdentifier is required and cannot be blank';
28
    public const SERIAL_NUMBER_REQUIRED = 'serialNumber is required and cannot be blank';
29
    public const TEAM_IDENTIFIER_REQUIRED = 'teamIdentifier is required and cannot be blank';
30
    public const ICON_REQUIRED = 'pass must have an icon image';
31
    public const BARCODE_FORMAT_INVALID = 'barcode format is invalid';
32
    public const BARCODE_MESSAGE_INVALID = 'barcode message is invalid; must be a string';
33
    public const LOCATION_LATITUDE_REQUIRED = 'location latitude is required';
34
    public const LOCATION_LONGITUDE_REQUIRED = 'location longitude is required';
35
    public const LOCATION_LATITUDE_INVALID = 'location latitude is invalid; must be numeric';
36
    public const LOCATION_LONGITUDE_INVALID = 'location longitude is invalid; must be numeric';
37
    public const LOCATION_ALTITUDE_INVALID = 'location altitude is invalid; must be numeric';
38
    public const BEACON_PROXIMITY_UUID_REQUIRED = 'beacon proximityUUID is required';
39
    public const BEACON_MAJOR_INVALID = 'beacon major is invalid; must be 16-bit unsigned integer';
40
    public const BEACON_MINOR_INVALID = 'beacon minor is invalid; must be 16-bit unsigned integer';
41
    public const NFC_MESSAGE_REQUIRED = 'NFC message is required';
42
    public const NFC_ENCRYPTION_PUBLIC_KEY_REQUIRED = 'NFC encryption public key is required';
43
    public const WEB_SERVICE_URL_INVALID = 'webServiceURL is invalid; must start with https (or http for development)';
44
    public const WEB_SERVICE_AUTHENTICATION_TOKEN_REQUIRED = 'authenticationToken required with webServiceURL and cannot be blank';
45
    public const WEB_SERVICE_AUTHENTICATION_TOKEN_INVALID = 'authenticationToken is invalid; must be at least 16 characters';
46
    public const ASSOCIATED_STORE_IDENTIFIER_INVALID = 'associatedStoreIdentifiers is invalid; must be an integer';
47
    public const ASSOCIATED_STORE_IDENTIFIER_REQUIRED = 'appLaunchURL is required when associatedStoreIdentifiers is present';
48
    public const IMAGE_TYPE_INVALID = 'image files must be PNG format';
49
    public const GROUPING_IDENTITY_INVALID = 'the grouping identity may only be used on boarding pass and event ticket types';
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 19
    public function validate(PassInterface $pass)
55
    {
56 19
        $this->errors = [];
57
58 19
        $this->validateRequiredFields($pass);
59 19
        $this->validateBeaconKeys($pass);
60 19
        $this->validateNfcKeys($pass);
61 19
        $this->validateLocationKeys($pass);
62 19
        $this->validateBarcodeKeys($pass);
63 19
        $this->validateWebServiceKeys($pass);
64 19
        $this->validateIcon($pass);
65 19
        $this->validateImageType($pass);
66 19
        $this->validateAssociatedStoreIdentifiers($pass);
67 19
        $this->validateGroupingIdentity($pass);
68
69 19
        return count($this->errors) === 0;
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 16
    public function getErrors()
76
    {
77 16
        return $this->errors;
78
    }
79
80 19
    private function validateRequiredFields(PassInterface $pass)
81
    {
82 19
        if ($this->isBlankOrNull($pass->getDescription())) {
83 1
            $this->addError(self::DESCRIPTION_REQUIRED);
84
        }
85
86 19
        if ($pass->getFormatVersion() !== 1) {
87 1
            $this->addError(self::FORMAT_VERSION_REQUIRED);
88
        }
89
90 19
        if ($this->isBlankOrNull($pass->getOrganizationName())) {
91 15
            $this->addError(self::ORGANIZATION_NAME_REQUIRED);
92
        }
93
94 19
        if ($this->isBlankOrNull($pass->getPassTypeIdentifier())) {
95 15
            $this->addError(self::PASS_TYPE_IDENTIFIER_REQUIRED);
96
        }
97
98 19
        if ($this->isBlankOrNull($pass->getSerialNumber())) {
99 1
            $this->addError(self::SERIAL_NUMBER_REQUIRED);
100
        }
101
102 19
        if ($this->isBlankOrNull($pass->getTeamIdentifier())) {
103 15
            $this->addError(self::TEAM_IDENTIFIER_REQUIRED);
104
        }
105
    }
106
107 19
    private function validateBeaconKeys(PassInterface $pass)
108
    {
109 19
        $beacons = $pass->getBeacons();
110
111 19
        foreach ($beacons as $beacon) {
112 1
            $this->validateBeacon($beacon);
113
        }
114
    }
115
116 19
    private function validateNfcKeys(PassInterface $pass)
117
    {
118 19
        $nfc = $pass->getNfc();
119 19
        if (!$nfc) {
0 ignored issues
show
introduced by
$nfc is of type Passbook\Pass\NfcInterface, thus it always evaluated to true.
Loading history...
120 19
            return;
121
        }
122
        $this->validateNfc($nfc);
123
    }
124
125 1
    private function validateBeacon(Beacon $beacon)
126
    {
127 1
        if ($this->isBlankOrNull($beacon->getProximityUUID())) {
128
            $this->addError(self::BEACON_PROXIMITY_UUID_REQUIRED);
129
        }
130
131 1
        if (null !== $beacon->getMajor()) {
0 ignored issues
show
introduced by
The condition null !== $beacon->getMajor() is always true.
Loading history...
132 1
            if (!is_int($beacon->getMajor()) || $beacon->getMajor() < 0 || $beacon->getMajor() > 65535) {
0 ignored issues
show
introduced by
The condition is_int($beacon->getMajor()) is always true.
Loading history...
133 1
                $this->addError(self::BEACON_MAJOR_INVALID);
134
            }
135
        }
136
137 1
        if (null !== $beacon->getMinor()) {
0 ignored issues
show
introduced by
The condition null !== $beacon->getMinor() is always true.
Loading history...
138 1
            if (!is_int($beacon->getMinor()) || $beacon->getMinor() < 0 || $beacon->getMinor() > 65535) {
0 ignored issues
show
introduced by
The condition is_int($beacon->getMinor()) is always true.
Loading history...
139 1
                $this->addError(self::BEACON_MINOR_INVALID);
140
            }
141
        }
142
    }
143
144
    private function validateNfc(Nfc $nfc)
145
    {
146
        if ($this->isBlankOrNull($nfc->getMessage())) {
147
            $this->addError(self::NFC_MESSAGE_REQUIRED);
148
        }
149
150
        if ($this->isBlankOrNull($nfc->getEncryptionPublicKey())) {
151
            $this->addError(self::NFC_ENCRYPTION_PUBLIC_KEY_REQUIRED);
152
        }
153
    }
154
155 19
    private function validateLocationKeys(PassInterface $pass)
156
    {
157 19
        $locations = $pass->getLocations();
158
159 19
        foreach ($locations as $location) {
160 1
            $this->validateLocation($location);
161
        }
162
    }
163
164 1
    private function validateLocation(Location $location)
165
    {
166 1
        if ($this->isBlankOrNull($location->getLatitude())) {
167 1
            $this->addError(self::LOCATION_LATITUDE_REQUIRED);
168
        }
169
170 1
        if (!is_numeric($location->getLatitude())) {
0 ignored issues
show
introduced by
The condition is_numeric($location->getLatitude()) is always true.
Loading history...
171 1
            $this->addError(self::LOCATION_LATITUDE_INVALID);
172
        }
173
174 1
        if ($this->isBlankOrNull($location->getLongitude())) {
175 1
            $this->addError(self::LOCATION_LONGITUDE_REQUIRED);
176
        }
177
178 1
        if (!is_numeric($location->getLongitude())) {
0 ignored issues
show
introduced by
The condition is_numeric($location->getLongitude()) is always true.
Loading history...
179 1
            $this->addError(self::LOCATION_LONGITUDE_INVALID);
180
        }
181
182 1
        if (!is_numeric($location->getAltitude()) && null !== $location->getAltitude()) {
0 ignored issues
show
introduced by
The condition is_numeric($location->getAltitude()) is always true.
Loading history...
183 1
            $this->addError(self::LOCATION_ALTITUDE_INVALID);
184
        }
185
    }
186
187 19
    private function validateBarcodeKeys(PassInterface $pass)
188
    {
189 19
        $validBarcodeFormats = [Barcode::TYPE_QR, Barcode::TYPE_AZTEC, Barcode::TYPE_PDF_417, Barcode::TYPE_CODE_128];
190
191 19
        $barcode = $pass->getBarcode();
0 ignored issues
show
Deprecated Code introduced by
The function Passbook\PassInterface::getBarcode() has been deprecated: use getBarcodes instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

191
        $barcode = /** @scrutinizer ignore-deprecated */ $pass->getBarcode();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
192
193 19
        if (!$barcode) {
0 ignored issues
show
introduced by
$barcode is of type Passbook\Pass\BarcodeInterface, thus it always evaluated to true.
Loading history...
194 18
            return;
195
        }
196
197 3
        if (!in_array($barcode->getFormat(), $validBarcodeFormats)) {
198 2
            $this->addError(self::BARCODE_FORMAT_INVALID);
199
        }
200
201 3
        if (!is_string($barcode->getMessage())) {
0 ignored issues
show
introduced by
The condition is_string($barcode->getMessage()) is always true.
Loading history...
202
            $this->addError(self::BARCODE_MESSAGE_INVALID);
203
        }
204
    }
205
206 19
    private function validateWebServiceKeys(PassInterface $pass)
207
    {
208 19
        if (null === $pass->getWebServiceURL()) {
209 19
            return;
210
        }
211
212 1
        if (strpos($pass->getWebServiceURL(), 'http') !== 0) {
213 1
            $this->addError(self::WEB_SERVICE_URL_INVALID);
214
        }
215
216 1
        if ($this->isBlankOrNull($pass->getAuthenticationToken())) {
217 1
            $this->addError(self::WEB_SERVICE_AUTHENTICATION_TOKEN_REQUIRED);
218
        }
219
220 1
        if (strlen($pass->getAuthenticationToken()) < 16) {
221 1
            $this->addError(self::WEB_SERVICE_AUTHENTICATION_TOKEN_INVALID);
222
        }
223
    }
224
225 19
    private function validateIcon(PassInterface $pass)
226
    {
227 19
        foreach ($pass->getImages() as $image) {
228 5
            if ($image->getContext() === 'icon') {
229 5
                return;
230
            }
231
        }
232
233 16
        $this->addError(self::ICON_REQUIRED);
234
    }
235
236 19
    private function validateImageType(PassInterface $pass)
237
    {
238 19
        foreach ($pass->getImages() as $image) {
239 5
            if ('png' !== strtolower($image->getExtension())) {
240 1
                $this->addError(self::IMAGE_TYPE_INVALID);
241
            }
242
        }
243
    }
244
245 19
    private function validateAssociatedStoreIdentifiers(PassInterface $pass)
246
    {
247
        //appLaunchURL
248
249 19
        $associatedStoreIdentifiers = $pass->getAssociatedStoreIdentifiers();
250
251 19
        if (null !== $pass->getAppLaunchURL() && count($associatedStoreIdentifiers) == 0) {
252 1
            $this->addError(self::ASSOCIATED_STORE_IDENTIFIER_REQUIRED);
253
        }
254
255
256 19
        foreach ($associatedStoreIdentifiers as $associatedStoreIdentifier) {
257 1
            if (!is_int($associatedStoreIdentifier)) {
258 1
                $this->addError(self::ASSOCIATED_STORE_IDENTIFIER_INVALID);
259
260 1
                return;
261
            }
262
        }
263
    }
264
265 19
    private function validateGroupingIdentity(PassInterface $pass)
266
    {
267 19
        if (null !== $pass->getGroupingIdentifier() && !in_array($pass->getType(), ['boardingPass', 'eventTicket'])) {
268 1
            $this->addError(self::GROUPING_IDENTITY_INVALID);
269
270 1
            return;
271
        }
272
    }
273
274 19
    private function isBlankOrNull($text)
275
    {
276 19
        return '' === $text || null === $text;
277
    }
278
279 16
    private function addError($string)
280
    {
281 16
        $this->errors[] = $string;
282
    }
283
}
284