diff --git a/calendar/inc/class.bocal.inc.php b/calendar/inc/class.bocal.inc.php index 53137b4629..49894a38f6 100644 --- a/calendar/inc/class.bocal.inc.php +++ b/calendar/inc/class.bocal.inc.php @@ -719,7 +719,7 @@ class bocal * * @param $date mixed date to convert * @param $server2user_time boolean conversation between user- and server-time default False == Off - * @return array with keys 'second', 'minute', 'hour', 'day', 'month', 'year', 'ras' (timestamp) and 'full' (Ymd-string) + * @return array with keys 'second', 'minute', 'hour', 'day', 'month', 'year', 'raw' (timestamp) and 'full' (Ymd-string) */ function date2array($date,$server2user=False) { diff --git a/calendar/inc/class.uicalendar.inc.php b/calendar/inc/class.uicalendar.inc.php index cdcb5f5dae..20eff16324 100755 --- a/calendar/inc/class.uicalendar.inc.php +++ b/calendar/inc/class.uicalendar.inc.php @@ -153,6 +153,9 @@ $GLOBALS['phpgw']->preferences->add('calendar','interval',30); $GLOBALS['phpgw']->preferences->save_repository(); } + + // calendar does not work with hidden sidebox atm. + unset($GLOBALS['phpgw_info']['user']['preferences']['common']['auto_hide_sidebox']); } /* Public functions */ @@ -4206,9 +4209,42 @@ return; { $GLOBALS['phpgw']->uiaccountsel = CreateObject('phpgwapi.uiaccountsel'); } + $fts_link = $GLOBALS['phpgw']->link('/index.php',array('menuaction'=>'calendar.uiforms.freetimesearch')); + + $fields = array('start[str]','start[hour]','start[min]','end[str]','end[hour]','end[min]'); + if ($this->bo->prefs['common']['timeformat'] == '12') + { + $fields[] = 'start[ampm]'; + $fields[] = 'end[ampm]'; + } $var['participants'] = array( 'field' => lang('Participants'), - 'data' => "\n ".$GLOBALS['phpgw']->uiaccountsel->selection('participants[]','uicalendar_select_participants',$event['participants'],'calendar+',7,$event['owner']), + 'data' => "\n ".$GLOBALS['phpgw']->uiaccountsel->selection('participants[]','uicalendar_select_participants',$event['participants'],'calendar+',7,$event['owner']).' + + '.$this->html->submit_button('freetimesearch',/*lang(*/'freetime search'/*)*/,"call_freetimesearch('".$fts_link."',this.form); return false;",0,' title="'.$this->html->htmlspecialchars(lang('Find free timeslots where the marked participants are availible for the given timespan')).'"') ); /* // External Participants diff --git a/calendar/inc/class.uiforms.inc.php b/calendar/inc/class.uiforms.inc.php new file mode 100644 index 0000000000..49374836af --- /dev/null +++ b/calendar/inc/class.uiforms.inc.php @@ -0,0 +1,286 @@ + * +* -------------------------------------------- * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation; either version 2 of the License, or (at your * +* option) any later version. * +\**************************************************************************/ + +/* $Id$ */ + +include_once(PHPGW_INCLUDE_ROOT . '/calendar/inc/class.uical.inc.php'); + +/** + * calendar UserInterface forms + * + * @package calendar + * @author RalfBecker@outdoor-training.de + * @license GPL + */ +class uiforms extends uical +{ + var $public_functions = array( + 'freetimesearch' => True, + ); + + /** + * Constructor + */ + function uiforms() + { + $this->uical(); // call the parent's constructor + } + + /** + * Freetime search + * + * As the function is called in a popup via javascript, parametes get initialy transfered via the url + * @param $content array/boolean array with parameters or false (default) to use the get-params + * @param start[str] string start-date + * @param start[hour] string start-hour + * @param start[min] string start-minutes + * @param end[str] string end-date + * @param end[hour] string end-hour + * @param end[min] string end-minutes + * @param participants string ':' delimited string of user-id's + */ + function freetimesearch($content = false) + { + if (!is_array($content)) + { + if ($this->debug) echo "
".print_r($_GET,true).""; + + foreach(array('start','end') as $name) + { + $arr = $this->jscal->input2date($_GET[$name]['str'],false); + $arr += $_GET[$name]; + $content[$name] = $this->bo->date2ts($arr); + } + $duration = $content['end'] - $content['start']; + + if ($duration <= 12*HOUR_s) + { + $content['duration_h'] = (int) ($duration / HOUR_s); + $content['duration_min'] = ($duration/60) % 60; + $content['end'] = 0; + } + else + { + $content['duration_h'] = $content['duration_min'] = 0; + } + foreach(explode(':',$_GET['participants']) as $uid) + { + if ((int) $uid) $content['participants'][] = (int) $uid; + } + // default search parameters + $content['start_time'] = $this->cal_prefs['workdaystarts']; + $content['end_time'] = $this->cal_prefs['workdayends']; + if ($this->cal_prefs['workdayends']*HOUR_s < $this->cal_prefs['workdaystarts']*HOUR_s+$duration) + { + $content['end_time'] = 0; // no end-time limit, as duration would never fit + } + $content['weekdays'] = MCAL_M_WEEKDAYS; + $content['search_window'] = 7 * DAY_s; + } + else + { + $duration = $content['end'] ? $content['end']-$content['start'] : 60*(60*$content['duration_h']+$content['duration_min']); + + if (is_array($content['freetime']['select'])) + { + list($selected) = each($content['freetime']['select']); + echo "$selected = ".date('D d.m.Y H:i',$content['freetime'][$selected]['start']); + $start = (int) $content['freetime'][$selected]['start']; + $end = $start + $duration; + $fields_to_set = array( + 'start[str]' => date($this->common_prefs['dateformat'],$start), + 'start[min]' => date('i',$start), + 'end[str]' => date($this->common_prefs['dateformat'],$end), + 'end[min]' => date('i',$end), + ); + if ($this->common_prefs['timeformat'] == 12) + { + $fields_to_set += array( + 'start[hour]' => date('h',$start), + 'start[ampm]' => date('a',$start), + 'end[hour]' => date('h',$end), + 'end[ampm]' => date('a',$end), + ); + } + else + { + $fields_to_set += array( + 'start[hour]' => date('H',$start), + 'end[hour]' => date('H',$end), + ); + } + echo " + +\n"; + exit; + } + } + $content['freetime'] = $this->freetime($content['participants'],$content['start'],$content['start']+$content['search_window'],$duration); + $content['freetime'] = $this->split_freetime_daywise($content['freetime'],$duration,$content['weekdays'],$content['start_time'],$content['end_time'],$sel_options); + $sel_options['search_window'] = array( + 7*DAY_s => lang('one week'), + 14*DAY_s => lang('two weeks'), + 31*DAY_s => lang('one month'), + 92*DAY_s => lang('three month'), + 365*DAY_s => lang('one year'), + ); + + $etpl = CreateObject('etemplate.etemplate','calendar.freetimesearch'); + + //echo "
".print_r($content,true)."\n"; + $GLOBALS['phpgw_info']['flags']['app_header'] = lang('calendar') . ' - ' . lang('freetime search'); + + $etpl->exec('calendar.uiforms.freetimesearch',$content,$sel_options,'',array( + 'participants' => $content['participants'], + ),2); + } + + /** + * calculate the freetime of given $participants in a certain time-span + * + * @param $start int start-time timestamp in user-time + * @param $end int end-time timestamp in user-time + * @param $participants array of user-id's + * @param $duration int min. duration in sec, default 1 + * @return array of free time-slots: array with start and end values + */ + function freetime($participants,$start,$end,$duration=1) + { + $busy = $this->bo->search(array( + 'start' => $start, + 'end' => $end, + 'users' => $participants, + )); + $busy[] = array( // add end-of-search-date as event, to cope with empty search and get freetime til that date + 'start' => array('raw'=>$end), + 'end' => array('raw'=>$end), + ); + $ft_start = $start; + $freetime = array(); + $n = 0; + foreach($busy as $event) + { + if ($this->debug) + { + echo "
ft_start=".date('D d.m.Y H:i',$ft_start)."
\n";
+ echo "event[title]=$event[title]
\n";
+ echo "event[start]=".date('D d.m.Y H:i',$event['start']['raw'])."
\n";
+ echo "event[end]=".date('D d.m.Y H:i',$event['end']['raw'])."
\n";
+ }
+ // $events ends before our actual position ==> ignore it
+ if ($event['end']['raw'] < $ft_start)
+ {
+ //echo "==> event ends before ft_start ==> continue
\n";
+ continue;
+ }
+ // $events starts before our actual position ==> set start to it's end and go to next event
+ if ($event['start']['raw'] < $ft_start)
+ {
+ //echo "==> event starts before ft_start ==> set ft_start to it's end & continue
\n";
+ $ft_start = $event['end']['raw'];
+ continue;
+ }
+ $ft_end = $event['start']['raw'];
+
+ // only show slots equal or bigger to min_length
+ if ($ft_end - $ft_start >= $duration)
+ {
+ $freetime[++$n] = array(
+ 'start' => $ft_start,
+ 'end' => $ft_end,
+ );
+ if ($this->debug) echo "
freetime: ".date('D d.m.Y H:i',$ft_start)." - ".date('D d.m.Y H:i',$ft_end)."
\n"; + } + $ft_start = $event['end']['raw']; + } + return $freetime; + } + + /** + * split the freetime in daywise slot, taking into account weekdays, start- and stop-times + * + * @param $freetime array of free time-slots: array with start and end values + * @param $duration int min. duration in sec + * @param $weekdays int allowed weekdays, bitfield of MCAL_M_... + * @param $start_time int minimum start-hour 0-23 + * @param $end_time int maximum end-hour 0-23, or 0 for none + * @param $sel_options array on return options for start-time selectbox + * @return array of free time-slots: array with start and end values + */ + function split_freetime_daywise($freetime,$duration,$weekdays,$start_time,$end_time,&$sel_options) + { + $freetime_daywise = $sel_options = array(); + $time_format = $this->common_prefs['timeformat'] == 12 ? 'h:i a' : 'H:i'; + + $n = 0; + foreach($freetime as $ft) + { + $daybegin = $this->bo->date2array($ft['start']); + $daybegin['hour'] = $daybegin['minute'] = $daybegin['second'] = 0; + unset($daybegin['raw']); + $daybegin = $this->bo->date2ts($daybegin); + + for($t = $daybegin; $t < $ft['end']; $t += DAY_s,$daybegin += DAY_s) + { + $dow = date('w',$daybegin+DAY_s/2); // 0=Sun, .., 6=Sat + $mcal_dow = pow(2,$dow); + if (!($weekdays & $mcal_dow)) + { + continue; // wrong day of week + } + $start = $t < $ft['start'] ? $ft['start'] : $t; + + if ($start-$daybegin < $start_time*HOUR_s) // start earlier then start_time + { + $start = $daybegin + $start_time*HOUR_s; + } + // if end_time given use it, else the original slot's end + $end = $end_time ? $daybegin + $end_time*HOUR_s : $ft['end']; + if ($end > $ft['end']) $end = $ft['end']; + + // slot to small for duration + if ($end - $start < $duration) + { + continue; + } + $freetime_daywise[++$n] = array( + 'start' => $start, + 'end' => $end, + ); + $times = array(); + for ($s = $start; $s+$duration <= $end; $s += 60*$this->cal_prefs['interval']) + { + $e = $s + $duration; + $end_date = $e-$daybegin > DAY_s ? lang(date('l',$e)).' '.date($this->common_prefs['dateformat'],$e).' ' : ''; + $times[$s] = date($time_format,$s).' - '.$end_date.date($time_format,$e); + } + $sel_options[$n.'[start]'] = $times; + } + } + return $freetime_daywise; + } +} diff --git a/calendar/setup/etemplates.inc.php b/calendar/setup/etemplates.inc.php new file mode 100644 index 0000000000..5f13bb0538 --- /dev/null +++ b/calendar/setup/etemplates.inc.php @@ -0,0 +1,9 @@ + 'calendar.freetimesearch','template' => '','lang' => '','group' => '0','version' => '1.0.1.001','data' => 'a:7:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:4:"span";s:12:"all,size120b";s:5:"label";s:15:"Freetime Search";}}i:2;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:17:"Startdate / -time";}s:1:"B";a:3:{s:4:"type";s:9:"date-time";s:4:"name";s:5:"start";s:4:"help";s:33:"Startdate and -time of the search";}}i:3;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:8:"Duration";}s:1:"B";a:6:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"4";i:1;a:4:{s:4:"type";s:13:"select-number";s:4:"size";s:5:",0,12";s:4:"name";s:10:"duration_h";s:4:"help";s:23:"Duration of the meeting";}i:2;a:5:{s:4:"type";s:13:"select-number";s:4:"size";s:8:",0,59,05";s:5:"label";s:1:":";s:4:"name";s:12:"duration_min";s:4:"help";s:19:"Timeframe to search";}i:3;a:2:{s:4:"type";s:5:"label";s:5:"label";s:18:"or Enddate / -time";}i:4;a:3:{s:4:"type";s:9:"date-time";s:4:"name";s:3:"end";s:4:"help";s:57:"Enddate / -time of the meeting, eg. for more then one day";}}}i:4;a:2:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:9:"Timeframe";}s:1:"B";a:7:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"5";i:1;a:3:{s:4:"type";s:13:"date-houronly";s:4:"name";s:10:"start_time";s:4:"help";s:19:"Timeframe to search";}i:2;a:2:{s:4:"type";s:5:"label";s:5:"label";s:3:"til";}i:3;a:3:{s:4:"type";s:13:"date-houronly";s:4:"name";s:8:"end_time";s:4:"help";s:19:"Timeframe to search";}i:4;a:2:{s:4:"type";s:5:"label";s:5:"label";s:8:"Weekdays";}i:5;a:4:{s:4:"type";s:10:"select-dow";s:4:"size";s:1:"3";s:4:"name";s:8:"weekdays";s:4:"help";s:25:"Weekdays to use in search";}}}i:5;a:2:{s:1:"A";a:4:{s:4:"type";s:6:"button";s:5:"label";s:10:"New search";s:4:"name";s:6:"search";s:4:"help";s:36:"new search with the above parameters";}s:1:"B";a:4:{s:4:"type";s:6:"select";s:7:"no_lang";s:1:"1";s:4:"name";s:13:"search_window";s:4:"help";s:34:"how far to search (from startdate)";}}i:6;a:2:{s:1:"A";a:4:{s:4:"type";s:8:"template";s:4:"size";s:8:"freetime";s:4:"span";s:3:"all";s:4:"name";s:4:"rows";}s:1:"B";a:1:{s:4:"type";s:5:"label";}}}','size' => '','style' => '.size120b { text-size: 120%; font-weight: bold;}','modified' => '1097184234',); + +$templ_data[] = array('name' => 'calendar.freetimesearch.rows','template' => '','lang' => '','group' => '0','version' => '1.0.1.001','data' => 'a:3:{i:0;a:2:{s:2:"c1";s:2:"th";s:2:"c2";s:3:"row";}i:1;a:4:{s:1:"A";a:2:{s:4:"type";s:5:"label";s:5:"label";s:4:"Date";}s:1:"B";a:2:{s:4:"type";s:5:"label";s:5:"label";s:4:"Time";}s:1:"C";a:2:{s:4:"type";s:5:"label";s:5:"label";s:6:"Select";}s:1:"D";a:2:{s:4:"type";s:5:"label";s:5:"label";s:7:"Enddate";}}i:2;a:4:{s:1:"A";a:4:{s:4:"type";s:4:"date";s:4:"size";s:3:",16";s:4:"name";s:13:"${row}[start]";s:8:"readonly";s:1:"1";}s:1:"B";a:4:{s:4:"type";s:6:"select";s:7:"no_lang";s:1:"1";s:4:"name";s:13:"${row}[start]";s:4:"help";s:13:"select a time";}s:1:"C";a:4:{s:4:"type";s:6:"button";s:5:"label";s:6:"Select";s:4:"name";s:12:"select[$row]";s:4:"help";s:41:"use the selected time and close the popup";}s:1:"D";a:3:{s:4:"type";s:9:"date-time";s:4:"name";s:11:"${row}[end]";s:8:"readonly";s:1:"1";}}}','size' => '','style' => '','modified' => '1097183756',); + diff --git a/calendar/setup/phpgw_de.lang b/calendar/setup/phpgw_de.lang index a8ce8e2ea9..16d2417c8e 100644 --- a/calendar/setup/phpgw_de.lang +++ b/calendar/setup/phpgw_de.lang @@ -93,6 +93,7 @@ do you want to receive a regulary summary of your appointsments via email?