Reservation   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 498
Duplicated Lines 2.41 %

Coupling/Cohesion

Components 2
Dependencies 19

Importance

Changes 0
Metric Value
wmc 57
lcom 2
cbo 19
dl 12
loc 498
rs 5.04
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A getCMSFields() 0 21 1
A getBetterButtonsActions() 0 8 1
A onBeforeWrite() 0 12 3
A onBeforeDelete() 0 17 6
A singular_name() 0 5 1
A getGatewayNice() 0 4 1
A isDiscarded() 0 5 2
A getName() 0 9 4
A getState() 0 4 1
A getStates() 0 6 1
A calculateTotal() 0 16 3
A changeState() 0 11 2
A complete() 0 7 1
A setMainContact() 0 5 1
A createReservationCode() 0 4 1
A createFiles() 0 8 2
A sendReservation() 3 28 4
B sendTickets() 3 39 7
B sendNotification() 6 28 6
A send() 0 8 1
A getDownloadLink() 0 13 4
A canView() 0 4 1
A canEdit() 0 4 1
A canDelete() 0 4 1
A canCreate() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Reservation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Reservation, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Reservation.php
4
 *
5
 * @author Bram de Leeuw
6
 * Date: 09/03/17
7
 */
8
9
namespace Broarm\EventTickets;
10
11
use BetterButtonCustomAction;
12
use CalendarEvent;
13
use CheckboxField;
14
use Config;
15
use DataObject;
16
use DropdownField;
17
use Email;
18
use FieldList;
19
use Folder;
20
use GridField;
21
use GridFieldConfig_RecordViewer;
22
use HasManyList;
23
use ManyManyList;
24
use ReadonlyField;
25
use SilverStripe\Omnipay\GatewayInfo;
26
use SiteConfig;
27
use Tab;
28
use TabSet;
29
30
/**
31
 * Class Reservation
32
 *
33
 * @package Broarm\EventTickets
34
 *
35
 * @property string Status
36
 * @property string Title
37
 * @property float  Subtotal
38
 * @property float  Total
39
 * @property string Comments
40
 * @property string ReservationCode
41
 * @property string Gateway
42
 * @property boolean SentTickets
43
 * @property boolean SentReservation
44
 * @property boolean SentNotification
45
 *
46
 * @property int    EventID
47
 * @property int    MainContactID
48
 *
49
 * @method CalendarEvent|TicketExtension Event()
50
 * @method Attendee MainContact()
51
 * @method HasManyList Payments()
52
 * @method HasManyList Attendees()
53
 * @method ManyManyList PriceModifiers()
54
 */
55
class Reservation extends DataObject
56
{
57
    const STATUS_CART = 'CART';
58
    const STATUS_PENDING = 'PENDING';
59
    const STATUS_PAID = 'PAID';
60
    const STATUS_CANCELED = 'CANCELED';
61
62
    /**
63
     * Time to wait before deleting the discarded cart
64
     * Give a string that is parsable by strtotime
65
     *
66
     * @var string
67
     */
68
    private static $delete_after = '+1 hour';
69
70
    /**
71
     * The address to whom the ticket notifications are sent
72
     * By default the admin email is used
73
     *
74
     * @config
75
     * @var string
76
     */
77
    private static $mail_sender;
78
79
    /**
80
     * The address from where the ticket mails are sent
81
     * By default the admin email is used
82
     *
83
     * @config
84
     * @var string
85
     */
86
    private static $mail_receiver;
87
88
    /**
89
     * Send the receipt mail
90
     * For organisations that only do free events you can configure
91
     * this to hold back the receipt and only send the tickets
92
     *
93
     * @config
94
     * @var bool
95
     */
96
    private static $send_receipt_mail = true;
97
98
    /**
99
     * Send the admin notification
100
     *
101
     * @config
102
     * @var bool
103
     */
104
    private static $send_admin_notification = true;
105
106
    private static $db = array(
107
        'Status' => 'Enum("CART,PENDING,PAID,CANCELED","CART")',
108
        'Title' => 'Varchar(255)',
109
        'Subtotal' => 'Currency',
110
        'Total' => 'Currency',
111
        'Gateway' => 'Varchar(255)',
112
        'Comments' => 'Text',
113
        'AgreeToTermsAndConditions' => 'Boolean',
114
        'ReservationCode' => 'Varchar(255)',
115
        'SentTickets' => 'Boolean',
116
        'SentReservation' => 'Boolean',
117
        'SentNotification' => 'Boolean',
118
    );
119
120
    private static $default_sort = 'Created DESC';
121
122
    private static $has_one = array(
123
        'Event' => 'CalendarEvent',
124
        'MainContact' => 'Broarm\EventTickets\Attendee'
125
    );
126
127
    private static $has_many = array(
128
        'Payments' => 'Payment',
129
        'Attendees' => 'Broarm\EventTickets\Attendee.Reservation'
130
    );
131
132
    private static $belongs_many_many = array(
133
        'PriceModifiers' => 'Broarm\EventTickets\PriceModifier'
134
    );
135
136
    private static $indexes = array(
137
        'ReservationCode' => 'unique("ReservationCode")'
138
    );
139
140
    private static $summary_fields = 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...
141
        'ReservationCode' => 'Reservation',
142
        'Title' => 'Customer',
143
        'Total.Nice' => 'Total',
144
        'State' => 'Status',
145
        'GatewayNice' => 'Payment method',
146
        'Created.Nice' => 'Date'
147
    );
