Completed
Push — master ( ae9a59...98f1ea )
by Bram
01:50
created

Attendee   C

Complexity

Total Complexity 37

Size/Duplication

Total Lines 365
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 20
dl 0
loc 365
rs 5.5647
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A generateTicketCode() 0 4 1
B createQRCode() 0 32 4
B createTicketFile() 0 35 3
B getCMSFields() 0 28 3
A getFirstName() 0 8 2
A getSurname() 0 8 2
A getName() 0 11 4
A getEmail() 0 8 2
A onBeforeWrite() 0 12 3
A onBeforeDelete() 0 15 3
A getTableFields() 0 11 2
A singular_name() 0 5 1
A getCheckInLink() 0 4 1
A checkIn() 0 4 1
A checkOut() 0 4 1
A canView() 0 4 1
A canEdit() 0 4 1
A canDelete() 0 4 1
A canCreate() 0 4 1
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 LiteralField;
22
use ManyManyList;
23
use Member;
24
use ReadonlyField;
25
use SSViewer;
26
use Tab;
27
use TabSet;
28
use TextField;
29
use ViewableData;
30
31
/**
32
 * Class Attendee
33
 *
34
 * @package Broarm\EventTickets
35
 *
36
 * @property string    Title
37
 * @property string    FirstName
38
 * @property string    Surname
39
 * @property string    Email
40
 * @property string    TicketCode
41
 * @property boolean   TicketReceiver
42
 * @property boolean   CheckedIn
43
 * @property FieldList SavableFields    Field to be set in AttendeesField
44
 *
45
 * @property int       TicketID
46
 * @property int       TicketQRCodeID
47
 * @property int       TicketFileID
48
 * @property int       ReservationID
49
 * @property int       EventID
50
 * @property int       MemberID
51
 *
52
 * @method Reservation Reservation()
53
 * @method Ticket Ticket()
54
 * @method Image TicketQRCode()
55
 * @method File TicketFile()
56
 * @method Member Member()
57
 * @method CalendarEvent|TicketExtension Event()
58
 * @method ManyManyList Fields()
59
 */
60
class Attendee extends DataObject
61
{
62
    /**
63
     * Set this to true when you want to have a QR code that opens the check in page and validates the code.
64
     * The validation is only done with proper authorisation so guest cannot check themselves in by mistake.
65
     * By default only the ticket number is translated to an QR code. (for use with USB QR scanners)
66
     *
67
     * @var bool
68
     */
69
    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...
70
71
    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...
72
        'FirstName' => array(
73
            'Title' => 'First name',
74
            'FieldType' => 'UserTextField',
75
            'Required' => true,
76
            'Editable' => false
77
        ),
78
        'Surname' => array(
79
            'Title' => 'Surname',
80
            'FieldType' => 'UserTextField',
81
            'Required' => true,
82
            'Editable' => false
83
        ),
84
        'Email' => array(
85
            'Title' => 'Email',
86
            'FieldType' => 'UserEmailField',
87
            'Required' => true,
88
            'Editable' => false
89
        )
90
    );
91
92
    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...
93
        'Title',
94
        'Email'
95
    );
96
97
    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...
98
        'Title' => 'Varchar(255)',
99
        'TicketReceiver' => 'Boolean',
100
        'TicketCode' => 'Varchar(255)',
101
        'CheckedIn' => 'Boolean'
102
    );
103
104
    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...
105
        'TicketCode' => 'unique("TicketCode")'
106
    );
107
108
    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...
109
        'Reservation' => 'Broarm\EventTickets\Reservation',
110
        'Ticket' => 'Broarm\EventTickets\Ticket',
111
        'Event' => 'CalendarEvent',
112
        'Member' => 'Member',
113
        'TicketQRCode' => 'Image',
114
        'TicketFile' => 'File'
115
    );
116
117
    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...
118
        'Fields' => 'Broarm\EventTickets\UserField'
119
    );
120
121
    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...
122
        'Fields' => array(
123
            'Value' => 'Varchar(255)'
124
        )
125
    );
126
127
    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...
128
        'Title' => 'Name',
129
        'Ticket.Title' => 'Ticket',
130
        'TicketCode' => 'Ticket #',
131
        'CheckedIn.Nice' => 'Checked in',
132
    );
133
134
    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...
