Helpdesk da PluGzOne, baseado no osTicket
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1458 lines
46 KiB

<?php
/*********************************************************************
class.schedule.php
Peter Rotich <peter@osticket.com>
Copyright (c) 2019 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
/**
* Schedule
*
*/
class Schedule extends VerySimpleModel {
static $meta = array(
'table' => SCHEDULE_TABLE,
'ordering' => array('name'),
'pk' => array('id'),
'joins' => array(
'entries' => array(
'reverse' => 'ScheduleEntry.schedule',
),
),
);
// Supported Schedule types based on flags.
protected static $types = array(
'bizhrs' =>
/* @trans */ 'Business Hours',
'hdays' =>
/* @trans */ 'Holiday Hours',
);
// Supported Flags
// FLAG_BIZHRS: Schedule type of Business Hours otherwise Holiday Hours
// is assumed.
const FLAG_BIZHRS = 0x0001;
protected $_entries;
protected $_holidays;
protected $_form;
protected $_config;
public function setFlag($flag, $val) {
if ($val)
$this->flags |= $flag;
else
$this->flags &= ~$flag;
}
function get($field, $default=false) {
try {
return parent::get($field, $default);
} catch (Exception $e) {}
if (!isset($this->_config))
$this->getConfig();
if (isset($this->_config[$field]))
return $this->_config[$field];
}
function getConfig() {
if (!isset($this->_config) && $this->getId()) {
$_config = new Config('schedule.'.$this->getId());
$this->_config = $_config->getInfo();
}
return $this->_config;
}
function getId() {
return $this->get('id');
}
function getName() {
return $this->getLocal('name');
}
function getType() {
return ($this->flags & self::FLAG_BIZHRS) ? 'bizhrs' : 'hdays';
}
function getTypeDesc() {
$types = self::getTypes();
return $types[$this->getType()];
}
function getTimezone() {
global $cfg;
return $this->get('timezone') ?: $cfg->getTimezone();
}
function getDatetimeZone() {
return new DatetimeZone($this->getTimezone());
}
function getCreated() {
return $this->get('created');
}
function getUpdated() {
return $this->get('updated');
}
function getInfo() {
$info = $this->ht;
$info['type'] = $this->getType();
return $info;
}
function getNumEntries() {
return $this->getEntries()->count();
}
function getEntries() {
if (!$this->_entries) {
$this->_entries = ScheduleEntry::objects()
->filter(array('schedule_id' => $this->getId()));
}
return $this->_entries;
}
function getEntry($id) {
return $this->entries->findFirst(array('id' => $id));
}
function addEntry(ScheduleEntryForm $form, &$errors) {
if (!($vars=$form->process()))
return false;
if (!$this->isEntryUnique($vars, $errors))
$errors['error'] = $errors['error'] ?: __('Entry must be unique');
if ($errors)
return false;
$vars['schedule_id'] = $this->getId();
if (!($entry = ScheduleEntry::create($vars)))
return false;
$this->_entries = false;
return $entry;
}
function isEntryNameUnique($name, $id=0) {
$entry = $this->entries->findFirst(array('name' => $name));
return !($entry && $entry->getId() != $id);
}
function isEntryUnique($vars, &$errors) {
// Make sure name is inique
if (!$vars['name']
|| !$this->isEntryNameUnique($vars['name'], $vars['id'] ?: 0))
$errors['name'] = __('Name must be unique');
switch ($vars['repeats']) {
case 'weekly':
if ($vars['day'] < 6 ) { // Weekday
if ($this->entries->findFirst(['repeats' => 'weekdays']))
$errors['error'] = __('Weekdays entry already exists');
} elseif ($vars['day'] > 5) { //Weekend
if ($this->entries->findFirst(['repeats' => 'weekends']))
$errors['error'] = __('Weekends entry already exists');
}
break;
case 'weekdays':
if ($this->entries->findFirst(['repeats' => 'weekly',
'day__lt' => 6]))
$errors['error'] = __('Week day entry already exists');
break;
case 'weekends':
if ($this->entries->findFirst(['repeats' => 'weekly',
'day__gt' => 5]))
$errors['error'] = __('Weekend day entry already exists');
break;
case 'daily':
if (!$vars['id'] && $this->entries->count())
$errors['error'] = __('Other entries already exists');
break;
}
// Daily entry cannot coexist with other entries - mf is selfish af.
if (!$errors['error'] && strcasecmp($vars['repeats'], 'daily'))
if ($this->entries->findFirst(['repeats' => 'daily']))
$errors['error'] = __('Daily entry already exists');
if (!$errors['error']) {
$keys = array_intersect_key($vars, array_flip(
['repeats', 'day', 'week', 'month']));
$keys['schedule_id'] = $this->getId();
// Once entries can repeat on different dates
if ($keys['repeats'] == 'never')
$keys['starts_on'] = $vars['starts_on'];
$entries= ScheduleEntry::objects()
->filter($keys);
if ($vars['id'])
$entries->exclude(['id' => $vars['id']]);
if ($entries->count())
$errors['error'] = __('Entry matching the selection already exists');
}
return !count($errors);
}
function isHolidays() {
return !$this->isBusinessHours();
}
function isBusinessHours() {
return ($this->flags & self::FLAG_BIZHRS);
}
function getForm($source=null) {
if (!isset($this->_form))
$this->_form = self::basicForm($source ?: $this->getInfo());
return $this->_form;
}
function getTranslateTag($subtag) {
return _H(sprintf('schedule.%s.%s', $subtag, $this->id));
}
function getLocal($subtag) {
$tag = $this->getTranslateTag($subtag);
$T = CustomDataTranslation::translate($tag);
return $T != $tag ? $T : $this->get($subtag);
}
function getHolidays() {
if (!$this->isBusinessHours())
return false;
if (!isset($this->_holidays)) {
$config = $this->getConfiguration();
$this->_holidays = $config['holidays'] ?: array();
}
return $this->_holidays;
}
function getNumHolidaysSchedules() {
return count($this->getHolidays() ?: array());
}
function getConfiguration() {
$config = $this->getConfig();
return JsonDataParser::decode($config['configuration']);
}
function saveConfiguration($vars) {
$config = new Config('schedule.'.$this->getId());
return $config->updateAll(array(
'configuration' => JsonDataEncoder::encode([
'holidays' => $vars['holidays'] ?: array()
]),
)
);
}
function update($vars, &$errors) {
$form = $this->getForm($vars);
if (!$form->isValid())
return false;
$data = $form->getClean();
foreach (['name', 'timezone', 'description'] as $f)
$this->set($f, $data[$f]);
// Set Schedule type flag only on create.
if (!$this->getId() && isset($data['type']))
$this->setFlag(self::FLAG_BIZHRS, ($data['type'] == 'bizhrs'));
if (!$this->save(true))
return false;
// Update the config information
$this->saveConfiguration(array(
'holidays' => $vars['holidays']));
// Update sorting
foreach ($this->getEntries() as $e) {
$id = $e->getId();
if (isset($_POST["sort-{$id}"])) {
$e->sort = $_POST["sort-$id"];
$e->save(true);
}
}
// Reset the form so we don't cache old data
unset($this->_form);
return true;
}
function save($refetch=false) {
if (count($this->dirty))
$this->set('updated', new SqlFunction('NOW'));
if (isset($this->dirty['description']))
$this->description = Format::sanitize($this->description);
return parent::save($refetch);
}
function cloneEntries($schedule) {
if (!$schedule) return;
foreach ($schedule->getEntries() as $entry) {
$vars = $entry->ht;
unset($vars['id']);
$vars['schedule_id'] = $this->getId();
ScheduleEntry::create($vars);
}
}
function delete() {
// TODO: Deny delete for in-use schedules
if (!parent::delete())
return false;
$this->entries->delete();
return true;
}
function getEntryForm($source=null) {
return new ScheduleEntryForm($source, array(
'timezone' => $this->getTimezone(),
'holidays' => !$this->isBusinessHours(),
));
}
function getDiagnosticForm($source=null) {
if (!$source)
$source = ['date' => time(), 'hours' => 24];
return new ScheduleDiagnosticForm($source, array(
'timezone' => $this->getTimezone(),
));
}
static function create($ht=false, &$errors=array()) {
$inst = new static($ht);
$inst->set('created', new SqlFunction('NOW'));
if ($inst->save())
return $inst;
}
static function __create($vars) {
$s = static::create($vars);
$s->set('created', new SqlFunction('NOW'));
$s->set('updated', new SqlFunction('NOW'));
$s->save(true);
foreach ($vars['entries'] ?: array() as $info) {
$entry = new ScheduleEntry($info);
$entry->schedule_id = $s->getId();
$entry->save();
}
if (isset($vars['configuration']))
$s->saveConfiguration($vars['configuration']);
return $s;
}
static function getIdByName($name, $pid=0) {
$schedule = self::objects()->filter(array(
'name' => $name,
))->values_flat('id')->first();
return $schedule ? $schedule[0] : 0;
}
static function lookup($id) {
if (!($schedule = parent::lookup($id)))
return null;
return $schedule;
}
static function getSchedules($criteria=array()) {
$schedules = self::objects();
if ($criteria)
$schedules->filter($criteria);
$schedules->order_by('name');
return $schedules;
}
static function getTypes() {
static $translated = false;
if (!$translated) {
foreach (self::$types as $k => $v)
self::$types[$k] = __($v);
}
return self::$types;
}
static function basicForm($source=null) {
return new SimpleForm(array(
'name' => new TextboxField(array(
'required' => true,
'label' => __('Name'),
'configuration' => array(
'size' => 60,
'length' => 0,
'autofocus' => true,
),
)),
'type' => new ChoiceField(array(
'required' => true,
'editable' => false,
'label' => __('Type'),
'validator-error' => __('Selection required'),
'choices' => self::getTypes(),
'configuration' => array(
'mode' => 'view',
),
)),
'timezone' => new TimezoneField(array(
'required'=>false,
'label'=>__('Timezone'),
'hint' => __('Leave selection empty for Floating Timezone'),
'configuration' => array(
'autodetect'=>false,
'prompt' => __("Schedule's timezone")
),
)),
'description' => new TextareaField(array(
'label'=> 'Description',
'required'=>false,
'default'=>'',
'configuration' => array(
'html' => true,
'size' => 'small',
'placeholder' => __('Brief description of the schedule'),
),
)),
), $source);
}
}
/*
* BusinessHoursSchedule - extension of schedule
*
*/
class BusinessHoursSchedule extends Schedule {
// Holidays schedules applicable to this schedule
protected $holidays;
public function addWorkingHours(Datetime $date, $hours,
&$timeline=array()) {
// Delegate adding working hours to Business Hours utility class
$bhrs = new BusinessHours($this);
if (!$bhrs->addWorkingHours($date, $hours))
return false;
$timeline = $bhrs->getTimeline();
return $date;
}
public function getHolidaysSchedules() {
if (!isset($this->holidays)) {
$this->holidays = array();
foreach ($this->getHolidays() ?:array() as $id) {
if (($s=HolidaysSchedule::lookup($id)))
$this->holidays[] = $s;
}
}
return $this->holidays;
}
static function getSchedules($criteria=array()) {
return parent::getSchedules($criteria + array(
'flags__hasbit' => Schedule::FLAG_BIZHRS));
}
static function lookup($id) {
return parent::lookup(array(
'id' => $id,
'flags__hasbit' => Schedule::FLAG_BIZHRS));
}
}
/*
* HolidaysSchedule
*
*/
class HolidaysSchedule extends Schedule {
static function getSchedules($criteria=array()) {
return parent::getSchedules($criteria)
->exclude(array('flags__hasbit' => Schedule::FLAG_BIZHRS));
}
static function lookup($id) {
return parent::lookup($id);
}
}
/**
* ScheduleEntry: An entry in a schedule.
*
*/
class ScheduleEntry extends VerySimpleModel {
static $meta = array(
'table' => SCHEDULE_ENTRY_TABLE,
'pk' => array('id'),
'joins' => array(
'schedule' => array(
'null' => true,
'constraint' => array('schedule_id' => 'Schedule.id'),
),
),
);
var $_form;
protected static $frequencies = array(
'never' =>
/* @trans */ 'Once',
'daily' =>
/* @trans */ 'Daily',
'weekly' =>
/* @trans */ 'Weekly',
'monthly' =>
/* @trans */ 'Monthly',
'yearly' =>
/* @trans */ 'Yearly',
);
protected static $days = array(
1 =>
/* @trans */ 'Monday',
2 =>
/* @trans */ 'Tuesday',
3 =>
/* @trans */ 'Wednesday',
4 =>
/* @trans */ 'Thursday',
5 =>
/* @trans */ 'Friday',
6 =>
/* @trans */ 'Saturday',
7 =>
/* @trans */ 'Sunday',
);
protected static $weeks = array(
1 =>
/* @trans */ 'First',
2 =>
/* @trans */ 'Second',
3 =>
/* @trans */ 'Third',
4 =>
/* @trans */ 'Fourth',
5 =>
/* @trans */ 'Fifth',
-1 =>
/* @trans */ 'Last',
);
protected static $months = array( 1 =>
/* @trans */ 'January',
/* @trans */ 'February',
/* @trans */ 'March',
/* @trans */ 'April',
/* @trans */ 'May',
/* @trans */ 'June',
/* @trans */ 'July',
/* @trans */ 'August',
/* @trans */ 'September',
/* @trans */ 'October',
/* @trans */ 'November',
/* @trans */ 'December'
);
protected $_timezone;
protected $_starts;
protected $_ends;
protected $_stops;
function getId() {
return $this->get('id');
}
function getSchedule() {
return $this->schedule;
}
function getScheduleId() {
return $this->get('schedule_id');
}
function getCreated() {
return $this->get('created');
}
function getUpdated() {
return $this->get('updated');
}
function getTimezone() {
if (!isset($this->_timezone))
$this->_timezone = $this->getSchedule()->getTimezone();
return $this->_timezone;
}
function getDatetimeZone() {
return new DatetimeZone($this->getTimezone());
}
function diffTime(Datetime $date) {
// Set the time based on datetime given
$start = clone $this->getStartsDatetime();
list($h, $m, $s) = explode(':', $date->format('H:i:s'));
$start->setTime($h, $m, $s);
return $this->getEndsDatetime()->getTimestamp() - $start->getTimestamp();
}
function diff() {
return ($this->getEndsDatetime()->getTimestamp() -
$this->getStartsDatetime()->getTimestamp());
}
function getHours() {
return ($this->diff()+1)/3600;
}
function getMinutes() {
return $this->diff()/60;
}
function isFullDay() {
return ($this->getHours() >= 24);
}
function isWithinHours(Datetime $dt) {
return !($this->isAfterHours($dt) || $this->isBeforeHours($dt));
}
function isBeforeHours(Datetime $dt) {
return strtotime($dt->format('H:i:s')) <
strtotime($this->getStartsTime());
}
function isAfterHours(Datetime $dt) {
return strtotime($dt->format('H:i:s')) >
strtotime($this->getEndsTime());
}
function isOneTime() {
return !strcasecmp($this->getRepeats(), 'never');
}
function getStartsDatetime() {
if (!isset($this->_starts))
$this->_starts = new Datetime(sprintf('%s %s',
$this->ht['starts_on'], $this->ht['starts_at']),
$this->getDatetimeZone());
return $this->_starts;
}
function getStartsTime() {
return $this->getStartsDatetime()->format('H:i:s');
}
function getEndsDatetime() {
if (!isset($this->_ends))
$this->_ends = new Datetime(sprintf('%s %s',
$this->ht['ends_on'], $this->ht['ends_at']),
$this->getDatetimeZone());
return $this->_ends;
}
function getEndsTime() {
return $this->getEndsDatetime()->format('H:i:s');
}
function getStopsDatetime() {
if (!isset($this->_stops)) {
if ($this->ht['stops_on'])
$this->_stops = new Datetime($this->ht['stops_on'],
$this->getDatetimeZone());
elseif ($this->isOneTime())
$this->_stops = $this->getEndsDatetime();
}
return $this->_stops;
}
function getIntervalSpec(Datetime $dt) {
$info = $this->getInfo();
switch ($info['repeats']) {
case 'never':
$starts = $this->getStartsDatetime();
return $starts->format('Y-m-d');
break;
case 'daily':
return sprintf('%s %s',
'today', $dt->format('Y-m-d'));
break;
case 'weekdays':
if ($dt->format('N') > 5)
return 'weekday';
else
return sprintf('%s %s',
'today', $dt->format('Y-m-d'));
break;
case 'weekends':
if ($dt->format('N') > 5)
return sprintf('%s %s',
'today', $dt->format('Y-m-d'));
else
return sprintf('Next Saturday %s',
$dt->format('Y-m-d'));
break;
case 'weekly':
return sprintf('%s %s',
self::$days[$info['day']],
$dt->format('Y-m-d'));
break;
case 'monthly':
if (!$info['week'] && $info['day']> 0)
return sprintf('%s-%s',
$dt->format('Y-m'),
$info['day']);
else
return sprintf('%s %s of %s %d',
self::$weeks[$info['week']],
self::$days[$info['day']],
$dt->format('F'),
$dt->format('Y'));
break;
case 'yearly':
if ($info['week'] > 0) {
return sprintf('%s %s %s %s',
self::$months[$info['month']],
$dt->format('Y'),
self::$weeks[$info['week']],
self::$days[$info['day']]);
} elseif ($info['week'] == -1) {
return sprintf('last %s of %s %s',
self::$days[$info['day']],
self::$months[$info['month']],
$dt->format('Y'));
} else {
return sprintf('%s %d %d',
self::$months[$info['month']],
$info['day'],
$dt->format('Y'));
}
break;
}
}
function getCurrent($from=null) {
if (!isset($this->_current) || $from) {
// Figure out starting point (from)
$from = is_object($from) ? clone $from : Format::parseDateTime($from ?: 'now');
$start = $this->getStartsDatetime();
if ($start->getTimestamp() > $from->getTimestamp())
$from = clone $start;
// Check to make sure we're still in scope
$stop = $this->getStopsDatetime();
if ($stop && $stop->getTimestamp() < $from->getTimestamp())
return null;
// Figure out start time for the entry.
$from->modify($this->getIntervalSpec($from));
$this->_current = clone $from;
}
return $this->_current;
}
function next() {
if (!($current=$this->getCurrent()))
return null;
// Advance the interval based on frequencry
switch ($this->ht['repeats']) {
case 'daily':
$current->modify('+1 day');
break;
case 'weekly':
$current->modify('+1 week');
break;
case 'weekdays':
$current->modify('+1 weekday');
break;
case 'weekends':
// Heavy lifting done in getIntervalSpec
$current->modify('+1 day');
break;
case 'monthly':
$current->modify('+1 month');
break;
case 'yearly':
$current->modify('+1 year');
break;
case 'never':
return null;
}
// Set interval spec for specific day/week/month
$current->modify($this->getIntervalSpec($current));
// Make sure we're still in scope
$stops = $this->getStopsDatetime();
if ($stops && $stops->getTimestamp() < $current->getTimestamp())
return null;
// Advance current position
$this->_current = $current;
return $current;
}
function getOccurrences($start=null, $end=null, $num=5) {
$occurrences = array();
if (($current = $this->getCurrent($start))) {
$start = $start ?: $current;
while (count($occurrences) < $num) {
$date = $current->format('Y-m-d');
if ($end && strtotime($date) > strtotime($end))
break;
if (strtotime($date) >= strtotime($start->format('Y-m-d')))
$occurrences[$date] = $this;
if (!($current=$this->next()))
break;
}
}
return $occurrences;
}
function getName() {
return $this->getLocal('name');
}
function getRepeats() {
return $this->get('repeats');
}
function getDesc() {
$info = $this->getInfo();
$repeats = $this->getRepeats();
$days = self::getDays();
$weeks = self::getWeeks();
$months = self::getMonths();
$frequencies = self::getFrequencies();
$starts = $this->getStartsDatetime();
$ends = $this->getEndsDatetime();
$when = '';
$desc = $frequencies[$info['repeats']];
switch ($info['repeats']) {
case 'weekly':
$when = $days[$info['day']];
break;
case 'weekdays':
$desc = sprintf('%s (%s)',
__('Weekdays'), __('Mon-Fri'));
break;
case 'weekends':
$desc = sprintf('%s (%s)',
__('Weekends'), __('Sat-Sun'));
break;
case 'monthly':
if (!$info['week'])
$when = sprintf(__('%s of the Month'),
Format::ordinalsuffix($info['day']));
else
$when = sprintf('the %s %s',
$weeks[$info['week']], $days[$info['day']]);
break;
case 'yearly':
if (!$info['week']) {
$when =sprintf('%s %s',
$months[$info['month']],
Format::ordinalsuffix($info['day']));
} else {
$when = sprintf('the %s %s in %s',
$weeks[$info['week']],
$days[$info['day']],
$months[$info['month']]);
}
break;
case 'never':
$when = $starts->format('F jS, Y');
break;
}
if ($when)
$desc .=' '.sprintf(__('on %s'), $when);
if (!$this->isFullDay()) {
$desc .= sprintf(' <small>(%s - %s)</small>',
$starts->format('h:i a'),
$ends->format('h:i a'));
}
return $desc;
}
function getSortOrder() {
return $this->get('sort');
}
function getInfo() {
return $this->ht;
}
function getForm($source=null) {
if (!$this->_form) {
if (!$source) {
$source = $this->getInfo();
$starts = $this->getStartsDatetime();
$source['starts_on'] = $starts->getTimestamp();
$source['starts_at'] = $starts->format('h:i a');
$ends = $this->getEndsDatetime();
// TODO: Add support for 'ends_on' - We don't support date
// range at the moment - assuming ends on the same day.
$source['ends_at'] = $ends->format('h:i a');
if (($stops=$this->getStopsDatetime()))
$source['stops_on'] = $stops->setTimestamp();
// See if time spans all day.
if ($this->isFullDay())
$source['allday'] = true;
// Map UI fields
switch ($source['repeats']) {
case 'weekly':
$source['weekly_day'] = $source['day'];
break;
case 'weekdays':
case 'weekends':
// Keep the UI dumb af
$source['weekly_day'] = $source['repeats'];
$source['repeats'] = 'weekly';
break;
case 'monthly':
$source['monthly'] = $source['week'] ?: 'day';
$source['monthly_week'] = $source['week'];
$source['monthly_day'] = $source['day'];
break;
case 'yearly':
$source['yearly'] = $source['week'] ?: 'date';
$source['yearly_month'] = $source['month'];
$source['yearly_week'] = $source['week'];
$source['yearly_day'] = $source['day'];
break;
}
}
$this->_form = new ScheduleEntryForm($source);
}
return $this->_form;
}
function getFields() {
return $this->getForm()->getFields();
}
function getTranslateTag($subtag) {
return _H(sprintf('schedulentry.%s.%s', $subtag, $this->id));
}
function getLocal($subtag) {
$tag = $this->getTranslateTag($subtag);
$T = CustomDataTranslation::translate($tag);
return $T != $tag ? $T : $this->get($subtag);
}
function toString() {
return $this->getName();
}
function __toString() {
return $this->toString();
}
function update(ScheduleEntryForm $form, &$errors=array()) {
if (!($vars=$form->process()))
return false;
$vars['id'] = $this->getId();
if (!$this->getSchedule()->isEntryUnique($vars, $errors))
return false;
foreach ($vars as $k => $v)
$this->set($k, $v);
return $this->save();
}
function delete() {
return parent::delete();
}
function save($refetch=false) {
if (count($this->dirty))
$this->set('updated', new SqlFunction('NOW'));
return parent::save($refetch);
}
static function getFrequencies() {
static $translated = false;
if (!$translated) {
foreach (static::$frequencies as $k=>$v)
static::$frequencies[$k] = __($v);
}
return static::$frequencies;
}
static function getDays() {
static $translated = false;
if (!$translated) {
foreach (static::$days as $k=>$v)
static::$days[$k] = __($v);
}
return static::$days;
}
static function getWeeks() {
static $translated = false;
if (!$translated) {
foreach (static::$weeks as $k=>$v)
static::$weeks[$k] = __($v);
}
return static::$weeks;
}
static function getMonths() {
static $translated = false;
if (!$translated) {
foreach (static::$months as $k=>$v)
static::$months[$k] = __($v);
}
return static::$months;
}
static function create($ht=false) {
$inst = new static($ht);
$inst->set('created', new SqlFunction('NOW'));
if ($inst->save())
return $inst;
}
}
class ScheduleEntryForm
extends AbstractForm {
static $layout = 'GridFormLayout';
function buildFields() {
$timezone = $this->options['timezone'];
$allday = ($this->options['holidays']);
$fields = array(
'name' => new TextboxField(array(
'required' => true,
'layout' => new GridFluidCell(8),
'label' => __('Name'),
'configuration' => array(
// 'translatable' => $item ? $item->getTranslateTag('value') : false,
'size' => 60,
'length' => 0,
'autofocus' => true,
),
)),
'starts_on' => new DatetimeField(array(
'label' => __('Starts on'),
'required' => true,
'layout' => new GridFluidCell(6),
'configuration' => array(
'time' => false,
'gmt' => false,
'future' => true,
'showtimezone' => false,
),
)),
'allday' => new BooleanField(array(
'required' => false,
'default' => $allday,
'label' => __('All Day'),
'layout' => new GridFluidCell(6),
'configuration'=>array(
'desc'=>__('Full 24-hour day'))
)),
'starts_at' => new TimeField(array(
'required' => true,
'default' => $allday ? '12:00 am' : '8:00 am',
'label' => __('Starts at'),
'layout' => new GridFluidCell(6),
'configuration' => array(
'timezone' => $timezone,
'showtimezone' => false,
),
'visibility' => new VisibilityConstraint(
new Q(array('allday__eq'=>false)),
VisibilityConstraint::HIDDEN),
)),
'ends_at' => new TimeField(array(
'required' => true,
'default' => $allday ? '11:59 pm' : '5:00 pm',
'label' => __('Ends at'),
'layout' => new GridFluidCell(6),
'configuration' => array(
'timezone' => $timezone,
'showtimezone' => false,
),
'visibility' => new VisibilityConstraint(
new Q(array('allday__eq'=>false)),
VisibilityConstraint::HIDDEN),
)),
'repeats' => new ChoiceField(array(
'required' => true,
'layout' => new GridFluidCell(6),
'label' => __('Repeats'),
'validator-error' => __('Selection required'),
'choices' => ScheduleEntry::getFrequencies(),
)),
'stops_on' => new DatetimeField(array(
'label' => __('Until'),
'required' => false,
'layout' => new GridFluidCell(4),
'configuration' => array(
'time' => false,
'gmt' => false,
'future' => true,
'placeholder' => __('Forever'),
'showtimezone' => false,
),
'visibility' => new VisibilityConstraint(
new Q(array('repeats__eq' => 'daily|weekly|monthly|yearly')),
VisibilityConstraint::VISIBLE ),
)),
'weekly_day' => new ChoiceField(array(
'required' => true,
'default' => "",
'layout' => new GridFluidCell(6),
'label' => __('Day of the Week'),
'choices' => ScheduleEntry::getDays() + array(
'weekdays' => sprintf('%s (%s)',
__('Weekdays'), __('Mon-Fri')),
'weekends' => sprintf('%s (%s)',
__('Weekends'), __('Sat-Sun'))),
'validator-error' => __('Selection required'),
'configuration'=>array('prompt'=>__('Select Day of the Week')),
'visibility' => new VisibilityConstraint(
new Q(array('repeats__eq'=>'weekly')),
VisibilityConstraint::HIDDEN ),
)),
'monthly' => new ChoiceField(array(
'required' => true,
'default' => 'day',
'layout' => new GridFluidCell(6, array(
'break' => true)),
'label' => __('On the'),
'validator-error' => __('Selection required'),
'choices' => array(
'day' => __('Day of the Month Entered'),
'1' => __('First'),
'2' => __('Second'),
'3' => __('Third'),
'4' => __('Fourth'),
'5' => __('Fifth'),
'-1' => __('Last'),
),
'visibility' => new VisibilityConstraint(
new Q(array('repeats__eq'=>'monthly')),
VisibilityConstraint::HIDDEN ),
)),
'monthly_day' => new ChoiceField(array(
'required' => true,
'default' => "",
'layout' => new GridFluidCell(6),
'label' => __('Day'),
'choices' => ScheduleEntry::getDays(),
'validator-error' => __('Selection required'),
'visibility' => new VisibilityConstraint(
new Q(array('monthly__neq'=>'day')),
VisibilityConstraint::HIDDEN ),
)),
'yearly' => new ChoiceField(array(
'required' => true,
'layout' => new GridFluidCell(4),
'label' => __('On the'),
'validator-error' => __('Selection required'),
'choices' => array(
'date' => __('Date Entered'),
'1' => __('First'),
'2' => __('Second'),
'3' => __('Third'),
'4' => __('Fourth'),
'5' => __('Fifth'),
'-1' => __('Last'),
),
'visibility' => new VisibilityConstraint(
new Q(array('repeats__eq'=>'yearly')),
VisibilityConstraint::HIDDEN ),
)),
'yearly_day' => new ChoiceField(array(
'required' => true,
'default' => '',
'layout' => new GridFluidCell(4),
'label' => __('Day'),
'choices' => ScheduleEntry::getDays(),
'validator-error' => __('Selection required'),
'configuration'=>array('prompt'=>__('Day')),
'visibility' => new VisibilityConstraint(
new Q(array('yearly__neq'=>'date')),
VisibilityConstraint::HIDDEN ),
)),
'yearly_month' => new ChoiceField(array(
'required' => true,
'default' => 0,
'layout' => new GridFluidCell(4),
'label' => __('In'),
'choices' => ScheduleEntry::getMonths(),
'validator-error' => __('Selection required'),
'configuration'=>array('prompt'=>__('Month')),
'visibility' => new VisibilityConstraint(
new Q(array('yearly__neq'=>'date')),
VisibilityConstraint::HIDDEN ),
)),
);
return $fields;
}
function process($validate=true) {
if (!$this->isValid())
return false;
// Parse && validate data
$errors = array();
$data = $this->getClean($validate);
$vars = array('name' => $data['name'], 'repeats' => $data['repeats']);
if (($startsOn=Format::parseDateTime($data['starts_on'])))
$vars['starts_on'] = $startsOn->format('Y-m-d');
else
$errors['starts_on'] = __('Valid Start Date required');
if ($data['stops_on'] && $startsOn &&
($stopsOn = Format::parseDateTime($data['stops_on']))) {
if ($stopsOn->getTimestamp() <= $startsOn->getTimestamp())
$errors['ends_on'] = __('Must be in the future');
}
if ($data['allday']) {
$data['starts_at'] = '00:00:00';
$data['ends_at'] = '23:59:59';
} else {
$data['starts_at'] = date('H:i:s', strtotime($data['starts_at']));
$data['ends_at'] = date('H:i:s', strtotime($data['ends_at']));
$startsAt = strtotime($vars['starts_on'].' '.$data['starts_at']);
$endsAt = strtotime($vars['starts_on'] .' '.$data['ends_at'])+59;
if ($startsAt >= $endsAt)
$errors['ends_at'] = __('Invalid time span');
}
if ($errors) {
// Replay any errors back on the form fields
foreach ($errors as $k => $error) {
if (($f=$this->getField($k)))
$f->addError($error);
}
return false;
}
$dt = DateTimeImmutable::createFromMutable($startsOn);
// Start time
list($h,$m,$s) = explode(':', $data['starts_at']);
$starts = $dt->setTime($h, $m, 00);
$vars['starts_on'] = $starts->format('Y-m-d');
$vars['starts_at'] = $starts->format('H:i:s');
// end time
list($h,$m,$s) = explode(':', $data['ends_at']);
$ends = $dt->setTime($h, $m, $m == '00' ? 00 : 59);
$vars['ends_on'] = $ends->format('Y-m-d');
$vars['ends_at'] = $ends->format('H:i:s');
// Stop date
if ($stopsOn)
$vars['stops_on'] = $stopsOn->format('Y-m-d H:i:s');
switch ($data['repeats']) {
case 'weekly':
switch ($data['weekly_day']) {
// Under the hood we're overloading repeats on weekdays &
// weekends to keep the UI stupid and simple.
case 'weekdays':
case 'weekends':
$vars['repeats'] = $data['weekly_day'];
break;
default:
$vars['day'] = $data['weekly_day'];
break;
}
break;
case 'monthly':
$vars['day'] = $data['monthly_day'] ?: null;
if ($data['monthly'] == 'day')
$vars['day'] = $startsOn->format('d');
else
$vars['week'] = $data['monthly'];
break;
case 'yearly':
if ($data['yearly'] == 'date') {
$vars['week'] = null;
$vars['day'] = $startsOn->format('d');
$vars['month'] = $startsOn->format('m');
} else {
$vars['week'] = $data['yearly'];
$vars['day'] = $data['yearly_day'];
$vars['month'] = $data['yearly_month'];
}
break;
}
return $vars;
}
function emitJavascript($options=array()) {
if (!($starts=$this->getField('starts_on')))
return;
$keys = array();
foreach (array('weekly_day' => 'day',
'monthly_day' => 'day',
'yearly_day' => 'day',
'yearly_week' => 'week',
'yearly_month' => 'month') as $k => $v) {
if (($f=$this->getField($k)))
$keys[$f->getWidget()->id] = $v;
}
$id = $starts->getWidget()->id;
?>
<script type="text/javascript">
$(function() {
$('#<?php echo $id; ?>').on('change', function() {
var keys = <?php echo JsonDataEncoder::encode($keys); ?>;
var d = $(this).datepicker('getDate');
if (d) {
var a = new Array();
a['month'] = d.getMonth()+1;
a['week'] = Math.ceil(d.getDate() / 7);
a['day'] = d.getDay() || 7;
console.log(a);
$.each(keys, function(key, value) {
$sel = $('#'+key);
$val = $('#'+key+' option:selected').val();
console.log(key, value, $val, a[value]);
$sel.removeClass('error');
if ($val.length == 0)
$sel.val(a[value]);
else if ($val != a[value])
$sel.addClass('error')
.bind('change fucus', function() {
$(this).removeClass('error');
});
});
}
});
});
</script>
<?php
parent::emitJavascript($options);
}
}
class ScheduleDiagnosticForm
extends AbstractForm {
static $layout = 'GridFormLayout';
function buildFields() {
$fields = array(
'date' => new DatetimeField(array(
'label' => __('Date Time'),
'required' => true,
'layout' => new GridFluidCell(6),
'configuration' => array(
'time' => true,
'gmt' => false,
'future' => false,
'max' => time(),
'showtimezone' => true,
),
)),
'hours' => new TextboxField(array(
'label' => __('Hours'),
'required' => true,
'layout' => new GridFluidCell(3),
'validator' => 'number',
'configuration' => ['size'=>10, 'length'=>10],
)),
);
return $fields;
}
function emitJavascript($options=array()) {
$date = $this->getField('date')->getWidget()->id;
$hours = $this->getField('hours')->getWidget()->id;
?>
<script type="text/javascript">
$(function() {
$('#<?php echo $date; ?>, #<?php echo $hours; ?>').on('change',
function() {
$('#diagnostic-results').hide();
});
});
</script>
<?php
parent::emitJavascript($options);
}
}
?>