Completed
Push — master ( b27c78...58969d )
by Bram
01:55
created

Reservation::fileFolder()   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
 * 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
 *
43
 * @property int    EventID
44
 * @property int    MainContactID
45
 *
46
 * @method CalendarEvent|TicketExtension Event()
47
 * @method Attendee MainContact()
48
 * @method HasManyList Payments()
49
 * @method HasManyList Attendees()
50
 * @method ManyManyList PriceModifiers()
51
 */
52
class Reservation extends DataObject
53
{
54
    /**
55
     * Time to wait before deleting the discarded cart
56
     * Give a string that is parsable by strtotime
57
     *
58
     * @var string
59
     */
60
    private static $delete_after = '+1 hour';
61
62
    /**
63
     * The address to whom the ticket notifications are sent
64
     * By default the admin email is used
65
     *
66
     * @config
67
     * @var string
68
     */
69
    private static $mail_sender;
70
71
    /**
72
     * The address from where the ticket mails are sent
73
     * By default the admin email is used
74
     *
75
     * @config
76
     * @var string
77
     */
78
    private static $mail_receiver;
79
80
    private static $db = array(
81
        'Status' => 'Enum("CART,PENDING,PAID,CANCELED","CART")',
82
        'Title' => 'Varchar(255)',
83
        'Subtotal' => 'Currency',
84
        'Total' => 'Currency',
85
        'Gateway' => 'Varchar(255)',
86
        'Comments' => 'Text',
87
        'AgreeToTermsAndConditions' => 'Boolean',
88
        'ReservationCode' => 'Varchar(255)'
89
    );
90
91
    private static $default_sort = 'Created DESC';
92
93
    private static $has_one = array(
94
        'Event' => 'CalendarEvent',
95
        'MainContact' => 'Broarm\EventTickets\Attendee'
96
    );
97
98
    private static $has_many = array(
99
        'Payments' => 'Payment',
100
        'Attendees' => 'Broarm\EventTickets\Attendee.Reservation'
101
    );
102
103
    private static $belongs_many_many = array(
104
        'PriceModifiers' => 'Broarm\EventTickets\PriceModifier'
105
    );
106
107
    private static $indexes = array(
108
        'ReservationCode' => 'unique("ReservationCode")'
109
    );
110
111
    private static $summary_fields = array(
112
        'ReservationCode' => 'Reservation',
113
        'Title' => 'Customer',
114
        'Total.Nice' => 'Total',
115
        'State' => 'Status',
116
        'GatewayNice' => 'Payment method',
117
        'Created.Nice' => 'Date'
118
    );
119
120
    /**
121
     * Actions usable on the cms detail view
122
     *
123
     * @var array
124
     */
125
    private static $better_buttons_actions = array(
126
        'send'
127
    );
128
129
    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...
130
    {
131
        $fields = new FieldList(new TabSet('Root', $mainTab = new Tab('Main')));
132
        $gridFieldConfig = GridFieldConfig_RecordViewer::create();
133
        $fields->addFieldsToTab('Root.Main', array(
134
            ReadonlyField::create('ReservationCode', _t('Reservation.Code', 'Code')),
135
            ReadonlyField::create('Created', _t('Reservation.Created', 'Date')),
136
            DropdownField::create('Status', _t('Reservation.Status', 'Status'), $this->getStates()),
137
            ReadonlyField::create('Title', _t('Reservation.MainContact', 'Main contact')),
138
            ReadonlyField::create('GateWayNice', _t('Reservation.Gateway', 'Gateway')),
139
            ReadonlyField::create('Total', _t('Reservation.Total', 'Total')),
140
            ReadonlyField::create('Comments', _t('Reservation.Comments', 'Comments')),
141
            CheckboxField::create('AgreeToTermsAndConditions', _t('Reservation.AgreeToTermsAndConditions', 'Agreed to terms and conditions'))->performReadonlyTransformation(),
142
            GridField::create('Attendees', 'Attendees', $this->Attendees(), $gridFieldConfig),
143
            GridField::create('Payments', 'Payments', $this->Payments(), $gridFieldConfig)
144
        ));
145
        $fields->addFieldsToTab('Root.Main', array());
146
        $this->extend('updateCMSFields', $fields);
147
        return $fields;
148
    }
149
150
    /**
151
     * Add utility actions to the reservation details view
152
     *
153
     * @return FieldList
154
     */
155
    public function getBetterButtonsActions()
156
    {
157
        /** @var FieldList $fields */
158
        $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...
159
        $fields->push(BetterButtonCustomAction::create('send', _t('Reservation.RESEND', 'Resend the reservation')));
160
161
        return $fields;
162
    }
163
164
    /**
165
     * Generate a reservation code if it does not yet exists
166
     */
167
    public function onBeforeWrite()
168
    {
169
        // Set the title to the name of the reservation holder
170
        $this->Title = $this->getName();
171
172
        // Create a validation code to be used for confirmation and in the barcode
173
        if ($this->exists() && empty($this->ReservationCode)) {
174
            $this->ReservationCode = $this->createReservationCode();
175
        }
176
177
        parent::onBeforeWrite();
178
    }
179
180
    /**
181
     * After deleting a reservation, delete the attendees and files
182
     */
183
    public function onBeforeDelete()
184
    {
185
        // If a reservation is deleted remove the names from the guest list
186
        foreach ($this->Attendees() as $attendee) {
187
            /** @var Attendee $attendee */
188
            if ($attendee->exists()) {
189
                $attendee->delete();
190
            }
191
        }
192
193
        // Remove the folder
194
        if (($folder = Folder::get()->find('Name', $this->ReservationCode)) && $folder->exists() && $folder->isEmpty()) {
195
            $folder->delete();
196
        }
197
198
        parent::onBeforeDelete();
199
    }
200
201
    /**
202
     * Gets a nice unnamespaced name
203
     *
204
     * @return string
205
     */
206
    public function singular_name()
207
    {
208
        $name = explode('\\', parent::singular_name());
209
        return trim(end($name));
210
    }
211
212
    /**
213
     * Returns the nice gateway title
214
     *
215
     * @return string
216
     */
217
    public function getGatewayNice()
218
    {
219
        return GatewayInfo::niceTitle($this->Gateway);
220
    }
221
222
    /**
223
     * Check if the cart is still in cart state and the delete_after time period has been exceeded
224
     *
225
     * @return bool
226
     */
227
    public function isDiscarded()
228
    {
229
        $deleteAfter = strtotime(self::config()->get('delete_after'), strtotime($this->Created));
230
        return ($this->Status === 'CART') && (time() > $deleteAfter);
231
    }
232
233
    /**
234
     * Get the full name
235
     *
236
     * @return string
237
     */
238
    public function getName()
239
    {
240
        /** @var Attendee $attendee */
241
        if (($mainContact = $this->MainContact()) && $mainContact->exists() && $name = $mainContact->getName()) {
242
            return $name;
243
        } else {
244
            return 'new reservation';
245
        }
246
    }
247
248
    /**
249
     * Return the translated state
250
     *
251
     * @return string
252
     */
253
    public function getState()
254
    {
255
        return _t("Reservation.{$this->Status}", $this->Status);
256
    }
257
258
    /**
259
     * Get a the translated map of available states
260
     *
261
     * @return array
262
     */
263
    private function getStates()
264
    {
265
        return array_map(function ($state) {
266
            return _t("Reservation.$state", $state);
267
        }, $this->dbObject('Status')->enumValues());
268
    }
269
270
    /**
271
     * Get the total by querying the sum of attendee ticket prices
272
     *
273
     * @return float
274
     */
275
    public function calculateTotal()
276
    {
277
        $total = $this->Subtotal = $this->Attendees()->leftJoin(
278
            'Broarm\EventTickets\Ticket',
279
            '`Broarm\EventTickets\Attendee`.`TicketID` = `Broarm\EventTickets\Ticket`.`ID`'
280
        )->sum('Price');
281
282
        // Calculate any price modifications if added
283
        if ($this->PriceModifiers()->exists()) {
284
            foreach ($this->PriceModifiers() as $priceModifier) {
285
                $priceModifier->updateTotal($total);
286
            }
287
        }
288
289
        return $this->Total = $total;
290
    }
291
292
    /**
293
     * Safely change to a state
294
     * todo check if state direction matches
295
     *
296
     * @param $state
297
     *
298
     * @return boolean
299
     */
300
    public function changeState($state)
301
    {
302
        $availableStates = $this->dbObject('Status')->enumValues();
303
        if (in_array($state, $availableStates)) {
304
            $this->Status = $state;
305
            return true;
306
        } else {
307
            user_error(_t('Reservation.STATE_CHANGE_ERROR', 'Selected state is not available'));
308
            return false;
309
        }
310
    }
311
312
    /**
313
     * Set the main contact id
314
     *
315
     * @param $id
316
     */
317
    public function setMainContact($id)
318
    {
319
        $this->MainContactID = $id;
320
        $this->write();
321
    }
322
323
    /**
324
     * Create a reservation code
325
     *
326
     * @return string
327
     */
328
    public function createReservationCode()
329
    {
330
        return uniqid($this->ID);
331
    }
332
333
    /**
334
     * Generate the qr codes and downloadable pdf
335
     */
336
    public function createFiles()
337
    {
338
        /** @var Attendee $attendee */
339
        foreach ($this->Attendees() as $attendee) {
340
            $attendee->createQRCode();
341
            $attendee->createTicketFile();
342
        }
343
    }
344
345
    /**
346
     * Send the reservation mail
347
     */
348 View Code Duplication
    public function sendReservation()
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...
349
    {
350
        // Get the mail sender or fallback to the admin email
351
        if (empty($from = self::config()->get('mail_sender'))) {
352
            $from = Config::inst()->get('Email', 'admin_email');
353
        }
354
355
        // Create the email with given template and reservation data
356
        $email = new Email();
357
        $email->setSubject(_t(
358
            'ReservationMail.TITLE',
359
            'Your order at {sitename}',
360
            null,
361
            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...
362
                'sitename' => SiteConfig::current_site_config()->Title
363
            )
364
        ));
365
        $email->setFrom($from);
366
        $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...
367
        $email->setTemplate('ReservationMail');
368
        $email->populateTemplate($this);
369
        $this->extend('updateReservationMail', $email);
370
        $email->send();
371
    }