135
    {
136
        $fields = new FieldList(new TabSet('Root', $mainTab = new Tab('Main')));
137
138
        $fields->addFieldsToTab('Root.Main', array(
139
            ReadonlyField::create('TicketCode', _t('Attendee.Ticket', 'Ticket')),
140
            ReadonlyField::create('MyCheckedIn', _t('Attendee.CheckedIn', 'Checked in'), $this->dbObject('CheckedIn')->Nice())
141
        ));
142
143
        foreach ($this->Fields() as $field) {
144
            $fields->addFieldToTab(
145
                'Root.Main',
146
                ReadonlyField::create("{$field->Name}_Preview", $field->Title, $field->getValue())
147
            );
148
        }
149
150
        if ($this->TicketFile()->exists()) {
151
            $fields->addFieldToTab('Root.Main', $reservationFileField = ReadonlyField::create(
152
                'ReservationFile',
153
                _t('Attendee.Reservation', 'Reservation'),
154
                "<a class='readonly' href='{$this->TicketFile()->Link()}' target='_blank'>Download reservation PDF</a>"
155
            ));
156
            $reservationFileField->dontEscape = true;
157
        }
158
159
        $this->extend('updateCMSFields', $fields);
160
        return $fields;
161
    }
162
163
    /**
164
     * Utility method for fetching the default field, FirstName, value
165
     *
166
     * @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...
167
     */
168
    public function getFirstName()
169
    {
170
        if ($firstName = $this->Fields()->find('Name', 'FirstName')) {
171
            return (string)$firstName->getField('Value');
172
        }
173
174
        return null;
175
    }
176
177
    /**
178
     * Utility method for fetching the default field, Surname, value
179
     *
180
     * @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...
181
     */
182
    public function getSurname()
183
    {
184
        if ($surname = $this->Fields()->find('Name', 'Surname')) {
185
            return (string)$surname->getField('Value');
186
        }
187
188
        return null;
189
    }
190
191
    /**
192
     * Get the combined first and last nave for display on the ticket and attendee list
193
     *
194
     * @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...
195
     */
196
    public function getName()
197
    {
198
        $mainContact = $this->Reservation()->MainContact();
199
        if (!empty($this->getSurname())) {
200
            return trim("{$this->getFirstName()} {$this->getSurname()}");
201
        } elseif ($mainContact->exists() && !empty($mainContact->getSurname())) {
202
            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...
203
        } else {
204
            return null;
205
        }
206
    }
207
208
    /**
209
     * Utility method for fetching the default field, Email, value
210
     *
211
     * @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...
212
     */
213
    public function getEmail()
214
    {
215
        if ($email = $this->Fields()->find('Name', 'Email')) {
216
            return (string)$email->getField('Value');
217
        }
218
219
        return null;
220
    }
221
222
    /**
223
     * Set the title and ticket code before writing
224
     */
225
    public function onBeforeWrite()
226
    {
227
        // Set the title of the attendee
228
        $this->Title = $this->getName();
229
230
        // Generate the ticket code
231
        if ($this->exists() && empty($this->TicketCode)) {
232
            $this->TicketCode = $this->generateTicketCode();
233
        }
234
235
        parent::onBeforeWrite();
236
    }
237
238
    /**
239
     * Delete any stray files before deleting the object
240
     */
241
    public function onBeforeDelete()
242
    {
243
        // If an attendee is deleted from the guest list remove it's qr code
244
        // after deleting the code it's not validatable anymore, simply here for cleanup
245
        if ($this->TicketQRCode()->exists()) {
246
            $this->TicketQRCode()->delete();
247
        }
248
249
        // cleanup the ticket file
250
        if ($this->TicketFile()->exists()) {
251
            $this->TicketFile()->delete();
252
        }
253
254
        parent::onBeforeDelete();
255
    }
256
257
    /**
258
     * Get the table fields for this attendee
259
     *
260
     * @return ArrayList
261
     */
262
    public function getTableFields()
263
    {
264
        $fields = new ArrayList();
265
        foreach (self::config()->get('table_fields') as $field) {
266
            $data = new ViewableData();
267
            $data->Header = _t("Attendee.$field", $field);
268
            $data->Value = $this->$field;
269
            $fields->add($data);
270
        }
271
        return $fields;
272
    }
273
274
    /**
275
     * Get the unnamespaced singular name for display in the CMS
276
     *
277
     * @return string
278
     */
279
    public function singular_name()
