Jump to content

Featured Replies

Posted
  • Admin
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 8601

3.Формирование изменений

Подготовьте обновления через 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 reality

Do not be indifferent. Support for motivation to continue on to engage in this further!

  • User
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
]);

  • Author
  • Admin
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'
]);
Ключевые моменты:
  1. OriginalStart должен точно соответствовать исходному времени изменяемого экземпляра

  2. Для серии создается новое исключение через ExceptionType

  3. Изменяется вся структура повторения (Recurrence)

  4. Сервер автоматически:

    • Сохраняет оригинальную серию

    • Создает скрытое исключение

    • Генерирует новый экземпляр с измененным временем

Альтернативный подход: удалить и создать заново

Если изменение времени критично и подход с исключениями не работает:

// 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 reality

Do not be indifferent. Support for motivation to continue on to engage in this further!

  • Author
  • Admin
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',
        '_' => 'Текст'
    ]
]);

Если проблемы осталась, попробуй:

  1. Включить логирование SOAP:

$api->getClient()->debugSoap();

Telegram site  "Typical society"

We don’t debug code — we debug reality

Do not be indifferent. Support for motivation to continue on to engage in this further!

  • 3 weeks later...
  • User
comment_49612
This post was recognized by AndrewPro!

a.buynovskiy was awarded the badge 'Great Content' and 5 points.

Проверенный код рабочий. другое не пробовал так как времени не было.

<?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';
    }
}

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.Privacy Policy