Skip to content

Commit db4f235

Browse files
committed
Add validation when packaging a pass using the PassValidator class
1 parent 9e52406 commit db4f235

File tree

9 files changed

+297
-75
lines changed

9 files changed

+297
-75
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Passbook\Exception;
4+
5+
/**
6+
* Thrown when validation of a pass fails.
7+
*/
8+
class PassInvalidException extends \RuntimeException
9+
{
10+
/**
11+
* Construct a PassInvalidException either with or without an array of errors.
12+
*
13+
* @param string[]|null $errors
14+
*/
15+
public function __construct(array $errors = null)
16+
{
17+
$this->errors = $errors ? $errors : array();
18+
}
19+
20+
/**
21+
* Returns the errors with the pass.
22+
*
23+
* @return string[]
24+
*/
25+
public function getErrors()
26+
{
27+
return $this->errors;
28+
}
29+
}

src/Passbook/Pass/Localization.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public function getStringsFileOutput()
102102
/**
103103
* {@inheritdoc}
104104
*/
105-
public function addImage(ImageInterface $image)
105+
public function addImage(Image $image)
106106
{
107107
$this->images[] = $image;
108108

src/Passbook/Pass/LocalizationInterface.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,14 @@ public function getStrings();
6868
public function getStringsFileOutput();
6969

7070
/**
71-
* @param ImageInterface $image
71+
* @param Image $image
7272
*
7373
* @return LocalizationInterface
7474
*/
75-
public function addImage(ImageInterface $image);
75+
public function addImage(Image $image);
7676

7777
/**
78-
* @return ImageInterface[]
78+
* @return Image[]
7979
*/
8080
public function getImages();
8181
}

src/Passbook/PassFactory.php

Lines changed: 153 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Passbook\Certificate\P12;
1717
use Passbook\Certificate\WWDR;
1818
use Passbook\Exception\FileException;
19+
use Passbook\Exception\PassInvalidException;
1920
use Passbook\Pass\Image;
2021
use RecursiveDirectoryIterator;
2122
use RecursiveIteratorIterator;
@@ -83,6 +84,11 @@ class PassFactory
8384
*/
8485
protected $skipSignature;
8586

87+
/**
88+
* @var PassValidatorInterface
89+
*/
90+
private $passValidator;
91+
8692
/**
8793
* Pass file extension
8894
*
@@ -96,9 +102,13 @@ public function __construct($passTypeIdentifier, $teamIdentifier, $organizationN
96102
$this->passTypeIdentifier = $passTypeIdentifier;
97103
$this->teamIdentifier = $teamIdentifier;
98104
$this->organizationName = $organizationName;
105+
99106
// Create certificate objects
100107
$this->p12 = new P12($p12File, $p12Pass);
101108
$this->wwdr = new WWDR($wwdrFile);
109+
110+
// By default use the PassValidator
111+
$this->passValidator = new PassValidator();
102112
}
103113

104114
/**
@@ -125,6 +135,16 @@ public function getOutputPath()
125135
return $this->outputPath;
126136
}
127137

138+
/**
139+
* The output path with a directory separator on the end.
140+
*
141+
* @return string
142+
*/
143+
public function getNormalizedOutputPath()
144+
{
145+
return rtrim($this->outputPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
146+
}
147+
128148
/**
129149
* Set overwrite
130150
*
@@ -156,6 +176,7 @@ public function isOverwrite()
156176
* be used for testing.
157177
*
158178
* @param boolean
179+
*
159180
* @return $this
160181
*/
161182
public function setSkipSignature($skipSignature)
@@ -166,14 +187,39 @@ public function setSkipSignature($skipSignature)
166187
}
167188

168189
/**
169-
* Get overwrite
190+
* Get skip signature
191+
*
170192
* @return boolean
171193
*/
172194
public function getSkipSignature()
173195
{
174196
return $this->skipSignature;
175197
}
176198

