diff --git a/admin/lang/egw_de.lang b/admin/lang/egw_de.lang index c2edbe05a3..2ca1d8a242 100644 --- a/admin/lang/egw_de.lang +++ b/admin/lang/egw_de.lang @@ -95,6 +95,7 @@ add new application admin de Neue Anwendung hinzufügen add new email address: admin de Neue E-Mail-Adresse hinzufügen: add peer server admin de Server zu Serververbund hinzufügen add profile admin de Profil hinzufügen +add to group admin de Zu Gruppe hinzufügen add sub-category admin de Unterkategorie hinzufügen add user admin de Neuen Benutzer erstellen add user or group admin de Benutzer oder Gruppen eingeben @@ -186,6 +187,7 @@ bi-dir passthrough admin de Weiterleitung in beide Richtungen bi-directional admin de beide Richtungen blocking after wrong password admin de Blockierung nach falschem Passwort bottom admin de Unten +bulk changes admin de Massenänderungen bulk password reset admin de Rücksetzen Passwörter by admin de Von by selecting a user or group you effectively delete the mail account for all other users!\n\nare you really sure you want to do that? admin de Durch die Auswahl eines Benutzer oder Gruppe löschen Sie das Mailkonto für alle anderen Benutzer!
Sind Sie wirklich sicher, dass Sie das wollen? diff --git a/admin/lang/egw_en.lang b/admin/lang/egw_en.lang index 6e5ad6d08f..5f444bbcab 100644 --- a/admin/lang/egw_en.lang +++ b/admin/lang/egw_en.lang @@ -95,6 +95,7 @@ add new email address: admin en Add new email address: add peer server admin en Add Peer Server add profile admin en Add profile add sub-category admin en Add sub category +add to group admin en Add to group add user admin en Add user add user or group admin en Add user or group added admin en added diff --git a/api/src/CalDAV/JsCalendar.php b/api/src/CalDAV/JsCalendar.php index 1bfef61402..9c30b193c0 100644 --- a/api/src/CalDAV/JsCalendar.php +++ b/api/src/CalDAV/JsCalendar.php @@ -925,7 +925,7 @@ class JsCalendar extends JsBase protected static function Recurrence(array $event, array $data, array $exceptions=[], ?array $rrule=null) { $overrides = []; - if (!empty($event['recur_type']) || isset($rrule)) + if ((!empty($event['recur_type']) || isset($rrule)) && $event['recur_type'] != \calendar_rrule::RDATE) { if (!isset($rrule)) { @@ -956,24 +956,36 @@ class JsCalendar extends JsBase { $rule['byMonthDay'] = [$rrule['BYMONTHDAY']]; // EGroupware supports only a single day! } - - // adding excludes to the overrides - if (!empty($event['recur_exception'])) + } + elseif (!empty($event['recur_rdates']) && $event['recur_type'] == \calendar_rrule::RDATE) + { + foreach($event['recur_rdates'] as $rdate) { - foreach ($event['recur_exception'] as $timestamp) + if ($rdate != $event['start']) { - $ex_date = new Api\DateTime($timestamp, Api\DateTime::$server_timezone); - if (!empty($event['whole_day'])) - { - $ex_date->setTime(0, 0, 0); - } - $overrides[self::DateTime($ex_date, $event['tzid'])] = [ - 'excluded' => true, + $overrides[self::DateTime($rdate, $event['tzid'])] = [ + 'start' => self::DateTime($rdate, $event['tzid']), ]; } } } + // adding excludes to the overrides + if (!empty($event['recur_type']) && !empty($event['recur_exception'])) + { + foreach ($event['recur_exception'] as $timestamp) + { + $ex_date = new Api\DateTime($timestamp, Api\DateTime::$server_timezone); + if (!empty($event['whole_day'])) + { + $ex_date->setTime(0, 0, 0); + } + $overrides[self::DateTime($ex_date, $event['tzid'])] = [ + 'excluded' => true, + ]; + } + } + // adding exceptions to the overrides foreach($exceptions as $exception) { diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php index 359c5a37f5..50c52e690f 100644 --- a/calendar/inc/class.calendar_bo.inc.php +++ b/calendar/inc/class.calendar_bo.inc.php @@ -129,7 +129,7 @@ class calendar_bo MCAL_RECUR_MONTHLY_WDAY => 'Monthly (by day)', MCAL_RECUR_MONTHLY_MDAY => 'Monthly (by date)', MCAL_RECUR_YEARLY => 'Yearly', - MCAL_RECUR_RDATE/*calendar_rrule::PERIOD*/ => 'Explicit recurrences', + MCAL_RECUR_RDATE => 'Explicit recurrences', ); /** * @var array recur_days translates MCAL recur-days to verbose labels diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php index fe5aa7aa88..4aaa314509 100644 --- a/calendar/inc/class.calendar_boupdate.inc.php +++ b/calendar/inc/class.calendar_boupdate.inc.php @@ -302,7 +302,7 @@ class calendar_boupdate extends calendar_bo $this->log2file($event2save,$event,$old_event); } // send notifications if the event is in the future - if(!$skip_notification && $event['end'] > $this->now_su) + if(!$skip_notification && $this->eventInFuture($event)) { if ($new_event) { @@ -857,13 +857,30 @@ class calendar_boupdate extends calendar_bo * @param array $new_event =null Event after the change * @param int|string $user =0 User/participant who started the notify, default current user * @param array $alarm =null values for "offset", "start", etc. + * @parqm boolean $ignore_prefs Ignore the user's preferences about when they want to be notified and send it * @return bool true/false */ - function send_update($msg_type, $to_notify, $old_event, $new_event=null, $user=0, array $alarm=null) + function send_update($msg_type, $to_notify, $old_event, $new_event=null, $user=0, ?array $alarm=null, $ignore_prefs=false) { Api\Egw::on_shutdown([$this, '_send_update'], func_get_args()); } + /** + * Check event is (ending) in the future + * + * @param array $event + * @param int $grace_time + * @return bool + */ + public function eventInFuture(array $event, int $grace_time=10) : bool + { + if ($event['recur_type'] != MCAL_RECUR_NONE) + { + return empty($event['recur_enddate']) || $event['recur_enddate'] > $this->now_su - $grace_time; + } + return $event['end'] > $this->now_su - $grace_time; + } + /** * sends update-messages to certain participants of an event * @@ -876,7 +893,7 @@ class calendar_boupdate extends calendar_bo * @parqm boolean $ignore_prefs Ignore the user's preferences about when they want to be notified and send it * @return bool true/false */ - function _send_update($msg_type, $to_notify, $old_event, $new_event=null, $user=0, array $alarm=null, $ignore_prefs = false) + function _send_update($msg_type, $to_notify, $old_event, $new_event=null, $user=0, ?array $alarm=null, $ignore_prefs = false) { //error_log(__METHOD__."($msg_type, ".json_encode($to_notify).", ..., ".json_encode($new_event).", ...)"); if (!is_array($to_notify)) @@ -893,9 +910,7 @@ class calendar_boupdate extends calendar_bo } // ignore events in the past (give a tolerance of 10 seconds for the script) - if($new_event && $this->date2ts($new_event['start']) < ($this->now_su - 10) || - !$new_event && $old_event && $this->date2ts($old_event['start']) < ($this->now_su - 10) - ) + if($new_event && !$this->eventInFuture($new_event) || !$new_event && $old_event && !$this->eventInFuture($old_event)) { error_log(__METHOD__."($msg_type, ".json_encode($to_notify).", ..., ".json_encode($new_event).", ...) --> ignoring event in the past: start=". date('Y-m-d H:i:s', ($new_event ?: $old_event)['start'])." < ".date('Y-m-d H:i:s', $this->now_su-10)); @@ -982,6 +997,10 @@ class calendar_boupdate extends calendar_bo $enddate = new Api\DateTime($event['end'], new DateTimeZone($user_prefs['common']['tz'])); $modified = new Api\DateTime($event['modified'], new DateTimeZone($user_prefs['common']['tz'])); if ($old_event) $olddate = new Api\DateTime($old_event['start'], new DateTimeZone($user_prefs['common']['tz'])); + $rdates = array_map(static function($rdate) use ($user_prefs) + { + return new Api\DateTime($rdate, new DateTimeZone($user_prefs['common']['tz'])); + }, $event['recur_rdates']); //error_log(__METHOD__."() date_default_timezone_get()=".date_default_timezone_get().", user-timezone=".Api\DateTime::$user_timezone->getName().", startdate=".$startdate->format().", enddate=".$enddate->format().", updated=".$modified->format().", olddate=".($olddate ? $olddate->format() : '')); $owner_prefs = $ics = null; @@ -1114,17 +1133,20 @@ class calendar_boupdate extends calendar_bo // Set dates: // $details in "preference" format, $cleared_event as DateTime so calendar_ical->exportVCal() gets // the times right, since it assumes a timestamp is in server time - $startdate->setTimezone($timezone); + $cleared_event['start'] = $startdate->setTimezone($timezone); $details['startdate'] = $startdate->format($timeformat); - $cleared_event['start'] = $startdate; - $enddate->setTimezone($timezone); + $cleared_event['end'] = $enddate->setTimezone($timezone); $details['enddate'] = $enddate->format($timeformat); - $cleared_event['end'] = $enddate; - $modified->setTimezone($timezone); + $cleared_event['updated'] = $modified->setTimezone($timezone); $details['updated'] = $modified->format($timeformat) . ', ' . Api\Accounts::username($event['modifier']); - $cleared_event['updated'] = $modified; + + // we also need to "fix" timezone for rdates, to not get wrong times! + $cleared_event['recur_rdates'] = array_map(static function ($rdate) use ($timezone) + { + return $rdate->setTimezone($timezone); + }, $rdates); // Current date doesn't need to go into the cleared event, just for details $date->setTimezone($timezone); @@ -1404,7 +1426,7 @@ class calendar_boupdate extends calendar_bo $this->check_reset_statuses($event, $old_event); // set recur-enddate/range-end to real end-date of last recurrence - if (!empty($event['recur_type']) && (!empty($event['recur_enddate']) || $event['recur_type'] == calendar_rrule::PERIOD) && $event['start']) + if (!empty($event['recur_type']) && (!empty($event['recur_enddate']) || $event['recur_type'] == calendar_rrule::RDATE) && $event['start']) { $event['recur_enddate'] = new Api\DateTime($event['recur_enddate'], calendar_timezones::DateTimeZone($event['tzid'])); $event['recur_enddate']->setTime(23,59,59); @@ -2064,7 +2086,7 @@ class calendar_boupdate extends calendar_bo $event_arr = $this->event2array($event); foreach($event_arr as $key => $val) { - if ($key == 'recur_type') $key = 'repetition'; + if ($key == 'recur_type') $details['repetition'] = $val['data']; $details[$key] = $val['data']; } $details['participants'] = $details['participants'] ? implode("\n",$details['participants']) : ''; diff --git a/calendar/inc/class.calendar_ical.inc.php b/calendar/inc/class.calendar_ical.inc.php index c4a22378c5..253f830628 100644 --- a/calendar/inc/class.calendar_ical.inc.php +++ b/calendar/inc/class.calendar_ical.inc.php @@ -222,6 +222,7 @@ class calendar_ical extends calendar_boupdate 'ORGANIZER' => 'owner', 'RRULE' => 'recur_type', 'EXDATE' => 'recur_exception', + 'RDATE' => 'recur_rdates', 'PRIORITY' => 'priority', 'TRANSP' => 'non_blocking', 'CATEGORIES' => 'category', @@ -630,8 +631,8 @@ class calendar_ical extends calendar_boupdate case 'RRULE': if ($event['recur_type'] == MCAL_RECUR_NONE) break; // no recuring event $rriter = calendar_rrule::event2rrule($event, false, $tzid); - $rrule = $rriter->generate_rrule($version); - if ($event['recur_enddate']) + if (!($rrule = $rriter->generate_rrule($version))) break; // no recurring event (with rrule) + if (isset($rrule['UNTIL'])) { if (!$tzid || $version != '1.0') { @@ -653,7 +654,7 @@ class calendar_ical extends calendar_boupdate } if ($version == '1.0') { - if ($event['recur_enddate'] && $tzid) + if (isset($rrule['UNTIL']) && $tzid) { $rrule['UNTIL'] = self::getDateTime($rrule['UNTIL'],$tzid); } @@ -670,24 +671,25 @@ class calendar_ical extends calendar_boupdate break; case 'EXDATE': + case 'RDATE': if ($event['recur_type'] == MCAL_RECUR_NONE) break; - if (!empty($event['recur_exception'])) + if (!empty($event[$egwFieldName])) { if (empty($event['whole_day'])) { - foreach ($event['recur_exception'] as $key => $timestamp) + foreach ($event[$egwFieldName] as $key => $timestamp) { // current Horde_Icalendar 2.1.4 exports EXDATE always in UTC, postfixed with a Z :( // so if we set a timezone here, we have to remove the Z, see the hack at the end of this method // Apple calendar on OS X 10.11.4 uses a timezone, so does Horde eg. for Recurrence-ID $ex_date = new Api\DateTime($timestamp, Api\DateTime::$server_timezone); - $event['recur_exception'][$key] = self::getDateTime($ex_date->format('ts') + $ex_date->getOffset(), $tzid, $parameters['EXDATE']); + $event[$egwFieldName][$key] = self::getDateTime($ex_date->format('ts') + $ex_date->getOffset(), $tzid, $parameters[$icalFieldName]); } } else { // use 'DATE' instead of 'DATE-TIME' on whole day events - foreach ($event['recur_exception'] as $id => $timestamp) + foreach ($event[$egwFieldName] as $id => $timestamp) { $time = new Api\DateTime($timestamp,Api\DateTime::$server_timezone); $time->setTimezone(self::$tz_cache[$event['tzid']]); @@ -698,10 +700,10 @@ class calendar_ical extends calendar_boupdate 'mday' => $arr['day'], ); } - $event['recur_exception'] = $days; - if ($version != '1.0') $parameters['EXDATE']['VALUE'] = 'DATE'; + $event[$egwFieldName] = $days; + if ($version != '1.0') $parameters[$icalFieldName]['VALUE'] = 'DATE'; } - $vevent->setAttribute('EXDATE', $event['recur_exception'], $parameters['EXDATE']); + $vevent->setAttribute($icalFieldName, $event[$egwFieldName], $parameters[$icalFieldName]); } break; @@ -1105,9 +1107,9 @@ class calendar_ical extends calendar_boupdate "()\n".array2string($retval)."\n",3,$this->logfile); } - // hack to fix iCalendar exporting EXDATE always postfixed with a Z + // hack to fix iCalendar exporting EXDATE|RDATE always postfixed with a Z // EXDATE can have multiple values and therefore be folded into multiple lines - return preg_replace_callback("/\nEXDATE;TZID=[^:]+:[0-9TZ \r\n,]+/", function($matches) + return preg_replace_callback("/\n(EXDATE|RDATE);TZID=[^:]+:[0-9TZ \r\n,]+/", static function($matches) { return preg_replace('/([0-9 ])Z/', '$1', $matches[0]); }, $retval); @@ -2179,6 +2181,7 @@ class calendar_ical extends calendar_boupdate 'recur_data' => 'recur_data', 'recur_enddate' => 'recur_enddate', 'recur_exception' => 'recur_exception', + 'recur_rdates' => 'recur_rdates', 'title' => 'title', 'alarm' => 'alarm', 'whole_day' => 'whole_day', @@ -2764,21 +2767,19 @@ class calendar_ical extends calendar_boupdate $hour = date('H', $vcardData['start']); $minutes = date('i', $vcardData['start']); $seconds = date('s', $vcardData['start']); - if($attributes['params']['VALUE'] == 'PERIOD') + $vcardData['recur_type'] = calendar_rrule::RDATE; + $vcardData['recur_rdates'] = []; + foreach($attributes['values'] as $date) { - $vcardData['recur_type'] = calendar_rrule::PERIOD; - $vcardData['recur_rdates'] = []; - foreach($attributes['values'] as $date) - { - $vcardData['recur_rdates'][] = mktime( - $hour, - $minutes, - $seconds, - $date['month'], - $date['mday'], - $date['year'] - ); - } + // ToDo: use $date['period'], if set, to allow a different duration than end- - start-time + $vcardData['recur_rdates'][] = mktime( + $date['hour'] ?? $hour, + $date['minute'] ?? $minutes, + $date['second'] ?? $seconds, + $date['month'], + $date['mday'], + $date['year'] + ); } break; case 'EXDATE': // current Horde_Icalendar returns dates, no timestamps diff --git a/calendar/inc/class.calendar_rrule.inc.php b/calendar/inc/class.calendar_rrule.inc.php index 4c9d5eacd2..a0179cde11 100644 --- a/calendar/inc/class.calendar_rrule.inc.php +++ b/calendar/inc/class.calendar_rrule.inc.php @@ -68,10 +68,10 @@ class calendar_rrule implements Iterator /** * RDATE: date or period (a list of dates, instead of a RRULE) */ - const PERIOD = 9; + const RDATE = 9; /** - * Translate recure types to labels + * Translate recurrence types to labels * * @var array */ @@ -82,11 +82,11 @@ class calendar_rrule implements Iterator self::MONTHLY_WDAY => 'Monthly (by day)', self::MONTHLY_MDAY => 'Monthly (by date)', self::YEARLY => 'Yearly', - self::PERIOD => 'By date or period' + self::RDATE => 'Explicit recurrences', ); /** - * @var array $recur_egw2ical_2_0 converstaion of egw recur-type => ical FREQ + * @var array $recur_egw2ical_2_0 conversation of egw recur-type => ical RULE;FREQ */ static private $recur_egw2ical_2_0 = array( self::DAILY => 'DAILY', @@ -96,11 +96,11 @@ class calendar_rrule implements Iterator self::YEARLY => 'YEARLY', self::HOURLY => 'HOURLY', self::MINUTELY => 'MINUTELY', - self::PERIOD => 'PERIOD' + self::RDATE => 'RDATE', ); /** - * @var array $recur_egw2ical_1_0 converstaion of egw recur-type => ical FREQ + * @var array $recur_egw2ical_1_0 conversation of egw recur-type => ical FREQ */ static private $recur_egw2ical_1_0 = array( self::DAILY => 'D', @@ -143,10 +143,10 @@ class calendar_rrule implements Iterator public $monthly_bymonthday; /** - * Period list - * @var + * Recurrence dates + * @var \DateTime[] */ - public $period = []; + public $rdates = []; /** * Enddate of recurring event or null, if not ending @@ -276,7 +276,7 @@ class calendar_rrule implements Iterator $this->time = $time instanceof Api\DateTime ? $time : new Api\DateTime($time); if(!in_array($type, array(self::NONE, self::DAILY, self::WEEKLY, self::MONTHLY_MDAY, self::MONTHLY_WDAY, - self::YEARLY, self::HOURLY, self::MINUTELY, self::PERIOD))) + self::YEARLY, self::HOURLY, self::MINUTELY, self::RDATE))) { throw new Api\Exception\WrongParameter(__METHOD__."($time,$type,$interval,$enddate,$weekdays,...) type $type is NOT valid!"); } @@ -326,22 +326,22 @@ class calendar_rrule implements Iterator $this->exceptions_objs = $exceptions; } $this->enddate = $enddate; - if($type == self::PERIOD) + if($type == self::RDATE) { foreach($rdates as $rdate) { $rdate->setTimezone($this->time->getTimezone()); - $this->period[] = $rdate; + $this->rdates[] = $rdate; } // if startdate is neither in the rdates, nor the exceptions --> prepend it to rdates - if (!in_array($this->time, $this->period) && !in_array($this->time, $this->exceptions_objs)) + if (!in_array($this->time, $this->rdates) && !in_array($this->time, $this->exceptions_objs)) { - array_unshift($this->period, clone($this->time)); + array_unshift($this->rdates, clone($this->time)); } - $enddate = clone(count($this->period) ? end($this->period) : $this->time); + $enddate = clone(count($this->rdates) ? end($this->rdates) : $this->time); // Make sure to include the last date as valid $enddate->modify('+1 second'); - reset($this->period); + reset($this->rdates); } // no recurrence --> current date is enddate if ($type == self::NONE) @@ -502,8 +502,8 @@ class calendar_rrule implements Iterator case self::MINUTELY: $this->current->modify($this->interval.' minute'); break; - case self::PERIOD: - if (($next = next($this->period))) + case self::RDATE: + if (($next = next($this->rdates))) { $this->current->setDate($next->format('Y'), $next->format('m'), $next->format('d')); $this->current->setTime($next->format('H'), $next->format('i'), $next->format('s'), 0); @@ -601,9 +601,9 @@ class calendar_rrule implements Iterator */ public function rewind(): void { - if ($this->type == self::PERIOD) + if ($this->type == self::RDATE) { - $this->current = $this->period ? clone reset($this->period) : null; + $this->current = $this->rdates ? clone reset($this->rdates) : null; } else { @@ -696,13 +696,18 @@ class calendar_rrule implements Iterator $str_extra[] = ($this->monthly_byday_num == -1 ? lang('last') : $this->monthly_byday_num.'.').' '.implode(', ',$repeat_days); } break; - + case self::RDATE: + $str_extra = array_map(static function (DateTime $rdate) + { + return Api\DateTime::server2user($rdate, ''); + }, $this->rdates); + break; } - if($this->interval > 1) + if($this->interval > 1 && $this->type != self::RDATE) { $str_extra[] = lang('Interval').': '.$this->interval; } - if ($this->enddate) + if ($this->enddate && $this->type != self::RDATE) { if ($this->enddate->getTimezone()->getName() != Api\DateTime::$user_timezone->getName()) { @@ -733,7 +738,7 @@ class calendar_rrule implements Iterator $repeat_days = array(); $rrule = array(); - if ($this->type == self::NONE) return false; // no recuring event + if ($this->type == self::NONE || $this->type == self::RDATE) return false; // no recurring event (with RRULE) if ($version == '1.0') { @@ -792,13 +797,6 @@ class calendar_rrule implements Iterator $rrule['BYDAY'] = $this->monthly_byday_num . strtoupper(substr($this->time->format('l'),0,2)); break; - case self::PERIOD: - $period = []; - foreach($this->period as $date) - { - $period[] = $date->format("Ymd\THms\Z"); - } - $rrule['PERIOD'] = implode(',', $period); } if ($this->interval > 1) { @@ -979,7 +977,7 @@ class calendar_rrule implements Iterator 'recur_enddate' => $this->enddate ? $this->enddate->format('ts') : null, 'recur_data' => $this->weekdays, 'recur_exception' => $this->exceptions, - 'recur_rdates' => $this->period, + 'recur_rdates' => $this->rdates, ); } @@ -987,7 +985,7 @@ class calendar_rrule implements Iterator * Shift a recurrence rule to a new timezone * * @param array $event recurring event - * @param DateTime/string starttime of the event (in servertime) + * @param DateTime|string $starttime of the event (in servertime) * @param string $to_tz new timezone */ public static function rrule2tz(array &$event,$starttime,$to_tz) diff --git a/calendar/inc/class.calendar_so.inc.php b/calendar/inc/class.calendar_so.inc.php index d740cf4422..8ac8a690c5 100644 --- a/calendar/inc/class.calendar_so.inc.php +++ b/calendar/inc/class.calendar_so.inc.php @@ -425,7 +425,7 @@ class calendar_so $this->db->update($this->cal_table, array('cal_uid' => $event['uid']), array('cal_id' => $event['id']),__LINE__,__FILE__,'calendar'); } - if (!(int)$recur_date && !empty($event['recur_type'])) + if (!(int)$recur_date && !empty($event['recur_type']) || $event['recur_type'] == MCAL_RECUR_RDATE) { foreach($this->db->select($this->dates_table, 'cal_id,cal_start,recur_exception', [ 'cal_id' => $ids, diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php index 35837a0d31..fcf48a03b2 100644 --- a/calendar/inc/class.calendar_uiforms.inc.php +++ b/calendar/inc/class.calendar_uiforms.inc.php @@ -3698,7 +3698,8 @@ class calendar_uiforms extends calendar_ui $this->setup_participants($event, $content, $sel_options, $readonlys,$preserve,true); $content = array_merge($event, $content); - $readonlys = []; + // disable notifying yourself, as it is ignored anyway and user is only confused, why no notification is send + $readonlys = ['participants[notify]['.$GLOBALS['egw_info']['user']['account_id'].']' => true]; $etpl = new Etemplate('calendar.notify_dialog'); $preserve = $content; diff --git a/calendar/js/app.ts b/calendar/js/app.ts index c934332210..745e92e912 100644 --- a/calendar/js/app.ts +++ b/calendar/js/app.ts @@ -1422,6 +1422,7 @@ export class CalendarApp extends EgwApp addRdate.set_disabled(recurType.value != 9); recurRdate.set_disabled(recurType.value != 9); this.et2.getWidgetById('recur_enddate')?.set_disabled(recurType.value == 9); + this.et2.getWidgetById('recur_interval')?.set_disabled(recurType.value == 9); } } diff --git a/calendar/lang/egw_de.lang b/calendar/lang/egw_de.lang index cf73c18e3d..d6e1c404ff 100644 --- a/calendar/lang/egw_de.lang +++ b/calendar/lang/egw_de.lang @@ -712,6 +712,7 @@ you are not allowed to book the resource selected: calendar de Sie sind nicht be you are not invited to that event! calendar de Sie sind zu diesem Termin nicht eingeladen! you attempt to mail a meetingrequest to the recipients above. depending on the client this mail is opened with, the recipient may or may not see the mailbody below, but only see the meeting request attached. calendar de Sie sind im Begriff eine Terminanfrage an die oben eingetragenen Empfänger zu versenden. you can either set a year or a occurrence, not both !!! calendar de Sie können nur entweder das Jahr oder die Wiederholung angeben, nicht beides! +you can only notify other users, not yourself! calendar de Sie können nur andere Benutzer benachrichtigen, nicht sich selbst! you can only set a year or a occurrence !!! calendar de Sie können nur ein Jahr oder eine Wiederholung angeben! you do not have permission to read this record! calendar de Sie haben keine Berechtigung diesen Eintrag zu lesen! you have a meeting scheduled for %1 calendar de Sie haben einen Termin am %1 diff --git a/calendar/lang/egw_en.lang b/calendar/lang/egw_en.lang index 45b28b7d1c..fa21d1bd7e 100644 --- a/calendar/lang/egw_en.lang +++ b/calendar/lang/egw_en.lang @@ -712,6 +712,7 @@ you are not allowed to book the resource selected: calendar en You are not allow you are not invited to that event! calendar en You are not invited to that event! you attempt to mail a meetingrequest to the recipients above. depending on the client this mail is opened with, the recipient may or may not see the mailbody below, but only see the meeting request attached. calendar en You attempt to mail a meetingrequest to the recipients above. Depending on the client this mail is opened with, the recipient may or may not see the message below, but only see the meeting request attached. you can either set a year or a occurrence, not both !!! calendar en You can either set a year or occurrence, not both! +you can only notify other users, not yourself! calendar en You can only notify other users, not yourself! you can only set a year or a occurrence !!! calendar en You can only set a year or occurrence! you do not have permission to read this record! calendar en You do not have permission to read this record! you have a meeting scheduled for %1 calendar en You have a meeting scheduled for %1 diff --git a/calendar/templates/default/notify_dialog.xet b/calendar/templates/default/notify_dialog.xet index f46c8e9657..6b3767b167 100644 --- a/calendar/templates/default/notify_dialog.xet +++ b/calendar/templates/default/notify_dialog.xet @@ -50,7 +50,7 @@ - + diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index b004b41082..edaebf2124 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -1404,7 +1404,7 @@ class mail_compose $attachment_block = '
Download attachments' . lang('Attachments') . '
'; if($content['is_html'] && strpos($content['mail_htmltext'], $attachment_block) == false) { - $content['mail_htmltext'] .= $attachment_block; + //$content['mail_htmltext'] .= $attachment_block; } foreach($content['attachments'] as &$attach) { diff --git a/mail/js/app.js b/mail/js/app.js index db539a1489..ea2607628f 100755 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -3977,6 +3977,7 @@ app.classes.mail = AppJS.extend( } if (_file_count && !jQuery.isEmptyObject(_event.data.getValue())) { + this.addAttachmentPlaceholder(); var widget = _event.data; this.et2_obj.submit(); } @@ -4023,6 +4024,7 @@ app.classes.mail = AppJS.extend( if (jQuery.isEmptyObject(_widget)) return; if (!jQuery.isEmptyObject(_widget.getValue())) { + this.addAttachmentPlaceholder(); this.et2_obj.submit(); } }, @@ -5087,6 +5089,7 @@ app.classes.mail = AppJS.extend( (mode == 'share_rw' || mode == 'share_ro') ? 'link' : mode; } this.et2.setArrayMgr('content', content); + this.addAttachmentPlaceholder(); attachments.set_value({content:content.data.attachments}); } }, @@ -6275,5 +6278,20 @@ app.classes.mail = AppJS.extend( { window.open("mailto:" + _address.value); } + }, + + addAttachmentPlaceholder: function () + { + if (this.et2.getArrayMgr("content").getEntry("is_html")) + { + // Add link placeholder box + const email = this.et2.getWidgetById("mail_htmltext"); + const placeholder = '
Download attachments' + this.egw.lang('Attachments') + '
'; + + if (email && !email.getValue().includes(placeholder)) + { + email.editor.execCommand('mceInsertContent', false, placeholder); + } + } } }); \ No newline at end of file