Completed
Push — master ( 22f31a...c89319 )
by Bram
02:59
created

Attendee::canCreate()   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 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' => 'TextField',
73
        'Surname' => 'TextField',
74
        'Email' => 'EmailField',
75
    );
76
77
    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...
78
        'Title',
79
        'Email'
80
    );
81
82
    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...
83
        'Title' => 'Varchar(255)',
84
        'TicketReceiver' => 'Boolean',
85
        'TicketCode' => 'Varchar(255)',
86
        'CheckedIn' => 'Boolean'
87
    );
88
89
    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...
90
        'TicketCode' => 'unique("TicketCode")'
91
    );
92
93
    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...
94
        'Reservation' => 'Broarm\EventTickets\Reservation',
95
        'Ticket' => 'Broarm\EventTickets\Ticket',
96
        'Event' => 'CalendarEvent',
97
        'Member' => 'Member',
98
        'TicketQRCode' => 'Image',
99
        'TicketFile' => 'File'
100
    );
101
102
    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...
103
        'Fields' => 'Broarm\EventTickets\AttendeeExtraField'
104
    );
105
106
    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...
107
        'Fields' => array(
108
            'Value' => 'Varchar(255)'
109
        )
110
    );
111
112
    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...
113
        'Title' => 'Name',
114
        'Email' => 'Email',
115
        'TicketCode' => 'Ticket',
116
        'CheckedInSummary' => 'Checked in',
117
    );
118
119
    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...
120
    {
121
        $fields = new FieldList(new TabSet('Root', $mainTab = new Tab('Main')));
122
        $fields->addFieldsToTab('Root.Main', array(
123
            ReadonlyField::create('FirstName', _t('Attendee.FirstName', 'First name')),
124
            ReadonlyField::create('Surname', _t('Attendee.Surname', 'Surname')),
125
            ReadonlyField::create('Email', _t('Attendee.Email', 'Email')),
126
            ReadonlyField::create('TicketCode', _t('Attendee.Ticket', 'Ticket')),
127
            ReadonlyField::create('MyCheckedIn', _t('Attendee.CheckedIn', 'Checked in'), $this->dbObject('CheckedIn')->Nice())
128
        ));
129
130
        foreach ($this->Fields() as $field) {
131
            $fields->addFieldToTab(
132
                'Root.Main',
133
                ReadonlyField::create("{$field->FieldName}_Preview", $field->Title, $field->Value)
134
            );
135
        }
136
137
        if ($this->TicketFile()->exists()) {
138
            $fields->addFieldToTab('Root.Main', $reservationFileField = ReadonlyField::create(
139
                'ReservationFile',
140
                _t('Attendee.Reservation', 'Reservation'),
141
                "<a class='readonly' href='{$this->TicketFile()->Link()}' target='_blank'>Download reservation PDF</a>"
142
            ));
143
            $reservationFileField->dontEscape = true;
144
        }
145
146
        $this->extend('updateCMSFields', $fields);
147
        return $fields;
148
    }
149
150
    /**
151
     * Utility method for fetching the default field, FirstName, value
152
     *
153
     * @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...
154
     */
155 View Code Duplication
    public function getFirstName()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
156
    {
157
        if ($firstName = $this->Fields()->find('FieldName', 'FirstName')) {
158
            return (string)$firstName->getField('Value');
159
        }
160
161
        return null;
162
    }
163
164
    /**
165
     * Utility method for fetching the default field, Surname, value
166
     *
167
     * @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...
168
     */
169 View Code Duplication
    public function getSurname()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
170
    {
171
        if ($surname = $this->Fields()->find('FieldName', 'Surname')) {
172
            return (string)$surname->getField('Value');
173
        }
174
175
        return null;
176
    }
177
178
    /**
179
     * Get the combined first and last nave for dispay on the ticket and attendee list
180
     *
181
     * @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...
182
     */
183
    public function getName()
184
    {
185
        if (!empty($this->getFirstName())) {
186
            return trim("{$this->getFirstName()} {$this->getSurname()}");
187
        } elseif ($this->Reservation()->MainContact()->exists() && $mainContact = $this->Reservation()->MainContact()) {
188
            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...
189
        } else {
190
            return null;
191
        }
192
    }
193
194
    /**
195
     * Utility method for fetching the default field, Email, value
196
     *
197
     * @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...
198
     */
199 View Code Duplication
    public function getEmail()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
    {
201
        if ($email = $this->Fields()->find('FieldName', 'Email')) {
202
            return (string)$email->getField('Value');
203
        }
204
205
        return null;
206
    }
207
208
    /**
209
     * Set the title and ticket code before writing
210
     */
211
    public function onBeforeWrite()
212
    {
213
        // Set the title of the attendee
214
        $this->Title = $this->getName();
215
216
        // Generate the ticket code
217
        if ($this->exists() && empty($this->TicketCode)) {
218
            $this->TicketCode = $this->generateTicketCode();
219
        }
220
221
        parent::onBeforeWrite();
222
    }
223
224
    /**
225
     * Delete any stray files before deleting the object
226
     */
227
    public function onBeforeDelete()
