Completed
Push — master ( 1c08ae...c4f688 )
by Bram
02:02
created

Attendee::canCheckOut()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * Attendee.php
4
 *
5
 * @author Bram de Leeuw
6
 * Date: 09/03/17
7
 */
8
9
namespace Broarm\EventTickets;
10
11
use ArrayList;
12
use BaconQrCode;
13
use CalendarEvent;
14
use DataObject;
15
use Director;
16
use Dompdf\Dompdf;
17
use FieldList;
18
use File;
19
use Folder;
20
use Image;
21
use ManyManyList;
22
use Member;
23
use ReadonlyField;
24
use SSViewer;
25
use Tab;
26
use TabSet;
27
use ViewableData;
28
29
/**
30
 * Class Attendee
31
 *
32
 * @package Broarm\EventTickets
33
 *
34
 * @property string    Title
35
 * @property string    FirstName
36
 * @property string    Surname
37
 * @property string    Email
38
 * @property string    TicketCode
39
 * @property boolean   TicketReceiver
40
 * @property boolean   CheckedIn
41
 * @property FieldList SavableFields    Field to be set in AttendeesField
42
 *
43
 * @property int       TicketID
44
 * @property int       TicketQRCodeID
45
 * @property int       TicketFileID
46
 * @property int       ReservationID
47
 * @property int       EventID
48
 * @property int       MemberID
49
 *
50
 * @method Reservation Reservation()
51
 * @method Ticket Ticket()
52
 * @method Image TicketQRCode()
53
 * @method File TicketFile()
54
 * @method Member Member()
55
 * @method CalendarEvent|TicketExtension Event()
56
 * @method ManyManyList Fields()
57
 */
58
class Attendee extends DataObject
59
{
60
    /**
61
     * Set this to true when you want to have a QR code that opens the check in page and validates the code.
62
     * The validation is only done with proper authorisation so guest cannot check themselves in by mistake.
63
     * By default only the ticket number is translated to an QR code. (for use with USB QR scanners)
64
     *
65
     * @var bool
66
     */
67
    private static $qr_as_link = false;
1 ignored issue
show
Unused Code introduced by
The property $qr_as_link is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
68
69
    private static $default_fields = array(
1 ignored issue
show
Unused Code introduced by
The property $default_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
70
        'FirstName' => array(
71
            'Title' => 'First name',
72
            'FieldType' => 'UserTextField',
73
            'Required' => true,
74
            'Editable' => false
75
        ),
76
        'Surname' => array(
77
            'Title' => 'Surname',
78
            'FieldType' => 'UserTextField',
79
            'Required' => true,
80
            'Editable' => false
81
        ),
82
        'Email' => array(
83
            'Title' => 'Email',
84
            'FieldType' => 'UserEmailField',
85
            'Required' => true,
86
            'Editable' => false
87
        )
88
    );
89
90
    private static $table_fields = array(
1 ignored issue
show
Unused Code introduced by
The property $table_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
91
        'Title',
92
        'Email'
93
    );
94
95
    private static $db = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
96
        'Title' => 'Varchar(255)',
97
        'TicketReceiver' => 'Boolean',
98
        'TicketCode' => 'Varchar(255)',
99
        'CheckedIn' => 'Boolean'
100
    );
101
102
    private static $indexes = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $indexes is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
103
        'TicketCode' => 'unique("TicketCode")'
104
    );
105
106
    private static $has_one = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
107
        'Reservation' => 'Broarm\EventTickets\Reservation',
108
        'Ticket' => 'Broarm\EventTickets\Ticket',
109
        'Event' => 'CalendarEvent',
110
        'Member' => 'Member',
111
        'TicketQRCode' => 'Image',
112
        'TicketFile' => 'File'
113
    );
114
115
    private static $many_many = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
116
        'Fields' => 'Broarm\EventTickets\UserField'
117
    );
118
119
    private static $many_many_extraFields = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $many_many_extraFields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
120
        'Fields' => array(
121
            'Value' => 'Varchar(255)'
122
        )
123
    );
124
125
    private static $summary_fields = array(
2 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $summary_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
126
        'Title' => 'Name',
127
        'Ticket.Title' => 'Ticket',
128
        'TicketCode' => 'Ticket #',
129
        'CheckedIn.Nice' => 'Checked in',
130
    );
131
132
    public function getCMSFields()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