372
373
    /**
374
     * Send the reserved tickets
375
     */
376
    public function sendTickets()
377
    {
378
        // Get the mail sender or fallback to the admin email
379
        if (empty($from = self::config()->get('mail_sender'))) {
380
            $from = Config::inst()->get('Email', 'admin_email');
381
        }
382
383
        // Send the tickets to the main contact
384
        $email = new Email();
385
        $email->setSubject(_t(
386
            'MainContactMail.TITLE',
387
            'Uw tickets voor {event}',
388
            null,
389
            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...
390
                'event' => $this->Event()->Title
391
            )
392
        ));
393
        $email->setFrom($from);
394
        $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...
395
        $email->setTemplate('MainContactMail');
396
        $email->populateTemplate($this);
397
        $this->extend('updateMainContactMail', $email);
398
        $email->send();
399
400
401
        // Get the attendees for this event that are checked as receiver
402
        $ticketReceivers = $this->Attendees()->filter('TicketReceiver', 1)->exclude('ID', $this->MainContactID);
403
        if ($ticketReceivers->exists()) {
404
            /** @var Attendee $ticketReceiver */
405
            foreach ($ticketReceivers as $ticketReceiver) {
406
                $ticketReceiver->sendTicket();
407
            }
408
        }
409
    }
410
411
412
    /**
413
     * Send a booking notification to the ticket mail sender or the site admin
414
     */