228
    {
229
        // If an attendee is deleted from the guest list remove it's qr code
230
        // after deleting the code it's not validatable anymore, simply here for cleanup
231
        if ($this->TicketQRCode()->exists()) {
232
            $this->TicketQRCode()->delete();
233
        }
234
235
        // cleanup the ticket file
236
        if ($this->TicketFile()->exists()) {
237
            $this->TicketFile()->delete();
238
        }
239
240
        parent::onBeforeDelete();
241
    }
242
243
    /**
244
     * Get the table fields for this attendee
245
     *
246
     * @return ArrayList
247
     */
248
    public function getTableFields()
249
    {
250
        $fields = new ArrayList();
251
        foreach (self::config()->get('table_fields') as $field) {
252
            $data = new ViewableData();
253
            $data->Header = _t("Attendee.$field", $field);
254
            $data->Value = $this->$field;
255
            $fields->add($data);
256
        }
257
        return $fields;
258
    }
259
260
    /**
261
     * Get the unnamespaced singular name for display in the CMS
262
     *
263
     * @return string
264
     */
265
    public function singular_name()
266
    {
267
        $name = explode('\\', parent::singular_name());
268
        return trim(end($name));
269
    }
270
271
    /**
272
     * Return the checked in state for use in grid fields
273
     *
274
     * @return LiteralField
275
     */
276
    public function getCheckedInSummary()
277
    {
278
        $checkedInNice = $this->dbObject('CheckedIn')->Nice();
279
        $checkedIn = $this->CheckedIn
280
            ? "<span style='color: #3adb76;'>$checkedInNice</span>"
281
            : "<span style='color: #cc4b37;'>$checkedInNice</span>";
282
283
        return new LiteralField('CheckedIn', $checkedIn);
284
    }
285
286
    /**
287
     * Generate a unique ticket id
288
     * Serves as the base for the QR code and ticket file
289
     *
290
     * @return string
291
     */
292
    public function generateTicketCode()
293
    {
294
        return uniqid($this->ID);
295
    }
296
297
    /**
298
     * Create a QRCode for the attendee based on the Ticket code
299
     *
300
     * @param Folder $folder
301
     *
302
     * @return Image
303
     */
304
    public function createQRCode(Folder $folder)
305
    {
306
        $relativeFilePath = "/{$folder->Filename}{$this->TicketCode}.png";
307
        $absoluteFilePath = Director::baseFolder() . $relativeFilePath;
308
309
        if (!$image = Image::get()->find('Filename', $relativeFilePath)) {
310
            // Generate the QR code
311
            $renderer = new BaconQrCode\Renderer\Image\Png();
312
            $renderer->setHeight(256);
313
            $renderer->setWidth(256);
314
            $writer = new BaconQrCode\Writer($renderer);
315
            if (self::config()->get('qr_as_link')) {
316
                $writer->writeFile($this->Event()->AbsoluteLink("checkin/$this->TicketCode"), $absoluteFilePath);
317
            } else {
318
                $writer->writeFile($this->TicketCode, $absoluteFilePath);
319
            }
320
321
            // Store the image in an image object
322
            $image = Image::create();
323
            $image->ParentID = $folder->ID;
324
            $image->OwnerID = (Member::currentUser()) ? Member::currentUser()->ID : 0;
325
            $image->Title = $this->TicketCode;
326
            $image->setFilename($relativeFilePath);
327
            $image->write();
328
329
            // Attach the QR code to the Attendee
330
            $this->TicketQRCodeID = $image->ID;
331
            $this->write();
332
        }
333
334
        return $image;
335
    }
336
337
    /**
338
     * Creates a printable ticket for the attendee
339
     *
340
     * @param Folder $folder
341
     *
342
     * @return File
343
     */
344
    public function createTicketFile(Folder $folder)
345
    {
346
        // Find or make a folder
347
        $relativeFilePath = "/{$folder->Filename}{$this->TicketCode}.pdf";
348
        $absoluteFilePath = Director::baseFolder() . $relativeFilePath;
349
350
        if (!$file = File::get()->find('Filename', $relativeFilePath)) {
351
            $file = File::create();
352
            $file->ParentID = $folder->ID;
353
            $file->OwnerID = (Member::currentUser()) ? Member::currentUser()->ID : 0;
354
            $file->Title = $this->TicketCode;
355
            $file->setFilename($relativeFilePath);
356
            $file->write();
357
        }
358
359
        // Set the template and parse the data
360
        $template = new SSViewer('PrintableTicket');
361
        $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...
362
363
        // Create a DomPDF instance
364
        $domPDF = new Dompdf();
365
        $domPDF->loadHtml($html);
366
        $domPDF->setPaper('A4');
367
        $domPDF->getOptions()->setDpi(150);
368
        $domPDF->render();
369
370
        // Save the pdf stream as a file
371
        file_put_contents($absoluteFilePath, $domPDF->output());
372
373
        // Attach the ticket file to the Attendee
374
        $this->TicketFileID = $file->ID;
375
        $this->write();
376
377
        return $file;
378
    }
379
380
    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...
381
    {
382
        return $this->Reservation()->canView($member);
383
    }
384
385
    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...
386
    {
387
        return $this->Reservation()->canEdit($member);
388
    }
389
390
    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...
391
    {
392
        return $this->Reservation()->canDelete($member);
393
    }
394
395
    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...
396
    {
397
        return $this->Reservation()->canCreate($member);
398
    }
399
}
400