280
    {
281
        $name = explode('\\', parent::singular_name());
282
        return trim(end($name));
283
    }
284
285
    /**
286
     * Generate a unique ticket id
287
     * Serves as the base for the QR code and ticket file
288
     *
289
     * @return string
290
     */
291
    public function generateTicketCode()
292
    {
293
        return uniqid($this->ID);
294
    }
295
296
    /**
297
     * Create a QRCode for the attendee based on the Ticket code
298
     *
299
     * @param Folder $folder
300
     *
301
     * @return Image
302
     */
303
    public function createQRCode(Folder $folder)
304
    {
305
        $relativeFilePath = "/{$folder->Filename}{$this->TicketCode}.png";
306
        $absoluteFilePath = Director::baseFolder() . $relativeFilePath;
307
308
        if (!$image = Image::get()->find('Filename', $relativeFilePath)) {
309
            // Generate the QR code
310
            $renderer = new BaconQrCode\Renderer\Image\Png();
311
            $renderer->setHeight(256);
312
            $renderer->setWidth(256);
313
            $writer = new BaconQrCode\Writer($renderer);
314
            if (self::config()->get('qr_as_link')) {
315
                $writer->writeFile($this->Event()->AbsoluteLink("checkin/$this->TicketCode"), $absoluteFilePath);
316
            } else {
317
                $writer->writeFile($this->TicketCode, $absoluteFilePath);
318
            }
319
320
            // Store the image in an image object
321
            $image = Image::create();
322
            $image->ParentID = $folder->ID;
323
            $image->OwnerID = (Member::currentUser()) ? Member::currentUser()->ID : 0;
324
            $image->Title = $this->TicketCode;
325
            $image->setFilename($relativeFilePath);
326
            $image->write();
327
328
            // Attach the QR code to the Attendee
329
            $this->TicketQRCodeID = $image->ID;
330
            $this->write();
331
        }
332
333
        return $image;
334
    }
335
336
    /**
337
     * Creates a printable ticket for the attendee
338
     *
339
     * @param Folder $folder
340
     *
341
     * @return File
342
     */
343
    public function createTicketFile(Folder $folder)
344
    {
345
        // Find or make a folder
346
        $relativeFilePath = "/{$folder->Filename}{$this->TicketCode}.pdf";
347
        $absoluteFilePath = Director::baseFolder() . $relativeFilePath;
348
349
        if (!$file = File::get()->find('Filename', $relativeFilePath)) {
350
            $file = File::create();
351
            $file->ParentID = $folder->ID;
352
            $file->OwnerID = (Member::currentUser()) ? Member::currentUser()->ID : 0;
353
            $file->Title = $this->TicketCode;
354
            $file->setFilename($relativeFilePath);
355
            $file->write();
356
        }
357
358
        // Set the template and parse the data
359
        $template = new SSViewer('PrintableTicket');
360
        $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...
361
362
        // Create a DomPDF instance
363
        $domPDF = new Dompdf();
364
        $domPDF->loadHtml($html);
365
        $domPDF->setPaper('A4');
366
        $domPDF->getOptions()->setDpi(150);
367
        $domPDF->render();
368
369
        // Save the pdf stream as a file
370
        file_put_contents($absoluteFilePath, $domPDF->output());
371
372
        // Attach the ticket file to the Attendee
373
        $this->TicketFileID = $file->ID;
374
        $this->write();
375
376
        return $file;
377
    }
378
379
    /**
380
     * Get the checkin link
381
     *
382
     * @return string
383
     */
384
    public function getCheckInLink()
385
    {
386
        return $this->Event()->Link("checkin/{$this->TicketCode}");
387
    }
388
    
389
    /**
390
     * Check the attendee out
391
     */
392
    public function checkIn() {
393
        $this->CheckedIn = true;
394
        $this->write();
395
    }
396
397
    /**
398
     * Check the attendee in
399
     */
400
    public function checkOut() {
401
        $this->CheckedIn = false;
402
        $this->write();
403
    }
404
405
    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...
406
    {
407
        return $this->Reservation()->canView($member);
408
    }
409
410
    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...
411
    {
412
        return $this->Reservation()->canEdit($member);
413
    }
414
415
    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...
416
    {
417
        return $this->Reservation()->canDelete($member);
418
    }
419
420
    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...
421
    {
422
        return $this->Reservation()->canCreate($member);
423
    }
424
}
425