Перейти к содержанию

Featured Replies

Опубликовано
  • Админ
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!

Опубликовано
  • Пользователь
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
]);

Опубликовано
  • Автор
  • Админ
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!

Опубликовано
  • Автор
  • Админ
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 недели спустя...
Опубликовано
  • Пользователь
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';
    }
}

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

Важная информация

Мы разместили cookie-файлы на ваше устройство, чтобы помочь сделать этот сайт лучше. Вы можете изменить свои настройки cookie-файлов, или продолжить без изменения настроек.Политика конфиденциальности