415
    public function sendNotification()
416
    {
417
        if (empty($from = self::config()->get('mail_sender'))) {
418
            $from = Config::inst()->get('Email', 'admin_email');
419
        }
420
421
        if (empty($to = self::config()->get('mail_receiver'))) {
422
            $to = Config::inst()->get('Email', 'admin_email');
423
        }
424
425
        $email = new Email();
426
        $email->setSubject(_t(
427
            'NotificationMail.TITLE',
428
            'Nieuwe reservering voor {event}',
429
            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...
430
        ));
431
432
        $email->setFrom($from);
433
        $email->setTo($to);
434
        $email->setTemplate('NotificationMail');
435
        $email->populateTemplate($this);
436
        $this->extend('updateNotificationMail', $email);
437
        $email->send();
438
    }
439
440
    /**
441
     * Create the files and send the reservation, notification and tickets
442
     */
443
    public function send()
444
    {
445
        $this->createFiles();
446
        $this->sendReservation();
447
        $this->sendNotification();
448
        $this->sendTickets();
449
    }
450
451
    /**
452
     * Get the download link
453
     *
454
     * @return string|null
455
     */
456
    public function getDownloadLink()
457
    {
458
        /** @var Attendee $attendee */
459
        if (
460
            ($attendee = $this->Attendees()->first())
461
            && ($file = $attendee->TicketFile())
462
            && $file->exists()
463
        ) {
464
            return $file->Link();
465
        }
466
467
        return null;
468
    }
469
470
    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...
471
    {
472
        return $this->Event()->canView($member);
473
    }
474
475
    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...
476
    {
477
        return $this->Event()->canEdit($member);
478
    }
479
480
    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...
481
    {
482
        return $this->Event()->canDelete($member);
483
    }
484
485
    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...
486
    {
487
        return $this->Event()->canCreate($member);
488
    }
489
}
490