* Copyright (C) 2024 Frédéric France * Copyright (C) 2025-2026 MDW * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file htdocs/dav/dav.class.php * \ingroup dav * \brief Server DAV */ /** * Define Common function to access calendar items and format it in vCalendar */ class CdavLib { /** * @var DoliDB */ private $db; /** * @var User */ private $user; /** * @var Translate */ private $langs; // @phpstan-ignore-line /** * Constructor * * @param User $user user * @param DoliDB $db Database handler * @param Translate $langs translation */ public function __construct($user, $db, $langs) { $this->user = $user; $this->db = $db; $this->langs = $langs; } /** * Base sql request for calendar events * * @param int $calendarId Calendar id * @param int|bool $oid Oid * @param int|string|bool $ouri Ouri (matches against a.recurid) * @return string */ public function getSqlCalEvents($calendarId, $oid = false, $ouri = false) { // TODO : replace GROUP_CONCAT by $sql = 'SELECT a.tms AS lastupd, a.*, sp.firstname, sp.lastname, sp.address, sp.zip, sp.town, co.label country_label, sp.office_phone as phone, sp.personal_mobile as phone_perso, sp.user_mobile as phone_mobile, s.nom AS soc_nom, s.address soc_address, s.zip soc_zip, s.town soc_town, cos.label soc_country_label, s.phone soc_phone'; $sql .= ', (SELECT GROUP_CONCAT(u.login) FROM ' . $this->db->prefix() . 'actioncomm_resources ar LEFT JOIN ' . $this->db->prefix() . 'user AS u ON (u.rowid=fk_element) WHERE ar.element_type=\'user\' AND fk_actioncomm=a.id) AS other_users FROM ' . $this->db->prefix() . 'actioncomm AS a'; $sql .= ' LEFT JOIN ' . $this->db->prefix() . 'user as sp ON sp.rowid = a.fk_user_action LEFT JOIN ' . $this->db->prefix() . 'societe as s ON s.rowid = a.fk_soc LEFT JOIN ' . $this->db->prefix() . 'c_country as co ON co.rowid = sp.fk_country LEFT JOIN ' . $this->db->prefix() . 'c_country as cos ON cos.rowid = s.fk_pays WHERE a.id IN (SELECT ar.fk_actioncomm FROM ' . $this->db->prefix() . 'actioncomm_resources ar WHERE ar.element_type=\'user\' AND ar.fk_element=' . ((int) $calendarId) . ') AND a.code IN (SELECT cac.code FROM ' . $this->db->prefix() . 'c_actioncomm cac WHERE cac.type <> \'systemauto\') AND a.entity IN (' . getEntity('societe', 1) . ')'; // TODO Restrict on external users if ($oid !== false) { if ($ouri === false) { $sql .= ' AND a.id = ' . ((int) $oid); } else { $sql .= ' AND (a.id = ' . ((int) $oid) . ' OR a.recurid = \''. $this->db->escape((string) $ouri) . '\')'; } } elseif ($ouri !== false) { $sql .= ' AND a.recurid = \''. $this->db->escape((string) $ouri) . '\''; } return $sql; } /** * Convert calendar row to VCalendar string * * @param int $calendarId Calendar id * @param Object $obj Object id * @return string */ public function toVCalendar($calendarId, $obj) { /*$categ = array(); if($obj->soc_client) { $nick[] = $obj->soc_code_client; $categ[] = $this->langs->transnoentitiesnoconv('Customer'); }*/ $location = $obj->location; // contact address if (empty($location) && !empty($obj->address)) { $location = trim(str_replace(array("\r", "\t", "\n"), ' ', $obj->address)); $location = trim($location.', '.$obj->zip); $location = trim($location.' '.$obj->town); $location = trim($location.', '.$obj->country_label); } // contact address if (empty($location) && !empty($obj->soc_address)) { $location = trim(str_replace(array("\r", "\t", "\n"), ' ', $obj->soc_address)); $location = trim($location.', '.$obj->soc_zip); $location = trim($location.' '.$obj->soc_town); $location = trim($location.', '.$obj->soc_country_label); } /* $address = explode("\n", $obj->address, 2); foreach ($address as $kAddr => $vAddr) { $address[$kAddr] = trim(str_replace(array("\r", "\t"), ' ', str_replace("\n", ' | ', trim($vAddr)))); } $address[] = ''; $address[] = ''; */ if ($obj->percent == -1 && trim($obj->datep) != '') { $type = 'VEVENT'; } else { $type = 'VTODO'; } $timezone = date_default_timezone_get(); $caldata = "BEGIN:VCALENDAR\n"; $caldata .= "VERSION:2.0\n"; $caldata .= "METHOD:PUBLISH\n"; $caldata .= "PRODID:-//Dolibarr CDav//FR\n"; $caldata .= "BEGIN:".$type."\n"; $caldata .= "CREATED:".gmdate('Ymd\THis', strtotime($obj->datec))."Z\n"; $caldata .= "LAST-MODIFIED:".gmdate('Ymd\THis', strtotime($obj->lastupd))."Z\n"; $caldata .= "DTSTAMP:".gmdate('Ymd\THis', strtotime($obj->lastupd))."Z\n"; $caldata .= "UID:".$obj->id.'-ev-'.$calendarId.'-cal-'.constant('CDAV_URI_KEY')."\n"; $caldata .= "SUMMARY:".$obj->label."\n"; $caldata .= "LOCATION:".$location."\n"; $caldata .= "PRIORITY:".$obj->priority."\n"; if ($obj->fulldayevent) { $caldata .= "DTSTART;VALUE=DATE:".date('Ymd', strtotime($obj->datep))."\n"; if ($type == 'VEVENT') { if (trim($obj->datep2) != '') { $caldata .= "DTEND;VALUE=DATE:".date('Ymd', strtotime($obj->datep2) + 1)."\n"; } else { $caldata .= "DTEND;VALUE=DATE:".date('Ymd', strtotime($obj->datep) + (25 * 3600))."\n"; } } elseif (trim($obj->datep2) != '') { $caldata .= "DUE;VALUE=DATE:".date('Ymd', strtotime($obj->datep2) + 1)."\n"; } } else { $caldata .= "DTSTART;TZID=".$timezone.":".strtr($obj->datep, array(" " => "T", ":" => "", "-" => ""))."\n"; if ($type == 'VEVENT') { if (trim($obj->datep2) != '') { $caldata .= "DTEND;TZID=".$timezone.":".strtr($obj->datep2, array(" " => "T", ":" => "", "-" => ""))."\n"; } else { $caldata .= "DTEND;TZID=".$timezone.":".strtr($obj->datep, array(" " => "T", ":" => "", "-" => ""))."\n"; } } elseif (trim($obj->datep2) != '') { $caldata .= "DUE;TZID=".$timezone.":".strtr($obj->datep2, array(" " => "T", ":" => "", "-" => ""))."\n"; } } $caldata .= "CLASS:PUBLIC\n"; if ($obj->transparency == 1) { $caldata .= "TRANSP:TRANSPARENT\n"; } else { $caldata .= "TRANSP:OPAQUE\n"; } if ($type == 'VEVENT') { $caldata .= "STATUS:CONFIRMED\n"; } elseif ($obj->percent == 0) { $caldata .= "STATUS:NEEDS-ACTION\n"; } elseif ($obj->percent == 100) { $caldata .= "STATUS:COMPLETED\n"; } else { $caldata .= "STATUS:IN-PROCESS\n"; $caldata .= "PERCENT-COMPLETE:".$obj->percent."\n"; } $caldata .= "DESCRIPTION:"; $caldata .= strtr($obj->note, array("\n" => "\\n", "\r" => "")); if (!empty($obj->soc_nom)) { $caldata .= "\\n*DOLIBARR-SOC: ".$obj->soc_nom; } if (!empty($obj->soc_phone)) { $caldata .= "\\n*DOLIBARR-SOC-TEL: ".$obj->soc_phone; } if (!empty($obj->firstname) || !empty($obj->lastname)) { $caldata .= "\\n*DOLIBARR-CTC: ".trim($obj->firstname.' '.$obj->lastname); } if (!empty($obj->phone) || !empty($obj->phone_perso) || !empty($obj->phone_mobile)) { $caldata .= "\\n*DOLIBARR-CTC-TEL: ".trim($obj->phone.' '.$obj->phone_perso.' '.$obj->phone_mobile); } if (strpos($obj->other_users, ',')) { // several $caldata .= "\\n*DOLIBARR-USR: ".$obj->other_users; } $caldata .= "\n"; $caldata .= "END:".$type."\n"; $caldata .= "END:VCALENDAR\n"; return $caldata; } /** * getFullCalendarObjects * * @param int $calendarId Calendar id * @param int<0,1> $bCalendarData Add calendar data if not 0 * @return array> */ public function getFullCalendarObjects($calendarId, $bCalendarData) { $calendarId = (int) $calendarId; $calevents = array(); if (!$this->user->rights->agenda->myactions->read) { return $calevents; } if ($calendarId != $this->user->id && (!isset($this->user->rights->agenda->allactions->read) || !$this->user->rights->agenda->allactions->read)) { return $calevents; } $sql = $this->getSqlCalEvents($calendarId); $result = $this->db->query($sql); if ($result) { while ($obj = $this->db->fetch_object($result)) { $calendardata = $this->toVCalendar($calendarId, $obj); if ($bCalendarData) { $calevents[] = array( 'calendardata' => $calendardata, 'uri' => $obj->id.'-ev-'.constant('CDAV_URI_KEY'), 'lastmodified' => strtotime($obj->lastupd), 'etag' => '"'.md5($calendardata).'"', 'calendarid' => $calendarId, 'size' => strlen($calendardata), 'component' => strpos($calendardata, 'BEGIN:VEVENT') > 0 ? 'vevent' : 'vtodo', ); } else { $calevents[] = array( // 'calendardata' => $calendardata, not necessary because etag+size are present 'uri' => $obj->id.'-ev-'.constant('CDAV_URI_KEY'), 'lastmodified' => strtotime($obj->lastupd), 'etag' => '"'.md5($calendardata).'"', 'calendarid' => $calendarId, 'size' => strlen($calendardata), 'component' => strpos($calendardata, 'BEGIN:VEVENT') > 0 ? 'vevent' : 'vtodo', ); } } } return $calevents; } }