Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ developers to maintain and readjust their custom modifications on the main proje
- Update unavailable dates after applying appointment data while rescheduling (#1662)
- Make sure that any-provider does not include hidden providers while generating availability (#1733)
- Provide "text" version of the emails in addition to HTML (#1711)
- Allow several "block"-servers to be defined for each provider (to allow multiple calendars to be synced)


## [1.5.1] - 2025-01-20
Expand Down
168 changes: 168 additions & 0 deletions application/controllers/Caldav.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,76 @@ public function connect_to_server(): void
}
}


/**
* Connect to the target CalDAV server
*
* @return void
*/
public function add_block_server(): void
{
try {
$provider_id = request('provider_id');
$user_id = session('user_id');

if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
throw new RuntimeException('You do not have the required permissions for this task.');
}

$caldav_url = request('caldav_url');
$caldav_username = request('caldav_username');
$caldav_password = request('caldav_password');

$this->caldav_sync->test_connection($caldav_url, $caldav_username, $caldav_password);

// Insert into caldav_block_servers
$this->db->insert('ea_caldav_block_servers', [
'user_id' => $provider_id,
'caldav_url' => $caldav_url,
'caldav_username' => $caldav_username,
'caldav_password' => $caldav_password,
]);

json_response([
'success' => true,
]);
} catch (GuzzleException | InvalidArgumentException $e) {
json_response([
'success' => false,
'message' => $e->getMessage(),
]);
} catch (Throwable $e) {
json_exception($e);
}
}

public function delete_block_server(): void{
try{

$provider_id = request('provider_id');
$block_server_id = request('block_server_id');
$user_id = session('user_id');
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
throw new RuntimeException('You do not have the required permissions for this task.');
}

// Delete all appointments associated with this block server and provider
$this->db->where('id_caldav_block_server', $block_server_id)
->where('id_users_provider', $provider_id)
->delete('ea_appointments');

// Delete the block server itself
$this->db->where('id', $block_server_id)
->delete('ea_caldav_block_servers');

json_response([
'success' => true,
]);
}catch (Throwable $e) {
json_exception($e);
}
}

/**
* Sync the provider events with the remote CalDAV calendar.
*
Expand Down Expand Up @@ -143,6 +213,7 @@ public static function sync(string $provider_id): void
$where = [
'start_datetime >=' => $start_date_time,
'end_datetime <=' => $end_date_time,
'read_only =' => '0',
'id_users_provider' => $provider['id'],
];

Expand Down Expand Up @@ -334,4 +405,101 @@ public function disable_provider_sync(): void
json_exception($e);
}
}

/**
* Sync events from block servers
*
* @return void
*/
public static function sync_block_servers(string $provider_id): void
{
/** @var EA_Controller $CI */
$CI = get_instance();


$CI->load->library('caldav_sync');
$CI->load->model('providers_model');
$CI->load->model('appointments_model');
$db = $CI->load->database('', true);
$user_id = session('user_id');
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id && !is_cli()) {
throw new RuntimeException('You do not have the required permissions for this task.');
}

$block_servers = $db->get_where('caldav_block_servers', ['user_id' => $provider_id])->result_array();