199+
/**
200+
* Set an implementation of PassValidatorInterface to validate the pass
201+
* before packaging. When set to null, no validation is performed when
202+
* packaging the pass.
203+
*
204+
* @param PassValidatorInterface|null $passValidator
205+
*
206+
* @return $this
207+
*/
208+
public function setPassValidator(PassValidatorInterface $passValidator = null)
209+
{
210+
$this->passValidator = $passValidator;
211+
212+
return $this;
213+
}
214+
215+
/**
216+
* @return PassValidatorInterface
217+
*/
218+
public function getPassValidator()
219+
{
220+
return $this->passValidator;
221+
}
222+
177223
/**
178224
* Serialize pass
179225
*
@@ -203,80 +249,31 @@ public function package(PassInterface $pass, $passName = '')
203249

204250
$this->populateRequiredInformation($pass);
205251

206-
// Serialize pass
207-
$json = self::serialize($pass);
208-
209-
$outputPath = rtrim($this->getOutputPath(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
210-
$passDir = $outputPath . $this->getPassName($passName, $pass) . DIRECTORY_SEPARATOR;
211-
$passDirExists = file_exists($passDir);
212-
if ($passDirExists && !$this->isOverwrite()) {
213-
throw new FileException("Temporary pass directory already exists");
214-
} elseif (!$passDirExists && !mkdir($passDir, 0777, true)) {
215-
throw new FileException("Couldn't create temporary pass directory");
252+
if ($this->passValidator) {
253+
if (!$this->passValidator->validate($pass)){
254+
throw new PassInvalidException($this->passValidator->getErrors());
255+
};
216256
}
217257

218-
// Pass.json
219-
$passJSONFile = $passDir . 'pass.json';
220-
file_put_contents($passJSONFile, $json);
258+
$passDir = $this->preparePassDirectory($pass);
259+
260+
// Serialize pass
261+
file_put_contents($passDir . 'pass.json', self::serialize($pass));
221262

222263
// Images
223-
/** @var Image $image */
224-
foreach ($pass->getImages() as $image) {
225-
$fileName = $passDir . $image->getContext();
226-
if ($image->isRetina()) {
227-
$fileName .= '@2x';
228-
}
229-
$fileName .= '.' . $image->getExtension();
230-
copy($image->getPathname(), $fileName);
231-
}
264+
$this->prepareImages($pass, $passDir);
232265

233266
// Localizations
234-
foreach ($pass->getLocalizations() as $localization) {
235-
// Create dir (LANGUAGE.lproj)
236-
$localizationDir = $passDir . $localization->getLanguage() . '.lproj' . DIRECTORY_SEPARATOR;
237-
mkdir($localizationDir, 0777, true);
238-
239-
// pass.strings File (Format: "token" = "value")
240-
$localizationStringsFile = $localizationDir . 'pass.strings';
241-
file_put_contents($localizationStringsFile, $localization->getStringsFileOutput());
242-
243-
// Localization images
244-
foreach ($localization->getImages() as $image) {
245-
$fileName = $localizationDir . $image->getContext();
246-
if ($image->isRetina()) {
247-
$fileName .= '@2x';
248-
}
249-
$fileName .= '.' . $image->getExtension();
250-
copy($image->getPathname(), $fileName);
251-
}
252-
}
267+
$this->prepareLocalizations($pass, $passDir);
253268

254269
// Manifest.json - recursive, also add files in sub directories
255-
$manifestJSONFile = $passDir . 'manifest.json';
256-
$manifest = array();
257-
$files = new RecursiveIteratorIterator(
258-
new RecursiveDirectoryIterator($passDir),
259-
RecursiveIteratorIterator::SELF_FIRST
260-
);
261-
foreach ($files as $file) {
262-
// Ignore "." and ".." folders
263-
if (in_array(substr($file, strrpos($file, '/') + 1), array('.', '..'))) {
264-
continue;
265-
}
266-
//
267-
$filePath = realpath($file);
268-
if (is_file($filePath) === true) {
269-
$relativePathName = str_replace($passDir, '', $file->getPathname());
270-
$manifest[$relativePathName] = sha1_file($filePath);
271-
}
272-
}
273-
file_put_contents($manifestJSONFile, $this->jsonEncode($manifest));
270+
$manifestJSONFile = $this->prepareManifest($passDir);
274271

275272
// Signature
276273
$this->sign($passDir, $manifestJSONFile);
277274

278275
// Zip pass
279-
$zipFile = $outputPath . $this->getPassName($passName, $pass) . self::PASS_EXTENSION;
276+
$zipFile = $this->getNormalizedOutputPath() . $this->getPassName($passName, $pass) . self::PASS_EXTENSION;
280277
$this->zip($passDir, $zipFile);
281278

282279
// Remove temporary pass directory
@@ -311,7 +308,7 @@ private function sign($passDir, $manifestJSONFile)
311308
$this->wwdr->getRealPath()
312309
);
313310
// Get signature content
314-
$signature = @file_get_contents($signatureFile);
311+
$signature = file_get_contents($signatureFile);
315312
// Check signature content
316313
if (!$signature) {
317314
throw new FileException("Couldn't read signature file.");
@@ -432,4 +429,97 @@ public function getPassName($passName, PassInterface $pass)
432429
return strlen($passNameSanitised) != 0 ? $passNameSanitised : $pass->getSerialNumber();
433430
}
434431