133
    {
134
        $fields = new FieldList(new TabSet('Root', $mainTab = new Tab('Main')));
135
136
        $fields->addFieldsToTab('Root.Main', array(
137
            ReadonlyField::create('TicketCode', _t('Attendee.Ticket', 'Ticket')),
138
            ReadonlyField::create('MyCheckedIn', _t('Attendee.CheckedIn', 'Checked in'),
139
                $this->dbObject('CheckedIn')->Nice())
140
        ));
141
142
        foreach ($this->Fields() as $field) {
143
            $fields->addFieldToTab(
144
                'Root.Main',
145
                ReadonlyField::create("{$field->Name}_Preview", $field->Title, $field->getValue())
146
            );
147
        }
148
149
        if ($this->TicketFile()->exists()) {
150
            $fields->addFieldToTab('Root.Main', $reservationFileField = ReadonlyField::create(
151
                'ReservationFile',
152
                _t('Attendee.Reservation', 'Reservation'),
153
                "<a class='readonly' href='{$this->TicketFile()->Link()}' target='_blank'>Download reservation PDF</a>"
154
            ));
155
            $reservationFileField->dontEscape = true;
156
        }
157
158
        $this->extend('updateCMSFields', $fields);
159
        return $fields;
160
    }
161
162
    /**
163
     * Utility method for fetching the default field, FirstName, value
164
     *
165
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
166
     */
167
    public function getFirstName()
168
    {
169
        if ($firstName = $this->Fields()->find('Name', 'FirstName')) {
170
            return (string)$firstName->getField('Value');
171
        }
172
173
        return null;
174
    }
175
176
    /**
177
     * Utility method for fetching the default field, Surname, value
178
     *
179
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
180
     */
181
    public function getSurname()
182
    {
183
        if ($surname = $this->Fields()->find('Name', 'Surname')) {
184
            return (string)$surname->getField('Value');
185
        }
186
187
        return null;
188
    }
189
190
    /**
191
     * Get the combined first and last nave for display on the ticket and attendee list
192
     *
193
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
194
     */
195
    public function getName()