foreach ($block_servers as $block_server) {
$start_date_time = date('Y-m-d 00:00:00');
$end_date_time = date('Y-m-d 23:59:59', strtotime('+30 days'));

$provider = $CI->providers_model->find($provider_id);

// Use block server credentials for sync
$events = $CI->caldav_sync->get_sync_events([
'settings' => [
'caldav_url' => $block_server['caldav_url'],
'caldav_username' => $block_server['caldav_username'],
'caldav_password' => $block_server['caldav_password'],
'caldav_sync' => true,
],
'timezone' => $provider['timezone'],
'id' => $provider_id,
], $start_date_time, $end_date_time);

// Collect current CalDAV event IDs
$current_event_ids = array_column($events, 'id');

// Fetch all read-only appointments for this block server and provider
$existing_appointments = $CI->appointments_model->get_blocker([
'id_caldav_block_server' => $block_server['id'],
'id_users_provider' => $provider_id,
]);

// Delete appointments not present in CalDAV anymore
foreach ($existing_appointments as $appointment) {
if (!in_array($appointment['id_caldav_calendar'], $current_event_ids)) {
$CI->appointments_model->delete($appointment['id']);
}
}

// Add or update current events
foreach ($events as $event) {
$exists = $CI->appointments_model->get_blocker([
'id_caldav_calendar' => $event['id'],
'id_caldav_block_server' => $block_server['id'],
]);
if ($exists ) {

$is_different =
$exists[0]['start_datetime'] !== $event['start_datetime'] ||
$exists[0]['end_datetime'] !== $event['end_datetime'];
if ($is_different) {
$exists[0]['start_datetime'] = $event['start_datetime'];
$exists[0]['end_datetime'] = $event['end_datetime'];
$exists[0]['is_blocker'] = true;
$CI->appointments_model->save($exists[0]);
}
continue; // Event already exists, skip to the next one.
}

$CI->appointments_model->save([
'start_datetime' => $event['start_datetime'],
'end_datetime' => $event['end_datetime'],
'location' => $event['location'],
'notes' => $event['summary'],
'id_users_provider' => $provider_id,
'id_services' => null,
'id_users_customer' => null,
'id_caldav_calendar' => $event['id'],
'is_unavailability' => true,
'read_only' => true,
'id_caldav_block_server' => $block_server['id'],
'is_blocker' => true,
'status' => 'CONFIRMED',
]);
}
}

json_response(['success' => true]);
}
}
18 changes: 18 additions & 0 deletions application/controllers/Calendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,23 @@ public function index(string $appointment_hash = ''): void

$appointment_status_options = setting('appointment_status_options');

// Query all block_servers for this provider
$block_servers = [];
if ($role_slug === DB_SLUG_PROVIDER) {
$block_servers = $this->blocked_periods_model->get(['user_id' => $user_id]);
} elseif ($role_slug === DB_SLUG_SECRETARY || $role_slug === DB_SLUG_ADMIN) {
// For secretaries, get blocked periods for all their providers
foreach ($available_providers as $provider_id) {
$block_servers = array_merge(
$block_servers,
$block_servers = $this->db->get_where('caldav_block_servers', ['user_id' => $provider_id['id']])->result_array()
);
}
} else {
// For admins or others, get all blocked periods
$block_servers = $this->blocked_periods_model->get();
}

script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
Expand All @@ -188,6 +205,7 @@ public function index(string $appointment_hash = ''): void
'available_services' => $available_services,
'secretary_providers' => $secretary_providers,
'edit_appointment' => $edit_appointment,
'block_servers' => $block_servers,
'google_sync_feature' => config('google_sync_feature'),
'customers' => $this->customers_model->get(null, 50, null, 'update_datetime DESC'),
'default_language' => setting('default_language'),
Expand Down
1 change: 1 addition & 0 deletions application/controllers/Console.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ public function sync(): void
if (filter_var($provider['settings']['caldav_sync'], FILTER_VALIDATE_BOOLEAN)) {
Caldav::sync((string) $provider['id']);
}
Caldav::sync_block_servers((string) $provider['id']);
}
}