432+
/**
433+
* @param $passDir
434+
*
435+
* @return string
436+
*/
437+
private function prepareManifest($passDir)
438+
{
439+
$manifestJSONFile = $passDir . 'manifest.json';
440+
$manifest = array();
441+
$files = new RecursiveIteratorIterator(
442+
new RecursiveDirectoryIterator($passDir),
443+
RecursiveIteratorIterator::SELF_FIRST
444+
);
445+
foreach ($files as $file) {
446+
// Ignore "." and ".." folders
447+
if (in_array(substr($file, strrpos($file, '/') + 1), array('.', '..'))) {
448+
continue;
449+
}
450+
//
451+
$filePath = realpath($file);
452+
if (is_file($filePath) === true) {
453+
$relativePathName = str_replace($passDir, '', $file->getPathname());
454+
$manifest[$relativePathName] = sha1_file($filePath);
455+
}
456+
}
457+
file_put_contents($manifestJSONFile, $this->jsonEncode($manifest));
458+
459+
return $manifestJSONFile;
460+
}
461+
462+
/**
463+
* @param PassInterface $pass
464+
*
465+
* @return string
466+
*/
467+
private function preparePassDirectory(PassInterface $pass)
468+
{
469+
$passDir = $this->getNormalizedOutputPath() . $pass->getSerialNumber() . DIRECTORY_SEPARATOR;
470+
$passDirExists = file_exists($passDir);
471+
if ($passDirExists && !$this->isOverwrite()) {
472+
throw new FileException("Temporary pass directory already exists");
473+
} elseif (!$passDirExists && !mkdir($passDir, 0777, true)) {
474+
throw new FileException("Couldn't create temporary pass directory");
475+
}
476+
477+
return $passDir;
478+
}
479+
480+
/**
481+
* @param PassInterface $pass
482+
* @param $passDir
483+
*/
484+
private function prepareImages(PassInterface $pass, $passDir)
485+
{
486+
/** @var Image $image */
487+
foreach ($pass->getImages() as $image) {
488+
$fileName = $passDir . $image->getContext();
489+
if ($image->isRetina()) {
490+
$fileName .= '@2x';
491+
}
492+
$fileName .= '.' . $image->getExtension();
493+
copy($image->getPathname(), $fileName);
494+
}
495+
}
496+
497+
/**
498+
* @param PassInterface $pass
499+
* @param $passDir
500+
*/
501+
private function prepareLocalizations(PassInterface $pass, $passDir)
502+
{
503+
foreach ($pass->getLocalizations() as $localization) {
504+
// Create dir (LANGUAGE.lproj)
505+
$localizationDir = $passDir . $localization->getLanguage() . '.lproj' . DIRECTORY_SEPARATOR;
506+
mkdir($localizationDir, 0777, true);
507+
508+
// pass.strings File (Format: "token" = "value")
509+
$localizationStringsFile = $localizationDir . 'pass.strings';
510+
file_put_contents($localizationStringsFile, $localization->getStringsFileOutput());
511+
512+
// Localization images
513+
foreach ($localization->getImages() as $image) {
514+
$fileName = $localizationDir . $image->getContext();
515+
if ($image->isRetina()) {
516+
$fileName .= '@2x';
517+
}
518+
$fileName .= '.' . $image->getExtension();
519+
copy($image->getPathname(), $fileName);
520+
}
521+
}
522+
}
523+
524+
435525
}

src/Passbook/PassInterface.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ public function setBarcode(BarcodeInterface $barcode);
156156
public function getBarcode();
157157

158158
/**
159+
* @param BarcodeInterface $barcode - barcode to add to the pass
160+
*
159161
* @return $this
160162
*/
161163
public function addBarcode(BarcodeInterface $barcode);
@@ -276,4 +278,15 @@ public function addLocalization(LocalizationInterface $localization);
276278
* @return LocalizationInterface[]
277279
*/
278280
public function getLocalizations();
281+
282+
/**
283+
* {@inheritdoc}
284+
*/
285+
public function setAppLaunchURL($appLaunchURL);
286+
287+
/**
288+
* {@inheritdoc}
289+
*/
290+
public function getAppLaunchURL();
291+
279292
}

0 commit comments

Comments
 (0)