* @copyright (c) 2004-8 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 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 bocal class !!! */ class calendar_uiviews extends calendar_ui { var $public_functions = array( 'day' => True, 'day4' => True, 'week' => True, 'weekN' => True, 'month' => True, 'planner' => True, 'index' => True, ); /** * integer level or string function- or widget-name * * @var mixed */ var $debug=false; /** * minimum width for an event * * @var int */ var $eventCol_min_width = 80; /** * extra rows above and below the workday * * @var int */ var $extraRows = 2; /** * 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 be the timeGridWidget * * @var int */ var $rowsToDisplay; /** * height in percent of one row, gets set be 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; /** * Dragdrop Object * * @var dragdrop; */ var $dragdrop; /** * 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; /** * 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; $app_header = array( 'day' => lang('Dayview'), '4day' => lang('Four days view'), 'week' => lang('Weekview'), 'month' => lang('Monthview'), 'planner' => lang('Group planner'), ); $GLOBALS['egw_info']['flags']['app_header'] = $GLOBALS['egw_info']['apps']['calendar']['title']. (isset($app_header[$this->view]) ? ' - '.$app_header[$this->view] : ''). // for a single owner we add it's name to the app-header (count(explode(',',$this->owner)) == 1 ? ': '.$this->bo->participant_name($this->owner) : ''); // standard params for calling bocal::search for all views $this->search_params = array( 'start' => $this->date, 'cat_id' => $this->cat_id, 'users' => explode(',',$this->owner), 'filter' => $this->filter, 'daywise' => True, ); $this->holidays = $this->bo->read_holidays($this->year); $this->check_owners_access(); if($GLOBALS['egw_info']['user']['preferences']['common']['enable_dragdrop']) { $this->dragdrop = new dragdrop(); // if the object would auto-disable itself unset object // to avoid unneccesary dragdrop calls later if(!$this->dragdrop->validateBrowser()) { $this->dragdrop = false; } } } /** * Show the last view or the default one, if no last */ function index() { if (!$this->view) $this->view = 'week'; // handle views in other files if (!isset($this->public_functions[$this->view])) { $GLOBALS['egw']->redirect_link('/index.php',array('menuaction'=>$this->view_menuaction)); } // get manual to load the right page $GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => 'ManualCalendar'.ucfirst($this->view)); $this->{$this->view}(); } /** * Show the calendar on the home page * * @return string with content */ function &home() { // set some stuff for the home-page $this->__construct(array( 'date' => $this->bo->date2string($this->bo->now_su), 'cat_id' => 0, 'filter' => 'all', 'owner' => substr($this->cal_prefs['defaultcalendar'],0,7) == 'planner' && $this->cal_prefs['planner_start_with_group'] ? $this->cal_prefs['planner_start_with_group'] : $this->user, 'multiple' => 0, 'view' => $this->bo->cal_prefs['defaultcalendar'], )); if (($error = $this->check_owners_access())) { return $error; } if ($this->group_warning) { $group_warning = '
'.$this->group_warning."
\n"; } switch($this->cal_prefs['defaultcalendar']) { case 'planner_user': case 'planner_cat': case 'planner': return $group_warning.$this->planner(true); case 'month': return $group_warning.$this->month(0,true); case 'weekN': return $group_warning.$this->weekN(true); default: case 'week': return $group_warning.$this->week(0,true); case 'day': return $group_warning.$this->day(true); } } /** * Displays the planner view * * @param boolean $home=false if true return content suitable for home-page */ function &planner($home=false) { if (!$this->planner_days) // planner monthview { if ($this->day < 15) // show one complete month { $this->_week_align_month($this->first,$this->last); $GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang(adodb_date('F',$this->bo->date2ts($this->date))).' '.$this->year; } else // show 2 half month { $this->_week_align_month($this->first,$this->last,15); $GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang(adodb_date('F',$this->first)).' / '.lang(adodb_date('F',$this->last)).' '.$this->year; } } elseif ($this->planner_days >= 5) // weeekview { $this->first = $this->datetime->get_weekday_start($this->year,$this->month,$this->day); $this->last = $this->bo->date2array($this->first); $this->last['day'] += (int) $this->planner_days - 1; $this->last['hour'] = 23; $this->last['minute'] = $this->last['sec'] = 59; unset($this->last['raw']); $this->last = $this->bo->date2ts($this->last); $GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang('Week').' '.adodb_date('W',$this->first).': '.$this->bo->long_date($this->first,$this->last); } else // dayview { $this->first = $this->bo->date2ts($this->date); $this->last = $this->bo->date2array($this->first); $this->last['day'] += (int) $this->planner_days - 1; $this->last['hour'] = 23; $this->last['minute'] = $this->last['sec'] = 59; unset($this->last['raw']); $this->last = $this->bo->date2ts($this->last); $GLOBALS['egw_info']['flags']['app_header'] .= ': '.($this->planner_days == 1 ? lang(date('l',$this->first)).', ' : ''). $this->bo->long_date($this->first,$this->planner_days > 1 ? $this->last : 0); } $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'; $events = $this->bo->search($search_params); 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)); $content =& $this->plannerWidget($events,$this->first,$this->last,$this->sortby == 'user' ? false : (int) $this->cat_id); if (!$home) { $this->do_header(); echo $content; } return $content; } /** * Displays a multiple week-view * * @param boolean $home=false if true return content suitable for home-page */ function &weekN($home=false) { if (($num = (int)$this->cal_prefs['multiple_weeks']) < 2) $num = 3; // default 3 weeks return $this->month($num,$home); } /** * 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 $home=false if true 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); $this->use_time_grid = !$this->cal_prefs['use_time_grid'] || $this->cal_prefs['use_time_grid'] == 'all'; // all views if ($weeks) { $this->first = $this->datetime->get_weekday_start($this->year,$this->month,$this->day); $this->last = strtotime("+$weeks weeks",$this->first) - 1; } else { $this->_week_align_month($this->first,$this->last); } 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)); $GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang(adodb_date('F',$this->bo->date2ts($this->date))).' '.$this->year; $days =& $this->bo->search(array( 'start' => $this->first, 'end' => $this->last, )+$this->search_params); $content = ''; // we add DAY_s/2 to $this->first (using 12h), to deal with daylight saving changes for ($week_start = $this->first; $week_start < $this->last; $week_start = strtotime("+1 week",$week_start)) { $week = array(); for ($i = 0; $i < 7; ++$i) { $day_ymd = $this->bo->date2string($i ? strtotime("+$i days",$week_start) : $week_start); $week[$day_ymd] = array_shift($days); } $week_view = array( 'menuaction' => 'calendar.calendar_uiviews.week', 'date' => $this->bo->date2string($week_start), ); $title = lang('Wk').' '.adodb_date('W',$week_start); if ($this->allowEdit) { $title = html::a_href($title,$week_view,'',' title="'.lang('Weekview').'"'); } $content .= $this->timeGridWidget($this->tagWholeDayOnTop($week),$weeks == 2 ? 30 : 60,200,'',$title,0,$week_start+WEEK_s >= $this->last); } if (!$home) { $this->do_header(); echo $content; } // make wz_dragdrop elements work if(is_object($this->dragdrop)) { $this->dragdrop->setJSCode(); } return $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); } /** * Four days view, everythings done by the week-view code ... * * @param boolean $home=false if true return content suitable for home-page * @return string */ function day4($home=false) { return $this->week(4,$home); } /** * 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 $home=false if true return content suitable for home-page */ function week($days=0,$home=false) { $this->use_time_grid = $days != 4 && !in_array($this->cal_prefs['use_time_grid'],array('day','day4')) || $days == 4 && $this->cal_prefs['use_time_grid'] != 'day'; 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; $GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang('Four days view').' '.$this->bo->long_date($this->first,$this->last); } 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; $GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang('Week').' '.adodb_date('W',$this->first).': '.$this->bo->long_date($this->first,$this->last); } # temporarly disabled, because it collides with the title for the website # # // add navigation for previous and next # // prev. week # $GLOBALS['egw_info']['flags']['app_header'] = html::a_href(html::image('phpgwpai','first',lang('previous'),$options=' alt="<<"'),array( # 'menuaction' => $this->view_menuaction, # 'date' => date('Ymd',$this->first-$days*DAY_s), # )) . ' '.$GLOBALS['egw_info']['flags']['app_header']; # // next week # $GLOBALS['egw_info']['flags']['app_header'] .= ' '.html::a_href(html::image('phpgwpai','last',lang('next'),$options=' alt=">>"'),array( # 'menuaction' => $this->view_menuaction, # 'date' => date('Ymd',$this->last+$days*DAY_s), # )); # # $class = $class == 'row_on' ? 'th' : 'row_on'; //echo "weekdaystarts='".$this->cal_prefs['weekdaystarts']."', get_weekday_start($this->year,$this->month,$this->day)=".date('l Y-m-d',$wd_start).", first=".date('l Y-m-d',$this->first)."
\n"; $search_params = array( 'start' => $this->first, 'end' => $this->last, ) + $this->search_params; $users = $this->search_params['users']; if (!is_array($users)) $users = array($users); if (count($users) == 1 || count($users) > 5) // for more then 3 users, show all in one row { $content =& $this->timeGridWidget($this->tagWholeDayOnTop($this->bo->search($search_params)),$this->cal_prefs['interval']); } else { $content = ''; foreach($this->_get_planner_users(false) as $uid => $label) { $search_params['users'] = $uid; $content .= ''.$label."\n"; $content .= $this->timeGridWidget($this->tagWholeDayOnTop($this->bo->search($search_params)), count($users) * $this->cal_prefs['interval'],400 / count($users),'','',$uid); } } if (!$home) { $this->do_header(); echo $content; } // make wz_dragdrop elements work if(is_object($this->dragdrop)) { $this->dragdrop->setJSCode(); } return $content; } /** * Displays the dayview * * @param boolean $home=false if true return content suitable for home-page */ function &day($home=false) { if ($this->debug > 0) $this->bo->debug_message('uiviews::day() date=%1',True,$this->date); $this->last = $this->first = $this->bo->date2ts((string)$this->date); $GLOBALS['egw_info']['flags']['app_header'] .= ': '.$this->bo->long_date($this->first,0,false,true); $this->use_time_grid = true; // day-view always uses a time-grid, independent what's set in the prefs! $this->search_params['end'] = $this->last = $this->first+DAY_s-1; if (!$home) { $this->do_header(); $users = $this->search_params['users']; if (!is_array($users)) $users = array($users); // for more then 5 users, show all in one row if (count($users) == 1 || count($users) > 5) { $dayEvents =& $this->bo->search($this->search_params); $owner = 0; } else { $dayEvents = $owner = array(); $search_params = $this->search_params; foreach($this->_get_planner_users(false) as $uid => $label) { $search_params['users'] = $uid; list(,$dayEvents[''.$label.'']) = each($this->bo->search($search_params)); $owner[] = $uid; } } $cols = array(); $cols[0] =& $this->timeGridWidget($this->tagWholeDayOnTop($dayEvents),$this->cal_prefs['interval'],450,'','',$owner); // only show todo's for a single user if (count($users) == 1 && ($todos = $this->get_todos($todo_label)) !== false) { if ($GLOBALS['egw_info']['user']['apps']['infolog']) { foreach(array('task','phone','note') as $type) { $todo_label .= ' '.html::a_href( html::image('infolog',$type,lang('Add')),'infolog.uiinfolog.edit',array( 'type' => $type, 'start_time' => $ts, ),' target="_blank" onclick="window.open(this.href,this.target,\'dependent=yes,width=750,height=590,scrollbars=yes,status=yes\'); return false;"'); } } $cols[1] = html::div( html::div($todo_label,'','calDayTodosHeader th')."\n". html::div($todos,'','calDayTodosTable'),'','calDayTodos'); $cols['.1'] = 'width=30%'; echo html::table(array( 0 => $cols, '.0' => 'valign="top"' ),'class="calDayView"'); } else { echo $cols[0]; } // make wz_dragdrop elements work if(is_object($this->dragdrop)) { $this->dragdrop->setJSCode(); } } else { $content = $this->timeGridWidget($this->bo->search($this->search_params),$this->cal_prefs['interval'],300); // make wz_dragdrop elements work if(is_object($this->dragdrop)) { $this->dragdrop->setJSCode(); } return $content; } } /** * 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 => $app) { $icons .= ($icons?' ':'').$GLOBALS['egw']->html->image($app,$name,lang($name),'border="0" width="15" height="15"'); } $class = $class == 'row_on' ? 'row_off' : 'row_on'; $content .= "uiviews::_get_planner_cats($cats=".$this->cats->id2name($cats).") (this->cat_id=$this->cat_id) = ".print_r($ret,true).'='.$this->cats->id2name($ret[0])."
\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.'_planner_pos(".date('Y-m-d H:i',$time).', '.date('Y-m-d H:i',$start).', '.date('Y-m-d H:i',$end).") = $percent
\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'; return $indent.'