Expand Down
11 changes: 11 additions & 0 deletions application/language/german/translations_lang.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,17 @@
$lang['synchronize'] = 'Synchronisieren';
$lang['enable_sync'] = 'Synchronisation einschalten';
$lang['disable_sync'] = 'Synchronisation ausschalten';
$lang['add_block_sync'] = 'Neue Block-Synchronisation hinzufügen';
$lang['sync_block_servers'] = 'Block-Kalender synchronisieren';
$lang['blocker_server_list'] = 'Block-Server Liste';
$lang['delete_block_server'] = 'Willst du den Block-Server löschen?';
$lang['block_server_deletion_failed'] = 'Block Server konnte nicht gelöscht werden.';
$lang['block_server_deletion_success'] = 'Block Server erfolgreich gelöscht.';
$lang['disable_sync_prompt'] = 'Sind Sie sicher, dass Sie die Kalendersynchronisation abschalten wollen?';
$lang['reload'] = 'Neu laden';
$lang['appointment'] = 'Termin';
$lang['unavailability'] = 'Nichtverfügbarkeit';
$lang['blocker'] = 'Blocker';
$lang['week'] = 'Woche';
$lang['month'] = 'Monat';
$lang['today'] = 'Heute';
Expand Down Expand Up @@ -230,6 +237,8 @@
$lang['unexpected_issues_occurred'] = 'Unerwartete Probleme aufgetreten.';
$lang['service_communication_error'] = 'Während der Kommunikation mit dem Server ist ein Fehler aufgetreten, bitte versuchen Sie es erneut.';
$lang['no_privileges_edit_appointments'] = 'Sie haben nicht die Berechtigung, um Termine zu bearbeiten.';
$lang['blocker_cannot_be_edited'] = 'Termine aus Blocker-Kalender können nicht bearbeitet werden.';
$lang['blocker_cannot_be_moved'] = 'Termine aus Block-Kalender sind nur zum ansehen, nicht zum bearbeiten.';
$lang['unavailability_updated'] = 'Nichtverfügbarkeit erfolgreich erneuert worden.';
$lang['appointments'] = 'Termine';
$lang['unexpected_warnings'] = 'Unerwartete Warnungen';
Expand Down Expand Up @@ -268,6 +277,7 @@
$lang['select_sync_calendar'] = 'Wählen Sie externe Kalender';
$lang['select_sync_calendar_prompt'] = 'Wählen Sie den Kalender, mit dem Sie Ihre Termine synchronisieren möchten. Wenn Sie das nicht wollen, wird ein Standard-Kalender verwendet.';
$lang['sync_calendar_selected'] = 'Externe-Kalender wurde erfolgreich ausgewählt.';
$lang['sync_block_calendar_selected'] = 'Block Kalender wurden erfolgreich synchronisiert.';
$lang['oops_something_went_wrong'] = 'Oops! Etwas ist schiefgelaufen.';
$lang['ea_update_success'] = 'Easy!Appointments wurde erfolgreich aktualisiert.';
$lang['require_captcha'] = 'CAPTCHA erfordern';
Expand Down Expand Up @@ -460,6 +470,7 @@
$lang['sync_method_prompt'] = 'Welche Synchronisierungsmethode möchten Sie verwenden?';
$lang['caldav_server'] = 'CalDAV-Server';
$lang['caldav_connection_info_prompt'] = 'Bitte geben Sie die Verbindungsinformationen des CalDAV-Zielservers ein.';
$lang['caldav_connection_info_prompt_block_only'] = 'Bitte geben Sie die Verbindungsinformationen des CalDAV-Servers ein, von dem nur Termine runtersynchronisiert werden ein (read-only).';
$lang['connect'] = 'Verbinden';
$lang['ldap'] = 'LDAP';
$lang['ldap_info'] = 'Diese Integration ermöglicht es Ihnen, sich mit einem bestehenden LDAP-Server zu verbinden und Benutzer automatisch in Easy!Appointments zu importieren und ihnen ein SSO mit ihrem Verzeichnispasswort zu ermöglichen (der Benutzername muss übereinstimmen).';
Expand Down
26 changes: 24 additions & 2 deletions application/libraries/Caldav_sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,30 @@ public function get_event(array $provider, string $caldav_event_id): ?array
public function get_sync_events(array $provider, string $start_date_time, string $end_date_time): array
{
try {
$client = $this->get_http_client_by_provider_id($provider['id']);
$provider_timezone_object = new DateTimeZone($provider['timezone']);
// Support both provider and block server parameter structures
if (isset($provider['settings'])) {
$settings = $provider['settings'];
$caldav_url = $settings['caldav_url'] ?? null;
$caldav_username = $settings['caldav_username'] ?? null;
$caldav_password = $settings['caldav_password'] ?? null;
} else {
$caldav_url = $provider['caldav_url'] ?? null;
$caldav_username = $provider['caldav_username'] ?? null;
$caldav_password = $provider['caldav_password'] ?? null;
}

// Timezone
$timezone = $provider['timezone'] ?? ($provider['settings']['timezone'] ?? 'UTC');
$provider_timezone_object = new DateTimeZone($timezone);

// Use credentials directly if present, otherwise fallback to provider ID
if ($caldav_url && $caldav_username && $caldav_password) {
$client = $this->get_http_client($caldav_url, $caldav_username, $caldav_password);
} elseif (isset($provider['id'])) {
$client = $this->get_http_client_by_provider_id($provider['id']);
} else {
throw new InvalidArgumentException('Missing CalDAV credentials or provider ID.');
}

$response = $this->fetch_events($client, $start_date_time, $end_date_time);

Expand Down
Loading