input->get('email', true) ?: '';
$dateYmd = $this->input->get('date', true) ?: date('Y-m-d');
$tz = $this->input->get('tz', true) ?: 'America/Toronto';
$base = $this->input->get('base', true) ?: '08:00';
$dur = (int)($this->input->get('dur', true) ?: 1);
$calId = $this->input->get('calendar', true) ?: 'primary';
header('Content-Type: text/html; charset=utf-8');
echo "
Debug GCal – Test de placement
";
echo "Paramètres
";
echo "email : $email\n";
echo "date : $dateYmd\n";
echo "timezone : $tz\n";
echo "base : $base\n";
echo "dur(min) : $dur\n";
echo "calendar : $calId\n";
echo "";
if (empty($email)) {
echo "⚠️ Fournis ?email=user@domaine.com dans l’URL.
";
$this->print_usage(); return;
}
// Fenêtre de la journée
try { $tzObj = new DateTimeZone($tz); } catch (Exception $e) { $tzObj = new DateTimeZone('America/Toronto'); }
$dayStart = new DateTime("$dateYmd 00:00:00", $tzObj);
$dayEnd = new DateTime("$dateYmd 23:59:59", $tzObj);
// Charge la lib telle qu’en prod
$this->load->library('GCalService');
echo "Librairie GCalService
";
echo "Class: " . get_class($this->gcalservice) . "\n";
echo "Méthodes disponibles:\n";
echo " - freeBusy : " . (method_exists($this->gcalservice, 'freeBusy') ? 'OUI' : 'non') . "\n";
echo " - listEvents : " . (method_exists($this->gcalservice, 'listEvents') ? 'OUI' : 'non') . "\n";
echo "Classes Google connues:\n";
echo " - Google_Service_Calendar: " . (class_exists('Google_Service_Calendar') ? 'OUI' : 'non') . "\n";
echo " - Google_Client : " . (class_exists('Google_Client') ? 'OUI' : 'non') . "\n";
echo "";
// ===== Introspection des propriétés de la librairie =====
$props = get_object_vars($this->gcalservice);
echo "Propriétés de GCalService
";
if (empty($props)) {
echo "(aucune propriété publique/protégée visible)
";
} else {
echo "| Nom | Type | Détail |
";
foreach ($props as $k => $v) {
$type = gettype($v);
$detail = '';
if (is_object($v)) {
$cls = get_class($v);
$type = "object";
$detail = $cls;
} elseif (is_array($v)) {
$detail = 'array('.count($v).')';
} else {
$detail = htmlspecialchars(var_export($v, true));
}
echo "| ".htmlspecialchars($k)." | $type | ".htmlspecialchars($detail)." |
";
}
echo "
";
}
// Tente de découvrir un service Calendar déjà initialisé
$calendarService = null;
$googleClient = null;
// 1) Cherche un objet déjà de type Google_Service_Calendar
foreach ($props as $v) {
if (is_object($v) && class_exists('Google_Service_Calendar') && $v instanceof Google_Service_Calendar) {
$calendarService = $v; break;
}
}
// 2) Sinon, cherche un Google_Client
if (!$calendarService && class_exists('Google_Client')) {
foreach ($props as $v) {
if (is_object($v) && $v instanceof Google_Client) {
$googleClient = $v; break;
}
}
}
// 3) Sinon, si la lib expose une méthode getClient(), on l’essaie
if (!$calendarService && !$googleClient && method_exists($this->gcalservice, 'getClient')) {
try {
$maybeClient = $this->gcalservice->getClient(); // sans effet de bord
if (is_object($maybeClient) && class_exists('Google_Client') && $maybeClient instanceof Google_Client) {
$googleClient = $maybeClient;
}
} catch (Exception $e) {
echo "getClient() a levé: ".htmlspecialchars($e->getMessage())."
";
}
}
// 4) Fabrique un service si on a un client
if (!$calendarService && $googleClient && class_exists('Google_Service_Calendar')) {
$calendarService = new Google_Service_Calendar($googleClient);
}
echo "Détection de service
";
echo "calendarService: ".($calendarService ? ('OK ('.get_class($calendarService).')') : 'non')."\n";
echo "googleClient : ".($googleClient ? ('OK ('.get_class($googleClient).')') : 'non')."\n";
echo "";
// ==== Appels en lecture en utilisant, si possible, le service détecté ====
$busyIntervals = [];
$events = [];
// FreeBusy via service direct (si dispo)
if ($calendarService && method_exists($calendarService->freebusy, 'query')) {
echo "freeBusy() via service détecté
";
try {
$fbReq = new Google_Service_Calendar_FreeBusyRequest();
$fbReq->setTimeMin($dayStart->format(DateTime::RFC3339));
$fbReq->setTimeMax($dayEnd->format(DateTime::RFC3339));
$fbReq->setTimeZone($tz);
$item = new Google_Service_Calendar_FreeBusyRequestItem();
$item->setId($calId);
$fbReq->setItems([$item]);
$resp = $calendarService->freebusy->query($fbReq);
$cals = $resp->getCalendars();
if (isset($cals[$calId])) {
$blocks = $cals[$calId]['busy'] ?? [];
foreach ($blocks as $b) {
$s = new DateTime($b['start']);
$e = new DateTime($b['end']);
$s->setTimezone($tzObj);
$e->setTimezone($tzObj);
$busyIntervals[] = [$s, $e];
}
}
if (empty($busyIntervals)) echo "(aucun busy remonté)
";
$this->print_busy($busyIntervals, $tzObj);
} catch (Exception $e) {
echo "Exception: ".htmlspecialchars($e->getMessage())."
";
}
} else {
echo "freeBusy() via service détecté
service indisponible
";
}
// listEvents via service direct (si dispo)
if ($calendarService && method_exists($calendarService->events, 'listEvents')) {
echo "listEvents() via service détecté
";
try {
$opt = [
'timeMin' => $dayStart->format(DateTime::RFC3339),
'timeMax' => $dayEnd->format(DateTime::RFC3339),
'singleEvents' => true,
'orderBy' => 'startTime',
'maxResults' => 2500,
];
$resp = $calendarService->events->listEvents($calId, $opt);
foreach ($resp->getItems() as $ev) {
$start = $ev->getStart();
$end = $ev->getEnd();
$events[] = [
'start' => [
'dateTime' => $start ? $start->getDateTime() : null,
'date' => $start ? $start->getDate() : null,
],
'end' => [
'dateTime' => $end ? $end->getDateTime() : null,
'date' => $end ? $end->getDate() : null,
],
];
}
if (empty($events)) echo "(aucun événement)
";
$this->print_events($events, $tzObj);
} catch (Exception $e) {
echo "Exception: ".htmlspecialchars($e->getMessage())."
";
}
} else {
echo "listEvents() via service détecté
service indisponible
";
}
// Si rien via service, on retombe sur tes méthodes existantes (si présentes) pour comparer
if (empty($busyIntervals) && method_exists($this->gcalservice, 'freeBusy')) {
echo "freeBusy() via librairie
";
try {
$busyLib = $this->gcalservice->freeBusy($email, $calId, $dayStart->format(DateTime::RFC3339), $dayEnd->format(DateTime::RFC3339), $tz);
if (is_array($busyLib)) {
// On accepte deux formats: paires [DateTime, DateTime] ou tableaux ['start'=>..., 'end'=>...]
foreach ($busyLib as $pair) {
if (is_array($pair) && count($pair) === 2 && $pair[0] instanceof DateTime) {
$s = $pair[0]; $e = $pair[1];
} else {
$s = isset($pair['start']) ? new DateTime($pair['start']) : null;
$e = isset($pair['end']) ? new DateTime($pair['end']) : null;
}
if ($s && $e) {
$s->setTimezone($tzObj);
$e->setTimezone($tzObj);
$busyIntervals[] = [$s, $e];
}
}
if (empty($busyIntervals)) echo "(aucun busy via librairie)
";
$this->print_busy($busyIntervals, $tzObj);
}
} catch (Exception $e) {
echo "Exception: ".htmlspecialchars($e->getMessage())."
";
}
}
if (empty($events) && method_exists($this->gcalservice, 'listEvents')) {
echo "listEvents() via librairie
";
try {
$events = $this->gcalservice->listEvents($email, $calId, $dayStart->format(DateTime::RFC3339), $dayEnd->format(DateTime::RFC3339));
$this->print_events($events, $tzObj);
} catch (Exception $e) {
echo "Exception: ".htmlspecialchars($e->getMessage())."
";
}
}
// Normalisation finale des intervals occupés
echo "Busy normalisés (utilisés pour la simulation)
";
$normBusy = [];
foreach ($busyIntervals as $pair) {
if (!is_array($pair) || count($pair) !== 2) continue;
$s = $pair[0]; $e = $pair[1];
if (!($s instanceof DateTime) || !($e instanceof DateTime)) continue;
$normBusy[] = [$s, $e];
}
$this->print_busy($normBusy, $tzObj);
// Simulation locale (ne crée rien)
$slot = $this->simulate_slot($normBusy, $dateYmd, $tzObj, $base, $dur);
echo "Simulation de placement
";
echo "Candidat retenu : " . $slot['startIso'] . " → " . $slot['endIso'] . "\n";
echo "";
$this->print_usage();
}
private function print_busy(array $busy, DateTimeZone $tzObj)
{
if (empty($busy)) { echo "(aucun)
"; return; }
echo "";
echo "| # | Start (".$tzObj->getName().") | End (".$tzObj->getName().") | Durée (min) |
";
$i = 1;
foreach ($busy as $pair) {
$s = $pair[0]; $e = $pair[1];
$mins = round(($e->getTimestamp() - $s->getTimestamp()) / 60);
echo "| ".$i++." | ".$s->format('Y-m-d H:i:s')." | ".$e->format('Y-m-d H:i:s')." | ".$mins." |
";
}
echo "
";
}
private function print_events(array $events, DateTimeZone $tzObj)
{
if (empty($events)) { echo "(aucun)
"; return; }
echo "";
echo "| # | start.dateTime | start.date | end.dateTime | end.date |
";
$i = 1;
foreach ($events as $ev) {
$sdt = $ev['start']['dateTime'] ?? '';
$sd = $ev['start']['date'] ?? '';
$edt = $ev['end']['dateTime'] ?? '';
$ed = $ev['end']['date'] ?? '';
echo "| ".$i++." | ".htmlspecialchars($sdt)." | ".htmlspecialchars($sd)." | ".htmlspecialchars($edt)." | ".htmlspecialchars($ed)." |
";
}
echo "
";
}
private function simulate_slot(array $busyIntervals, string $dateYmd, DateTimeZone $tzObj, string $baseTime, int $durationMin): array
{
$candidate = new DateTime("$dateYmd $baseTime:00", $tzObj);
$dayEnd = new DateTime("$dateYmd 23:59:59", $tzObj);
usort($busyIntervals, function($a,$b){ return $a[0] <=> $b[0]; });
for ($i=0; $i<1440; $i++) {
if ($candidate > $dayEnd) break;
$candEnd = (clone $candidate)->modify("+$durationMin minutes");
$conflict = false;
foreach ($busyIntervals as $pair) {
$bs = $pair[0]; $be = $pair[1];
if ($candidate < $be && $candEnd > $bs) { $conflict = true; break; }
}
if (!$conflict) {
return [
'startIso' => $candidate->format(DateTime::RFC3339),
'endIso' => $candEnd->format(DateTime::RFC3339),
];
}
$candidate->modify('+1 minute');
}
$fallbackStart = new DateTime("$dateYmd $baseTime:00", $tzObj);
$fallbackEnd = (clone $fallbackStart)->modify("+$durationMin minutes");
return [
'startIso' => $fallbackStart->format(DateTime::RFC3339),
'endIso' => $fallbackEnd->format(DateTime::RFC3339),
];
}
private function print_usage()
{
echo "Usage
";
echo "GET /index.php/debug-gcal?email=prenom.nom@domaine.com&date=2025-09-23&tz=America/Toronto&base=08:00&dur=1&calendar=primary\n";
echo "";
}
}