forked from extern/egroupware
989d1ca389
- Fix calendar toolbar messed up with too many shown actions - Fix SVG icons for list, planner, 4days, multiweek and today buttons
2751 lines
89 KiB
PHP
2751 lines
89 KiB
PHP
<?php
|
|
/**
|
|
* eGroupWare - Calendar's views and widgets
|
|
*
|
|
* @link http://www.egroupware.org
|
|
* @package calendar
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @copyright (c) 2004-15 by RalfBecker-At-outdoor-training.de
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @version $Id$
|
|
*/
|
|
|
|
/**
|
|
* Class to generate the calendar views and the necesary widgets
|
|
*
|
|
* The listview is in a separate class calendar_uilist!
|
|
*
|
|
* The new UI, BO and SO classes have a strikt definition, in which time-zone they operate:
|
|
* UI only operates in user-time, so there have to be no conversation at all !!!
|
|
* BO's functions take and return user-time only (!), they convert internaly everything to servertime, because
|
|
* SO operates only on server-time
|
|
*
|
|
* The state of the UI elements is managed in the uical class, which all UI classes extend.
|
|
*
|
|
* All permanent debug messages of the calendar-code should done via the debug-message method of the calendar_bo class !!!
|
|
*/
|
|
class calendar_uiviews extends calendar_ui
|
|
{
|
|
var $public_functions = array(
|
|
'index' => True,
|
|
);
|
|
|
|
/**
|
|
* integer level or string function- or widget-name
|
|
*
|
|
* @var mixed
|
|
*/
|
|
var $debug=false;
|
|
|
|
/**
|
|
* extra rows above and below the workday
|
|
*
|
|
* @var int
|
|
*/
|
|
var $extraRows = 2;
|
|
|
|
/**
|
|
* removes n extra rows below the workday
|
|
*
|
|
* @var int
|
|
*/
|
|
var $remBotExtraRows = 0;
|
|
|
|
/**
|
|
* extra rows original (save original value even if it gets changed in the class)
|
|
*
|
|
* @var int
|
|
*/
|
|
var $extraRowsOriginal;
|
|
|
|
var $timeRow_width = 40;
|
|
|
|
/**
|
|
* how many rows per day get displayed, gets set by the timeGridWidget
|
|
*
|
|
* @var int
|
|
*/
|
|
var $rowsToDisplay;
|
|
|
|
/**
|
|
* height in percent of one row, gets set by the timeGridWidget
|
|
*
|
|
* @var int
|
|
*/
|
|
var $rowHeight;
|
|
|
|
/**
|
|
* standard params for calling bocal::search for all views, set by the constructor
|
|
*
|
|
* @var array
|
|
*/
|
|
var $search_params;
|
|
|
|
/**
|
|
* should we use a time grid, can be set for week- and month-view to false by the cal_pref no_time_grid
|
|
*
|
|
* @var boolean
|
|
*/
|
|
var $use_time_grid=true;
|
|
|
|
/**
|
|
* Pref value of use_time_grid preference
|
|
* @var string
|
|
*/
|
|
var $use_time_grid_pref = '';
|
|
|
|
/**
|
|
* Can we display the whole day in a timeGrid of the size of the workday and just scroll to workday start
|
|
*
|
|
* @var boolean
|
|
*/
|
|
var $scroll_to_wdstart=false;
|
|
|
|
/**
|
|
* counter for the current whole day event of a single day
|
|
*
|
|
* @var int
|
|
*/
|
|
var $wholeDayPosCounter=1;
|
|
|
|
/**
|
|
* Switch to disable private data and possibility to view and edit events
|
|
* in case of a public view (sitemgr)
|
|
*/
|
|
var $allowEdit = true;
|
|
|
|
/**
|
|
* Display holidays as event, currenlty only used in day-view
|
|
*
|
|
* @var array
|
|
*/
|
|
var $display_holiday_event_types = array(
|
|
'bdays' => false,
|
|
'hdays' => false
|
|
);
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param array $set_states = null to manualy set / change one of the states, default NULL = use $_REQUEST
|
|
*/
|
|
function __construct($set_states=null)
|
|
{
|
|
parent::__construct(false,$set_states); // call the parent's constructor
|
|
$this->extraRowsOriginal = $this->extraRows; //save original extraRows value
|
|
|
|
$GLOBALS['egw_info']['flags']['nonavbar'] = False;
|
|
|
|
// Check for GET message (from merge)
|
|
if($_GET['msg'])
|
|
{
|
|
egw_framework::message($_GET['msg']);
|
|
unset($_GET['msg']);
|
|
}
|
|
// standard params for calling bocal::search for all views
|
|
$this->owner = str_replace('%2C',',',$this->owner);
|
|
$this->search_params = array(
|
|
'start' => $this->date,
|
|
'cat_id' => $this->cat_id ? (is_array($this->cat_id)?$this->cat_id:explode(',',$this->cat_id)) : 0,
|
|
'users' => explode(',',$this->owner),
|
|
'filter' => $this->filter,
|
|
'daywise' => True,
|
|
'use_so_events' => $this->test === 'true',
|
|
);
|
|
$this->holidays = $this->bo->read_holidays($this->year);
|
|
|
|
$this->check_owners_access();
|
|
|
|
//ATM: Forces use_time_grid preference to use all views by ignoring the preference value
|
|
//@TODO: the whole use_time_grid preference should be removed (including dependent vars)
|
|
// after we decided that is not neccessary to have it at all
|
|
$this->use_time_grid_pref = 'all'; //$this->cal_prefs['use_time_grid'];
|
|
}
|
|
|
|
|
|
/**
|
|
* Calculate iso8601 week-number, which is defined for Monday as first day of week only
|
|
*
|
|
* We addjust the day, if user prefs want a different week-start-day
|
|
*
|
|
* @param int|string|DateTime $time
|
|
* @return string
|
|
*/
|
|
public function week_number($time)
|
|
{
|
|
if (!is_a($time,'DateTime')) $time = new egw_time($time);
|
|
|
|
// if week does not start Monday and $time is Sunday --> add one day
|
|
if ($this->cal_prefs['weekdaystarts'] != 'Monday' && !($wday = $time->format('w')))
|
|
{
|
|
$time->modify('+1day');
|
|
}
|
|
// if week does start Saturday and $time is Saturday --> add two days
|
|
elseif ($this->cal_prefs['weekdaystarts'] == 'Saturday' && $wday == 6)
|
|
{
|
|
$time->modify('+2days');
|
|
}
|
|
return $time->format('W');
|
|
}
|
|
|
|
/**
|
|
* Load all views used by calendar, client side switches between them as needed
|
|
*/
|
|
function index($content=array())
|
|
{
|
|
if($content['merge'])
|
|
{
|
|
// View from sidebox is JSON encoded
|
|
$this->manage_states(array_merge($content,json_decode($content['view'],true)));
|
|
if($content['first'])
|
|
{
|
|
$this->first = egw_time::to($content['first'],'ts');
|
|
}
|
|
if($content['last'])
|
|
{
|
|
$this->last = egw_time::to($content['last'],'ts');
|
|
}
|
|
$_GET['merge'] = $content['merge'];
|
|
$this->merge();
|
|
return;
|
|
}
|
|
|
|
// handle views in other files
|
|
if (!isset($this->public_functions[$this->view]) && $this->view !== 'listview')
|
|
{
|
|
$this->view = 'week';
|
|
}
|
|
// get manual to load the right page
|
|
$GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => 'ManualCalendar'.ucfirst($this->view));
|
|
|
|
// Sidebox & iframe for old views
|
|
if(in_array($this->view,array('year')) && $_GET['view'])
|
|
{
|
|
$GLOBALS['egw_info']['flags']['nonavbar'] = true;
|
|
$this->manage_states($_GET);
|
|
$old_calendar = $this->{$this->view}();
|
|
return;
|
|
}
|
|
|
|
// Toolbar
|
|
$tmpl = new etemplate_new('calendar.toolbar');
|
|
$tmpl->setElementAttribute('toolbar', 'actions', $this->getToolbarActions($content));
|
|
// Adjust toolbar for mobile
|
|
if(html::$ua_mobile){
|
|
$tmpl->setElementAttribute('toolbar','class', 'et2_head_toolbar');
|
|
$tmpl->setElementAttribute('toolbar','view_range', '3');
|
|
}
|
|
$tmpl->exec('calendar_uiviews::index',array());
|
|
|
|
// Load the different views once, we'll switch between them on the client side
|
|
$tmpl = new etemplate_new('calendar.todo');
|
|
$label = '';
|
|
$tmpl->exec('calendar_uiviews::index',array('todos'=>$this->get_todos($label), 'label' => $label));
|
|
|
|
// Actually, this takes care of most of it...
|
|
$this->week();
|
|
|
|
$tmpl = new etemplate_new('calendar.planner');
|
|
// Get the actions
|
|
$tmpl->setElementAttribute('planner','actions',$this->get_actions());
|
|
$tmpl->exec('calendar_uiviews::index',array());
|
|
|
|
// List view in a separate file
|
|
$list_ui = new calendar_uilist();
|
|
$list_ui->listview();
|
|
}
|
|
|
|
/**
|
|
* Generate the calendar toolbar actions
|
|
*
|
|
* @param Array $content
|
|
*/
|
|
protected function getToolbarActions($content = array())
|
|
{
|
|
$group = 0;
|
|
$actions = array(
|
|
'add' => array(
|
|
'caption' => 'Add',
|
|
'icon' => 'add',
|
|
'group' => ++$group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Add',
|
|
'toolbarDefault' => true,
|
|
),
|
|
'day_view' => array(
|
|
'caption' => 'Day view',
|
|
'icon' => 'day',
|
|
'group' => ++$group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Day view',
|
|
'toolbarDefault' => true,
|
|
'data' => array('state' => array('view' => 'day'))
|
|
),
|
|
'4day_view' => array(
|
|
'caption' => 'Four days view',
|
|
'icon' => 'cal4',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Four days view',
|
|
'toolbarDefault' => false,
|
|
'data' => array('state' => array('view' => 'day4'))
|
|
),
|
|
'week_view' => array(
|
|
'caption' => 'Week view',
|
|
'icon' => 'week',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Week view',
|
|
'toolbarDefault' => true,
|
|
'data' => array('state' => array('view' => 'week'))
|
|
),
|
|
'weekN_view' => array(
|
|
'caption' => 'Multiple week view',
|
|
'icon' => 'multiweek',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Multiple week view',
|
|
'toolbarDefault' => true,
|
|
'data' => array('state' => array('view' => 'weekN'))
|
|
),
|
|
'month_view' => array(
|
|
'caption' => 'Month view',
|
|
'icon' => 'month',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Month view',
|
|
'toolbarDefault' => false,
|
|
'data' => array('state' => array('view' => 'month'))
|
|
),
|
|
'planner_category' => array(
|
|
'caption' => 'Planner by category',
|
|
'icon' => 'planner_category',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Planner by category',
|
|
'toolbarDefault' => false,
|
|
'data' => array('state' => array('view' => 'planner', 'sortby' => 'category')),
|
|
),
|
|
'planner_user' => array(
|
|
'caption' => 'Planner by user',
|
|
'icon' => 'planner',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Planner by user',
|
|
'toolbarDefault' => false,
|
|
'data' => array('state' => array('view' => 'planner', 'sortby' => 'user')),
|
|
),
|
|
'planner_month' => array(
|
|
'caption' => 'Yearly planner',
|
|
'icon' => 'year',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Yearly planner',
|
|
'toolbarDefault' => false,
|
|
'data' => array('state' => array('view' => 'planner', 'sortby' => 'month')),
|
|
),
|
|
'list' => array(
|
|
'caption' => 'Listview',
|
|
'icon' => 'list',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Listview',
|
|
'toolbarDefault' => true,
|
|
'data' => array('state' => array('view' => 'listview')),
|
|
),
|
|
'weekend' => array(
|
|
'caption' => 'Weekend',
|
|
'icon' => 'weekend',
|
|
'checkbox' => true,
|
|
'checked' => $this->cal_prefs['saved_states']['weekend'],
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Toggle weekend',
|
|
'toolbarDefault' => false,
|
|
'data' => array('toggle_off' => '5', 'toggle_on' => '7')
|
|
),
|
|
'previous' => array(
|
|
'caption' => 'Previous',
|
|
'icon' => 'previous',
|
|
'group' => ++$group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Previous',
|
|
'toolbarDefault' => true,
|
|
),
|
|
'today' => array(
|
|
'caption' => 'Today',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Today',
|
|
'toolbarDefault' => true,
|
|
),
|
|
'next' => array(
|
|
'caption' => 'Next',
|
|
'icon' => 'next',
|
|
'group' => $group,
|
|
'onExecute' => 'javaScript:app.calendar.toolbar_action',
|
|
'hint' => 'Next',
|
|
'toolbarDefault' => true,
|
|
),
|
|
);
|
|
if (html::$ua_mobile)
|
|
{
|
|
foreach (array_keys($actions) as $key)
|
|
{
|
|
if (!in_array($key, array('add','weekend','next', 'today','previous'))) {
|
|
$actions[$key]['toolbarDefault'] = false;
|
|
}
|
|
else {
|
|
$actions[$key]['toolbarDefault'] = true;
|
|
}
|
|
}
|
|
$actions['weekend']['data'] = array('toggle_off' => '7', 'toggle_on' => '5');
|
|
unset($actions['pgp']);
|
|
}
|
|
return $actions;
|
|
}
|
|
|
|
/**
|
|
* Displays the planner view
|
|
*
|
|
* @param boolean|etemplate_new $home = false if etemplate return content suitable for home-page
|
|
*/
|
|
function &planner($content = array(), $home=false)
|
|
{
|
|
if ($this->sortby == 'month') // yearly planner with month rows
|
|
{
|
|
$this->first = $this->bo->date2array($this->date);
|
|
$this->first['day'] = 1;
|
|
unset($this->first['raw']);
|
|
$this->last = $this->first;
|
|
$this->last['year']++;
|
|
$this->last = $this->bo->date2ts($this->last)-1;
|
|
}
|
|
elseif (!$this->planner_view || $this->planner_view == 'month') // planner monthview
|
|
{
|
|
if ($this->day < 15) // show one complete month
|
|
{
|
|
$this->_week_align_month($this->first,$this->last);
|
|
}
|
|
else // show 2 half month
|
|
{
|
|
$this->_week_align_month($this->first,$this->last,15);
|
|
}
|
|
}
|
|
elseif ($this->planner_view == 'week' || $this->planner_view == 'weekN') // weeekview
|
|
{
|
|
$this->first = $this->datetime->get_weekday_start($this->year,$this->month,$this->day);
|
|
$this->last = $this->bo->date2array($this->first);
|
|
$this->last['day'] += ($this->planner_view == 'week' ? 7 : 7 * $this->cal_prefs['multiple_weeks'])-1;
|
|
$this->last['hour'] = 23; $this->last['minute'] = $this->last['sec'] = 59;
|
|
unset($this->last['raw']);
|
|
$this->last = $this->bo->date2ts($this->last);
|
|
}
|
|
else // dayview
|
|
{
|
|
$this->first = $this->bo->date2ts($this->date);
|
|
$this->last = $this->bo->date2array($this->first);
|
|
$this->last['day'] += 0;
|
|
$this->last['hour'] = 23; $this->last['minute'] = $this->last['sec'] = 59;
|
|
unset($this->last['raw']);
|
|
$this->last = $this->bo->date2ts($this->last);
|
|
}
|
|
|
|
$merge = $this->merge();
|
|
if($merge)
|
|
{
|
|
egw::redirect_link('/index.php',array(
|
|
'menuaction' => 'calendar.calendar_uiviews.index',
|
|
'msg' => $merge,
|
|
));
|
|
}
|
|
|
|
$search_params = $this->search_params;
|
|
$search_params['daywise'] = false;
|
|
$search_params['start'] = $this->first;
|
|
$search_params['end'] = $this->last;
|
|
$search_params['enum_groups'] = $this->sortby == 'user';
|
|
$content['planner'] = $this->bo->search($search_params);
|
|
foreach($content['planner'] as &$event)
|
|
{
|
|
$this->to_client($event);
|
|
}
|
|
|
|
if ($this->debug > 0) $this->bo->debug_message('uiviews::planner() date=%1: first=%2, last=%3',False,$this->date,$this->bo->date2string($this->first),$this->bo->date2string($this->last));
|
|
|
|
$tmpl = $home ? $home :new etemplate_new('calendar.planner');
|
|
|
|
$tmpl->setElementAttribute('planner','start_date', egw_time::to($this->first, egw_time::ET2));
|
|
$tmpl->setElementAttribute('planner','end_date', egw_time::to($this->last, egw_time::ET2));
|
|
$tmpl->setElementAttribute('planner','owner', $search_params['users']);
|
|
$tmpl->setElementAttribute('planner','group_by', $this->sortby);
|
|
// Get the actions
|
|
$tmpl->setElementAttribute('planner','actions',$this->get_actions());
|
|
|
|
$tmpl->exec(__METHOD__, $content);
|
|
}
|
|
|
|
/**
|
|
* Displays the monthview or a multiple week-view
|
|
*
|
|
* @param int $weeks = 0 number of weeks to show, if 0 (default) all weeks of one month are shown
|
|
* @param boolean|etemplate2 $home = false if not false return content suitable for home-page
|
|
*/
|
|
function &month($weeks=0,$home=false)
|
|
{
|
|
if ($this->debug > 0) $this->bo->debug_message('uiviews::month(weeks=%1) date=%2',True,$weeks,$this->date);
|
|
|
|
if (!$home)
|
|
{
|
|
trigger_error(__METHOD__ .' only used by home app', E_USER_DEPRECATED);
|
|
return;
|
|
}
|
|
|
|
$this->use_time_grid = !$this->use_time_grid_pref || $this->use_time_grid_pref == 'all'; // all views
|
|
$granularity = 0;
|
|
if($weeks)
|
|
{
|
|
$granularity = ($this->cal_prefs['interval'] ? (int)$this->cal_prefs['interval'] : 30);
|
|
|
|
$list = $this->cal_prefs['use_time_grid'];
|
|
if(!is_array($list))
|
|
{
|
|
$list = explode(',',$list);
|
|
}
|
|
if(is_array($list))
|
|
{
|
|
$granularity = in_array('weekN',$list) ? 0 : $granularity;
|
|
}
|
|
}
|
|
$content = array('view' => array());
|
|
|
|
if ($weeks)
|
|
{
|
|
$this->first = $this->datetime->get_weekday_start($this->year,$this->month,$this->day);
|
|
$this->last = strtotime("+$weeks weeks",$this->first) - 1;
|
|
$weekNavH = "$weeks weeks";
|
|
$navHeader = lang('Week').' '.$this->week_number($this->first).' - '.$this->week_number($this->last).': '.
|
|
$this->bo->long_date($this->first,$this->last);
|
|
}
|
|
else
|
|
{
|
|
$this->_week_align_month($this->first,$this->last);
|
|
$weekNavH = "1 month";
|
|
$navHeader = lang(adodb_date('F',$this->bo->date2ts($this->date))).' '.$this->year;
|
|
}
|
|
if ($this->debug > 0) $this->bo->debug_message('uiviews::month(%1) date=%2: first=%3, last=%4',False,$weeks,$this->date,$this->bo->date2string($this->first),$this->bo->date2string($this->last));
|
|
|
|
// Loop through, using egw_time to handle DST
|
|
$week = 0;
|
|
$week_start = new EGroupware\Api\DateTime($this->first);
|
|
$week_start->setTime(0,0,0);
|
|
$week_end = new egw_time($week_start);
|
|
$week_end->add(new DateInterval('P6DT23H59M59S'));
|
|
$last = new EGroupware\Api\DateTime($this->last);
|
|
for ($week_start; $week_start < $last; $week_start->add('1 week'), $week_end->add('1 week'))
|
|
{
|
|
$search_params = $this->search_params;
|
|
$search_params['start'] = $week_start->format('ts');
|
|
$search_params['end'] = $week_end->format('ts');
|
|
|
|
$content['view'][] = (array)$this->tagWholeDayOnTop($this->bo->search($search_params)) +
|
|
array(
|
|
'id' => $week_start->format('Ymd')
|
|
);
|
|
$home->setElementAttribute("view[$week]",'onchange',false);
|
|
$home->setElementAttribute("view[$week]",'granularity',$granularity);
|
|
$home->setElementAttribute("view[$week]",'show_weekend', $this->search_params['weekend']);
|
|
$week++;
|
|
}
|
|
|
|
// Get the actions
|
|
$home->setElementAttribute('view','actions',$this->get_actions());
|
|
|
|
$home->exec(__METHOD__, $content);
|
|
}
|
|
|
|
/**
|
|
* get start and end of a month aligned to full weeks
|
|
*
|
|
* @param int &$first timestamp 0h of first day of week containing the first of the current month
|
|
* @param int &$last timestamp 23:59:59 of last day of week containg the last day of the current month
|
|
* @param int $day = 1 should the alignment be based on the 1. of the month or an other date, eg. the 15.
|
|
*/
|
|
function _week_align_month(&$first,&$last,$day=1)
|
|
{
|
|
$first = $this->datetime->get_weekday_start($this->year,$this->month,$this->day=$day);
|
|
if ($day == 1)
|
|
{
|
|
$last = $this->datetime->get_weekday_start($this->year,$this->month,
|
|
$this->datetime->days_in_month($this->month,$this->year));
|
|
}
|
|
else
|
|
{
|
|
$last = $this->datetime->get_weekday_start($this->year,$this->month+1,$day);
|
|
}
|
|
// now we need to calculate the end of the last day of that week
|
|
// as simple $last += WEEK_s - 1; does NOT work, if daylight saving changes in that week!!!
|
|
$last = $this->bo->date2array($last);
|
|
$last['day'] += 6;
|
|
$last['hour'] = 23;
|
|
$last['min'] = $last['sec'] = 59;
|
|
unset($last['raw']); // otherwise date2ts does not calc raw new, but uses it
|
|
$last = $this->bo->date2ts($last);
|
|
}
|
|
|
|
/**
|
|
* Display a button to close one of multiple calendars shown
|
|
*
|
|
* Necessare to eg. close the calendar of a contact
|
|
*
|
|
* @param string $uid calendar to close
|
|
*/
|
|
private function close_button($uid)
|
|
{
|
|
return html::a_href(html::image('phpgwapi', 'close.button', 'Close','style="width: 12px; padding-top: 1px;"'), array(
|
|
'menuaction' => 'calendar.calendar_uiviews.index',
|
|
'close' => $uid,
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Displays the weekview, with 5 or 7 days
|
|
*
|
|
* @param int $days = 0 number of days to show, if 0 (default) the value from the URL or the prefs is used
|
|
* @param boolean|etemplate2 $home = false if not false return content suitable for home-page
|
|
*/
|
|
function week($days=0,$home=false)
|
|
{
|
|
if (!$days)
|
|
{
|
|
$days = isset($_GET['days']) ? $_GET['days'] : $this->cal_prefs['days_in_weekview'];
|
|
if ($days != 5) $days = 7;
|
|
if ($days != $this->cal_prefs['days_in_weekview']) // save the preference
|
|
{
|
|
$GLOBALS['egw']->preferences->add('calendar','days_in_weekview',$days);
|
|
$GLOBALS['egw']->preferences->save_repository();
|
|
$this->cal_prefs['days_in_weekview'] = $days;
|
|
}
|
|
}
|
|
if ($this->debug > 0) $this->bo->debug_message('uiviews::week(days=%1) date=%2',True,$days,$this->date);
|
|
|
|
if ($days <= 4) // next 4 days view
|
|
{
|
|
$wd_start = $this->first = $this->bo->date2ts($this->date);
|
|
$this->last = strtotime("+$days days",$this->first) - 1;
|
|
$view = $days == 1 ? 'day' : 'day4';
|
|
}
|
|
else
|
|
{
|
|
$wd_start = $this->first = $this->datetime->get_weekday_start($this->year,$this->month,$this->day);
|
|
if ($days <= 5) // no weekend-days
|
|
{
|
|
switch($this->cal_prefs['weekdaystarts'])
|
|
{
|
|
case 'Saturday':
|
|
$this->first = strtotime("+2 days",$this->first);
|
|
break;
|
|
case 'Sunday':
|
|
$this->first = strtotime("+1 day",$this->first);
|
|
break;
|
|
}
|
|
}
|
|
$this->last = strtotime("+$days days",$this->first) - 1;
|
|
$view = 'week';
|
|
}
|
|
|
|
$granularity = ($this->cal_prefs['interval'] ? (int)$this->cal_prefs['interval'] : 30);
|
|
|
|
$list = $this->cal_prefs['use_time_grid'];
|
|
if(!is_array($list))
|
|
{
|
|
$list = explode(',',$list);
|
|
}
|
|
if(is_array($list))
|
|
{
|
|
$granularity = in_array($view,$list) ? 0 : $granularity;
|
|
}
|
|
|
|
$search_params = array(
|
|
'start' => $this->first,
|
|
'end' => $this->last,
|
|
) + $this->search_params;
|
|
|
|
$users = $this->search_params['users'];
|
|
if (!is_array($users)) $users = array($users);
|
|
|
|
$content = array('view' => array());
|
|
|
|
if(!$home)
|
|
{
|
|
// Fill with the minimum needed 'weeks'
|
|
$min = max(
|
|
6, // Some months need 6 weeks for full display
|
|
$this->cal_prefs['multiple_weeks'], // WeekN view
|
|
$this->cal_prefs['week_consolidate'] // We collapse after this many users
|
|
);
|
|
for($i = 0; $i < $min; $i++)
|
|
{
|
|
$content['view'][] = array();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Always do 7 days for a week so scrolling works properly
|
|
$this->last = ($days == 4 ? $this->last : $search_params['end'] = strtotime("+$days days",$this->first) - 1);
|
|
if (count($users) == 1 || count($users) >= $this->cal_prefs['week_consolidate'] ||// for more then X users, show all in one row
|
|
$days == 1 // Showing just 1 day
|
|
)
|
|
{
|
|
$content['view'][] = (array)$this->tagWholeDayOnTop($this->bo->search($search_params)) +
|
|
array('owner' => $users);
|
|
}
|
|
else
|
|
{
|
|
foreach($this->_get_planner_users(false) as $uid => $label)
|
|
{
|
|
$search_params['users'] = $uid;
|
|
$content['view'][] = $this->tagWholeDayOnTop($this->bo->search($search_params))
|
|
+ array('owner' => $uid);
|
|
}
|
|
}
|
|
}
|
|
$tmpl = $home ? $home :new etemplate_new('calendar.view');
|
|
foreach($content['view'] as $index => $week)
|
|
{
|
|
$tmpl->setElementAttribute("view[$index]",'granularity',$granularity);
|
|
$tmpl->setElementAttribute("view[$index]",'show_weekend',$this->search_params['weekend']);
|
|
}
|
|
|
|
// Get the actions
|
|
$tmpl->setElementAttribute('view','actions',$this->get_actions());
|
|
|
|
$tmpl->exec(__METHOD__, $content);
|
|
}
|
|
|
|
/**
|
|
* Get todos via AJAX
|
|
*/
|
|
public static function ajax_get_todos($date, $owner)
|
|
{
|
|
$date = egw_time::to($date, 'array');
|
|
$ui = new calendar_uiviews();
|
|
$ui->year = $date['year'];
|
|
$ui->month = $date['month'];
|
|
$ui->day = $date['day'];
|
|
$ui->owner = (int)$owner;
|
|
|
|
$label = '';
|
|
$todos = $ui->get_todos($label);
|
|
egw_json_response::get()->data(array(
|
|
'label' => $label,
|
|
'todos' => $todos
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Query the open ToDo's via a hook from InfoLog or any other 'calendar_include_todos' provider
|
|
*
|
|
* @param array/string $todo_label label for the todo-box or array with 2 values: the label and a boolean show_all
|
|
* On return $todo_label contains the label for the todo-box
|
|
* @return string/boolean html with a table of open todo's or false if no hook availible
|
|
*/
|
|
function get_todos(&$todo_label)
|
|
{
|
|
$todos_from_hook = $GLOBALS['egw']->hooks->process(array(
|
|
'location' => 'calendar_include_todos',
|
|
'year' => $this->year,
|
|
'month' => $this->month,
|
|
'day' => $this->day,
|
|
'owner' => $this->owner // num. id of the user, not necessary current user
|
|
));
|
|
|
|
if(is_array($todo_label))
|
|
{
|
|
list($label,$showall)=$todo_label;
|
|
}
|
|
else
|
|
{
|
|
$label=$todo_label;
|
|
$showall=true;
|
|
}
|
|
$maxshow = (int)$GLOBALS['egw_info']['user']['preferences']['infolog']['mainscreen_maxshow'];
|
|
if($maxshow <= 0)
|
|
{
|
|
$maxshow=10;
|
|
}
|
|
//print_debug("get_todos(): label=$label; showall=$showall; max=$maxshow");
|
|
|
|
$content = $todo_label = '';
|
|
if (is_array($todos_from_hook) && count($todos_from_hook))
|
|
{
|
|
foreach($todos_from_hook as $todos)
|
|
{
|
|
$i = 0;
|
|
if (is_array($todos))
|
|
{
|
|
$todo_label = !empty($label) ? $label : lang("open ToDo's:");
|
|
|
|
foreach($todos as &$todo)
|
|
{
|
|
if(!$showall && ($i++ > $maxshow))
|
|
{
|
|
break;
|
|
}
|
|
$icons = '';
|
|
foreach($todo['icons'] as $name => $alt)
|
|
{
|
|
$icons .= ($icons?' ':'').$GLOBALS['egw']->html->image('infolog',$name,lang($alt),'border="0" width="15" height="15"');
|
|
}
|
|
$todo['icons'] = $icons;
|
|
$class = $class == 'row_on' ? 'row_off' : 'row_on';
|
|
if($todo['edit']) {
|
|
$todo['edit_size'] = $todo['edit']['size'];
|
|
unset($todo['edit']['size']);
|
|
$edit_icon_href = html::a_href( $icons, $todo['edit'],'',' data-todo="app|'.$width.'x'.$height.'" ');
|
|
$edit_href = html::a_href( $todo['title'], $todo['edit'],'',' data-todo="app|750x590" ');
|
|
$todo['edit'] = egw_framework::link('/index.php',$todo['edit'],true);
|
|
}
|
|
$icon_href = html::a_href($icons,$todo['view']);
|
|
$href = html::a_href($todo['title'], $todo['view']);
|
|
$content .= " <tr class=\"$class\">\n <td valign=\"top\" width=\"15%\" nowrap>".
|
|
($this->bo->printer_friendly?$icons:($edit_icon_href ? $edit_icon_href : $icon_href)).
|
|
"</td>\n <td>".($this->printer_friendly?$todo['title']:
|
|
$edit_href)."</td>\n </tr>\n";
|
|
/**
|
|
* ToDo: add delete and closing action
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $todos;
|
|
}
|
|
|
|
/**
|
|
* Calculates the vertical position based on the time
|
|
*
|
|
* workday start- and end-time, is taken into account, as well as timeGrids px_m - minutes per pixel param
|
|
*
|
|
* @param int $time in minutes
|
|
* @return float position in percent
|
|
*/
|
|
function time2pos($time)
|
|
{
|
|
if ($this->scroll_to_wdstart) // we display the complete day - thought only workday is visible without scrolling
|
|
{
|
|
return $this->rowHeight * (1 + $this->extraRows + $time/$this->granularity_m);
|
|
}
|
|
// time before workday => condensed in the first $this->extraRows rows
|
|
if ($this->wd_start > 0 && $time < $this->wd_start)
|
|
{
|
|
$pos = (($this->extraRows - $this->extraRowsOriginal + 1) + ($time / $this->wd_start * ($this->extraRowsOriginal - 1))) * $this->rowHeight;
|
|
}
|
|
// time after workday => condensed in the last row
|
|
elseif ($this->wd_end < 24*60 && $time > $this->wd_end+1*$this->granularity_m)
|
|
{
|
|
$pos = 100 - (($this->extraRows - $this->remBotExtraRows) * $this->rowHeight * (1 - ($time - $this->wd_end) / (24*60 - $this->wd_end)));
|
|
}
|
|
// time during the workday => 2. row on (= + granularity)
|
|
else
|
|
{
|
|
$pos = $this->rowHeight * (1+$this->extraRows+($time-$this->wd_start)/$this->granularity_m);
|
|
}
|
|
$pos = round($pos,1);
|
|
|
|
if ($this->debug > 3) $this->bo->debug_message('uiviews::time2pos(%1)=%2',False,$time,$pos);
|
|
|
|
return $pos;
|
|
}
|
|
|
|
/**
|
|
* Calculates the height of a difference between 2 times
|
|
*
|
|
* workday start- and end-time, is taken into account, as well as timeGrids px_m - minutes per pixel param
|
|
*
|
|
* @param int $start time in minutes
|
|
* @param int $end time in minutes
|
|
* @param int $minimum = 0 minimum height
|
|
* @return float height in percent
|
|
*/
|
|
function times2height($start,$end,$minimum=0)
|
|
{
|
|
$minimum = $this->rowHeight;
|
|
$height = $this->time2pos($end) - $this->time2pos($start);
|
|
|
|
if ($this->debug > 3) $this->bo->debug_message('uiviews::times2height(%1,%2,min=%3)=%4',False,$start,$end,$minimum,$height);
|
|
|
|
return $height >= $minimum ? $height : $minimum;
|
|
}
|
|
|
|
/**
|
|
* Creates a grid with rows for the time, columns for (multiple) days containing events
|
|
*
|
|
* Uses the dayColWidget to display each day.
|
|
*
|
|
* @param $daysEvents array with subarrays of events for each day to show, day as YYYYMMDD as key
|
|
* @param int $granularity_m = 30 granularity in minutes of the rows
|
|
* @param int $height = 400 height of the widget
|
|
* @param string $indent = '' string for correct indention
|
|
* @param string $title = '' title of the time-grid
|
|
* @param int/array $owner = 0 owner of the calendar (default 0 = $this->owner) or array with owner for each column
|
|
* @param boolean $last = true last timeGrid displayed, default true
|
|
*/
|
|
function &timeGridWidget($daysEvents,$granularity_m=30,$height=400,$indent='',$title='',$owner=0,$last=true)
|
|
{
|
|
if ($this->debug > 1 || $this->debug==='timeGridWidget') $this->bo->debug_message('uiviews::timeGridWidget(events=%1,granularity_m=%2,height=%3,,title=%4)',True,$daysEvents,$granularity_m,$height,$title);
|
|
|
|
// determine if the browser supports scrollIntoView: IE4+, firefox1.0+ and safari2.0+ does
|
|
// then show all hours in a div of the size of the workday and scroll to the workday start
|
|
// still disabled, as things need to be re-aranged first, to that the column headers are not scrolled
|
|
$this->scroll_to_wdstart = false;/*$this->use_time_grid && (html::$user_agent == 'msie' ||
|
|
html::$user_agent == 'mozilla' && html::ua_version >= 5.0 ||
|
|
html::$user_agent == 'safari' && html::ua_version >= 2.0);*/
|
|
|
|
if ($this->scroll_to_wdstart)
|
|
{
|
|
$this->extraRows = 0; // no extra rows necessary
|
|
$this->remBotExtraRows = 0;
|
|
$overflow = 'overflow: scroll;';
|
|
}
|
|
$this->granularity_m = $granularity_m;
|
|
$this->display_start = $this->wd_start - ($this->extraRows * $this->granularity_m);
|
|
$this->display_end = $this->wd_end + (($this->extraRows - $this->remBotExtraRows) * $this->granularity_m);
|
|
|
|
if (!$this->wd_end) $this->wd_end = 1440;
|
|
$totalDisplayMinutes = $this->wd_end - $this->wd_start;
|
|
$this->rowsToDisplay = ($totalDisplayMinutes/$granularity_m)+2+2*$this->extraRows - $this->remBotExtraRows;
|
|
$this->rowHeight = round(100/$this->rowsToDisplay,1);
|
|
|
|
// ensure a minimum height of each row
|
|
if ($height < ($this->rowsToDisplay+1) * 12)
|
|
{
|
|
$height = ($this->rowsToDisplay+1) * 12;
|
|
}
|
|
$html = $indent.'<div class="calendar_calTimeGrid" style="height: '.$height.'px;'.$overflow.'">'."\n";
|
|
|
|
$html .= $indent."\t".'<div class="calendar_calGridHeader" style="height: '.
|
|
$this->rowHeight.'%;">'.$title."</div>\n";
|
|
|
|
if ($this->use_time_grid)
|
|
{
|
|
$off = false; // Off-row means a different bgcolor
|
|
$add_links = count($daysEvents) == 1;
|
|
|
|
// the hour rows
|
|
for($t = $this->scroll_to_wdstart ? 0 : $this->wd_start,$i = 1+$this->extraRows;
|
|
$t <= $this->wd_end || $this->scroll_to_wdstart && $t < 24*60;
|
|
$t += $this->granularity_m,++$i)
|
|
{
|
|
$set_id = '';
|
|
if ($t == $this->wd_start)
|
|
{
|
|
list($id) = @each($daysEvents);
|
|
if (is_numeric($id))
|
|
{
|
|
$id = 'wd_start_'.$id;
|
|
$set_id = ' id="'.$id.'"';
|
|
}
|
|
}
|
|
$html .= $indent."\t".'<div'.$set_id.' class="calendar_calTimeRow'.($off ? 'Off row_off' : ' row_on').
|
|
'" style="height: '.$this->rowHeight.'%; top:'. $i*$this->rowHeight .'%;">'."\n";
|
|
// show time for full hours, allways for 45min interval and at least on every 3 row
|
|
$time = '';
|
|
static $show = array(
|
|
5 => array(0,15,30,45),
|
|
10 => array(0,30),
|
|
15 => array(0,30),
|
|
45 => array(0,15,30,45),
|
|
);
|
|
if (!isset($show[$this->granularity_m]) ? $t % 60 == 0 : in_array($t % 60,$show[$this->granularity_m]))
|
|
{
|
|
$time = $GLOBALS['egw']->common->formattime(sprintf('%02d',$t/60),sprintf('%02d',$t%60));
|
|
}
|
|
if ($add_links) $time = $this->add_link($time,$this->date,(int) ($t/60),$t%60);
|
|
$html .= $indent."\t\t".'<div class="calendar_calTimeRowTime">'.$time."</div>\n";
|
|
$html .= $indent."\t</div>\n"; // calendar_calTimeRow
|
|
$off = !$off;
|
|
}
|
|
}
|
|
if (is_array($daysEvents) && count($daysEvents))
|
|
{
|
|
$numberOfDays = count($daysEvents);
|
|
$dayColWidth = 100/$numberOfDays;
|
|
|
|
$dayCols_width = $width - $this->timeRow_width - 1;
|
|
|
|
$html .= $indent."\t".'<div id="calendar_calDayCols" class="calendar_calDayCols'.
|
|
($this->use_time_grid ? ($this->bo->common_prefs['timeformat'] == 12 ? '12h' : '') : 'NoTime').'">'."\n";
|
|
|
|
if (html::$user_agent == 'msie') // necessary IE hack - stupid thing ...
|
|
{
|
|
// Lars Kneschke 2005-08-28
|
|
// why do we use a div in a div which has the same height and width???
|
|
// To make IE6 happy!!! Without the second div you can't use
|
|
// style="left: 50px; right: 0px;"
|
|
//$html .= '<div style="width=100%; height: 100%;">'."\n";
|
|
|
|
// Ralf Becker 2006-06-19
|
|
// Lars original typo "width=100%; height: 100%;" is important ;-)
|
|
// means you width: 100% does NOT work, you need no width!
|
|
$html .= '<div style="height: 100%;">'."\n";
|
|
}
|
|
$dayCol_width = $dayCols_width / count($daysEvents);
|
|
$n = 0;
|
|
foreach($daysEvents as $day => $events)
|
|
{
|
|
$this->wholeDayPosCounter=1;
|
|
$short_title = count($daysEvents) > 1;
|
|
$col_owner = $owner;
|
|
if (!is_numeric($day))
|
|
{
|
|
$short_title = $day;
|
|
$day = $this->date;
|
|
$col_owner = $owner[$n];
|
|
}
|
|
$html .= $this->dayColWidget($day,$events,$n*$dayColWidth,
|
|
$dayColWidth,$indent."\t\t",$short_title,++$on_off & 1,$col_owner);
|
|
++$n;
|
|
}
|
|
if (html::$user_agent == 'msie') $html .= "</div>\n";
|
|
|
|
$html .= $indent."\t</div>\n"; // calendar_calDayCols
|
|
}
|
|
$html .= $indent."</div>\n"; // calendar_calTimeGrid
|
|
|
|
if ($this->scroll_to_wdstart)
|
|
{
|
|
$html .= "<script>\n\tdocument.getElementById('$id').scrollIntoView();\n";
|
|
if ($last) // last timeGrid --> scroll whole document back up
|
|
{
|
|
$html .= "\tdocument.getElementById('divMain').scrollIntoView();\n";
|
|
}
|
|
$html .= "</script>\n";
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Sorts the events of a day into columns with non-overlapping events, the events
|
|
* are already sorted by start-time
|
|
*
|
|
* @param string/int $day_ymd date as Ymd
|
|
* @param array &$events events to split into non-overlapping groups
|
|
*/
|
|
function getEventCols($day_ymd, &$events)
|
|
{
|
|
$day_start = $this->bo->date2ts((string)$day_ymd);
|
|
|
|
// if daylight saving is switched on or off, correct $day_start
|
|
// gives correct times after 2am, times between 0am and 2am are wrong
|
|
if(($daylight_diff = $day_start + 12*HOUR_s - ($this->bo->date2ts($day_ymd."T120000"))))
|
|
{
|
|
$day_start -= $daylight_diff;
|
|
}
|
|
|
|
$eventCols = $col_ends = array();
|
|
foreach($events as $event)
|
|
{
|
|
$event['multiday'] = False;
|
|
$event['start_m'] = ($event['start'] - $day_start) / 60;
|
|
if ($event['start_m'] < 0)
|
|
{
|
|
$event['start_m'] = 0;
|
|
$event['multiday'] = True;
|
|
}
|
|
$event['end_m'] = ($event['end'] - $day_start) / 60;
|
|
if ($event['end_m'] >= 24*60)
|
|
{
|
|
$event['end_m'] = 24*60-1;
|
|
$event['multiday'] = True;
|
|
}
|
|
if ($this->use_time_grid && !$event['whole_day_on_top'])
|
|
{
|
|
for($c = 0; $event['start_m'] < $col_ends[$c]; ++$c);
|
|
$col_ends[$c] = $event['end_m'];
|
|
}
|
|
else
|
|
{
|
|
$c = 0; // without grid we only use one column
|
|
}
|
|
$eventCols[$c][] = $event;
|
|
}
|
|
return $eventCols;
|
|
}
|
|
|
|
/**
|
|
* Creates (if necessary multiple) columns for the events of a day
|
|
*
|
|
* Uses the eventColWidget to display each column.
|
|
*
|
|
* @param string/int $day_ymd date as Ymd
|
|
* @param array $events of events to show
|
|
* @param int $pleft start of the widget
|
|
* @param int $pwidth width of the widget
|
|
* @param string $indent string for correct indention
|
|
* @param boolean/string $short_title = True should we add a label (weekday, day) with link to the day-view above each day or string with title
|
|
* @param boolean $on_off = false start with row_on or row_off, default false=row_off
|
|
* @param int $owner = 0 if != 0 owner to add to the add-event link
|
|
*/
|
|
function dayColWidget($day_ymd,$events,$pleft,$pwidth,$indent,$short_title=True,$on_off=False,$owner=0)
|
|
{
|
|
if ($this->debug > 1 || $this->debug==='dayColWidget') $this->bo->debug_message('uiviews::dayColWidget(%1,%2,left=%3,width=%4,)',False,$day_ymd,$events,$pleft,$pwidth);
|
|
|
|
$html = $indent.'<div id="calColumn'.$this->calColumnCounter++.'" class="calendar_calDayCol" '.'data-sortable-id="'.$owner.'" style="left: '.$pleft.
|
|
'%;width: '.$pwidth.'%;">'."\n";
|
|
|
|
// Creation of the header-column with date, evtl. holiday-names and a matching background-color
|
|
$ts = $this->bo->date2ts((string)$day_ymd);
|
|
$title = !is_bool($short_title) ? $short_title :
|
|
($short_title ? lang(adodb_date('l',$ts)).' '.adodb_date('d.',$ts) : $this->bo->long_date($ts,0,false,true));
|
|
|
|
$day_view = array(
|
|
'menuaction' => 'calendar.calendar_uiviews.day',
|
|
'date' => $day_ymd,
|
|
);
|
|
$this->_day_class_holiday($day_ymd,$class,$holidays);
|
|
// the weekday and date
|
|
if (!$short_title && $holidays) $title .= html::htmlspecialchars(': '.$holidays);
|
|
|
|
if ($short_title === true)
|
|
{
|
|
if ($this->allowEdit)
|
|
{
|
|
$title = html::a_href($title,$day_view,'',
|
|
!isset($this->holidays[$day_ymd])?' title="'.$this->bo->long_date($ts,0,false,true).'"':'');
|
|
}
|
|
}
|
|
elseif ($short_title === false)
|
|
{
|
|
// add arrows to go to the previous and next day (dayview only)
|
|
$day_view['date'] = $this->bo->date2string($ts -= 12*HOUR_s);
|
|
if ($this->allowEdit)
|
|
{
|
|
$title = html::a_href(html::image('phpgwapi','left',$this->bo->long_date($ts)),$day_view).' <span> '.$title;
|
|
}
|
|
else
|
|
{
|
|
$title = $day_view.' <span> '.$title;
|
|
}
|
|
$day_view['date'] = $this->bo->date2string($ts += 48*HOUR_s);
|
|
if ($this->allowEdit)
|
|
{
|
|
$title .= ' </span> '.html::a_href(html::image('phpgwapi','right',$this->bo->long_date($ts)),$day_view);
|
|
}
|
|
else
|
|
{
|
|
$title .= ' </span> '.$day_view;
|
|
}
|
|
}
|
|
if (is_bool($short_title) || ($short_title != "")) {
|
|
$html .= $indent."\t".'<div style="height: '. $this->rowHeight .'%;" class="calendar_calDayColHeader '.$class.'"'.
|
|
($holidays ? ' title="'.html::htmlspecialchars($holidays).'"':'').'>'.$title."</div>\n";
|
|
}
|
|
|
|
if ($this->use_time_grid)
|
|
{
|
|
// drag and drop: check if the current user has EDIT permissions on the grid
|
|
if($owner)
|
|
{
|
|
$dropPermission = $this->bo->check_perms(EGW_ACL_EDIT,0,$owner);
|
|
}
|
|
else
|
|
{
|
|
$dropPermission = true;
|
|
}
|
|
|
|
// adding divs to click on for each row / time-span
|
|
for($t = $this->scroll_to_wdstart ? 0 : $this->wd_start,$i = 1 + $this->extraRows;
|
|
$t <= $this->wd_end || $this->scroll_to_wdstart && $t < 24*60;
|
|
$t += $this->granularity_m,++$i)
|
|
{
|
|
$linkData = array(
|
|
'menuaction' =>'calendar.calendar_uiforms.edit',
|
|
'date' => $day_ymd,
|
|
'hour' => sprintf("%02d",floor($t / 60)),
|
|
'minute' => sprintf("%02d",floor($t % 60)),
|
|
);
|
|
if ($owner) $linkData['owner'] = $owner;
|
|
|
|
$droppableDateTime = $linkData['date'] . "T" . $linkData['hour'] . $linkData['minute'];
|
|
$droppableID='drop_'.$droppableDateTime.'_O'.($owner<0?str_replace('-','group',$owner):$owner);
|
|
|
|
$html .= $indent."\t".'<div id="' . $droppableID . '" style="height:'. $this->rowHeight .'%; top: '. $i*$this->rowHeight .
|
|
'%;" class="calendar_calAddEvent"';
|
|
if ($this->allowEdit)
|
|
{
|
|
$html .= ' data-date=' .$linkData['date'].'|'.$linkData['hour'].'|'.$linkData['minute'];
|
|
}
|
|
if($dropPermission && $owner)
|
|
{
|
|
$html .= ' data-owner ='.$owner;
|
|
|
|
}
|
|
$html .= '></div>'."\n";
|
|
|
|
}
|
|
}
|
|
|
|
$eventCols = $this->getEventCols($day_ymd,$events);
|
|
// displaying all event columns of the day
|
|
foreach($eventCols as $n => $eventCol)
|
|
{
|
|
// equal sized columns
|
|
//$width = 95.0 / count($eventCols);
|
|
//$left = 2.5 + $n * $width;
|
|
// alternative overlapping columns
|
|
$left = 2.5 + (1.5 * 100 / $pwidth);
|
|
if (count($eventCols) == 1)
|
|
{
|
|
$width = 100 - $left;
|
|
}
|
|
else
|
|
{
|
|
$width = !$n ? 70 : 50;
|
|
$left += $n * (100.0-$left) / count($eventCols);
|
|
}
|
|
if ($left + $width > 100.0) $width = 100.0 - $left;
|
|
//echo "<p>count(\$eventCols)=".count($eventCols).", n=$n, pWidth=$pwidth, pLeft=$pleft, width=$width, left=$left</p>\n";
|
|
$html .= $this->eventColWidget($eventCol,$left,$width,$indent."\t",
|
|
$owner ? $owner : $this->user, 20+10*$n);
|
|
}
|
|
$html .= $indent."</div>\n"; // calendar_calDayCol
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* get the CSS class and holidays for a given day
|
|
*
|
|
* @param string $day_ymd date
|
|
* @param string &$class class to use
|
|
* @param string &$holidays commaseparted holidays or empty if none
|
|
* @param boolean $only_weekend = false show only the weekend in header-color, otherwise every second days is shown too
|
|
* @param boolean $show_bdays = true If available, also show birthdays (or hide Bdays)
|
|
* Note that this is not the place to disable a preference.
|
|
* If the preferences allow birthdays to be displayed, they are cached within the holidays structure.
|
|
* This setting just suppressing the available data in the view.
|
|
*/
|
|
function _day_class_holiday($day_ymd,&$class,&$holidays,$only_weekend=false,$show_bdays=true)
|
|
{
|
|
$class = $holidays = '';
|
|
$bday = false;
|
|
if (isset($this->holidays[$day_ymd]))
|
|
{
|
|
$h = array();
|
|
foreach($this->holidays[$day_ymd] as $holiday)
|
|
{
|
|
if (isset($holiday['birthyear']))
|
|
{
|
|
if ($show_bdays)
|
|
{
|
|
$bday = true;
|
|
|
|
//If the birthdays are already displayed as event, don't
|
|
//show them in the caption
|
|
if (!$this->display_holiday_event_types['bdays'])
|
|
{
|
|
$h[] = $holiday['name'];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$class = 'calendar_calHoliday';
|
|
|
|
//If the birthdays are already displayed as event, don't
|
|
//show them in the caption
|
|
if (!$this->display_holiday_event_types['hdays'])
|
|
{
|
|
$h[] = $holiday['name'];
|
|
}
|
|
}
|
|
}
|
|
$holidays = implode(', ',$h);
|
|
}
|
|
if (!$class)
|
|
{
|
|
if ($day_ymd == $this->bo->date2string($this->bo->now_su))
|
|
{
|
|
$class = 'calendar_calToday';
|
|
}
|
|
else
|
|
{
|
|
$day = (int) date('w',$this->bo->date2ts((string) $day_ymd));
|
|
|
|
if ($only_weekend)
|
|
{
|
|
$class = $day == 0 || $day == 6 ? 'th' : 'row_off';
|
|
}
|
|
else
|
|
{
|
|
$class = $day & 1 ? 'row_on' : 'th';
|
|
}
|
|
}
|
|
}
|
|
if ($bday) $class .= ' calendar_calBirthday';
|
|
}
|
|
|
|
/**
|
|
* Creates colunm for non-overlaping (!) events
|
|
*
|
|
* Uses the eventWidget to display each event.
|
|
*
|
|
* @param array $events of events to show
|
|
* @param int $left start of the widget
|
|
* @param int $width width of the widget
|
|
* @param string $indent string for correct indention
|
|
* @param int $owner owner of the eventCol
|
|
*/
|
|
function eventColWidget($events,$left,$width,$indent,$owner,$z_index=null)
|
|
{
|
|
if ($this->debug > 1 || $this->debug==='eventColWidget') $this->bo->debug_message('uiviews::eventColWidget(%1,left=%2,width=%3,)',False,$events,$left,$width);
|
|
|
|
$html = $indent.'<div class="calendar_calEventCol" style="left: '.$left.'%; width:'.$width.'%;'.
|
|
// the "calendar_calEventCol" spans across a whole column (as the name suggests) - setting the
|
|
// z-index here would give the whole invisible column a z-index and thus the underlying
|
|
// regions are not clickable anymore. The z_index has now moved the the eventWidget
|
|
// function.
|
|
//(!is_null($z_index) ? ' z-index:'.$z_index.';' : '').
|
|
(!$this->use_time_grid ? ' top: '.$this->rowHeight.'%;' : '').'">'."\n";
|
|
foreach($events as $event)
|
|
{
|
|
$html .= $this->eventWidget($event,$width,$indent."\t",$owner,false,'event_widget',$z_index);
|
|
}
|
|
$html .= $indent."</div>\n";
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Shows one event
|
|
*
|
|
* The display of the event and it's tooltip is done via the event_widget.tpl template
|
|
*
|
|
* @param $event array with the data of event to show
|
|
* @param $width int width of the widget
|
|
* @param string $indent string for correct indention
|
|
* @param int $owner owner of the calendar the event is in
|
|
* @param boolean $return_array = false should an array with keys(tooltip,popup,html) be returned or the complete widget as string
|
|
* @param string $block = 'event_widget' template used the render the widget
|
|
* @param int $z_index is the z-index of the drag-drobable outer box of the event.
|
|
* @return string/array
|
|
*/
|
|
function eventWidget($event,$width,$indent,$owner,$return_array=false,$block='event_widget',$z_index=null)
|
|
{
|
|
if ($this->debug > 1 || $this->debug==='eventWidget') $this->bo->debug_message('uiviews::eventWidget(%1,width=%2)',False,$event,$width);
|
|
|
|
if($this->use_time_grid && $event['whole_day_on_top']) $block = 'event_widget_wholeday_on_top';
|
|
|
|
static $tpl = False;
|
|
if (!$tpl)
|
|
{
|
|
$tpl = new Template(common::get_tpl_dir('calendar'));
|
|
|
|
$tpl->set_file('event_widget_t','event_widget.tpl');
|
|
$tpl->set_block('event_widget_t','event_widget');
|
|
$tpl->set_block('event_widget_t','event_widget_wholeday_on_top');
|
|
$tpl->set_block('event_widget_t','event_tooltip');
|
|
$tpl->set_block('event_widget_t','planner_event');
|
|
}
|
|
if (($return_array || $event['start_m'] == 0) && $event['end_m'] >= 24*60-1)
|
|
{
|
|
if ($return_array && $event['end_m'] > 24*60)
|
|
{
|
|
$timespan = $this->bo->format_date($event['start'],false).' - '.$this->bo->format_date($event['end']);
|
|
}
|
|
else
|
|
{
|
|
$timespan = lang('all day');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$timespan = $this->bo->timespan($event['start_m'],$event['end_m']);
|
|
}
|
|
$icons = array();
|
|
if(!(int)$event['id'] && preg_match('/^([a-z_-]+)([0-9]+)$/i',$event['id'],$matches))
|
|
{
|
|
$app = $matches[1];
|
|
$app_id = $matches[2];
|
|
if (($is_private = calendar_bo::integration_get_private($app,$app_id,$event)))
|
|
{
|
|
$icons[] = html::image('calendar','private');
|
|
}
|
|
else
|
|
{
|
|
$icons = self::integration_get_icons($app,$app_id,$event);
|
|
}
|
|
}
|
|
elseif($event['id'])
|
|
{
|
|
if (($is_private = !$this->bo->check_perms(EGW_ACL_READ,$event)))
|
|
{
|
|
$icons = array(html::image('calendar','private'));
|
|
}
|
|
else
|
|
{
|
|
$icons = $this->event_icons($event);
|
|
}
|
|
}
|
|
$cats = $this->bo->categories($this->categories->check_list(EGW_ACL_READ, $event['category']),$color);
|
|
// these values control varius aspects of the geometry of the eventWidget
|
|
$small_trigger_width = 120 + 20*count($icons);
|
|
$corner_radius=$width > $small_trigger_width ? 10 : 5;
|
|
$header_height=$width > $small_trigger_width ? 19 : 12; // multi_3 icon has a height of 19=16+2*1padding+1border !
|
|
if (!$return_array)
|
|
{
|
|
$height = $this->times2height($event['start_m'],$event['end_m'],$header_height);
|
|
}
|
|
//$body_height = max(0,$height - $header_height - $corner_radius);
|
|
$border=1;
|
|
$headerbgcolor = $color ? $color : '#808080';
|
|
$headercolor = self::brightness($headerbgcolor) > 128 ? 'black' : 'white';
|
|
// the body-colors (gradient) are calculated from the headercolor, which depends on the cat of an event
|
|
$bodybgcolor1 = $this->brighter($headerbgcolor,$headerbgcolor == '#808080' ? 100 : 170);
|
|
$bodybgcolor2 = $this->brighter($headerbgcolor,220);
|
|
|
|
// mark event as invitation, by NOT using category based background color, but plain white
|
|
if ($event['participants'][$this->user][0] == 'U')
|
|
{
|
|
$bodybgcolor1 = $bodybgcolor2 = 'white';
|
|
}
|
|
|
|
// get status class of event: calendar_calEventAllAccepted, calendar_calEventAllAnswered or calendar_calEventSomeUnknown
|
|
$status_class = 'calendar_calEventAllAccepted';
|
|
foreach($event['participants'] as $id => $status)
|
|
{
|
|
if ($id < 0) continue; // as we cant accept/reject groups, we dont care about them here
|
|
|
|
calendar_so::split_status($status,$quantity,$role);
|
|
|
|
switch ($status)
|
|
{
|
|
case 'A':
|
|
case '': // app without status
|
|
break;
|
|
case 'U':
|
|
$status_class = 'calendar_calEventSomeUnknown';
|
|
break 2; // break foreach
|
|
default:
|
|
$status_class = 'calendar_calEventAllAnswered';
|
|
break;
|
|
}
|
|
}
|
|
// seperate each participant types
|
|
$part_array = array();
|
|
if ($this->allowEdit)
|
|
{
|
|
foreach($this->bo->participants($event) as $part_key => $participant)
|
|
{
|
|
if(is_numeric($part_key))
|
|
{
|
|
$part_array[lang('Participants')][$part_key] = $participant;
|
|
}
|
|
elseif(isset($this->bo->resources[$part_key[0]]))
|
|
{
|
|
$part_array[((isset($this->bo->resources[$part_key[0]]['participants_header'])) ? $this->bo->resources[$part_key[0]]['participants_header'] : lang($this->bo->resources[$part_key[0]]['app']))][$part_key] = $participant;
|
|
}
|
|
}
|
|
foreach($part_array as $part_group => $participant)
|
|
{
|
|
$participants .= $this->add_nonempty($participant,$part_group,True,False,false);
|
|
}
|
|
}
|
|
// as we only deal with percentual widht, we consider only the full dayview (1 colum) as NOT small
|
|
$small = $this->view != 'day' || $width < 50;
|
|
// $small = $width <= $small_trigger_width
|
|
|
|
$small_height = $this->use_time_grid && ( $event['end_m']-$event['start_m'] < 2*$this->granularity_m ||
|
|
$event['end_m'] <= $this->wd_start || $event['start_m'] >= $this->wd_end);
|
|
|
|
$tpl->set_var(array(
|
|
// event-content, some of it displays only if it really has content or is needed
|
|
'owner' => $GLOBALS['egw']->common->grab_owner_name($event['owner']),
|
|
'header_icons' => $small ? '' : implode("",$icons),
|
|
'body_icons' => $small ? implode("\n",$icons) : '',
|
|
'icons' => implode('',$icons),
|
|
'timespan' => $timespan,
|
|
'title' => ($title = !$is_private ? html::htmlspecialchars($event['title']) : lang('private')),
|
|
'header' => $small_height ? $title : $timespan,
|
|
'description' => !$is_private ? nl2br(html::htmlspecialchars($event['description'])) : '',
|
|
'location' => !$is_private ? $this->add_nonempty($event['location'],lang('Location')) : '',
|
|
'participants' => $participants,
|
|
'times' => !$event['multiday'] ? $this->add_nonempty($this->bo->timespan($event['start_m'],$event['end_m'],true),lang('Time')) :
|
|
$this->add_nonempty($this->bo->format_date($event['start']),lang('Start')).
|
|
$this->add_nonempty($this->bo->format_date($event['end']),lang('End')),
|
|
'multidaytimes' => !$event['multiday'] ? '' :
|
|
$this->add_nonempty($this->bo->format_date($event['start']),lang('Start')).
|
|
$this->add_nonempty($this->bo->format_date($event['end']),lang('End')),
|
|
'category' => !$is_private ? $this->add_nonempty($cats,lang('Category')) : '',
|
|
// the tooltip is based on the content of the actual widget, this way it takes no extra bandwidth/volum
|
|
// 'tooltip' => html::tooltip(False,False,array('BorderWidth'=>0,'Padding'=>0)),
|
|
// various aspects of the geometry or style
|
|
'corner_radius' => $corner_radius.'px',
|
|
'header_height' => $header_height.'px',
|
|
//'body_height' => $body_height.'px',
|
|
'height' => $height,
|
|
'width' => ($width-20).'px',
|
|
'border' => $border,
|
|
'bordercolor' => $headerbgcolor,
|
|
'headerbgcolor' => $headerbgcolor,
|
|
'headercolor' => $headercolor,
|
|
'bodybackground' => ($background = 'url('.$GLOBALS['egw_info']['server']['webserver_url'].
|
|
'/calendar/inc/gradient.php?color1='.urlencode($bodybgcolor1).'&color2='.urlencode($bodybgcolor2).
|
|
'&width='.$width.') repeat-y '.$bodybgcolor2),
|
|
'Small' => $small ? 'Small' : '', // to use in css class-names
|
|
'indent' => $indent."\t",
|
|
'status_class' => $status_class,
|
|
));
|
|
/* not used at the moment
|
|
foreach(array(
|
|
'upper_left'=>array('width'=>-$corner_radius,'height'=>$header_height,'border'=>0,'bgcolor'=>$headerbgcolor),
|
|
'upper_right'=>array('width'=>$corner_radius,'height'=>$header_height,'border'=>0,'bgcolor'=>$headerbgcolor),
|
|
'lower_left'=>array('width'=>-$corner_radius,'height'=>-$corner_radius,'border'=>$border,'color'=>$headerbgcolor,'bgcolor'=>$bodybgcolor1),
|
|
'lower_right'=>array('width'=>$corner_radius,'height'=>-$corner_radius,'border'=>$border,'color'=>$headerbgcolor,'bgcolor'=>$bodybgcolor2),
|
|
) as $name => $data)
|
|
{
|
|
$tpl->set_var($name.'_corner',$GLOBALS['egw_info']['server']['webserver_url'].
|
|
'/calendar/inc/round_corners.php?width='.$data['width'].'&height='.$data['height'].
|
|
'&bgcolor='.urlencode($data['bgcolor']).
|
|
(isset($data['color']) ? '&color='.urlencode($data['color']) : '').
|
|
(isset($data['border']) ? '&border='.urlencode($data['border']) : ''));
|
|
}
|
|
*/
|
|
// Add event description to cal event view body if the event is long enough (ATM 3 times longer than interval)
|
|
// to be able to show some lines of description text
|
|
if ($event['whole_day'] || ($event['end'] - $event['start']) > ($this->cal_prefs['interval'] * 3 * 60))
|
|
{
|
|
$tpl->set_var('bodydescription', !$is_private ? nl2br(html::htmlspecialchars($event['description'])) : '');
|
|
}
|
|
// set the bodydescription to empty if it is not visible
|
|
else
|
|
{
|
|
$tpl->set_var('bodydescription', '');
|
|
}
|
|
|
|
$tooltip = $tpl->fp('tooltip','event_tooltip');
|
|
$html = $tpl->fp('out',$block);
|
|
|
|
if ($is_private || !$this->allowEdit)
|
|
{
|
|
$popup = '';
|
|
}
|
|
elseif($app && $app_id)
|
|
{
|
|
$popup = $this->integration_get_popup($app,$app_id);
|
|
}
|
|
else
|
|
{
|
|
if ($event['recur_type'] != MCAL_RECUR_NONE)
|
|
{
|
|
$popup = $event['id']."|r";
|
|
}
|
|
else
|
|
{
|
|
$popup = $event['id']."|n";
|
|
}
|
|
}
|
|
$tooltip = html::htmlspecialchars($tooltip, true); // true=need double-encoding, as it is transported as attribute!
|
|
//_debug_array($event);
|
|
|
|
if ($return_array)
|
|
{
|
|
return array(
|
|
'tooltip' => $tooltip,
|
|
'popup' => $popup,
|
|
'html' => $html,
|
|
'private' => $is_private,
|
|
'color' => $color,
|
|
);
|
|
}
|
|
|
|
$draggableID = $event['id'].'_O'.$event['owner'].'_C'.($owner<0?str_replace('-','group',$owner):$owner);
|
|
|
|
if ($this->use_time_grid)
|
|
{
|
|
if($event['whole_day_on_top'])
|
|
{
|
|
$style = 'top: '.($this->rowHeight*$this->wholeDayPosCounter).'%; height: '.$this->rowHeight.'%;';
|
|
$this->wholeDayPosCounter++;
|
|
}
|
|
else
|
|
{ $view_link = $GLOBALS['egw']->link('/index.php',array('menuaction'=>'calendar.calendar_uiforms.edit','cal_id'=>$event['id'],'date'=>$this->bo->date2string($event['start'])));
|
|
|
|
$style = 'top: '.$this->time2pos($event['start_m']).'%; height: '.$height.'%;';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$style = 'position: relative; margin-top: 3px;';
|
|
}
|
|
|
|
$prefix_icon = isset($event['prepend_icon']) ? $event['prepend_icon'] : '';
|
|
|
|
$z_index = is_null($z_index) ? 20 : (int)$z_index;
|
|
|
|
if ($this->use_time_grid &&
|
|
((int)$event['id'] || substr($event['id'],0,7) == 'infolog') && $this->bo->check_perms(EGW_ACL_EDIT,$event))
|
|
{
|
|
if (!$event['whole_day_on_top'] &&
|
|
!$event['whole_day'] &&
|
|
!$event['recur_type'])
|
|
{
|
|
$draggableID = 'drag_'.$event['id'].'_O'.$event['owner'].'_C'.($owner<0?str_replace('-','group',$owner):$owner);
|
|
}
|
|
else
|
|
{
|
|
$draggableID = 'drag_'.$event['id'].'_O'.$event['owner'].'_C'.($owner<0?str_replace('-','group',$owner):$owner);
|
|
|
|
}
|
|
}
|
|
if (!$event['whole_day_on_top'] &&
|
|
!$event['whole_day'])
|
|
{
|
|
// S represents Series
|
|
// '' represents Single
|
|
$eventTypeTag = $event['recur_type']?'S':'';
|
|
}
|
|
else if (!$event['recur_type'])
|
|
{
|
|
// WD represents Whole Day
|
|
$eventTypeTag = 'WD';
|
|
}
|
|
else
|
|
{
|
|
// WDS represents Whole Day Series (recurrent whole day event)
|
|
$eventTypeTag = 'WDS';
|
|
}
|
|
// Helps client-side to bind handler to events with specific types tag
|
|
$resizableHelper = $this->bo->date2string($event['start']). '|' .$this->bo->format_date($event['start'],false) . '|' . $this->cal_prefs['interval'].'|'.$eventTypeTag;
|
|
|
|
$html = $indent.'<div id="'.$draggableID.'" data-tooltip ="'.$tooltip .'" data-resize="'.$resizableHelper.'" class="calendar_calEvent'.($is_private ? 'Private' : '').' '.$status_class.
|
|
'" style="'.$style.' border-color: '.$headerbgcolor.'; background: '.$background.'; z-index: '.$z_index.';"'.
|
|
'>'.$prefix_icon."\n".$html."\n".
|
|
$indent."</div>"."\n";
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Get onclick attribute to open integration item for edit
|
|
*
|
|
* Name of the attribute is 'edit_link' and it should be an array with values for keys:
|
|
* - 'edit' => array('menuaction' => 'app.class.method')
|
|
* - 'edit_id' => 'app_id'
|
|
* - 'edit_popup' => '400x300' (optional)
|
|
*
|
|
* @param string $app
|
|
* @param int|string $id
|
|
* @return string
|
|
*/
|
|
public static function integration_get_popup($app,$id)
|
|
{
|
|
$app_data = calendar_bo::integration_get_data($app,'edit_link');
|
|
|
|
if (is_array($app_data) && isset($app_data['edit']))
|
|
{
|
|
$popup_size = $app_data['edit_popup'];
|
|
$edit = $app_data['edit'];
|
|
$edit[$app_data['edit_id']] = $id;
|
|
}
|
|
else
|
|
{
|
|
$edit = egw_link::edit($app,$id,$popup_size);
|
|
}
|
|
if ($edit)
|
|
{
|
|
$view_link = egw::link('/index.php',$edit);
|
|
|
|
if ($popup_size)
|
|
{
|
|
$popup = ' data-app="app|'.$popup_size.'"';
|
|
}
|
|
else
|
|
{
|
|
$popup = ' data-app="app|'.$app.'|'.'"';
|
|
}
|
|
}
|
|
return $popup;
|
|
}
|
|
|
|
/**
|
|
* Get icons for an integration event
|
|
*
|
|
* Attribute 'icons' is either null (--> navbar icon), false (--> no icon)
|
|
* or a callback with parameters $id,$event
|
|
*
|
|
* Icons specified in $events['icons'] are always displayed!
|
|
*
|
|
* @param string $app
|
|
* @param int|string $id
|
|
* @param array $event
|
|
* @return array
|
|
*/
|
|
static function integration_get_icons($app,$id,$event)
|
|
{
|
|
$icons = array();
|
|
if ($event['icons'])
|
|
{
|
|
foreach(explode(',',$event['icons']) as $icon)
|
|
{
|
|
list($icon_app,$icon) = explode(':',$icon);
|
|
if (common::find_image($icon_app,$icon))
|
|
{
|
|
$icons[] = html::image($icon_app,$icon);
|
|
}
|
|
}
|
|
}
|
|
$app_data = calendar_bo::integration_get_data($app,'icons');
|
|
if (is_null($app_data))
|
|
{
|
|
$icons[] = html::image($app,'navbar'); // use navbar icon
|
|
}
|
|
elseif ($app_data)
|
|
{
|
|
$icons += (array)ExecMethod2($app_data,$id,$event);
|
|
}
|
|
return $icons;
|
|
}
|
|
|
|
function add_nonempty($content,$label,$one_per_line=False,$space = True,$htmlspecialchars=true)
|
|
{
|
|
if (is_array($content))
|
|
{
|
|
if($space)
|
|
{
|
|
$content = implode($one_per_line ? ",\n" : ', ',$content);
|
|
}
|
|
else
|
|
{
|
|
$content = implode($one_per_line ? "\n" : ', ',$content);
|
|
}
|
|
}
|
|
if (!empty($content))
|
|
{
|
|
return '<span class="calendar_calEventLabel">'.$label.'</span>:'.
|
|
($one_per_line ? '<br>' : ' ').
|
|
nl2br($htmlspecialchars?html::htmlspecialchars($content):$content).'<br>';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Calculates a brighter color for a given color
|
|
*
|
|
* @param $rgb string color as #rrggbb value
|
|
* @param $decr int value to add to each component, default 64
|
|
* @return string the brighter color
|
|
*/
|
|
static function brighter($rgb,$decr=64)
|
|
{
|
|
if (!preg_match('/^#?([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/',$rgb,$components))
|
|
{
|
|
return '#ffffff';
|
|
}
|
|
$brighter = '#';
|
|
for ($i = 1; $i <=3; ++$i)
|
|
{
|
|
$val = hexdec($components[$i]) + $decr;
|
|
if ($val > 255) $val = 255;
|
|
$brighter .= sprintf('%02x',$val);
|
|
}
|
|
//echo "brighter($rgb=".print_r($components,True).")=$brighter</p>\n";
|
|
return $brighter;
|
|
}
|
|
|
|
/**
|
|
* Calculates the brightness of a hexadecimal rgb color (median of the r, g and b components)
|
|
*
|
|
* @param string $rgb eg. #808080
|
|
* @return int between 0 and 255
|
|
*/
|
|
static function brightness($rgb)
|
|
{
|
|
if ($rgb[0] != '#' || strlen($rgb) != 7)
|
|
{
|
|
return 128; // no rgb color, return some default
|
|
}
|
|
$dec = hexdec(substr($rgb,1));
|
|
for($i = 0; $i < 24; $i += 8)
|
|
{
|
|
$sum += ($dec >> $i) & 255;
|
|
}
|
|
return (int)round($sum / 3.0, 0);
|
|
}
|
|
|
|
/**
|
|
* Number of month to display in yearly planner
|
|
*/
|
|
const YEARLY_PLANNER_NUM_MONTH = 12;
|
|
|
|
/**
|
|
* Creates a planner view: grid with columns for the time and rows for categories or users
|
|
*
|
|
* Uses the plannerRowWidget to display rows
|
|
*
|
|
* @param array $events events to show
|
|
* @param mixed $start start-time of the grid
|
|
* @param mixed $end end-time of the grid
|
|
* @param string|int $by_cat rows by sub-categories of $by_cat (cat_id or 0 for upmost level) or by 'user' or 'month'
|
|
* @param string $indent = '' string for correct indention
|
|
* @return string with widget
|
|
*/
|
|
function &plannerWidget(&$events,$start,$end,$by_cat=0,$indent='')
|
|
{
|
|
$content = $indent.'<div class="calendar_plannerWidget">'."\n";
|
|
|
|
// display the header, containing a headerTitle and multiple headerRows with the scales
|
|
$content .= $indent."\t".'<div class="calendar_plannerHeader">'."\n";
|
|
|
|
// display the headerTitle, and get sort2labels
|
|
switch($by_cat)
|
|
{
|
|
case 'user':
|
|
$title = lang('User');
|
|
$sort2label = $this->_get_planner_users();
|
|
break;
|
|
case 'month':
|
|
$title = lang('Month');
|
|
$sort2label = array();
|
|
$time = new egw_time($start);
|
|
for($n = 0; $n < self::YEARLY_PLANNER_NUM_MONTH; ++$n)
|
|
{
|
|
$sort2label[$time->format('Y-m')] = lang($time->format('F')).' '.$time->format('Y');
|
|
$time->modify('+1 month');
|
|
}
|
|
break;
|
|
default:
|
|
$title = lang('Category');
|
|
$sort2label = array();
|
|
break;
|
|
}
|
|
$content .= $indent."\t\t".'<div class="calendar_plannerHeaderTitle th">'.$title."</div>\n";
|
|
|
|
// display the headerRows with the scales
|
|
$content .= $indent."\t\t".'<div class="calendar_plannerHeaderRows">'."\n";
|
|
// set start & end to timestamp and first & last to timestamp of 12h midday, to avoid trouble with daylight saving
|
|
foreach(array('start' => 'first','end' => 'last') as $t => $v)
|
|
{
|
|
$$t = $this->bo->date2ts($$t);
|
|
$$v = $this->bo->date2array($$t);
|
|
unset(${$v}['raw']);
|
|
${$v}['hour'] = 12;
|
|
${$v}['minute'] = ${$v}['second'] = 0;
|
|
${$v} = $this->bo->date2ts($$v);
|
|
}
|
|
if ($by_cat === 'month')
|
|
{
|
|
$content .= $this->plannerDayOfMonthScale($indent."\t\t\t");
|
|
}
|
|
else
|
|
{
|
|
$days = 1 + (int) round(($last - $first) / DAY_s); // we have to use round to get the right number if daylight saving changes
|
|
if ($days >= 28) // display the month scale
|
|
{
|
|
$content .= $this->plannerMonthScale($first,$days,$indent."\t\t\t");
|
|
}
|
|
if ($days >= 5) // display the week scale
|
|
{
|
|
$content .= $this->plannerWeekScale($first,$days,$indent."\t\t\t");
|
|
}
|
|
$content .= $this->plannerDayScale($first,$days,$indent."\t\t\t"); // day-scale, always displayed
|
|
if ($days <= 7) // display the hour scale
|
|
{
|
|
$content .= $this->plannerHourScale($start,$days,$indent."\t\t\t");
|
|
}
|
|
}
|
|
$content .= $indent."\t\t</div>\n"; // end of the plannerHeaderRows
|
|
$content .= $indent."\t</div>\n"; // end of the plannerHeader
|
|
|
|
// sort the events after user or category
|
|
$rows = array();
|
|
if (!is_array($events)) $events = array();
|
|
|
|
if ($by_cat === 'user') // planner by user
|
|
{
|
|
// convert filter to allowed status
|
|
switch($this->filter)
|
|
{
|
|
case 'unknown':
|
|
$status_to_show = array('U','G'); break;
|
|
case 'accepted':
|
|
$status_to_show = array('A'); break;
|
|
case 'tentative':
|
|
$status_to_show = array('T'); break;
|
|
case 'rejected':
|
|
$status_to_show = array('R'); break;
|
|
case 'delegated':
|
|
$status_to_show = array('D'); break;
|
|
case 'all':
|
|
$status_to_show = array('U','A','T','D','G','R'); break;
|
|
default:
|
|
$status_to_show = array('U','A','T','D','G'); break;
|
|
}
|
|
}
|
|
foreach($events as $key => $event)
|
|
{
|
|
if ($by_cat === 'user') // planner by user
|
|
{
|
|
foreach($event['participants'] as $sort => $status)
|
|
{
|
|
calendar_so::split_status($status,$nul,$nul);
|
|
// only show if participant with status visible with current filter
|
|
if (isset($sort2label[$sort]) && (in_array($status,$status_to_show) ||
|
|
$this->filter == 'owner' && $event['owner'] == $sort)) // owner too additionally uses owner
|
|
{
|
|
$rows[$sort][] =& $events[$key];
|
|
}
|
|
}
|
|
}
|
|
elseif ($by_cat === 'month') // planner by month / yearly planner
|
|
{
|
|
$sort = date('Y-m',$event['start']);
|
|
$rows[$sort][] =& $events[$key];
|
|
// end in a different month?
|
|
if ($sort != ($end_sort = date('Y-m',$event['end'])))
|
|
{
|
|
while($sort != $end_sort)
|
|
{
|
|
list($y,$m) = explode('-',$sort);
|
|
if (++$m > 12)
|
|
{
|
|
++$y;
|
|
$m = 1;
|
|
}
|
|
$sort = sprintf('%04d-%02d',$y,$m);
|
|
$rows[$sort][] =& $events[$key];
|
|
}
|
|
}
|
|
}
|
|
else // planner by cat
|
|
{
|
|
foreach($this->_get_planner_cats($event['category'],$sort2label,$sort2color) as $sort)
|
|
{
|
|
if (!is_array($rows[$sort])) $rows[$sort] = array();
|
|
|
|
$rows[$sort][] =& $events[$key];
|
|
}
|
|
}
|
|
}
|
|
$owners = explode(',',$this->owner);
|
|
// display a plannerRowWidget for each row (user or category)
|
|
foreach($sort2label as $sort => $label)
|
|
{
|
|
if (!isset($rows[$sort]) && (!$this->cal_prefs['planner_show_empty_rows'] ||
|
|
$by_cat === 'user' && $this->cal_prefs['planner_show_empty_rows'] == 'cat' ||
|
|
is_int($by_cat) && $this->cal_prefs['planner_show_empty_rows'] == 'user'))
|
|
{
|
|
continue; // dont show empty categories or user rows
|
|
}
|
|
$class = $class == 'row_on' ? 'row_off' : 'row_on';
|
|
if ($by_cat === 'month')
|
|
{
|
|
$time = new egw_time($sort.'-01');
|
|
$start = $time->format('ts');
|
|
$time->modify('+1month -1second');
|
|
$end = $time->format('ts');
|
|
}
|
|
// display close button only for directly set users, eg. not group-members (as we cant unset them!)
|
|
if ($by_cat === 'user' && in_array($sort, $owners))
|
|
{
|
|
$label .= $this->close_button($sort);
|
|
}
|
|
$content .= $this->plannerRowWidget(isset($rows[$sort]) ? $rows[$sort] : array(),$start,$end,$label,$class,$indent."\t");
|
|
}
|
|
$content .= $indent."</div>\n"; // end of the plannerWidget
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* get all users to display in the planner_by_user
|
|
*
|
|
* @param boolean $enum_groups = true should groups be returned as there members (eg. planner) or not (day & week)
|
|
* @return array with uid => label pairs, first all users alphabetically sorted, then all resources
|
|
*/
|
|
function _get_planner_users($enum_groups=true)
|
|
{
|
|
$users = $resources = array();
|
|
foreach(explode(',',$this->owner) as $user)
|
|
{
|
|
if($user === '0')
|
|
{
|
|
// 0 means current user
|
|
$user = $this->user;
|
|
}
|
|
if (!is_numeric($user)) // resources
|
|
{
|
|
$resources[$user] = $this->bo->participant_name($user);
|
|
}
|
|
elseif ($enum_groups && $GLOBALS['egw']->accounts->get_type($user) == 'g') // groups
|
|
{
|
|
foreach((array) $GLOBALS['egw']->accounts->members($user, true) as $user)
|
|
{
|
|
if ($this->bo->check_perms(EGW_ACL_READ | EGW_ACL_FREEBUSY,0,$user))
|
|
{
|
|
$users[$user] = $this->bo->participant_name($user);
|
|
}
|
|
}
|
|
}
|
|
else // users
|
|
{
|
|
$users[$user] = $this->bo->participant_name($user);
|
|
}
|
|
}
|
|
// Only sort users in planner views
|
|
if ($enum_groups)
|
|
{
|
|
asort($users);
|
|
asort($resources);
|
|
}
|
|
|
|
return $users+$resources;
|
|
}
|
|
|
|
/**
|
|
* get all categories used as sort criteria for the planner by category
|
|
*
|
|
* the returned cat is as direct sub-category of $this->cat_id or a main (level 1) category if !$this->cat_id
|
|
*
|
|
* @param string $cats comma-delimited cat_id's or empty for no cat
|
|
* @param array &$sort2label labels for the returned cats
|
|
* @return array with cat_id's
|
|
*/
|
|
function _get_planner_cats($cats,&$sort2label)
|
|
{
|
|
static $cat2sort;
|
|
|
|
if (!is_array($cat2sort))
|
|
{
|
|
$cat2sort = array();
|
|
$cat_filter = $this->cat_id ? (array)$this->cat_id : array();
|
|
foreach((array)$this->categories->return_sorted_array(0,false,'','','',true) as $data)
|
|
{
|
|
if (in_array($data['parent'], $cat_filter) || in_array($data['id'], $cat_filter) ||
|
|
!$data['parent'] && !$cat_filter) // cat is a direct sub of $this->cat_id
|
|
{
|
|
$cat2sort[$data['id']] = $data['id'];
|
|
$sort2label[$data['id']] = stripslashes($data['name']);
|
|
}
|
|
elseif(isset($cat2sort[$data['parent']])) // parent is already in the array => add us with same target
|
|
{
|
|
$cat2sort[$data['id']] = $cat2sort[$data['parent']];
|
|
}
|
|
}
|
|
}
|
|
$ret = array();
|
|
foreach(!is_array($cats) ? explode(',',$cats) : $cats as $cat)
|
|
{
|
|
if (isset($cat2sort[$cat]) && !in_array($cat2sort[$cat],$ret))
|
|
{
|
|
$ret[] = $cat2sort[$cat];
|
|
}
|
|
}
|
|
if (!count($ret))
|
|
{
|
|
$sort2label[0] = lang('none');
|
|
$ret[] = 0;
|
|
}
|
|
//echo "<p>uiviews::_get_planner_cats($cats=".$this->categories->id2name($cats).") (this->cat_id=$this->cat_id) = ".print_r($ret,true).'='.$this->categories->id2name($ret[0])."</p>\n";
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Creates month scale for the planner
|
|
*
|
|
* @param int $start start-time (12h) of the scale
|
|
* @param int $days number of days to display
|
|
* @param string $indent = '' string for correct indention
|
|
* @return string with scale
|
|
*/
|
|
function plannerMonthScale($start,$days,$indent)
|
|
{
|
|
$day_width = round(100 / $days,2);
|
|
|
|
$content .= $indent.'<div class="calendar_plannerScale">'."\n";
|
|
for($t = $start,$left = 0,$i = 0; $i < $days; $t += $days_in_month*DAY_s,$left += $days_in_month*$day_width,$i += $days_in_month)
|
|
{
|
|
$t_arr = $this->bo->date2array($t);
|
|
unset($t_arr['raw']); // force recalculation
|
|
unset($t_arr['full']);
|
|
$days_in_month = $this->datetime->days_in_month($t_arr['month'],$t_arr['year']) - ($t_arr['day']-1);
|
|
if ($i + $days_in_month > $days)
|
|
{
|
|
$days_in_month = $days - $i;
|
|
}
|
|
if ($days_in_month > 10)
|
|
{
|
|
$title = lang(date('F',$t)).' '.$t_arr['year'];
|
|
// previous links
|
|
$prev = $t_arr;
|
|
$prev['day'] = 1;
|
|
if ($prev['month']-- <= 1)
|
|
{
|
|
$prev['month'] = 12;
|
|
$prev['year']--;
|
|
}
|
|
if ($this->bo->date2ts($prev) < $start-20*DAY_s)
|
|
{
|
|
$prev['day'] = $this->day;
|
|
$full = $this->bo->date2string($prev);
|
|
if ($this->day >= 15) $prev = $t_arr; // we stay in the same month
|
|
$prev['day'] = $this->day < 15 ? 15 : 1;
|
|
$half = $this->bo->date2string($prev);
|
|
$title = html::a_href(html::image('phpgwapi','first',lang('back one month'),$options=' alt="<<"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $full,
|
|
)) . ' '.
|
|
html::a_href(html::image('phpgwapi','left',lang('back half a month'),$options=' alt="<"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $half,
|
|
)) . ' '.$title;
|
|
}
|
|
// next links
|
|
$next = $t_arr;
|
|
if ($next['month']++ >= 12)
|
|
{
|
|
$next['month'] = 1;
|
|
$next['year']++;
|
|
}
|
|
// dont show next scales, if there are more then 10 days in the next month or there is no next month
|
|
$days_in_next_month = (int) date('d',$end = $start+$days*DAY_s);
|
|
if ($days_in_next_month <= 10 || date('m',$end) == date('m',$t))
|
|
{
|
|
if ($this->day >= 15) $next = $t_arr; // we stay in the same month
|
|
$next['day'] = $this->day;
|
|
$full = $this->bo->date2string($next);
|
|
if ($this->day < 15) $next = $t_arr; // we stay in the same month
|
|
$next['day'] = $this->day < 15 ? 15 : 1;
|
|
$half = $this->bo->date2string($next);
|
|
$title .= ' '.html::a_href(html::image('phpgwapi','right',lang('forward half a month'),$options=' alt=">>"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $half,
|
|
)). ' '.
|
|
html::a_href(html::image('phpgwapi','last',lang('forward one month'),$options=' alt=">>"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $full,
|
|
));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$title = ' ';
|
|
}
|
|
$class = $class == 'row_on' ? 'th' : 'row_on';
|
|
$content .= $indent."\t".'<div class="calendar_plannerMonthScale '.$class.'" style="left: '.$left.'%; width: '.($day_width*$days_in_month).'%;">'.
|
|
$title."</div>\n";
|
|
}
|
|
$content .= $indent."</div>\n"; // end of plannerScale
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Creates a week scale for the planner
|
|
*
|
|
* @param int $start start-time (12h) of the scale
|
|
* @param int $days number of days to display
|
|
* @param string $indent = '' string for correct indention
|
|
* @return string with scale
|
|
*/
|
|
function plannerWeekScale($start,$days,$indent)
|
|
{
|
|
$week_width = round(100 / $days * ($days <= 7 ? $days : 7),2);
|
|
|
|
$content .= $indent.'<div class="calendar_plannerScale">'."\n";
|
|
for($t = $start,$left = 0,$i = 0; $i < $days; $t += 7*DAY_s,$left += $week_width,$i += 7)
|
|
{
|
|
$title = lang('Week').' '.$this->week_number($t);
|
|
if ($days > 7)
|
|
{
|
|
$title = html::a_href($title,array(
|
|
'menuaction' => 'calendar.calendar_uiviews.planner',
|
|
'planner_days' => 7,
|
|
'date' => date('Ymd',$t),
|
|
),false,' title="'.html::htmlspecialchars(lang('Weekview')).'"');
|
|
}
|
|
else
|
|
{
|
|
// prev. week
|
|
$title = html::a_href(html::image('phpgwapi','first',lang('previous'),$options=' alt="<<"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => date('Ymd',$t-7*DAY_s),
|
|
)) . ' <b>'.$title;
|
|
// next week
|
|
$title .= '</b> '.html::a_href(html::image('phpgwapi','last',lang('next'),$options=' alt=">>"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => date('Ymd',$t+7*DAY_s),
|
|
));
|
|
}
|
|
$class = $class == 'row_on' ? 'th' : 'row_on';
|
|
$content .= $indent."\t".'<div class="calendar_plannerWeekScale '.$class.'" style="left: '.$left.'%; width: '.$week_width.'%;">'.$title."</div>\n";
|
|
}
|
|
$content .= $indent."</div>\n"; // end of plannerScale
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Creates day scale for the planner
|
|
*
|
|
* @param int $start start-time (12h) of the scale
|
|
* @param int $days number of days to display
|
|
* @param string $indent = '' string for correct indention
|
|
* @return string with scale
|
|
*/
|
|
function plannerDayScale($start,$days,$indent)
|
|
{
|
|
$day_width = round(100 / $days,2);
|
|
|
|
$content .= $indent.'<div class="calendar_plannerScale'.($days > 3 ? 'Day' : '').'">'."\n";
|
|
for($t = $start,$left = 0,$i = 0; $i < $days; $t += DAY_s,$left += $day_width,++$i)
|
|
{
|
|
$this->_day_class_holiday($this->bo->date2string($t),$class,$holidays,$days > 7);
|
|
|
|
if ($days <= 3)
|
|
{
|
|
$title = '<b>'.lang(date('l',$t)).', '.date('j',$t).'. '.lang(date('F',$t)).'</b>';
|
|
}
|
|
elseif ($days <= 7)
|
|
{
|
|
$title = lang(date('l',$t)).'<br />'.date('j',$t).'. '.lang(date('F',$t));
|
|
}
|
|
else
|
|
{
|
|
$title = substr(lang(date('D',$t)),0,2).'<br />'.date('j',$t);
|
|
}
|
|
if ($days > 1)
|
|
{
|
|
$title = html::a_href($title,array(
|
|
'menuaction' => 'calendar.calendar_uiviews.planner',
|
|
'planner_days' => 1,
|
|
'date' => date('Ymd',$t),
|
|
),false,strpos($class,'calendar_calHoliday') !== false || strpos($class,'calendar_calBirthday') !== false ? '' : ' title="'.html::htmlspecialchars(lang('Dayview')).'"');
|
|
}
|
|
if ($days < 5)
|
|
{
|
|
if (!$i) // prev. day only for the first day
|
|
{
|
|
$title = html::a_href(html::image('phpgwapi','first',lang('previous'),$options=' alt="<<"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => date('Ymd',$start-DAY_s),
|
|
)) . ' '.$title;
|
|
}
|
|
if ($i == $days-1) // next day only for the last day
|
|
{
|
|
$title .= ' '.html::a_href(html::image('phpgwapi','last',lang('next'),$options=' alt=">>"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => date('Ymd',$start+DAY_s),
|
|
));
|
|
}
|
|
}
|
|
$content .= $indent."\t".'<div class="calendar_plannerDayScale '.$class.'" style="left: '.$left.'%; width: '.$day_width.'%;"'.
|
|
($holidays ? ' title="'.html::htmlspecialchars($holidays).'"' : '').'>'.$title."</div>\n";
|
|
}
|
|
$content .= $indent."</div>\n"; // end of plannerScale
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Creates DayOfMonth scale for planner by month
|
|
*
|
|
* @param string $indent
|
|
* @return string
|
|
*/
|
|
function plannerDayOfMonthScale($indent)
|
|
{
|
|
$day_width = round(100 / 31,2);
|
|
|
|
// month scale with navigation
|
|
$content .= $indent.'<div class="calendar_plannerScale">'."\n";
|
|
|
|
$title = lang(egw_time::to($this->first,'F')).' '.egw_time::to($this->first,'Y').' - '.
|
|
lang(egw_time::to($this->last,'F')).' '.egw_time::to($this->last,'Y');
|
|
|
|
// calculate date for navigation links
|
|
$time = new egw_time($this->first);
|
|
$time->modify('-1year');
|
|
$last_year = $time->format('Ymd');
|
|
$time->modify('+11month');
|
|
$last_month = $time->format('Ymd');
|
|
$time->modify('+2month');
|
|
$next_month = $time->format('Ymd');
|
|
$time->modify('+11month');
|
|
$next_year = $time->format('Ymd');
|
|
|
|
$title = html::a_href(html::image('phpgwapi','first',lang('back one year'),$options=' alt="<<"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $last_year,
|
|
)) . ' '.
|
|
html::a_href(html::image('phpgwapi','left',lang('back one month'),$options=' alt="<"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $last_month,
|
|
)) . ' '.$title;
|
|
$title .= ' '.html::a_href(html::image('phpgwapi','right',lang('forward one month'),$options=' alt=">>"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $next_month,
|
|
)). ' '.
|
|
html::a_href(html::image('phpgwapi','last',lang('forward one year'),$options=' alt=">>"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $next_year,
|
|
));
|
|
|
|
$content .= $indent."\t".'<div class="calendar_plannerMonthScale th" style="left: 0; width: 100%;">'.
|
|
$title."</div>\n";
|
|
$content .= $indent."</div>\n"; // end of plannerScale
|
|
|
|
// day of month scale
|
|
$content .= $indent.'<div class="calendar_plannerScale">'."\n";
|
|
$today = egw_time::to('now','d');
|
|
for($left = 0,$i = 0; $i < 31; $left += $day_width,++$i)
|
|
{
|
|
$class = $i & 1 ? 'row_on' : 'row_off';
|
|
$content .= $indent."\t".'<div class="calendar_plannerDayOfMonthScale '.$class.'" style="left: '.$left.'%; width: '.$day_width.'%;">'.
|
|
(1+$i)."</div>\n";
|
|
}
|
|
$content .= $indent."</div>\n"; // end of plannerScale
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Creates hour scale for the planner
|
|
*
|
|
* @param int $start start-time (12h) of the scale
|
|
* @param int $days number of days to display
|
|
* @param string $indent = '' string for correct indention
|
|
* @return string with scale
|
|
*/
|
|
function plannerHourScale($start,$days,$indent)
|
|
{
|
|
foreach(array(1,2,3,4,6,8,12) as $d) // numbers dividing 24 without rest
|
|
{
|
|
if ($d > $days) break;
|
|
$decr = $d;
|
|
}
|
|
$hours = $days * 24;
|
|
if ($days == 1) // for a single day we calculate the hours of a days, to take into account daylight saving changes (23 or 25 hours)
|
|
{
|
|
$t_arr = $this->bo->date2array($start);
|
|
unset($t_arr['raw']);
|
|
$t_arr['hour'] = $t_arr['minute'] = $t_arr['second'] = 0;
|
|
$s = $this->bo->date2ts($t_arr);
|
|
$t_arr['hour'] = 23; $t_arr['minute'] = $t_arr['second'] = 59;
|
|
$hours = ($this->bo->date2ts($t_arr) - $s) / HOUR_s;
|
|
}
|
|
$cell_width = round(100 / $hours * $decr,2);
|
|
|
|
$content .= $indent.'<div class="calendar_plannerScale">'."\n";
|
|
for($t = $start,$left = 0,$i = 0; $i < $hours; $t += $decr*HOUR_s,$left += $cell_width,$i += $decr)
|
|
{
|
|
$title = date($this->cal_prefs['timeformat'] == 12 ? 'ha' : 'H',$t);
|
|
|
|
$class = $class == 'row_on' ? 'th' : 'row_on';
|
|
$content .= $indent."\t".'<div class="calendar_plannerHourScale '.$class.'" style="left: '.$left.'%; width: '.($cell_width).'%;">'.$title."</div>\n";
|
|
}
|
|
$content .= $indent."</div>\n"; // end of plannerScale
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Creates a row for one user or category, with a header (user or category name) and (multiple) rows with non-overlapping events
|
|
*
|
|
* Uses the calendar_eventRowWidget to display a row of non-overlapping events
|
|
*
|
|
* @param array $events to show
|
|
* @param int $start start-time of the row
|
|
* @param int $end end-time of the row
|
|
* @param string $header user or category name for the row-header
|
|
* @param string $class additional css class for the row
|
|
* @param string $indent = '' string for correct indention
|
|
* @return string with widget
|
|
*/
|
|
function plannerRowWidget($events,$start,$end,$header,$class,$indent='')
|
|
{
|
|
$content = $indent.'<div class="calendar_plannerRowWidget '.$class.'">'."\n";
|
|
|
|
// display the row-header
|
|
$content .= $indent."\t".'<div class="calendar_plannerRowHeader">'.$header."</div>\n";
|
|
|
|
// sorting the events in non-overlapping rows
|
|
$rows = array(array());
|
|
$row_end = array();
|
|
foreach($events as $n => $event)
|
|
{
|
|
for($row = 0; (int) $row_end[$row] > $event['start']; ++$row); // find a "free" row (no other event)
|
|
$rows[$row][] =& $events[$n];
|
|
$row_end[$row] = $event['end'];
|
|
}
|
|
//echo $header; _debug_array($rows);
|
|
// display the rows
|
|
$content .= $indent."\t".'<div class="calendar_eventRows"';
|
|
|
|
if ($this->sortby == 'month' && ($days = date('j',$end)) < 31)
|
|
{
|
|
$width = round(85*$days/31,2);
|
|
$content .= ' style="width: '.$width.'%;"';
|
|
}
|
|
$content .= ">\n";
|
|
|
|
// mark weekends and other special days in yearly planner
|
|
if ($this->sortby == 'month')
|
|
{
|
|
$content .= $this->yearlyPlannerMarkDays($start,$days,$indent."\t\t");
|
|
}
|
|
foreach($rows as $row)
|
|
{
|
|
$content .= $this->eventRowWidget($row,$start,$end,$indent."\t\t");
|
|
}
|
|
$content .= $indent."\t</div>\n"; // end of the eventRows
|
|
|
|
if ($this->sortby == 'month' && $days < 31)
|
|
{
|
|
// add a filler for non existing days in that month
|
|
$content .= $indent."\t".'<div class="calendar_eventRowsFiller"'.
|
|
' style="left:'.(15+$width).'%; width:'.(85-$width).'%;" ></div>'."\n";
|
|
}
|
|
$content .= $indent."</div>\n"; // end of the plannerRowWidget
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Mark weekends and other special days in yearly planner
|
|
*
|
|
* @param int $start timestamp of start of row
|
|
* @param int $days number of days in month of row
|
|
* @param string $indent = ''
|
|
* @return string
|
|
*/
|
|
function yearlyPlannerMarkDays($start,$days,$indent='')
|
|
{
|
|
$day_width = round(100/$days,2);
|
|
for($t = $start,$left = 0,$i = 0; $i < $days; $t += DAY_s,$left += $day_width,++$i)
|
|
{
|
|
$this->_day_class_holiday($this->bo->date2string($t),$class,$holidays,true);
|
|
|
|
$class = trim(str_replace(array('row_on','row_off'),'',$class));
|
|
if ($class) // no regular weekday
|
|
{
|
|
$content .= $indent.'<div class="calendar_eventRowsMarkedDay '.$class.
|
|
'" style="left: '.$left.'%; width:'.$day_width.'%;"'.
|
|
($holidays ? ' title="'.html::htmlspecialchars($holidays).'"' : '').
|
|
' ></div>'."\n";
|
|
}
|
|
}
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Creates a row with non-overlapping events
|
|
*
|
|
* Uses the plannerEventWidget to display the events
|
|
*
|
|
* @param array $events non-overlapping events to show
|
|
* @param int $start start-time of the row
|
|
* @param int $end end-time of the row
|
|
* @param string $indent = '' string for correct indention
|
|
* @return string with widget
|
|
*/
|
|
function eventRowWidget($events,$start,$end,$indent='')
|
|
{
|
|
$content = $indent.'<div class="calendar_eventRowWidget">'."\n";
|
|
|
|
foreach($events as $event)
|
|
{
|
|
$content .= $this->plannerEventWidget($event,$start,$end,$indent."\t");
|
|
}
|
|
$content .= $indent."</div>\n";
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Calculate a time-dependent position in the planner
|
|
*
|
|
* We use a non-linear scale in the planner monthview, which shows the workday start or end
|
|
* as start or end of the whole day. This improves the resolution a bit.
|
|
*
|
|
* @param int $time
|
|
* @param int $start start-time of the planner
|
|
* @param int $end end-time of the planner
|
|
* @return float percentage position between 0-100
|
|
*/
|
|
function _planner_pos($time,$start,$end)
|
|
{
|
|
if ($time <= $start) return 0; // we are left of our scale
|
|
if ($time >= $end) return 100; // we are right of our scale
|
|
|
|
if ($this->planner_days || $this->sortby == 'month')
|
|
{
|
|
$percent = ($time - $start) / ($end - $start);
|
|
}
|
|
else // monthview
|
|
{
|
|
$t_arr = $this->bo->date2array($time);
|
|
$day_start = $this->bo->date2ts((string)$t_arr['full']);
|
|
$percent = ($day_start - $start) / ($end - $start);
|
|
|
|
$time_of_day = 60 * $t_arr['hour'] + $t_arr['minute'];
|
|
if ($time_of_day >= $this->wd_start)
|
|
{
|
|
if ($time_of_day > $this->wd_end)
|
|
{
|
|
$day_percentage = 1;
|
|
}
|
|
else
|
|
{
|
|
$wd_lenght = $this->wd_end - $this->wd_start;
|
|
if ($wd_lenght <= 0) $wd_lenght = 24*60;
|
|
$day_percentage = ($time_of_day-$this->wd_start) / $wd_lenght; // between 0 and 1
|
|
}
|
|
$days = ($end - $start) / DAY_s;
|
|
$percent += $day_percentage / $days;
|
|
}
|
|
}
|
|
$percent = round(100 * $percent,2);
|
|
|
|
//echo "<p>_planner_pos(".date('Y-m-d H:i',$time).', '.date('Y-m-d H:i',$start).', '.date('Y-m-d H:i',$end).") = $percent</p>\n";
|
|
return $percent;
|
|
}
|
|
|
|
/**
|
|
* Displays one event for the planner, using the eventWidget of the other views
|
|
*
|
|
* @param array $event
|
|
* @param int $start start-time of the planner
|
|
* @param int $end end-time of the planner
|
|
* @return string with widget
|
|
*/
|
|
function plannerEventWidget($event,$start,$end,$indent='')
|
|
{
|
|
// some fields set by the dayColWidget for the other views
|
|
$day_start = $this->bo->date2ts((string)$this->bo->date2string($event['start']));
|
|
$event['start_m'] = ($event['start'] - $day_start) / 60;
|
|
$event['end_m'] = round(($event['end'] - $day_start) / 60);
|
|
$event['multiday'] = true;
|
|
unset($event['whole_day_on_top']);
|
|
|
|
$data = $this->eventWidget($event,200,$indent,$this->owner,true,'planner_event');
|
|
|
|
$left = $this->_planner_pos($event['start'],$start,$end);
|
|
$width = $this->_planner_pos($event['end'],$start,$end) - $left;
|
|
$color = $data['color'] ? $data['color'] : 'gray';
|
|
|
|
$tooltip = html::htmlspecialchars(str_replace(array("\n","\r","'",'"'),array('','',"\\'",'"'),$data['tooltip']));
|
|
return $indent.'<div class="calendar_plannerEvent'.($data['private'] ? 'Private' : '').'" style="left: '.$left.
|
|
'%; width: '.$width.'%; background-color: '.$color.';"'.'data-tooltip="'. $tooltip.'" '.
|
|
'" data-date ="'.$this->bo->date2string($event['start']).'|'.$data['popup'].'">'."\n".$data['html'].$indent."</div>\n";
|
|
}
|
|
|
|
/**
|
|
* Get the actions for the non-list views
|
|
*
|
|
* We use the actions from the list as a base, and only change what we have to
|
|
* to get it to work outside of a nextmatch.
|
|
*
|
|
* @return Array
|
|
*/
|
|
protected static function get_actions()
|
|
{
|
|
// Just copy from the list, but change to match our needs
|
|
$ui = new calendar_uilist();
|
|
$actions = $ui->get_actions();
|
|
|
|
unset($actions['no_notifications']);
|
|
unset($actions['select_all']);
|
|
|
|
// This disables the event actions for the grid rows (calendar weeks/owners)
|
|
foreach($actions as $id => &$action)
|
|
{
|
|
if($id=='add') continue;
|
|
if(!$action['enabled'])
|
|
{
|
|
$action['enabled'] = 'javaScript:app.calendar.is_event';
|
|
}
|
|
}
|
|
$actions['add']['open'] = '{"app":"calendar","type":"add"}';
|
|
$actions['add']['onExecute'] = 'javaScript:app.calendar.action_open';
|
|
$actions['copy']['open'] = '{"app": "calendar", "type": "add", "extra": "cal_id=$id&action=copy"}';
|
|
$actions['copy']['onExecute'] = 'javaScript:app.calendar.action_open';
|
|
|
|
foreach($actions['status']['children'] as $id => &$status)
|
|
{
|
|
$status = array(
|
|
'id' => $id,
|
|
'caption' => $status,
|
|
'onExecute' => 'javaScript:app.calendar.status'
|
|
);
|
|
}
|
|
|
|
if ($actions['filemanager'])
|
|
{
|
|
$actions['filemanager']['url'] = '/index.php?'. $actions['filemanager']['url'];
|
|
$actions['filemanager']['onExecute'] = 'javaScript:app.calendar.action_open';
|
|
}
|
|
if ($actions['infolog_app'])
|
|
{
|
|
$actions['infolog_app']['open'] = '{"app": "infolog", "type": "add", "extra": "type=task&action=$app&action_id=$id"}';
|
|
$actions['infolog_app']['onExecute'] = 'javaScript:app.calendar.action_open';
|
|
}
|
|
if ($actions['timesheet'])
|
|
{
|
|
$actions['timesheet']['open'] = '{"app": "timesheet", "type": "add", "extra": "link_app[]=$app&link_id[]=$id"}';
|
|
$actions['timesheet']['onExecute'] = 'javaScript:app.calendar.action_open';
|
|
}
|
|
if ($actions['documents'])
|
|
{
|
|
// TODO: See if we can get this working sensibly
|
|
$actions['documents']['enabled'] = false;
|
|
}
|
|
|
|
$actions['delete']['onExecute'] = 'javaScript:app.calendar.delete';
|
|
|
|
return $actions;
|
|
}
|
|
|
|
/**
|
|
* Marks whole day events for later usage and increments extraRows
|
|
*
|
|
* @param array $dayEvents
|
|
* @return array $dayEvents
|
|
*/
|
|
function tagWholeDayOnTop($dayEvents)
|
|
{
|
|
$this->extraRows = $this->extraRowsOriginal;
|
|
$this->remBotExtraRows = 0;
|
|
|
|
if (is_array($dayEvents))
|
|
{
|
|
foreach ($dayEvents as $day=>$oneDayEvents)
|
|
{
|
|
$extraRowsToAdd = 0;
|
|
foreach ($oneDayEvents as $num => $event)
|
|
{
|
|
$start = $this->bo->date2array($event['start']);
|
|
$end = $this->bo->date2array($event['end']);
|
|
if(!$start['hour'] && !$start['minute'] && $end['hour'] == 23 && $end['minute'] == 59)
|
|
{
|
|
if($event['non_blocking'])
|
|
{
|
|
$dayEvents[$day][$num]['whole_day_on_top']=true;
|
|
$this->whole_day_positions[$num]=($this->rowHeight*($num+2));
|
|
$extraRowsToAdd++;
|
|
}
|
|
else
|
|
{
|
|
$dayEvents[$day][$num]['whole_day']=true;
|
|
}
|
|
}
|
|
$this->to_client($dayEvents[$day][$num]);
|
|
}
|
|
// check after every day if we have to increase $this->extraRows
|
|
if(($this->extraRowsOriginal+$extraRowsToAdd) > $this->extraRows)
|
|
{
|
|
$this->remBotExtraRows = $extraRowsToAdd;
|
|
$this->extraRows = ($this->extraRowsOriginal+$extraRowsToAdd);
|
|
}
|
|
}
|
|
}
|
|
return $dayEvents;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Returns the special icon html code for holidays
|
|
*
|
|
* @param string $type is the type of the holiday, currently either 'hday' or
|
|
* 'bday'
|
|
*/
|
|
function _get_holiday_icon($type)
|
|
{
|
|
//Set the special icon which will be prepended to the event
|
|
switch ($type) {
|
|
case "bday":
|
|
return html::image('calendar', 'cake', '', "style=\"float:left; padding: 1px 2px 0px 2px;\"");
|
|
case "hday":
|
|
return html::image('calendar', 'date', '', "style=\"float:left; padding: 1px 2px 0px 2px;\"");
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Creates a dummy holiday event. This event is shown in the day view, when
|
|
* added to the event list.
|
|
*
|
|
* @param int $day_start is a unix timestamp which contains the start of the day
|
|
* when the event occurs.
|
|
* @param string $title is the title of the dummy event which will be shown
|
|
* @param string $description is the long description of the event which will
|
|
* be shown in the event tooltip
|
|
*/
|
|
function _make_holiday_event($day_start, $title, $description, $type = 'bday')
|
|
{
|
|
//Calculate the end of the day by adding 23h:59min seconds
|
|
$day_end = $day_start + 24 * 3600 - 60;
|
|
|
|
//Setup the event data
|
|
$event = array(
|
|
'title' => $title,
|
|
'description' => $description,
|
|
'participants' => array(
|
|
),
|
|
'whole_day_on_top' => true,
|
|
'start' => $day_start,
|
|
'end' => $day_end,
|
|
'non_blocking' => true,
|
|
'prepend_icon' => $this->_get_holiday_icon($type)
|
|
);
|
|
|
|
return $event;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Collects all holidays/birthdays corresponding to the given day and creates
|
|
* an array containing all this events.
|
|
*
|
|
* @param string $day_ymd contains the Ymd of the day
|
|
* @param array $types is an array which determines which types of events should
|
|
* be added to the holiday list. May contain the indices "bdays" and "hdays".
|
|
* The default is "bdays => true"
|
|
*/
|
|
function _get_holiday_events($day_ymd, $types = array("bdays" => true, "hdays" => false))
|
|
{
|
|
//Check whether there are any holidays set for the current day_ymd
|
|
$events = array();
|
|
if (isset($this->holidays[$day_ymd]))
|
|
{
|
|
//Translate the day_ymd to a timestamp
|
|
$day_start = $this->bo->date2ts((string)$day_ymd);
|
|
|
|
//Iterate over the holidays array and add those the the events list
|
|
foreach($this->holidays[$day_ymd] as $holiday)
|
|
{
|
|
if (isset($holiday['birthyear']))
|
|
{
|
|
if (array_key_exists("bdays", $types) && $types['bdays'])
|
|
{
|
|
$events[] = $this->_make_holiday_event(
|
|
$day_start, $holiday['name'],
|
|
lang('Age:').(date('Y') - $holiday['birthyear']));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (array_key_exists("hdays", $types) && $types['hdays'])
|
|
{
|
|
$events[] = $this->_make_holiday_event($day_start, $holiday['name'], '', 'hday');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
}
|