Опубликовано 6 июня6 июнь Админ comment_48527 1.Получение идентификаторовСначала получите RecurringMasterId (ID родительской серии) и дату конкретного экземпляра через FindItem:php use garethp\ews\API; use garethp\ews\API\Type; $api = API::withUsernameAndPassword('server', 'username', 'password'); // Поиск серии событий $events = $api->findItem($api->getCalendarFolderId(), [ 'start' => new DateTime('2025-06-01'), 'end' => new DateTime('2025-06-30'), ]); foreach ($events as $event) { if ($event->getItemId() && $event->getRecurrence()) { $recurringMasterId = $event->getItemId()->getId(); $changeKey = $event->getItemId()->getChangeKey(); break; } } 2.Создание объекта для изменения экземпляра Используйте RecurringMasterItemIdType для указания изменяемого экземпляра:php use garethp\ews\API\Type\RecurringMasterItemIdType; $occurrenceDate = new DateTime('2025-06-10T14:00:00+00:00'); // UTC $masterId = new RecurringMasterItemIdType(); $masterId->setItemId($recurringMasterId); $masterId->setChangeKey($changeKey); $masterId->setOccurrenceId($occurrenceDate->format('c')); // ISO 86013.Формирование измененийПодготовьте обновления через SetItemFieldType:php use garethp\ews\API\Type\SetItemFieldType; use garethp\ews\API\Type\CalendarItemType; $updates = []; // Изменение темы $subjectField = new SetItemFieldType(); $subjectField->setFieldURI('calendar:Subject'); $subjectField->setCalendarItem(new CalendarItemType()); $subjectField->getCalendarItem()->setSubject('Новый заголовок экземпляра'); $updates[] = $subjectField; // Другие поля (пример) $bodyField = new SetItemFieldType(); $bodyField->setFieldURI('calendar:Body'); $bodyField->setCalendarItem(new CalendarItemType()); $bodyField->getCalendarItem()->setBody('Новое описание'); $bodyField->getCalendarItem()->setBodyType('HTML'); $updates[] = $bodyField;4.Отправка запроса на обновлениеИспользуйте updateItem с параметрами:php use garethp\ews\API\Enumeration\ConflictResolutionType; use garethp\ews\API\Enumeration\CalendarItemUpdateOperationType; $result = $api->updateItem($masterId, $updates, [ 'ConflictResolution' => ConflictResolutionType::ALWAYS_OVERWRITE, 'SendMeetingInvitationsOrCancellations' => CalendarItemUpdateOperationType::SEND_ONLY_TO_CHANGED ]);Ключевые моменты:- OccurrenceId: Должен точно совпадать с датой экземпляра в серии (включая часовой пояс).- Только неструктурные изменения: Не изменяйте `Start`, `End` или `Recurrence` — это нарушит серию. Telegram site "Typical society"We don’t debug code — we debug realityDo not be indifferent. Support for motivation to continue on to engage in this further!
Опубликовано 6 июня6 июнь Пользователь comment_48540 Возможно у меня версия не та, но 3 часа назад, AndrewPro сказал:$bodyField->getCalendarItem()->setBody('Новое описание'); $bodyField->getCalendarItem()->setBodyType('HTML'); идет не так, там сразу обьект надо закидывать $body = new BodyType('Новое описание', 'HTML'); $bodyField->getCalendarItem()->setBody($body);тут метод тоже показывает как ошибку 3 часа назад, AndrewPro сказал:$result = $api->updateItem($masterId, $updates, [ 'ConflictResolution' => ConflictResolutionType::ALWAYS_OVERWRITE, 'SendMeetingInvitationsOrCancellations' => CalendarItemUpdateOperationType::SEND_ONLY_TO_CHANGED ]);
Опубликовано 7 июня7 июнь Автор Админ comment_48553 Изменение даты или времени конкретного экземпляра в повторяющейся серии требует особого подходатак как это считается структурным изменением. Вот как это правильно сделать через EWS:Почему нельзя просто изменить времяИзменение Start/End затрагивает структуру повторенияСервер Exchange рассматривает это как создание исключения (exception) в серииТребуется модификация всего шаблона повторенияПравильный способ изменения времени событияИспользуй подход с созданием исключения в серии:use garethp\ews\API; use garethp\ews\API\Type; $api = API::withUsernameAndPassword('server', 'username', 'password'); // 1. Получаем мастер-серию $masterId = $api->getItemIdFromString('AAMkAG...AAA='); // ID родительской серии // 2. Создаем объект исключения $exception = new Type\ExceptionType(); $exception->setDeleted(false); // Указываем оригинальную дату экземпляра (который меняем) $originalDate = new DateTime('2025-06-10T14:00:00+00:00'); $exception->setOriginalStart($originalDate); // 3. Создаем измененный экземпляр $modifiedEvent = new Type\CalendarItemType(); $modifiedEvent->setStart(new DateTime('2025-06-10T15:30:00+00:00')); // Новое время $modifiedEvent->setEnd(new DateTime('2025-06-10T16:30:00+00:00')); $modifiedEvent->setSubject('Измененное время встречи'); // 4. Собираем исключение $exception->setItem($modifiedEvent); // 5. Добавляем исключение в серию $update = new Type\SetItemFieldType(); $update->setFieldURI('calendar:Recurrence'); $update->setCalendarItem(new Type\CalendarItemType()); $recurrence = new Type\RecurrenceType(); $recurrence->setExceptions([$exception]); // Добавляем наше исключение $update->getCalendarItem()->setRecurrence($recurrence); // 6. Отправляем обновление $api->updateItem($masterId, [$update], [ 'ConflictResolution' => 'AlwaysOverwrite', 'SendMeetingInvitationsOrCancellations' => 'SendToAll' ]);Ключевые моменты:OriginalStart должен точно соответствовать исходному времени изменяемого экземпляраДля серии создается новое исключение через ExceptionTypeИзменяется вся структура повторения (Recurrence)Сервер автоматически:Сохраняет оригинальную сериюСоздает скрытое исключениеГенерирует новый экземпляр с измененным временемАльтернативный подход: удалить и создать зановоЕсли изменение времени критично и подход с исключениями не работает:// 1. Удаляем конкретный экземпляр $occurrenceId = new Type\RecurringMasterItemIdType(); $occurrenceId->setItemId($masterId->getId()); $occurrenceId->setOccurrenceId('2025-06-10T14:00:00Z'); $api->deleteItem($occurrenceId, [ 'SendMeetingCancellations' => 'SendToAll' ]); // 2. Создаем новое событие на это время $newEvent = new Type\CalendarItemType(); $newEvent->setStart(new DateTime('2025-06-10T15:30:00+00:00')); $newEvent->setEnd(new DateTime('2025-06-10T16:30:00+00:00')); // ... остальные параметры $api->createItem($newEvent); Telegram site "Typical society"We don’t debug code — we debug realityDo not be indifferent. Support for motivation to continue on to engage in this further!
Опубликовано 7 июня7 июнь Автор Админ comment_48554 15 часов назад, a.buynovskiy сказал:Возможно у меня версия не та, ноидет не так, там сразу обьект надо закидывать$body = new BodyType('Новое описание', 'HTML'); $bodyField->getCalendarItem()->setBody($body);тут метод тоже показывает как ошибкуВ разных версиях php-ews и EWS API могут быть различия в синтаксисе.Способ работы с телом сообщения и другими полями:Для установки тела сообщения нужно использовать BodyType:use garethp\ews\API\Type\BodyType; // Создаем объект BodyType $body = new BodyType(); $body->setBodyType('HTML'); // или 'Text' $body->set_( 'Новое описание' ); // Метод set_() для содержимого // Формируем поле для обновления $bodyField = new SetItemFieldType(); $bodyField->setFieldURI('calendar:Body'); $bodyField->setCalendarItem(new CalendarItemType()); $bodyField->getCalendarItem()->setBody($body);Некоторые версии требуют иного подхода:$bodyField = new SetItemFieldType(); $bodyField->setFieldURI('calendar:Body'); $calendarItem = new CalendarItemType(); $calendarItem->setBodyType('HTML'); $calendarItem->setBody('Новое описание'); $bodyField->setCalendarItem($calendarItem);Исправленный вариантuse garethp\ews\API; use garethp\ews\API\Type; use garethp\ews\API\Type\BodyType; use garethp\ews\API\Enumeration\ConflictResolutionType; use garethp\ews\API\Enumeration\CalendarItemUpdateOperationType; $api = API::withUsernameAndPassword('server', 'username', 'password'); // Подготовка обновлений $updates = []; // 1. Обновление темы $subjectField = new Type\SetItemFieldType(); $subjectField->setFieldURI('calendar:Subject'); $subjectField->setCalendarItem(new Type\CalendarItemType()); $subjectField->getCalendarItem()->setSubject('Новый заголовок'); $updates[] = $subjectField; // 2. Обновление тела сообщения $bodyField = new Type\SetItemFieldType(); $bodyField->setFieldURI('calendar:Body'); $body = new BodyType(); $body->setBodyType('HTML'); $body->set_('Новое описание <b>с HTML</b>'); $calendarItem = new Type\CalendarItemType(); $calendarItem->setBody($body); $bodyField->setCalendarItem($calendarItem); $updates[] = $bodyField; // Отправка обновления $result = $api->updateItem($masterId, $updates, [ 'ConflictResolution' => ConflictResolutionType::ALWAYS_OVERWRITE, 'SendMeetingInvitationsOrCancellations' => CalendarItemUpdateOperationType::SEND_ONLY_TO_CHANGED ]);Если методы не найденыОбновите библиотеку:composer update garethp/php-ewsИспользуй "магические" методы (если автодополнение не работает):$calendarItem->__set('Body', [ 'BodyType' => 'HTML', '_' => 'Текст сообщения' ]);Ручное создание массива (крайний случай):$bodyField->setCalendarItem([ 'Body' => [ 'BodyType' => 'HTML', '_' => 'Текст' ] ]);Если проблемы осталась, попробуй:Включить логирование SOAP:$api->getClient()->debugSoap(); Telegram site "Typical society"We don’t debug code — we debug realityDo not be indifferent. Support for motivation to continue on to engage in this further!
Опубликовано 27 июня27 июнь Пользователь comment_49612 сообщение оценил AndrewPro! a.buynovskiy получил значок 'Great Content' и 5 баллов. Проверенный код рабочий. другое не пробовал так как времени не было.<?php namespace App\Modules\Calendar\Actions\OutlookCalendar; use App\Modules\Calendar\Actions\OutlookCalendar\Helpers\Helpers; use App\Modules\Calendar\Models\CalendarEvent; use garethp\ews\API\Type\ItemIdType; use garethp\ews\CalendarAPI; use Illuminate\Support\Facades\Log; class UpdateEventInOutlookCalendarAction { public function __construct( protected GetEwsClientAction $getClientAction, protected PrepareEventForExportAction $prepareEventForExportAction, protected ExportCreateToOutlookCalendarAction $exportCreatedEventAction, ) { } public function __invoke(CalendarEvent $event, string $mode = ''): bool { if (empty($event->uid) || !$event->calendarData) { Log::warning('Missing calendar data or external_id', ['event_id' => $event->id]); return false; } if (strtotime($event->start_date) >= strtotime($event->end_date)) { Log::warning('Invalid time range for event', ['event_id' => $event->id]); return false; } try { $token = ($this->getClientAction)($event->calendarData); $api = ($this->getClientAction)->getApiClient($token['access_token'], $event->createdByData->email); $calendar = $event->calendarData->connection->is_default ? $api->getCalendar() : $api->getCalendar($event->calendarData->name); $eventData = ($this->prepareEventForExportAction)($event, $event->timezone ?? 'UTC', 'update'); if ($event->parent_recurrence_event !== null) { Log::debug('Обновление одного из серии: ', [print_r($eventData, 1)]); $this->updateSingleOccurrence($api, $event, $calendar, $eventData); return true; } $this->updateSeriesOrSingle($calendar, $event, $eventData); return true; } catch (\Throwable $e) { Log::error('UpdateOutlookEventAction failed: ' . $e->getMessage(), [ 'event_id' => $event->id, ]); return false; } } /** * Обновление всей серии или одиночного события * @param CalendarAPI $calendar * @param CalendarEvent $event * @param array $data * @return bool */ protected function updateSeriesOrSingle(CalendarAPI $calendar, CalendarEvent $event, array $data): bool { try { $itemId = new ItemIdType(); $itemId->setId($event->uid); $itemId->setChangeKey($event->change_key_outlook); $calendar->updateCalendarItem( $itemId, $data, ['SendMeetingInvitationsOrCancellations' => 'SendToAllAndSaveCopy'] ); return true; } catch (\Throwable $e) { Log::error('Failed to update Outlook event: ' . $e->getMessage(), [ 'event_id' => $event->id, ]); return false; } } public function updateSingleOccurrence($api, CalendarEvent $event, CalendarAPI $calendar, $updates): bool { try { if (!$event->recurrence_id && !$event->parent_recurrence_event) { throw new \Exception('Событие должно иметь recurrence_id и ссылку на родительскую серию.'); } //получаем конкретное событие для его изменения $itemId = (new Helpers())->getRecurrenceItem($event, $api); if (!$itemId || !$itemId instanceof ItemIdType) { throw new \Exception('Не удалось получить ItemId для обновления экземпляра события.'); } $calendar->updateCalendarItem( $itemId, $updates, ['SendMeetingInvitationsOrCancellations' => 'SendToAllAndSaveCopy'] ); } catch (\Exception $e) { Log::error('Update', [ 'error' => $e->getMessage(), 'event_id' => $event->id ]); } return true; } } И вот вспомогательный метод для получения отдельного вхождения <?php namespace App\Modules\Calendar\Actions\OutlookCalendar\Helpers; use App\Modules\Calendar\Models\CalendarEvent; use Carbon\Carbon; use garethp\ews\API\Type\ItemIdType; use Illuminate\Support\Facades\Log; class Helpers { public function getRecurrenceItem(CalendarEvent $event, $api): ItemIdType { $itemId = new ItemIdType(); /** @var CalendarEvent $master */ $master = CalendarEvent::find($event->parent_recurrence_event); if (!$master || !$master->uid || !$master->change_key_outlook) { Log::error('Missing parent info for recurrence exception', ['event_id' => $event->id]); return $itemId; } Log::debug('1111 - : '.$this->calculateRecurrenceIndex($master->start_date, $event->recurrence_id, $master->recurrence)); try { $request = [ 'ItemShape' => [ 'BaseShape' => 'IdOnly' ], 'ItemIds' => [ 'OccurrenceItemId' => [ 'RecurringMasterId' => $master->uid, 'InstanceIndex' => $this->calculateRecurrenceIndex($master->start_date, $event->recurrence_id, $master->recurrence) ] ] ]; $response = $api->getClient()->GetItem($request); Log::debug('SOAP request: '.$api->getClient()->getClient()->__getLastRequest()); Log::debug('SOAP request: ', [print_r($response, true)]); $itemId = $response; } catch (\Exception $e) { Log::error('GetItem failed', [ 'error' => $e->getMessage(), 'event_id' => $event->id ]); return $itemId; } return $itemId; } public function calculateRecurrenceIndex(string $startDate, string $occurrenceDate, array $recurrence): ?int { $start = Carbon::parse($startDate)->startOfDay(); $occurrence = Carbon::parse($occurrenceDate)->startOfDay(); $interval = (int)($recurrence['INTERVAL'] ?? 1); if ($occurrence->lessThan($start)) { return null; // Некорректная дата вхождения } switch (strtoupper($recurrence['FREQ'])) { case 'DAILY': return floor($start->diffInDays($occurrence) / $interval) + 1; case 'WEEKLY': $daysOfWeek = array_map(fn($d) => strtoupper($this->dayMap($d)), $recurrence['BYDAY'] ?? []); if (empty($daysOfWeek)) { return null; } $count = 0; $current = $start->copy(); while ($current <= $occurrence) { if (in_array(strtoupper($current->format('l')), $daysOfWeek)) { $count++; } $current->addDay(); if ($current->diffInWeeks($start) % $interval !== 0) { // Пропускаем недели вне интервала continue; } } return $count; case 'MONTHLY': if (!empty($recurrence['BYMONTHDAY'])) { $day = (int)$recurrence['BYMONTHDAY']; $count = 0; $current = $start->copy(); while ($current <= $occurrence) { if ((int)$current->format('d') === $day) { $count++; if ($current->isSameDay($occurrence)) { return $count; } } $current->addMonthNoOverflow($interval); } return null; } break; case 'YEARLY': if (!empty($recurrence['BYMONTH']) && !empty($recurrence['BYMONTHDAY'])) { $count = 0; $current = $start->copy(); while ($current <= $occurrence) { if ( (int)$current->format('m') === (int)$recurrence['BYMONTH'] && (int)$current->format('d') === (int)$recurrence['BYMONTHDAY'] ) { $count++; if ($current->isSameDay($occurrence)) { return $count; } } $current->addYear(); } return null; } break; } return null; // Неподдерживаемый тип или не удалось вычислить } protected function dayMap(string $day): string { return [ 'MO' => 'Monday', 'TU' => 'Tuesday', 'WE' => 'Wednesday', 'TH' => 'Thursday', 'FR' => 'Friday', 'SA' => 'Saturday', 'SU' => 'Sunday', ][strtoupper($day)] ?? 'Monday'; } }
Присоединяйтесь к обсуждению
Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.