148
149
    /**
150
     * Actions usable on the cms detail view
151
     *
152
     * @var array
153
     */
154
    private static $better_buttons_actions = array(
155
        'send'
156
    );
157
158
    public function getCMSFields()
159
    {
160
        $fields = new FieldList(new TabSet('Root', $mainTab = new Tab('Main')));
161
        $gridFieldConfig = GridFieldConfig_RecordViewer::create();
162
        $fields->addFieldsToTab('Root.Main', array(
163
            ReadonlyField::create('ReservationCode', _t('Reservation.Code', 'Code')),
164
            ReadonlyField::create('Created', _t('Reservation.Created', 'Date')),
165
            DropdownField::create('Status', _t('Reservation.Status', 'Status'), $this->getStates()),
166
            ReadonlyField::create('Title', _t('Reservation.MainContact', 'Main contact')),
167
            ReadonlyField::create('GateWayNice', _t('Reservation.Gateway', 'Gateway')),
168
            ReadonlyField::create('Total', _t('Reservation.Total', 'Total')),
169
            ReadonlyField::create('Comments', _t('Reservation.Comments', 'Comments')),
170
            CheckboxField::create('AgreeToTermsAndConditions', _t('Reservation.AgreeToTermsAndConditions', 'Agreed to terms and conditions'))->performReadonlyTransformation(),
171
            GridField::create('Attendees', 'Attendees', $this->Attendees(), $gridFieldConfig),
172
            GridField::create('Payments', 'Payments', $this->Payments(), $gridFieldConfig),
173
            GridField::create('PriceModifiers', 'PriceModifiers', $this->PriceModifiers(), $gridFieldConfig)
174
        ));
175
        $fields->addFieldsToTab('Root.Main', array());
176
        $this->extend('updateCMSFields', $fields);
177
        return $fields;
178
    }
179
180
    /**
181
     * Add utility actions to the reservation details view
182
     *
183
     * @return FieldList
184
     */
185
    public function getBetterButtonsActions()
186
    {
187
        /** @var FieldList $fields */
188
        $fields = parent::getBetterButtonsActions();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class DataObject as the method getBetterButtonsActions() does only exist in the following sub-classes of DataObject: Broarm\EventTickets\Attendee, Broarm\EventTickets\Reservation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
189
        $fields->push(BetterButtonCustomAction::create('send', _t('Reservation.RESEND', 'Resend the reservation')));
190
191
        return $fields;
192
    }
193
194
    /**
195
     * Generate a reservation code if it does not yet exists
196
     */
197
    public function onBeforeWrite()
198
    {
199
        // Set the title to the name of the reservation holder
200
        $this->Title = $this->getName();
201
202
        // Create a validation code to be used for confirmation and in the barcode
203
        if ($this->exists() && empty($this->ReservationCode)) {
204
            $this->ReservationCode = $this->createReservationCode();
205
        }
206
207
        parent::onBeforeWrite();
208
    }
209
210
    /**
211
     * After deleting a reservation, delete the attendees and files
212
     */
213
    public function onBeforeDelete()
214
    {
215
        // If a reservation is deleted remove the names from the guest list
216
        foreach ($this->Attendees() as $attendee) {
217
            /** @var Attendee $attendee */
218
            if ($attendee->exists()) {
219
                $attendee->delete();
220
            }
221
        }
222
223
        // Remove the folder
224
        if (($folder = Folder::get()->find('Name', $this->ReservationCode)) && $folder->exists() && $folder->isEmpty()) {
225
            $folder->delete();
226
        }
227
228
        parent::onBeforeDelete();
229
    }
230
231
    /**
232
     * Gets a nice unnamespaced name
233
     *
234
     * @return string
235
     */
236
    public function singular_name()
237
    {
238
        $name = explode('\\', parent::singular_name());
239
        return trim(end($name));
240
    }
241
242
    /**
243
     * Returns the nice gateway title
244
     *
245
     * @return string
246
     */
