From 4bc7fd0261118f4de1e3f824ce5bd3c60993e3bc Mon Sep 17 00:00:00 2001 From: gevorgmansuryan Date: Fri, 8 May 2026 00:28:31 +0400 Subject: [PATCH 1/2] Calendar: Filter & Recurring Visibility --- Events.php | 15 +++++++ config.php | 1 + controllers/EntryController.php | 4 ++ controllers/GlobalController.php | 18 +++++++- integration/BirthdayCalendarQuery.php | 5 +++ interfaces/event/AbstractCalendarQuery.php | 7 +-- models/CalendarEntry.php | 50 +++++++++++++++++++++ models/CalendarEntryQuery.php | 11 +++++ models/forms/CalendarEntryForm.php | 52 +++++++++++++--------- widgets/views/calendarFilterBar.php | 3 ++ 10 files changed, 140 insertions(+), 26 deletions(-) diff --git a/Events.php b/Events.php index 7499939c..9885c8aa 100644 --- a/Events.php +++ b/Events.php @@ -449,4 +449,19 @@ public static function onContentAfterSoftDelete(ContentEvent $event): void } } } + + public static function onContentAfterUpdate($event): void + { + if (!CalendarEntry::isRecurrenceContentVisibilitySyncEnabled() + || !array_key_exists('visibility', $event->changedAttributes) + || $event->sender->object_model !== CalendarEntry::class) { + return; + } + + /* @var CalendarEntry $calendarEntry */ + $calendarEntry = $event->sender->getModel(); + if ($calendarEntry) { + $calendarEntry->syncRecurrenceContentVisibility(); + } + } } diff --git a/config.php b/config.php index 02c30875..44db14e6 100644 --- a/config.php +++ b/config.php @@ -42,6 +42,7 @@ ['class' => 'humhub\modules\rest\Module', 'event' => 'restApiAddRules', 'callback' => [Events::class, 'onRestApiAddRules']], ['class' => 'humhub\modules\custom_pages\modules\template\services\ElementTypeService', 'event' => 'init', 'callback' => [Events::class, 'onCustomPagesTemplateElementTypeServiceInit']], ['class' => Content::class, 'event' => Content::EVENT_AFTER_SOFT_DELETE, 'callback' => [Events::class, 'onContentAfterSoftDelete']], + ['class' => Content::class, 'event' => Content::EVENT_AFTER_UPDATE, 'callback' => [Events::class, 'onContentAfterUpdate']], ], 'urlManagerRules' => [ 'calendar' => 'calendar/global', diff --git a/controllers/EntryController.php b/controllers/EntryController.php index 78d0f6b2..11f889ff 100644 --- a/controllers/EntryController.php +++ b/controllers/EntryController.php @@ -102,6 +102,10 @@ public function actionViewRecurrence($parent_id, $recurrence_id, $cal = null) throw new NotFoundHttpException(); } + if (!$recurrence->content->canView()) { + throw new ForbiddenHttpException('You have no permission to view the event!'); + } + return $this->renderEntry($recurrence, $cal); } diff --git a/controllers/GlobalController.php b/controllers/GlobalController.php index bb390541..da9e753d 100644 --- a/controllers/GlobalController.php +++ b/controllers/GlobalController.php @@ -13,6 +13,7 @@ use humhub\modules\calendar\models\fullcalendar\FullCalendar; use humhub\modules\calendar\models\SnippetModuleSettings; use humhub\modules\calendar\widgets\FilterType; +use humhub\modules\content\components\ActiveQueryContent; use humhub\modules\content\components\ContentContainerModuleManager; use humhub\modules\content\models\ContentContainer; use humhub\modules\content\models\ContentContainerModuleState; @@ -98,7 +99,22 @@ private function getSelectorSettings() return []; } - return $this->getUserSettings()->getSerialized('lastSelectors', []); + return $this->getUserSettings()->getSerialized('lastSelectors') ?? $this->getDefaultSelectorSettings(); + } + + private function getDefaultSelectorSettings(): array + { + $selectors = []; + + $selectors[] = ActiveQueryContent::USER_RELATED_SCOPE_OWN_PROFILE; + $selectors[] = ActiveQueryContent::USER_RELATED_SCOPE_SPACES; + + if (!Yii::$app->getModule('user')->disableFollow) { + $selectors[] = ActiveQueryContent::USER_RELATED_SCOPE_FOLLOWED_SPACES; + $selectors[] = ActiveQueryContent::USER_RELATED_SCOPE_FOLLOWED_USERS; + } + + return $selectors; } /** diff --git a/integration/BirthdayCalendarQuery.php b/integration/BirthdayCalendarQuery.php index 1764df31..62675330 100644 --- a/integration/BirthdayCalendarQuery.php +++ b/integration/BirthdayCalendarQuery.php @@ -88,6 +88,11 @@ protected function filterDashboard() protected function filterUserRelated() { + if (empty($this->_userScopes)) { + $this->_query->andWhere('1=2'); + return; + } + if (!empty($this->_userScopes) && !(in_array(ActiveQueryContent::USER_RELATED_SCOPE_FOLLOWED_USERS, $this->_userScopes) || in_array(ActiveQueryContent::USER_RELATED_SCOPE_OWN_PROFILE, $this->_userScopes))) { throw new FilterNotSupportedException('Non supported user related filters'); } diff --git a/interfaces/event/AbstractCalendarQuery.php b/interfaces/event/AbstractCalendarQuery.php index f39f5249..00807c4f 100644 --- a/interfaces/event/AbstractCalendarQuery.php +++ b/interfaces/event/AbstractCalendarQuery.php @@ -252,7 +252,7 @@ public static function findForFilter(?DateTime $start = null, ?DateTime $end = n } } - return $expand ? $expandResult : $result; + return $expand ? $query->preFilter($expandResult) : $result; } /** @@ -950,11 +950,12 @@ protected function setupFilters() if (Yii::$app->user->isGuest) { $this->filterGuests($this->_container); } else { - if ($this->hasFilter(self::FILTER_USERRELATED)) { + $hasUserRelatedFilter = $this->hasFilter(self::FILTER_USERRELATED); + if ($hasUserRelatedFilter) { $this->_userScopes = $this->_filters[self::FILTER_USERRELATED]; } - if (!empty($this->_userScopes)) { + if ($hasUserRelatedFilter || !empty($this->_userScopes)) { $this->filterUserRelated(); } diff --git a/models/CalendarEntry.php b/models/CalendarEntry.php index 30565a39..e98f3779 100644 --- a/models/CalendarEntry.php +++ b/models/CalendarEntry.php @@ -145,6 +145,9 @@ class CalendarEntry extends ContentActiveRecord implements */ private $_recurrenceRoot = false; + private static bool $recurrenceContentVisibilitySyncEnabled = true; + private static bool $syncingRecurrenceContentVisibility = false; + /** * @var bool Cached of reminder enabled */ @@ -844,6 +847,7 @@ public function setRecurrenceId($recurrenceId) public function setRecurrenceRootId($rootId) { $this->parent_event_id = $rootId; + $this->_recurrenceRoot = false; } public function getRecurring(): bool @@ -904,6 +908,52 @@ public function getRecurrenceRoot(): ?self return $this->_recurrenceRoot; } + public static function withoutRecurrenceContentVisibilitySync(callable $callback) + { + $enabled = static::$recurrenceContentVisibilitySyncEnabled; + static::$recurrenceContentVisibilitySyncEnabled = false; + + try { + return $callback(); + } finally { + static::$recurrenceContentVisibilitySyncEnabled = $enabled; + } + } + + public static function isRecurrenceContentVisibilitySyncEnabled(): bool + { + return static::$recurrenceContentVisibilitySyncEnabled && !static::$syncingRecurrenceContentVisibility; + } + + public function syncRecurrenceContentVisibility(): void + { + if (!RecurrenceHelper::isRecurrent($this) || static::$syncingRecurrenceContentVisibility || empty($this->id)) { + return; + } + + $root = empty($this->parent_event_id) ? $this : self::findOne(['id' => $this->parent_event_id]); + if (!$root) { + return; + } + + $entryIds = $root->getRecurrenceInstances()->select('calendar_entry.id')->column(); + $entryIds[] = $root->id; + + static::$syncingRecurrenceContentVisibility = true; + try { + foreach (Content::find() + ->where(['object_model' => static::getObjectModel(), 'object_id' => $entryIds]) + ->andWhere(['<>', 'visibility', $this->content->visibility]) + ->each() as $content) { + /* @var Content $content */ + $content->visibility = $this->content->visibility; + $content->save(); + } + } finally { + static::$syncingRecurrenceContentVisibility = false; + } + } + /** * @param $start * @param $end diff --git a/models/CalendarEntryQuery.php b/models/CalendarEntryQuery.php index c2c04c1a..4883be19 100644 --- a/models/CalendarEntryQuery.php +++ b/models/CalendarEntryQuery.php @@ -7,6 +7,7 @@ use DateTime; use yii\db\Query; use yii\helpers\ArrayHelper; +use Yii; /** * CalendarEntryQuery class can be used for creating filter queries for [[CalendarEntry]] models. @@ -127,5 +128,15 @@ private function participantJoin() } } + protected function preFilter($result = []) + { + if (!method_exists(Yii::$app, 'getUser')) { + return parent::preFilter($result); + } + + return array_values(array_filter(parent::preFilter($result), static function (CalendarEntry $entry) { + return $entry->content->isNewRecord || $entry->content->canView(); + })); + } } diff --git a/models/forms/CalendarEntryForm.php b/models/forms/CalendarEntryForm.php index 92180e5d..f81dfa0c 100644 --- a/models/forms/CalendarEntryForm.php +++ b/models/forms/CalendarEntryForm.php @@ -472,37 +472,45 @@ public function save() //$this->translateDateTimes($this->entry->start_datetime, $this->entry->end_datetime, Yii::$app->timeZone, $this->timeZone); return CalendarEntry::getDb()->transaction(function ($db) { + $result = CalendarEntry::withoutRecurrenceContentVisibilitySync(function () { - if (!$this->entry->saveEvent()) { - return false; - } + if (!$this->entry->saveEvent()) { + return false; + } - // Patch for https://github.com/humhub/humhub/issues/4847 in 1.8.beta1 - if ($this->entry->description) { - RichText::postProcess($this->entry->description, $this->entry); - } + // Patch for https://github.com/humhub/humhub/issues/4847 in 1.8.beta1 + if ($this->entry->description) { + RichText::postProcess($this->entry->description, $this->entry); + } - $this->entry->setType($this->type_id); + $this->entry->setType($this->type_id); - Topic::attach($this->entry->content, $this->topics); + Topic::attach($this->entry->content, $this->topics); - if ($this->sendUpdateNotification && !$this->entry->isNewRecord && !$this->entry->closed) { - $this->entry->participation->sendUpdateNotification(); - } + if ($this->sendUpdateNotification && !$this->entry->isNewRecord && !$this->entry->closed) { + $this->entry->participation->sendUpdateNotification(); + } - if (!$this->reminder) { - $this->reminderSettings->reminderType = ReminderSettings::REMINDER_TYPE_NONE; - $this->reminderSettings->reminders = []; - } - $result = $this->reminderSettings->save(); + if (!$this->reminder) { + $this->reminderSettings->reminderType = ReminderSettings::REMINDER_TYPE_NONE; + $this->reminderSettings->reminders = []; + } + $result = $this->reminderSettings->save(); - if (!$this->recurring) { - $this->recurrenceForm->frequency = RecurrenceFormModel::FREQUENCY_NEVER; - } - $result = $this->recurrenceForm->save($this->original) && $result; + if (!$this->recurring) { + $this->recurrenceForm->frequency = RecurrenceFormModel::FREQUENCY_NEVER; + } + $result = $this->recurrenceForm->save($this->original) && $result; + + if ($result) { + $this->sequenceCheck(); + } + + return $result; + }); if ($result) { - $this->sequenceCheck(); + $this->entry->syncRecurrenceContentVisibility(); } return $result; diff --git a/widgets/views/calendarFilterBar.php b/widgets/views/calendarFilterBar.php index 558f6447..d8fcbe9c 100644 --- a/widgets/views/calendarFilterBar.php +++ b/widgets/views/calendarFilterBar.php @@ -56,6 +56,9 @@ + +