Source for file bookingentry.php
Documentation is available at bookingentry.php
* Booking entry object for creating/editing booking
* @author Stuart Prescott
* @copyright Copyright Stuart Prescott
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
/** Load ancillary functions */
require_once 'inc/typeinfo.php';
require_once 'inc/bb/configreader.php';
require_once 'inc/formslib/dbrow.php';
require_once 'inc/formslib/idfield.php';
require_once 'inc/formslib/textfield.php';
require_once 'inc/formslib/datetimefield.php';
require_once 'inc/formslib/timefield.php';
require_once 'inc/formslib/droplist.php';
require_once 'inc/formslib/referencefield.php';
require_once 'inc/formslib/dummyfield.php';
require_once 'inc/formslib/textfield.php';
/** uses time slot rules for management */
require_once 'inc/bookings/timeslotrule.php';
/** status codes for success/failure of database actions */
require_once 'inc/statuscodes.php';
* Booking entry object for creating/editing booking
/** @var TimeSlotRule rules for when the instrument can be booked */
/** @var integer EUID of booking user @see BumblebeeAuth */
/** @var integer UID of booking user @see BumblebeeAuth */
/** @var BumblebeeAuth auth object for checking user permissions */
/** @var integer minimum notice in hours to be given for unbooking an instrument */
/** @var boolean object not fully constructed (using short constructor for deleting booking only */
/** @var array list of instrument id numbers */
* Create a new BookingEntry object
* @param integer $id booking id number (existing number or -1 for new)
* @param BumblebeeAuth $auth authorisation object
* @param array $instrumentid list of instrument id of instruments to be booked
* @param integer $minunbook minimum notice to be given for unbooking (optional)
* @param string $ip IP address of person making booking (for recording) (optional)
* @param SimpleDate $start when the booking should start (optional)
* @param SimpleTime $duration length of the booking (optional)
* @param string $granlist timeslotrule picture (optional)
function BookingEntry($id, $auth, $instrumentid, $minunbook=
'', $ip=
'', $start=
'', $duration=
'', $granlist=
'') {
$this->DBRow('bookings', $id);
// check if lots of the input data is empty, then the constructor is only being used to delete the booking
if ($ip==
'' &&
$start==
'' &&
$duration==
'' &&
$granlist==
'') {
$f->duplicateName =
'bookid';
$f->extraInfo('instruments', 'id', 'name');
$f->duplicateName =
'instrid';
$f->defaultValue =
join(',', $instrumentid);
$f->value =
$startticks->ticks;
// $this->starttime = &$startf;
$startf->defaultValue =
$start;
$startf->isValidTest =
'is_valid_datetime';
$attrs =
array('size' =>
'24');
$startf->setAttr($attrs);
$startf->setManualRepresentation(TF_AUTO);
// echo $f->manualRepresentation .'-'.$f->time->manualRepresentation."\n";
$startf->setSlotStart($start);
$startf->setEditableOutput(false, true);
$durationf =
new TimeField('duration', T_('Duration'));
// $this->duration = &$durationf;
$durationf->required =
1;
$durationf->isValidTest =
'is_valid_nonzero_time';
$durationf->defaultValue =
$duration;
$durationf->setManualRepresentation(TF_AUTO);
// echo $f->manualRepresentation .'-'.$f->time->manualRepresentation."\n";
$durationf->setSlotStart($start);
$durationf->maxDateDropDown =
$nextBooking->booking;
// load in instrument settings for how the dropdowns should be configured
$durationf->extendDropDown =
issetSet($instrrow, 'bookacrossslots', true);
$durationf->maxSlotsDropDown =
issetSet($instrrow, 'maxslotsbook', 20);
$durationf->maxPeriodDropDown =
issetSet($instrrow, 'maxbooklength', 86400);
$f->connectDB('projects',
array('id', 'name', 'longname'),
array('userprojects'=>
'projectid=id'));
$f->setFormat('id', '%s', array('name'), ' (%35.35s)', array('longname'));
$f->isValidTest =
'is_valid_radiochoice';
$attrs =
array('size' =>
'48');
$f =
new TextField('comments', T_('Comment to show on calendar'));
$f->extraInfo('users', 'id', 'name');
$f->extraInfo('users', 'id', 'name');
$f->hidden =
! $f->editable;
$f->isValidTest =
'is_number';
$f->hidden =
! $f->editable;
* secondary constructor that we can use just for deleting
* @param integer $id booking id number (existing number or -1 for new)
* @param integer $instrumentid instrument id of instrument to be booked
$f =
new Field('instrument'); //not necessary, but for peace-of-mind.
$f->value =
$instrumentid;
$f =
new Field('bookwhen');
$f =
new Field('userid', T_('User'));
* @param BumblebeeAuth $auth authorisation object
* @param integer $instrumentid instrument id of instrument to be booked
$this->euid =
$row['userid'];
$this->euid =
$auth->getEUID();
* override the default update() method with a custom one that allows us to:
* - munge the start and finish times to fit in with the permitted granularity
$this->fields['bookwhen']->setSlotStart($this->fields['bookwhen']->getValue());
$this->fields['duration']->setSlotStart($this->fields['bookwhen']->getValue());
* override the default fill() method with a custom one that allows us to...
* - work out what the startticks parameter is for generating links to the current calendar
* - check permissions on whether we should be allowed to change the dates
if (isset
($this->fields['startticks']) &&
! $this->fields['startticks']->value) {
$this->fields['startticks']->value =
$this->fields['bookwhen']->getValue();
// check whether we are allowed to modify time fields: this picks up existing objects immediately
* override the default sync() method with a custom one that allows us to...
* - send a booking confirmation email to the instrument supervisors
* - update the representation of times
foreach ($this->children as $c) {
$status =
parent::sync();
* Work out what the default discount for this timeslot is from the timeslotrules
$this->fields['discount']->value =
(isset
($slot->discount) ?
$slot->discount :
0);
$this->log('BookingEntry::_setDefaultDiscount value '.
$starttime->dateTimeString().
' '.
$slot->discount.
'%');
if (! isset
($this->fields['discount']->value)) { // handle missing values in the submission
//preDump($this->slotrules); preDump($slot);
$this->fields['discount']->defaultValue =
(isset
($slot->discount) ?
$slot->discount :
0);
$this->log('BookingEntry::_setDefaultDiscount defaultValue '.
$starttime->dateTimeString().
' '.
$slot->discount.
'%');
* make sure that a non-admin user is not trying to unbook the instrument with less than the minimum notice
// get some cursory checks out of the way to save the expensive checks for later
//then we are unrestricted
$this->log('Booking changes not limited by time restrictions as we are admin or new booking.',9);
$booking->addTime(-
1*
$timeoffset);
$this->log('Booking times comparison: now='.
$now->dateTimeString()
.
', minunbook='.
$booking->dateTimeString());
if ($booking->ticks <
$now->ticks) {
// then we can't edit the date and time and we shouldn't delete the booking
$this->log('Within limitation period, preventing time changes and deletion',9);
$this->fields['bookwhen']->editable =
0;
$this->fields['duration']->editable =
0;
$this->log('Booking changes not limited by time restrictions.',9);
* if appropriate, send an email to the instrument supervisors to let them know that the
//preDump($this->fields['instrument']);
if (! $instrument['emailonbooking']) {
foreach(preg_split('/,\s*/', $instrument['supervisors']) as $username) {
$emails[] =
$user['email'];
$from =
$instrument['name'].
' '.
$conf->value('instruments', 'emailFromName')
.
' <'.
$conf->value('main', 'SystemEmail').
'>';
$replyto =
$bookinguser['name'].
' <'.
$bookinguser['email'].
'>';
$to =
join($emails, ',');
$id =
'<bumblebee-'.
time().
'-'.
rand().
'@'.
$_SERVER['SERVER_NAME'].
'>';
$headers =
'From: '.
$from .
$eol;
$headers .=
'Reply-To: '.
$replyto.
$eol;
$headers .=
'Message-id: ' .
$id .
$eol;
$subject =
$instrument['name'].
': '.
($conf->value('instruments', 'emailSubject')
?
$conf->value('instruments', 'emailSubject') :
'Instrument booking notification');
$ok =
@mail($to, $subject, $message, $headers);
* get the email text from the configured template with standard substitutions
* @param array $instrument instrument data (name => , longname => )
* @param array $user user data (name => , username => )
* @todo //TODO: graceful error handling for fopen, fread
$fh =
fopen($conf->value('instruments', 'emailTemplate'), 'r');
$txt =
fread($fh, filesize($conf->value('instruments', 'emailTemplate')));
'/__instrumentname__/' =>
$instrument['name'],
'/__instrumentlongname__/' =>
$instrument['longname'],
'/__start__/' =>
$start->dateTimeString(),
'/__duration__/' =>
$duration->timeString(),
'/__name__/' =>
$user['name'],
'/__username__/' =>
$user['username'],
* override the default checkValid() method with a custom one that also checks that the
* booking is permissible (i.e. the instrument is indeed free)
* A temp booking is made by _checkIsFree if all tests are OK. This temporary booking
* secures the slot (no race conditions) and is then updated by the sync() method.
$this->log('Fields are INVALID; bailing out');
$this->log('Individual fields are VALID');
$this->log('After checking for legality of timeslot: '.
($this->isValid ?
'VALID' :
'INVALID'));
$this->log('After checking for double bookings: '.
($this->isValid ?
'VALID' :
'INVALID'));
// check again whether we are allowed to modify time objects -- after sync() we might not
// be allowed to any more.
* check that the booking slot is indeed free before booking it
* Here, we make a temporary booking and make sure that it is unique for that timeslot
* This is to prevent a race condition for checking and then making the new booking.
* @global string prefix for table names
$this->children =
array();
$clone->instrumentid =
$instr;
$clone->fields['instrument']->value =
$instr;
$status =
$clone->_checkIsFree();
$this->children[] =
$clone;
$instrument =
$this->fields['instrument']->getValue();
$start =
$startdate->dateTimeString();
$stop =
$d->dateTimeString();
$tmpid =
$this->_makeTempBooking($instrument, $start, $duration->getHMSstring());
$this->log('Created temp row for locking, id='.
$tmpid.
'(origid='.
$this->id.
')');
$q =
'SELECT bookings.id AS bookid, bookwhen, duration, '
.
'DATE_ADD( bookwhen, INTERVAL duration HOUR_SECOND ) AS stoptime, '
.
'FROM '.
$TABLEPREFIX.
'bookings AS bookings '
.
'LEFT JOIN '.
$TABLEPREFIX.
'users AS users ON '
.
'bookings.userid = users.id '
.
'WHERE instrument='.
qw($instrument).
' '
.
'AND bookings.id<>'.
qw($this->id).
' '
.
'AND bookings.id<>'.
qw($tmpid).
' '
.
'AND bookings.deleted<>1 ' // old version of MySQL cannot handle true, use 1 instead
.
'HAVING (bookwhen <= '.
qw($start).
' AND stoptime > '.
qw($start).
') '
.
'OR (bookwhen < '.
qw($stop).
' AND stoptime >= '.
qw($stop).
') '
.
'OR (bookwhen >= '.
qw($start).
' AND stoptime <= '.
qw($stop).
')';
// then the booking actually overlaps another!
$this->log('Overlapping bookings, error');
$this->errorMessage .=
T_('Sorry, the instrument is not free at this time.').
'<br /><br />'
.
sprintf(T_('Instrument booked by %s (%s) from %s until %s.'),
array('instrid' =>
$instrument,
'bookid' =>
$row['bookid'],
'isodate' =>
$startdate->dateString())
.
T_('booking #').
$row['bookid'].
'</a>',
xssqw($row['stoptime']));
// then the new booking should take over this one, and we delete the old one.
$this->log('Booking slot OK, taking over tmp slot');
* Ensure that the entered data fits the granularity criteria specified for this instrument
$this->log('BookingEntry::_legalSlot '.
$starttime->dateTimeString()
.
' '.
$stoptime->dateTimeString());
$this->log('This slot isn\'t legal so far... perhaps it is FreeForm?');
#echo $startslot->start->dump();
#echo $starttime->dump();
#echo $stopslot->stop->dump();
$validslot =
$startslot->isFreeForm &&
$stopslot->isFreeForm;
$this->log('It '.
($validslot ?
'is' :
'is not').
'!');
$this->log('Perhaps it is adjoining another booking with funny times?');
$startok =
($startslot->start->ticks ==
$starttime->ticks);
$this->log('Checking start time for adjoining stop');
$stopok =
($stopslot->stop->ticks ==
$stoptime->ticks);
$this->log('Checking stop time for adjoining start');
$validslot =
($startok ||
$startvalid) &&
($stopok ||
$stopvalid);
$this->log('It '.
($validslot ?
'is' :
'is not').
'!');
$this->errorMessage .=
T_('Sorry, the timeslot you have selected is not valid, due to restrictions imposed by the instrument administrator.');
* Check that the booking is not too far into the future
* @returns boolean the booking is permitted
// permit bookings today or in the past
if ($now->ticks >
$starttime->ticks) return true;
$now->addDays($row['calfuture'] +
7 +
1);
if ($now->ticks <
$starttime->ticks) {
$this->errorMessage .=
T_('Sorry, you cannot book that far into the future, due to restrictions imposed by the instrument administrator.');
* check if this booking is adjoining existing bookings -- it can explain why the booking
* @param string $field SQL name of the field to be checked (stoptime, bookwhen)
* @param SimpleDate $checktime time to check to see if it is adjoining the new booking
* @return boolean there is a booking adjoining this time
* @global string prefix prepended to all table names in the db
$instrument =
$this->fields['instrument']->getValue();
$time =
$checktime->dateTimeString();
$q =
'SELECT bookings.id AS bookid, bookwhen, duration, '
.
'DATE_ADD( bookwhen, INTERVAL duration HOUR_SECOND ) AS stoptime '
.
'FROM '.
$TABLEPREFIX.
'bookings AS bookings '
.
'WHERE instrument='.
qw($instrument).
' '
.
'AND bookings.deleted<>1 ' // old version of MySQL cannot handle true, use 1 instead
.
'HAVING '.
$field.
' = '.
qw($time);
$this->log(is_array($row) ?
'Found a matching booking' :
'No matching booking');
* make a temporary booking for this slot to eliminate race conditions for this booking
* @param integer $instrument instrument id
* @param string $start date time string for the start of the booking
* @param string $duration time string for the duration of the booking
* @return integer booking id number of the temporary booking
$row =
new DBRow('bookings', -
1, 'id');
$f =
new Field('instrument');
$f =
new Field('bookwhen');
$f =
new Field('duration');
* remove the temporary booking for this slot
* @param integer $tmpid booking id number of the temporary booking
$this->log('Removing row, id='.
$tmpid);
$row =
new DBRow('bookings', $tmpid, 'id');
* delete the entry by marking it as deleted, don't actually delete the
* @return integer from statuscodes
function delete($unused=
null) {
// we're not allowed to do so
$this->errorMessage =
T_('Sorry, this booking cannot be deleted due to booking policy.');
$newlog =
$this->fields['log']->value
.
sprintf(T_('Booking deleted by %s (user #%s) on %s.'),
$today->dateTimeString());
/* .'Booking deleted by '.$this->_auth->username
.' (user #'.$this->uid.') on '.$today->dateTimeString().'.';*/
return parent::delete('log='.
qw($newlog));
Documentation generated on Tue, 06 Mar 2007 10:00:53 +0000 by phpDocumentor 1.3.0