247
    public function getGatewayNice()
248
    {
249
        return GatewayInfo::niceTitle($this->Gateway);
250
    }
251
252
    /**
253
     * Check if the cart is still in cart state and the delete_after time period has been exceeded
254
     *
255
     * @return bool
256
     */
257
    public function isDiscarded()
258
    {
259
        $deleteAfter = strtotime(self::config()->get('delete_after'), strtotime($this->Created));
260
        return ($this->Status === 'CART') && (time() > $deleteAfter);
261
    }
262
263
    /**
264
     * Get the full name
265
     *
266
     * @return string
267
     */
268
    public function getName()
269
    {
270
        /** @var Attendee $attendee */
271
        if (($mainContact = $this->MainContact()) && $mainContact->exists() && $name = $mainContact->getName()) {
272
            return $name;
273
        } else {
274
            return 'new reservation';
275
        }
276
    }
277
278
    /**
279
     * Return the translated state
280
     *
281
     * @return string
282
     */
283
    public function getState()
284
    {
285
        return _t("Reservation.{$this->Status}", $this->Status);
286
    }
287
288
    /**
289
     * Get a the translated map of available states
290
     *
291
     * @return array
292
     */
293
    private function getStates()
294
    {
295
        return array_map(function ($state) {
296
            return _t("Reservation.$state", $state);
297
        }, $this->dbObject('Status')->enumValues());
298
    }
299
300
    /**
301
     * Get the total by querying the sum of attendee ticket prices
302
     *
303
     * @return float
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

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...
304
     */
305
    public function calculateTotal()
306
    {
307
        $total = $this->Subtotal = $this->Attendees()->leftJoin(
308
            'Broarm\EventTickets\Ticket',
309
            '`Broarm\EventTickets\Attendee`.`TicketID` = `Broarm\EventTickets\Ticket`.`ID`'
310
        )->sum('Price');
311
312
        // Calculate any price modifications if added
313
        if ($this->PriceModifiers()->exists()) {
314
            foreach ($this->PriceModifiers() as $priceModifier) {
315
                $priceModifier->updateTotal($total, $this);
316
            }
317
        }
318
319
        return $this->Total = $total;
320
    }
321
322
    /**
323
     * Safely change to a state
324
     * todo check if state direction matches
325
     *
326
     * @param $state
327
     *
328
     * @return boolean
329
     */
330
    public function changeState($state)
331
    {
332
        $availableStates = $this->dbObject('Status')->enumValues();
333
        if (in_array($state, $availableStates)) {
334
            $this->Status = $state;
335
            return true;
336
        } else {
337
            user_error(_t('Reservation.STATE_CHANGE_ERROR', 'Selected state is not available'));
338
            return false;
339
        }
340
    }
341
342
    /**
343
     * Complete the reservation
344
     *
345
     * @throws \ValidationException
346
     */
347
    public function complete()
348
    {
349
        $this->changeState('PAID');
350
        $this->send();
351
        $this->write();
352
        $this->extend('onAfterComplete');
353
    }
354
355
    /**
356
     * Set the main contact id
357
     * @param $id
358
     *
359
     * @throws \ValidationException
360
     */
361
    public function setMainContact($id)
362
    {
363
        $this->MainContactID = $id;
364
        $this->write();
365
    }
366
367
    /**
368
     * Create a reservation code
369
     *
370
     * @return string
371
     */
372
    public function createReservationCode()
373
    {
374
        return uniqid($this->ID);
375
    }
376
377
    /**
378
     * Generate the qr codes and downloadable pdf
379
     */
380
    public function createFiles()
381
    {
382
        /** @var Attendee $attendee */
383
        foreach ($this->Attendees() as $attendee) {
384
            $attendee->createQRCode();
385
            $attendee->createTicketFile();
386
        }
387
    }
388
389
    /**
390
     * Send the reservation mail
391
     *
392
     * @return mixed
393
     */
394
    public function sendReservation()
395
    {
396
        if (!self::config()->get('send_receipt_mail')) {
397
            return true;
398
        }
399
400
        // Get the mail sender or fallback to the admin email
401 View Code Duplication
        if (($from = self::config()->get('mail_sender')) && empty($from)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
402
            $from = Config::inst()->get('Email', 'admin_email');
403
        }
404
405
        // Create the email with given template and reservation data
406
        $email = new Email();
407
        $email->setSubject(_t(
408
            'ReservationMail.TITLE',
409
            'Your order at {sitename}',
410
            null,
411
            array(
0 ignored issues
show
Documentation introduced by
array('sitename' => \Sit...t_site_config()->Title) is of type array<string,string,{"sitename":"string"}>, 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...
412
                'sitename' => SiteConfig::current_site_config()->Title
413
            )
414
        ));
415
        $email->setFrom($from);
416
        $email->setTo($this->MainContact()->Email);
0 ignored issues
show
Documentation introduced by
The property Email does not exist on object<Broarm\EventTickets\Attendee>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
417
        $email->setTemplate('ReservationMail');
418
        $email->populateTemplate($this);
419
        $this->extend('updateReservationMail', $email);
420
        return $email->send();
421
    }