196
    {
197
        $mainContact = $this->Reservation()->MainContact();
198
        if (!empty($this->getSurname())) {
199
            return trim("{$this->getFirstName()} {$this->getSurname()}");
200
        } elseif ($mainContact->exists() && !empty($mainContact->getSurname())) {
201
            return _t('Attendee.GUEST_OF', 'Guest of {name}', null, array('name' => $mainContact->getName()));
0 ignored issues
show
Documentation introduced by
array('name' => $mainContact->getName()) is of type array<string,?,{"name":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
202
        } else {
203
            return null;
204
        }
205
    }
206
207
    /**
208
     * Utility method for fetching the default field, Email, value
209
     *
210
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
211
     */
212
    public function getEmail()
213
    {
214
        if ($email = $this->Fields()->find('Name', 'Email')) {
215
            return (string)$email->getField('Value');
216
        }
217
218
        return null;
219
    }
220
221
    /**
222
     * Set the title and ticket code before writing
223
     */
224
    public function onBeforeWrite()
225
    {
226
        // Set the title of the attendee
227
        $this->Title = $this->getName();
228
229
        // Generate the ticket code
230
        if ($this->exists() && empty($this->TicketCode)) {
231
            $this->TicketCode = $this->generateTicketCode();
232
        }
233
234
        parent::onBeforeWrite();
235
    }
236
237
    /**
238
     * Delete any stray files before deleting the object
239
     */
240
    public function onBeforeDelete()
241
    {
242
        // If an attendee is deleted from the guest list remove it's qr code
243
        // after deleting the code it's not validatable anymore, simply here for cleanup
244
        if ($this->TicketQRCode()->exists()) {
245
            $this->TicketQRCode()->delete();
246
        }
247
248
        // cleanup the ticket file
249
        if ($this->TicketFile()->exists()) {
250
            $this->TicketFile()->delete();
251
        }
252
253
        parent::onBeforeDelete();
254
    }
255
256
    /**
257
     * Get the table fields for this attendee
258
     *
259
     * @return ArrayList
260
     */
261
    public function getTableFields()
262
    {
263
        $fields = new ArrayList();
264
        foreach (self::config()->get('table_fields') as $field) {
265
            $data = new ViewableData();
266
            $data->Header = _t("Attendee.$field", $field);
267
            $data->Value = $this->$field;
268
            $fields->add($data);
269
        }
270
        return $fields;
271
    }
272
273
    /**
274
     * Get the unnamespaced singular name for display in the CMS
275
     *
276
     * @return string
277
     */
278
    public function singular_name()
279
    {
280
        $name = explode('\\', parent::singular_name());
281
        return trim(end($name));
282
    }
283
284
    /**
285
     * Generate a unique ticket id
286
     * Serves as the base for the QR code and ticket file
287
     *
288
     * @return string
289
     */
290
    public function generateTicketCode()
291
    {
292
        return uniqid($this->ID);
293
    }
294
295
    /**
296
     * Create a QRCode for the attendee based on the Ticket code
297
     *
298
     * @param Folder $folder
299
     *
300
     * @return Image
301
     */
302
    public function createQRCode(Folder $folder)
303
    {
304
        $relativeFilePath = "/{$folder->Filename}{$this->TicketCode}.png";
305
        $absoluteFilePath = Director::baseFolder() . $relativeFilePath;
306
307
        if (!$image = Image::get()->find('Filename', $relativeFilePath)) {
308
            // Generate the QR code
309
            $renderer = new BaconQrCode\Renderer\Image\Png();
310
            $renderer->setHeight(256);
311
            $renderer->setWidth(256);
312
            $writer = new BaconQrCode\Writer($renderer);
313
            if (self::config()->get('qr_as_link')) {
314
                $writer->writeFile($this->getCheckInLink(), $absoluteFilePath);
315
            } else {
316
                $writer->writeFile($this->TicketCode, $absoluteFilePath);
317
            }
318
319
            // Store the image in an image object
320
            $image = Image::create();
321
            $image->ParentID = $folder->ID;
322
            $image->OwnerID = (Member::currentUser()) ? Member::currentUser()->ID : 0;
323
            $image->Title = $this->TicketCode;
324
            $image->setFilename($relativeFilePath);
325
            $image->write();
326
327
            // Attach the QR code to the Attendee
328
            $this->TicketQRCodeID = $image->ID;
329
            $this->write();
330
        }
331
332
        return $image;
333
    }
334
335
    /**
336
     * Creates a printable ticket for the attendee
337
     *
338
     * @param Folder $folder
339
     *
340
     * @return File
341
     */
342
    public function createTicketFile(Folder $folder)
343
    {
344
        // Find or make a folder
345
        $relativeFilePath = "/{$folder->Filename}{$this->TicketCode}.pdf";
346
        $absoluteFilePath = Director::baseFolder() . $relativeFilePath;
347
348
        if (!$file = File::get()->find('Filename', $relativeFilePath)) {
349
            $file = File::create();
350
            $file->ParentID = $folder->ID;
351
            $file->OwnerID = (Member::currentUser()) ? Member::currentUser()->ID : 0;
352
            $file->Title = $this->TicketCode;
353
            $file->setFilename($relativeFilePath);
354
            $file->write();
355
        }
356
357
        // Set the template and parse the data
358
        $template = new SSViewer('PrintableTicket');
359
        $html = $template->process($this->data());// getViewableData());
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
360
361
        // Create a DomPDF instance
362
        $domPDF = new Dompdf();
363
        $domPDF->loadHtml($html);
364
        $domPDF->setPaper('A4');
365
        $domPDF->getOptions()->setDpi(150);
366
        $domPDF->render();
367
368
        // Save the pdf stream as a file
369
        file_put_contents($absoluteFilePath, $domPDF->output());
370
371
        // Attach the ticket file to the Attendee
372
        $this->TicketFileID = $file->ID;
373
        $this->write();
374
375
        return $file;
376
    }
377
378
    /**
379
     * Get the checkin link
380
     *
381
     * @return string
382
     */
383
    public function getCheckInLink()
384
    {
385
        return $this->Event()->AbsoluteLink("checkin/{$this->TicketCode}");
386
    }
387
388
    /**
389
     * Check the attendee out
390
     */
391
    public function checkIn()
392
    {
393
        $this->CheckedIn = true;
394
        $this->write();
395
    }
396
397
    public function canCheckOut()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
398
    {
399
        return CheckInValidator::config()->get('allow_checkout');
400
    }
401
402
    /**
403
     * Check the attendee in
404
     */
405
    public function checkOut()
406
    {
407
        if ($this->canCheckOut()) {
408
            $this->CheckedIn = false;
409
            $this->write();
410
        }
411
    }
412
413
    public function canView($member = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
414
    {
415
        return $this->Reservation()->canView($member);
416
    }
417
418
    public function canEdit($member = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
419
    {
420
        return $this->Reservation()->canEdit($member);
421
    }
422
423
    public function canDelete($member = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
424
    {
425
        return $this->Reservation()->canDelete($member);
426
    }
427
428
    public function canCreate($member = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
429
    {
430
        return $this->Reservation()->canCreate($member);
431
    }
432
}
433