Passed
Push — master ( 570713...f0e99b )
by Manuel
02:15
created

QrBill::getQrCodeData()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 17
rs 9.8666
c 0
b 0
f 0
cc 3
nc 1
nop 0
1
<?php
2
3
namespace Sprain\SwissQrBill;
4
5
use Endroid\QrCode\ErrorCorrectionLevel;
6
use Sprain\SwissQrBill\Constraint\ValidCreditorInformationPaymentReferenceCombination;
7
use Sprain\SwissQrBill\DataGroup\AddressInterface;
8
use Sprain\SwissQrBill\DataGroup\Element\AdditionalInformation;
9
use Sprain\SwissQrBill\DataGroup\Element\AlternativeScheme;
10
use Sprain\SwissQrBill\DataGroup\Element\CreditorInformation;
11
use Sprain\SwissQrBill\DataGroup\Element\Header;
12
use Sprain\SwissQrBill\DataGroup\Element\PaymentAmountInformation;
13
use Sprain\SwissQrBill\DataGroup\Element\PaymentReference;
14
use Sprain\SwissQrBill\DataGroup\Element\StructuredAddress;
15
use Sprain\SwissQrBill\DataGroup\QrCodeableInterface;
16
use Sprain\SwissQrBill\Exception\InvalidQrBillDataException;
17
use Sprain\SwissQrBill\QrCode\QrCode;
18
use Sprain\SwissQrBill\String\StringModifier;
19
use Sprain\SwissQrBill\Validator\SelfValidatableInterface;
20
use Sprain\SwissQrBill\Validator\SelfValidatableTrait;
21
use Symfony\Component\Validator\Constraints as Assert;
22
use Symfony\Component\Validator\Mapping\ClassMetadata;
23
24
class QrBill implements SelfValidatableInterface
25
{
26
    use SelfValidatableTrait;
27
28
    const SWISS_CROSS_LOGO_FILE = __DIR__ . '/../assets/swiss-cross.png';
29
30
    const ERROR_CORRECTION_LEVEL_HIGH = ErrorCorrectionLevel::HIGH;
31
    const ERROR_CORRECTION_LEVEL_MEDIUM = ErrorCorrectionLevel::MEDIUM;
32
    const ERROR_CORRECTION_LEVEL_LOW = ErrorCorrectionLevel::LOW;
33
34
    /** @var Header */
35
    private $header;
36
37
    /** @var CreditorInformation */
38
    private $creditorInformation;
39
40
    /** @var AddressInterface*/
41
    private $creditor;
42
43
    /** @var PaymentAmountInformation */
44
    private $paymentAmountInformation;
45
46
    /** @var AddressInterface*/
47
    private $ultimateDebtor;
48
49
    /** @var PaymentReference */
50
    private $paymentReference;
51
52
    /** @var AdditionalInformation */
53
    private $additionalInformation;
54
55
    /** @var AlternativeScheme[] */
56
    private $alternativeSchemes = [];
57
58
    /** @var string  */
59
    private $errorCorrectionLevel = self::ERROR_CORRECTION_LEVEL_MEDIUM;
60
61
    public static function create(): self
62
    {
63
        $header = Header::create(
64
            Header::QRTYPE_SPC,
65
            Header::VERSION_0200,
66
            Header::CODING_LATIN
67
        );
68
69
        $qrBill = new self();
70
        $qrBill->header = $header;
71
72
        return $qrBill;
73
    }
74
75
    public function getHeader(): ?Header
76
    {
77
        return $this->header;
78
    }
79
80
    public function setHeader(Header $header): self
81
    {
82
        $this->header = $header;
83
84
        return $this;
85
    }
86
87
    public function getCreditorInformation(): ?CreditorInformation
88
    {
89
        return $this->creditorInformation;
90
    }
91
92
    public function setCreditorInformation(CreditorInformation $creditorInformation): self
93
    {
94
        $this->creditorInformation = $creditorInformation;
95
96
        return $this;
97
    }
98
99
    public function getCreditor(): ?AddressInterface
100
    {
101
        return $this->creditor;
102
    }
103
104
    public function setCreditor(AddressInterface $creditor): self
105
    {
106
        $this->creditor = $creditor;
107
        
108
        return $this;
109
    }
110
111
    public function getPaymentAmountInformation(): ?PaymentAmountInformation
112
    {
113
        return $this->paymentAmountInformation;
114
    }
115
116
    public function setPaymentAmountInformation(PaymentAmountInformation $paymentAmountInformation): self
117
    {
118
        $this->paymentAmountInformation = $paymentAmountInformation;
119
        
120
        return $this;
121
    }
122
123
    public function getUltimateDebtor(): ?AddressInterface
124
    {
125
        return $this->ultimateDebtor;
126
    }
127
128
    public function setUltimateDebtor(AddressInterface $ultimateDebtor): self
129
    {
130
        $this->ultimateDebtor = $ultimateDebtor;
131
        
132
        return $this;
133
    }
134
135
    public function getPaymentReference(): ?PaymentReference
136
    {
137
        return $this->paymentReference;
138
    }
139
140
    public function setPaymentReference(PaymentReference $paymentReference): self
141
    {
142
        $this->paymentReference = $paymentReference;
143
        
144
        return $this;
145
    }
146
147
    public function getAdditionalInformation(): ?AdditionalInformation
148
    {
149
        return $this->additionalInformation;
150
    }
151
152
    public function setAdditionalInformation(AdditionalInformation $additionalInformation): self
153
    {
154
        $this->additionalInformation = $additionalInformation;
155
156
        return $this;
157
    }
158
159
    public function getAlternativeSchemes(): array
160
    {
161
        return $this->alternativeSchemes;
162
    }
163
164
    public function setAlternativeSchemes(array $alternativeSchemes): self
165
    {
166
        $this->alternativeSchemes = $alternativeSchemes;
167
168
        return $this;
169
    }
170
171
    public function addAlternativeScheme(AlternativeScheme $alternativeScheme): self
172
    {
173
        $this->alternativeSchemes[] = $alternativeScheme;
174
175
        return $this;
176
    }
177
178
    /**
179
     * @deprecated Will be removed in v3. The specs require the error correction level to be fixed at medium.
180
     */
181
    public function setErrorCorrectionLevel(string $errorCorrectionLevel): self
182
    {
183
        $this->errorCorrectionLevel = $errorCorrectionLevel;
184
185
        return $this;
186
    }
187
188
    public function getQrCode(): QrCode
189
    {
190
        if (!$this->isValid()) {
191
            throw new InvalidQrBillDataException(
192
                'The provided data is not valid to generate a qr code. Use getViolations() to find details.'
193
            );
194
        }
195
196
        $qrCode = new QrCode();
197
        $qrCode->setText($this->getQrCodeContent());
198
        $qrCode->setSize(543); // recommended 46x46 mm in px @ 300dpi
199
        $qrCode->setLogoPath(self::SWISS_CROSS_LOGO_FILE);
200
        $qrCode->setLogoWidth(83); // recommended 7x7 mm in px @ 300dpi
201
        $qrCode->setRoundBlockSize(false);
202
        $qrCode->setMargin(0);
203
        $qrCode->setErrorCorrectionLevel(new ErrorCorrectionLevel($this->errorCorrectionLevel));
204
205
        return $qrCode;
206
    }
207
208
    private function getQrCodeContent(): string
209
    {
210
        $elements = [
211
            $this->getHeader(),
212
            $this->getCreditorInformation(),
213
            $this->getCreditor(),
214
            new StructuredAddress(), # Placeholder for ultimateCreditor, which is currently not yet allowed to be used by the implementation guidelines
215
            $this->getPaymentAmountInformation(),
216
            $this->getUltimateDebtor() ?: new StructuredAddress(),
217
            $this->getPaymentReference(),
218
            $this->getAdditionalInformation() ?: new AdditionalInformation(),
219
            $this->getAlternativeSchemes()
220
        ];
221
222
        $qrCodeStringElements = $this->extractQrCodeDataFromElements($elements);
223
224
        return implode("\r\n", $qrCodeStringElements);
225
    }
226
227
    private function extractQrCodeDataFromElements(array $elements): array
228
    {
229
        $qrCodeElements = [];
230
231
        foreach ($elements as $element) {
232
            if ($element instanceof QrCodeableInterface) {
233
                $qrCodeElements = array_merge($qrCodeElements, $element->getQrCodeData());
234
            } elseif (is_array($element)) {
235
                $qrCodeElements = array_merge($qrCodeElements, $this->extractQrCodeDataFromElements($element));
236
            }
237
        }
238
239
        array_walk($qrCodeElements, function(&$string){
240
            $string = StringModifier::replaceLineBreaksWithString($string);
241
            $string = StringModifier::replaceMultipleSpacesWithOne($string);
242
            $string = trim($string);
243
        });
244
245
        return $qrCodeElements;
246
    }
247
248
    public static function loadValidatorMetadata(ClassMetadata $metadata): void
249
    {
250
        $metadata->addConstraint(
251
            new ValidCreditorInformationPaymentReferenceCombination()
252
        );
253
254
        $metadata->addPropertyConstraints('header', [
255
            new Assert\NotNull(),
256
            new Assert\Valid()
257
        ]);
258
259
        $metadata->addPropertyConstraints('creditorInformation', [
260
            new Assert\NotNull(),
261
            new Assert\Valid()
262
        ]);
263
264
        $metadata->addPropertyConstraints('creditor', [
265
            new Assert\NotNull(),
266
            new Assert\Valid()
267
        ]);
268
269
        $metadata->addPropertyConstraints('paymentAmountInformation', [
270
            new Assert\NotNull(),
271
            new Assert\Valid()
272
        ]);
273
274
        $metadata->addPropertyConstraints('ultimateDebtor', [
275
            new Assert\Valid()
276
        ]);
277
278
        $metadata->addPropertyConstraints('paymentReference', [
279
            new Assert\NotNull(),
280
            new Assert\Valid()
281
        ]);
282
283
        $metadata->addPropertyConstraints('alternativeSchemes', [
284
            new Assert\Count([
285
                'max' => 2
286
            ]),
287
            new Assert\Valid([
288
                'traverse' => true
289
            ])
290
        ]);
291
    }
292
}
293