422
423
    /**
424
     * Send the reserved tickets
425
     *
426
     * @return mixed
427
     */
428
    public function sendTickets()
429
    {
430
        // Get the mail sender or fallback to the admin email
431 View Code Duplication
        if (($from = self::config()->get('mail_sender')) && empty($from)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
432
            $from = Config::inst()->get('Email', 'admin_email');
433
        }
434
435
        // Send the tickets to the main contact
436
        $email = new Email();
437
        $email->setSubject(_t(
438
            'MainContactMail.TITLE',
439
            'Uw tickets voor {event}',
440
            null,
441
            array(
0 ignored issues
show
Documentation introduced by
array('event' => $this->Event()->Title) is of type array<string,?,{"event":"?"}>, 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...
442
                'event' => $this->Event()->Title
443
            )
444
        ));
445
        $email->setFrom($from);
446
        $email->setTo($this->MainContact()->Email);
0 ignored issues
show
Documentation introduced by
The property Email does not exist on object<Broarm\EventTickets\Attendee>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
447
        $email->setTemplate('MainContactMail');
448
        $email->populateTemplate($this);
449
        $this->extend('updateMainContactMail', $email);
450
        $sent = $email->send();
451
452
453
        // Get the attendees for this event that are checked as receiver
454
        $ticketReceivers = $this->Attendees()->filter('TicketReceiver', 1)->exclude('ID', $this->MainContactID);
455
        if ($ticketReceivers->exists()) {
456
            /** @var Attendee $ticketReceiver */
457
            foreach ($ticketReceivers as $ticketReceiver) {
458
                $sentAttendee = $ticketReceiver->sendTicket();
459
                if ($sent && !$sentAttendee) {
460
                    $sent = $sentAttendee;
461
                }
462
            }
463
        }
464
465
        return $sent;
466
    }
467
468
469
    /**
470
     * Send a booking notification to the ticket mail sender or the site admin
471
     * @return mixed
472
     */
473
    public function sendNotification()
474
    {
475
        if (!self::config()->get('send_admin_notification')) {
476
            return true;
477
        }
478
479 View Code Duplication
        if (($from = self::config()->get('mail_sender')) && empty($from)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
480
            $from = Config::inst()->get('Email', 'admin_email');
481
        }
482
483 View Code Duplication
        if (($to = self::config()->get('mail_receiver')) && empty($to)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
484
            $to = Config::inst()->get('Email', 'admin_email');
485
        }
486
487
        $email = new Email();
488
        $email->setSubject(_t(
489
            'NotificationMail.TITLE',
490
            'Nieuwe reservering voor {event}',
491
            null, array('event' => $this->Event()->Title)
0 ignored issues
show
Documentation introduced by
array('event' => $this->Event()->Title) is of type array<string,?,{"event":"?"}>, 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...
492
        ));
493
494
        $email->setFrom($from);
495
        $email->setTo($to);
496
        $email->setTemplate('NotificationMail');
497
        $email->populateTemplate($this);
498
        $this->extend('updateNotificationMail', $email);
499
        return $email->send();
500
    }
501
502
    /**
503
     * Create the files and send the reservation, notification and tickets
504
     */
505
    public function send()
506
    {
507
        $this->extend('onBeforeSend');
508
        $this->createFiles();
509
        $this->SentReservation = (boolean)$this->sendReservation();
510
        $this->SentNotification = (boolean)$this->sendNotification();
511
        $this->SentTickets = (boolean)$this->sendTickets();
512
    }
513
514
    /**
515
     * Get the download link
516
     *
517
     * @return string|null
518
     */
519
    public function getDownloadLink()
520
    {
521
        /** @var Attendee $attendee */
522
        if (
523
            ($attendee = $this->Attendees()->first())
524
            && ($file = $attendee->TicketFile())
525
            && $file->exists()
526
        ) {
527
            return $file->Link();
528
        }
529
530
        return null;
531
    }
532
533
    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...
534
    {
535
        return $this->Event()->canView($member);
536
    }
537
538
    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...
539
    {
540
        return $this->Event()->canEdit($member);
541
    }
542
543
    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...
544
    {
545
        return $this->Event()->canDelete($member);
546
    }
547
548
    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...
549
    {
550
        return $this->Event()->canCreate($member);
551
    }
552
}
553