@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
ob_start();
|
||||
require_once dirname(__DIR__, 2) . '/includes/auth.php';
|
||||
require_login();
|
||||
require_once dirname(__DIR__, 2) . '/includes/db.php';
|
||||
require_once dirname(__DIR__, 2) . '/includes/meta_db.php';
|
||||
require_once __DIR__ . '/caldav_sync.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$userId = (int)($_SESSION['user']['id'] ?? 0);
|
||||
$familyId = (int)($_SESSION['user']['family_id'] ?? 0);
|
||||
|
||||
$meta_pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS user_calendar_integrations (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
provider VARCHAR(50) NOT NULL DEFAULT 'icloud_caldav',
|
||||
username VARCHAR(255) NOT NULL,
|
||||
secret_encrypted TEXT NOT NULL,
|
||||
dav_principal_url VARCHAR(1024) DEFAULT NULL,
|
||||
calendar_url VARCHAR(1024) DEFAULT NULL,
|
||||
status VARCHAR(30) DEFAULT 'connected',
|
||||
last_sync_at DATETIME DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_user_provider (user_id, provider)
|
||||
)");
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS pf_calendar_events (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
family_id INT NOT NULL,
|
||||
created_by_user_id INT NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT DEFAULT NULL,
|
||||
location VARCHAR(255) DEFAULT NULL,
|
||||
start_at DATETIME NOT NULL,
|
||||
end_at DATETIME NOT NULL,
|
||||
is_all_day TINYINT(1) DEFAULT 0,
|
||||
timezone VARCHAR(64) DEFAULT 'Europe/Paris',
|
||||
rrule VARCHAR(500) DEFAULT NULL,
|
||||
status VARCHAR(50) DEFAULT 'confirmed',
|
||||
external_uid VARCHAR(255) DEFAULT NULL,
|
||||
sync_state VARCHAR(30) DEFAULT 'pending_push',
|
||||
deleted_at DATETIME DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_external_uid (external_uid)
|
||||
)");
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS pf_calendar_event_links (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
calendar_event_id INT NOT NULL,
|
||||
external_uid VARCHAR(255) NOT NULL,
|
||||
external_etag VARCHAR(255) DEFAULT NULL,
|
||||
calendar_url VARCHAR(1024) DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_calendar_event (calendar_event_id),
|
||||
UNIQUE KEY uq_external_link (external_uid)
|
||||
)");
|
||||
|
||||
function ios_ok($data): void { echo json_encode(['ok' => true, 'data' => $data], JSON_UNESCAPED_UNICODE); exit; }
|
||||
function ios_err(string $message, int $status = 400): void { http_response_code($status); echo json_encode(['ok' => false, 'error' => $message]); exit; }
|
||||
function ios_body(): array { return json_decode(file_get_contents('php://input'), true) ?? []; }
|
||||
function ios_require_csrf(): void {
|
||||
$token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;
|
||||
if (!verify_csrf($token)) {
|
||||
ios_err('Token CSRF invalide', 403);
|
||||
}
|
||||
}
|
||||
|
||||
$integrationStmt = $meta_pdo->prepare("SELECT * FROM user_calendar_integrations WHERE user_id = ? AND provider='icloud_caldav'");
|
||||
$integrationStmt->execute([$userId]);
|
||||
$integration = $integrationStmt->fetch();
|
||||
|
||||
if ($action === 'events' && $method === 'GET') {
|
||||
$rows = $pdo->query("SELECT * FROM pf_calendar_events ORDER BY start_at ASC")->fetchAll();
|
||||
ios_ok($rows);
|
||||
}
|
||||
|
||||
if ($action === 'events' && $method === 'POST') {
|
||||
ios_require_csrf();
|
||||
$d = ios_body();
|
||||
if (empty($d['title']) || empty($d['start_at']) || empty($d['end_at'])) {
|
||||
ios_err('Champs requis manquants');
|
||||
}
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO pf_calendar_events (family_id, created_by_user_id, title, description, location, start_at, end_at, timezone, sync_state)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'Europe/Paris', 'pending_push')
|
||||
");
|
||||
$stmt->execute([$familyId, $userId, trim($d['title']), $d['description'] ?? null, $d['location'] ?? null, $d['start_at'], $d['end_at']]);
|
||||
ios_ok(['id' => (int)$pdo->lastInsertId()]);
|
||||
}
|
||||
|
||||
if ($action === 'events' && $method === 'PUT') {
|
||||
ios_require_csrf();
|
||||
$d = ios_body();
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
if ($id <= 0) ios_err('ID invalide');
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE pf_calendar_events
|
||||
SET title=?, description=?, location=?, start_at=?, end_at=?, updated_at=NOW(), sync_state='pending_push'
|
||||
WHERE id=?
|
||||
");
|
||||
$stmt->execute([trim($d['title'] ?? ''), $d['description'] ?? null, $d['location'] ?? null, $d['start_at'] ?? null, $d['end_at'] ?? null, $id]);
|
||||
ios_ok(['updated' => true]);
|
||||
}
|
||||
|
||||
if ($action === 'events' && $method === 'DELETE') {
|
||||
ios_require_csrf();
|
||||
$d = ios_body();
|
||||
$id = (int)($d['id'] ?? 0);
|
||||
if ($id <= 0) ios_err('ID invalide');
|
||||
$row = $pdo->prepare("SELECT id FROM pf_calendar_events WHERE id=?");
|
||||
$row->execute([$id]);
|
||||
if (!$row->fetch()) ios_err('Événement introuvable', 404);
|
||||
$pdo->prepare("UPDATE pf_calendar_events SET deleted_at=NOW(), sync_state='pending_delete' WHERE id=?")->execute([$id]);
|
||||
ios_ok(['deleted' => true]);
|
||||
}
|
||||
|
||||
if ($action === 'sync_status' && $method === 'GET') {
|
||||
if (!$integration) {
|
||||
ios_ok(['message' => 'Aucune intégration iCloud configurée.']);
|
||||
}
|
||||
$msg = 'Connecté iCloud. Dernière synchro: ' . ($integration['last_sync_at'] ?? 'jamais');
|
||||
ios_ok(['message' => $msg]);
|
||||
}
|
||||
|
||||
if ($action === 'sync' && $method === 'POST') {
|
||||
ios_require_csrf();
|
||||
if (!$integration) ios_err('Connexion iCloud non configurée.', 400);
|
||||
|
||||
$remoteEvents = ios_fetch_remote_events($integration);
|
||||
$remoteByUid = [];
|
||||
foreach ($remoteEvents as $re) $remoteByUid[$re['external_uid']] = $re;
|
||||
|
||||
$localStmt = $pdo->query("SELECT * FROM pf_calendar_events WHERE deleted_at IS NULL");
|
||||
$localEvents = $localStmt->fetchAll();
|
||||
|
||||
foreach ($localEvents as $evt) {
|
||||
if ($evt['sync_state'] === 'pending_push' || empty($evt['external_uid'])) {
|
||||
$push = ios_push_event_to_remote($integration, $evt);
|
||||
if ($push['code'] >= 200 && $push['code'] < 300) {
|
||||
$uid = $evt['external_uid'] ?: ('hh-' . $evt['id'] . '@househub');
|
||||
$pdo->prepare("UPDATE pf_calendar_events SET external_uid=?, sync_state='synced', updated_at=NOW() WHERE id=?")->execute([$uid, $evt['id']]);
|
||||
$pdo->prepare("INSERT INTO pf_calendar_event_links (calendar_event_id, external_uid, calendar_url, external_etag) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE external_etag=VALUES(external_etag), calendar_url=VALUES(calendar_url), updated_at=NOW()")
|
||||
->execute([$evt['id'], $uid, $integration['calendar_url'], null]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pendingDelete = $pdo->query("SELECT id, external_uid FROM pf_calendar_events WHERE deleted_at IS NOT NULL AND sync_state='pending_delete'")->fetchAll();
|
||||
foreach ($pendingDelete as $evt) {
|
||||
if (!empty($evt['external_uid'])) {
|
||||
ios_delete_remote_event($integration, $evt['external_uid']);
|
||||
}
|
||||
$pdo->prepare("DELETE FROM pf_calendar_event_links WHERE calendar_event_id=?")->execute([$evt['id']]);
|
||||
$pdo->prepare("DELETE FROM pf_calendar_events WHERE id=?")->execute([$evt['id']]);
|
||||
}
|
||||
|
||||
foreach ($remoteEvents as $remote) {
|
||||
$existing = $pdo->prepare("SELECT id, updated_at FROM pf_calendar_events WHERE external_uid=? LIMIT 1");
|
||||
$existing->execute([$remote['external_uid']]);
|
||||
$row = $existing->fetch();
|
||||
if (!$row) {
|
||||
$pdo->prepare("
|
||||
INSERT INTO pf_calendar_events (family_id, created_by_user_id, title, description, location, start_at, end_at, timezone, external_uid, sync_state)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'Europe/Paris', ?, 'synced')
|
||||
")->execute([$familyId, $userId, $remote['title'], $remote['description'], $remote['location'], $remote['start_at'], $remote['end_at'], $remote['external_uid']]);
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
$pdo->prepare("INSERT INTO pf_calendar_event_links (calendar_event_id, external_uid, calendar_url, external_etag) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE updated_at=NOW()")
|
||||
->execute([$newId, $remote['external_uid'], $integration['calendar_url'], null]);
|
||||
}
|
||||
}
|
||||
|
||||
$meta_pdo->prepare("UPDATE user_calendar_integrations SET last_sync_at=NOW(), status='connected' WHERE id=?")->execute([$integration['id']]);
|
||||
ios_ok(['message' => 'Synchronisation terminée.']);
|
||||
}
|
||||
|
||||
ios_err('Action inconnue', 404);
|
||||
@@ -0,0 +1,79 @@
|
||||
.ios-calendar-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.ios-calendar-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ios-calendar-head h1 {
|
||||
margin: 0;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.ios-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ios-form-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.ios-form-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ios-events-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ios-event-card {
|
||||
border: 1px solid var(--pf-border);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
background: var(--pf-bg-page);
|
||||
}
|
||||
|
||||
.ios-event-card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ios-event-card-title {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ios-event-card-meta {
|
||||
color: var(--pf-text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.ios-event-card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ios-form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.ios-calendar-head {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.ios-event-card-head {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
const iosEventsList = document.getElementById("ios-events-list");
|
||||
const iosSyncStatus = document.getElementById("ios-sync-status");
|
||||
const iosForm = document.getElementById("ios-event-form");
|
||||
|
||||
async function iosApi(action, options = {}) {
|
||||
const method = options.method || "GET";
|
||||
const headers = {
|
||||
"Accept": "application/json",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
...(options.headers || {}),
|
||||
};
|
||||
if (method !== "GET") {
|
||||
headers["X-CSRF-Token"] = window.CSRF_TOKEN || "";
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
const response = await fetch(`/modules/calendar-ios/api.php?action=${encodeURIComponent(action)}`, {
|
||||
method,
|
||||
credentials: "same-origin",
|
||||
headers,
|
||||
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||
});
|
||||
const json = await response.json();
|
||||
if (!json.ok) throw new Error(json.error || "Erreur inconnue");
|
||||
return json.data;
|
||||
}
|
||||
|
||||
function formatDate(iso) {
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleString();
|
||||
}
|
||||
|
||||
function fillForm(evt) {
|
||||
document.getElementById("ios-event-id").value = evt.id;
|
||||
document.getElementById("ios-title").value = evt.title || "";
|
||||
document.getElementById("ios-location").value = evt.location || "";
|
||||
document.getElementById("ios-description").value = evt.description || "";
|
||||
document.getElementById("ios-start").value = (evt.start_at || "").slice(0, 16);
|
||||
document.getElementById("ios-end").value = (evt.end_at || "").slice(0, 16);
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
iosForm.reset();
|
||||
document.getElementById("ios-event-id").value = "";
|
||||
}
|
||||
|
||||
async function loadEvents() {
|
||||
const events = await iosApi("events");
|
||||
iosEventsList.innerHTML = "";
|
||||
if (!events.length) {
|
||||
iosEventsList.innerHTML = "<div class='pf-muted-note'>Aucun événement.</div>";
|
||||
return;
|
||||
}
|
||||
events.forEach((evt) => {
|
||||
const card = document.createElement("div");
|
||||
card.className = "ios-event-card";
|
||||
card.innerHTML = `
|
||||
<div class="ios-event-card-head">
|
||||
<div>
|
||||
<div class="ios-event-card-title">${evt.title || "(Sans titre)"}</div>
|
||||
<div class="ios-event-card-meta">${formatDate(evt.start_at)} → ${formatDate(evt.end_at)}</div>
|
||||
<div class="ios-event-card-meta">${evt.location || ""}</div>
|
||||
</div>
|
||||
<div class="ios-event-card-actions">
|
||||
<button class="pf-btn btn-secondary" data-edit="${evt.id}">Modifier</button>
|
||||
<button class="pf-btn btn-secondary" data-delete="${evt.id}">Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
card.querySelector("[data-edit]").addEventListener("click", () => fillForm(evt));
|
||||
card.querySelector("[data-delete]").addEventListener("click", async () => {
|
||||
if (!confirm("Supprimer cet événement ?")) return;
|
||||
await iosApi("events", { method: "DELETE", body: { id: evt.id } });
|
||||
await loadEvents();
|
||||
});
|
||||
iosEventsList.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSyncStatus() {
|
||||
const status = await iosApi("sync_status");
|
||||
iosSyncStatus.textContent = status.message;
|
||||
}
|
||||
|
||||
iosForm.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const id = document.getElementById("ios-event-id").value;
|
||||
const payload = {
|
||||
id: id ? parseInt(id, 10) : null,
|
||||
title: document.getElementById("ios-title").value.trim(),
|
||||
location: document.getElementById("ios-location").value.trim(),
|
||||
description: document.getElementById("ios-description").value.trim(),
|
||||
start_at: document.getElementById("ios-start").value,
|
||||
end_at: document.getElementById("ios-end").value,
|
||||
};
|
||||
await iosApi("events", { method: id ? "PUT" : "POST", body: payload });
|
||||
resetForm();
|
||||
await loadEvents();
|
||||
});
|
||||
|
||||
document.getElementById("ios-form-reset").addEventListener("click", resetForm);
|
||||
document.getElementById("ios-sync-btn").addEventListener("click", async () => {
|
||||
document.getElementById("ios-sync-btn").disabled = true;
|
||||
try {
|
||||
const data = await iosApi("sync", { method: "POST", body: {} });
|
||||
iosSyncStatus.textContent = data.message;
|
||||
await loadEvents();
|
||||
} finally {
|
||||
document.getElementById("ios-sync-btn").disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("DOMContentLoaded", async () => {
|
||||
try {
|
||||
await loadEvents();
|
||||
await loadSyncStatus();
|
||||
} catch (e) {
|
||||
iosSyncStatus.textContent = e.message;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/includes/crypto.php';
|
||||
|
||||
function ios_make_ics(array $event): string
|
||||
{
|
||||
$uid = $event['external_uid'] ?: ('hh-' . $event['id'] . '@househub');
|
||||
$start = gmdate('Ymd\THis\Z', strtotime($event['start_at']));
|
||||
$end = gmdate('Ymd\THis\Z', strtotime($event['end_at']));
|
||||
$summary = addcslashes($event['title'] ?? '', ",;");
|
||||
$description = addcslashes($event['description'] ?? '', ",;");
|
||||
$location = addcslashes($event['location'] ?? '', ",;");
|
||||
$updated = gmdate('Ymd\THis\Z');
|
||||
|
||||
return "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//HouseHub//Calendar iOS//FR\r\nBEGIN:VEVENT\r\nUID:$uid\r\nDTSTAMP:$updated\r\nDTSTART:$start\r\nDTEND:$end\r\nSUMMARY:$summary\r\nDESCRIPTION:$description\r\nLOCATION:$location\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
|
||||
}
|
||||
|
||||
function ios_caldav_request(string $url, string $username, string $password, string $method = 'GET', ?string $body = null, array $headers = []): array
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
$requestHeaders = array_merge([
|
||||
'Content-Type: text/calendar; charset=utf-8',
|
||||
], $headers);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 20,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
|
||||
CURLOPT_USERPWD => $username . ':' . $password,
|
||||
CURLOPT_HTTPHEADER => $requestHeaders,
|
||||
CURLOPT_HEADER => true,
|
||||
]);
|
||||
if ($body !== null) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
}
|
||||
$raw = curl_exec($ch);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($raw === false) {
|
||||
return ['code' => 0, 'headers' => '', 'body' => ''];
|
||||
}
|
||||
return [
|
||||
'code' => $code,
|
||||
'headers' => substr($raw, 0, $headerSize),
|
||||
'body' => substr($raw, $headerSize),
|
||||
];
|
||||
}
|
||||
|
||||
function ios_fetch_remote_events(array $integration): array
|
||||
{
|
||||
$password = hh_decrypt_secret($integration['secret_encrypted']);
|
||||
$res = ios_caldav_request($integration['calendar_url'], $integration['username'], $password, 'GET', null, ['Accept: text/calendar']);
|
||||
if ($res['code'] < 200 || $res['code'] >= 400) {
|
||||
throw new RuntimeException('Lecture CalDAV impossible (HTTP ' . $res['code'] . ')');
|
||||
}
|
||||
$blocks = preg_split('/BEGIN:VEVENT|END:VEVENT/', $res['body']);
|
||||
$events = [];
|
||||
foreach ($blocks as $chunk) {
|
||||
if (strpos($chunk, 'UID:') === false) {
|
||||
continue;
|
||||
}
|
||||
preg_match('/UID:(.+)\R/', $chunk, $uidM);
|
||||
preg_match('/SUMMARY:(.+)\R/', $chunk, $sumM);
|
||||
preg_match('/DESCRIPTION:(.+)\R/', $chunk, $descM);
|
||||
preg_match('/LOCATION:(.+)\R/', $chunk, $locM);
|
||||
preg_match('/DTSTART(?:;VALUE=DATE)?:(.+)\R/', $chunk, $startM);
|
||||
preg_match('/DTEND(?:;VALUE=DATE)?:(.+)\R/', $chunk, $endM);
|
||||
if (empty($uidM[1]) || empty($startM[1])) {
|
||||
continue;
|
||||
}
|
||||
$start = date('Y-m-d H:i:s', strtotime(trim($startM[1])));
|
||||
$end = !empty($endM[1]) ? date('Y-m-d H:i:s', strtotime(trim($endM[1]))) : $start;
|
||||
$events[] = [
|
||||
'external_uid' => trim($uidM[1]),
|
||||
'title' => trim($sumM[1] ?? ''),
|
||||
'description' => trim($descM[1] ?? ''),
|
||||
'location' => trim($locM[1] ?? ''),
|
||||
'start_at' => $start,
|
||||
'end_at' => $end,
|
||||
];
|
||||
}
|
||||
return $events;
|
||||
}
|
||||
|
||||
function ios_push_event_to_remote(array $integration, array $event): array
|
||||
{
|
||||
$password = hh_decrypt_secret($integration['secret_encrypted']);
|
||||
$uid = $event['external_uid'] ?: ('hh-' . $event['id'] . '@househub');
|
||||
$url = rtrim($integration['calendar_url'], '/') . '/' . rawurlencode($uid) . '.ics';
|
||||
$ics = ios_make_ics($event);
|
||||
return ios_caldav_request($url, $integration['username'], $password, 'PUT', $ics);
|
||||
}
|
||||
|
||||
function ios_delete_remote_event(array $integration, string $externalUid): array
|
||||
{
|
||||
$password = hh_decrypt_secret($integration['secret_encrypted']);
|
||||
$url = rtrim($integration['calendar_url'], '/') . '/' . rawurlencode($externalUid) . '.ics';
|
||||
return ios_caldav_request($url, $integration['username'], $password, 'DELETE', null, ['Accept: */*']);
|
||||
}
|
||||
Reference in New Issue
Block a user