%PDF- %PDF-
| Direktori : /home/vacivi36/intranet.vacivitta.com.br/protected/modules/calendar/models/ |
| Current File : /home/vacivi36/intranet.vacivitta.com.br/protected/modules/calendar/models/CalendarEntry.php |
<?php
namespace humhub\modules\calendar\models;
use humhub\modules\calendar\helpers\RecurrenceHelper;
use humhub\modules\calendar\interfaces\event\CalendarEntryTypeSetting;
use humhub\modules\calendar\interfaces\fullcalendar\FullCalendarEventIF;
use humhub\modules\calendar\interfaces\participation\CalendarEventParticipationIF;
use humhub\modules\calendar\interfaces\recurrence\RecurrentEventIF;
use humhub\modules\calendar\interfaces\reminder\CalendarEventReminderIF;
use humhub\modules\calendar\interfaces\recurrence\AbstractRecurrenceQuery;
use humhub\modules\calendar\models\participation\CalendarEntryParticipation;
use humhub\modules\calendar\models\recurrence\CalendarEntryRecurrenceQuery;
use humhub\modules\calendar\models\reminder\CalendarReminder;
use humhub\modules\calendar\permissions\CreateEntry;
use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\user\components\ActiveQueryUser;
use Yii;
use DateTime;
use humhub\modules\calendar\helpers\Url;
use humhub\modules\calendar\helpers\CalendarUtils;
use humhub\modules\calendar\interfaces\event\CalendarEventStatusIF;
use humhub\modules\calendar\notifications\CanceledEvent;
use humhub\modules\calendar\notifications\ReopenedEvent;
use humhub\modules\calendar\permissions\ManageEntry;
use humhub\modules\calendar\widgets\WallEntry;
use humhub\modules\content\models\Content;
use humhub\modules\content\models\ContentTag;
use humhub\modules\search\interfaces\Searchable;
use humhub\modules\space\models\Membership;
use humhub\modules\space\models\Space;
use humhub\widgets\Label;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\user\models\User;
/**
* This is the model class for table "calendar_entry".
*
* The followings are the available columns in table 'calendar_entry':
* @property integer $id
* @property string $title
* @property string $description
* @property string $start_datetime
* @property string $end_datetime
* @property integer $all_day
* @property integer $participation_mode
* @property string $color
* @property string $uid
* @property integer $allow_decline
* @property integer $allow_maybe
* @property string $participant_info
* @property integer $closed
* @property integer $max_participants
* @property string $rrule
* @property string $recurrence_id
* @property int $parent_event_id
* @property string $exdate
* @property string $sequence
* @property CalendarEntryParticipant[] $participantEntries
* @property string $time_zone The timeZone this entry was saved, note the dates itself are always saved in app timeZone
* @property string $location
* @property-read bool $recurring
* @property-read bool $reminder
* @property-read CalendarEntry|null $recurrenceRoot
*/
class CalendarEntry extends ContentActiveRecord implements Searchable, RecurrentEventIF, FullCalendarEventIF,
CalendarEventStatusIF, CalendarEventReminderIF, CalendarEventParticipationIF
{
/**
* @inheritdoc
*/
public $wallEntryClass = WallEntry::class;
/**
* Flag for Entry Form to set this content to public
*/
public $is_public = Content::VISIBILITY_PUBLIC;
/**
* @inheritdoc
*/
protected $createPermission = CreateEntry::class;
/**
* @inheritdoc
*/
public $managePermission = ManageEntry::class;
/**
* @var CalendarDateFormatter
*/
public $formatter;
/**
* @var array attached files
*/
public $files = [];
/**
* @inheritdoc
*/
public $canMove = true;
/**
* @var CalendarEntryRecurrenceQuery
*/
private $query;
/**
* @inheritdoc
*/
public $moduleId = 'calendar';
/**
* Participation Modes
*/
const PARTICIPATION_MODE_NONE = 0;
const PARTICIPATION_MODE_INVITE = 1;
const PARTICIPATION_MODE_ALL = 2;
/**
* @var array all given participation modes as array
*/
public static $participationModes = [
self::PARTICIPATION_MODE_NONE,
self::PARTICIPATION_MODE_INVITE,
self::PARTICIPATION_MODE_ALL
];
/**
* Filters
*/
const FILTER_PARTICIPATE = 1;
const FILTER_NOT_RESPONDED = 3;
const FILTER_RESPONDED = 4;
const FILTER_MINE = 5;
/**
* @var CalendarEntryParticipation
*/
public $participation;
/**
* @var self|null|false Cached recurrence root event
*/
private $_recurrenceRoot = false;
/**
* @var bool Cached of reminder enabled
*/
private $_reminder;
public function init()
{
parent::init();
$this->query = new CalendarEntryRecurrenceQuery(['event' => $this]);
if (!$this->time_zone) {
$this->time_zone = CalendarUtils::getUserTimeZone(true);
}
if ($this->sequence === null) {
$this->sequence = 0;
}
$this->isAllDay(); // initialize all day
$this->participation = new CalendarEntryParticipation(['entry' => $this]);
$this->formatter = new CalendarDateFormatter(['calendarItem' => $this]);
}
public function setDefaults()
{
// Note we can't call this in `init()` because of https://github.com/humhub/humhub/issues/3734
$this->participation->setDefautls();
if(!$this->color && $this->content->container) {
$typeSetting = new CalendarEntryTypeSetting(['type' => $this->getEventType(), 'contentContainer' => $this->content->container]);
$this->color = $typeSetting->getColor();
}
}
/**
* @inheritdoc
*/
public static function tableName()
{
return 'calendar_entry';
}
/**
* @inheritdoc
*/
public function getContentName()
{
return Yii::t('CalendarModule.base', "Event");
}
/**
* @inheritdoc
*/
public function getContentDescription()
{
return $this->title;
}
/**
* @inheritdoc
*/
public function getIcon()
{
return 'fa-calendar';
}
/**
* @inheritdoc
*/
public function getLabels($result = [], $includeContentName = true)
{
$labels = [];
if ($this->closed) {
$labels[] = Label::danger(Yii::t('CalendarModule.base', 'canceled'))->sortOrder(15);
}
$type = $this->getEventType();
if ($type) {
$labels[] = Label::asColor($type->color, $type->name)->sortOrder(310);
}
return parent::getLabels($labels);
}
/**
* @inheritdoc
*/
public function rules()
{
$dateFormat = 'php:' . CalendarUtils::DB_DATE_FORMAT;
return [
[['files'], 'safe'],
[['title', 'start_datetime', 'end_datetime'], 'required'],
[['color'], 'string', 'max' => 7],
[['start_datetime'], 'date', 'format' => $dateFormat],
[['end_datetime'], 'date', 'format' => $dateFormat],
[['all_day', 'allow_decline', 'allow_maybe', 'max_participants'], 'integer'],
[['title'], 'string', 'max' => 200],
[['participation_mode'], 'in', 'range' => self::$participationModes],
[['end_datetime'], 'validateEndTime'],
[['recurrence_id'], 'validateRecurrenceId'],
[['description', 'participant_info'], 'safe'],
['location', 'string', 'max' => 128],
];
}
/**
* This validation rules should prevent the creation of duplicate recurrent instances
*/
public function validateRecurrenceId()
{
if($this->isNewRecord && RecurrenceHelper::isRecurrentInstance($this)) {
if(static::findOne(['recurrence_id' => $this->recurrence_id, 'parent_event_id' => $this->parent_event_id])) {
$this->addError('recurrence_id', 'Recurrence instance with the same recurrence_id already persisted');
}
}
}
public function afterFind()
{
if (!$this->isAllDay()) {
parent::afterFind();
return;
}
/**
* Here we translate legacy all day events as follows:
*
* Old all day events were translated into system timezone, now we ignore timezone translation for all day events
* so we calculate back and ensure valid all day format.
*
* Old all day events ended right before with end time 23:59, now we save in moment after format.
*/
if (!CalendarUtils::isAllDay($this->start_datetime, $this->end_datetime)) {
$startDT = CalendarUtils::translateTimezone($this->start_datetime, CalendarUtils::getSystemTimeZone(), $this->time_zone, false);
$startDT->setTime(0, 0);
$endDT = CalendarUtils::translateTimezone($this->end_datetime, CalendarUtils::getSystemTimeZone(), $this->time_zone, false);
$endDT->modify('+1 day')->setTime(0, 0, 0);
$this->updateAttributes([
'start_datetime' => CalendarUtils::toDBDateFormat($startDT),
'end_datetime' => CalendarUtils::toDBDateFormat($endDT)
]);
} else if (!CalendarUtils::isAllDay($this->start_datetime, $this->end_datetime, true)) {
// Translate from 23:59 end dates to 00:00 end dates
$start = $this->getStartDateTime();
$end = $this->getEndDateTime()->modify('+1 hour');
CalendarUtils::ensureAllDay($start, $end);
$this->updateAttributes([
'start_datetime' => CalendarUtils::toDBDateFormat($start),
'end_datetime' => CalendarUtils::toDBDateFormat($end)
]);
}
parent::afterFind();
}
/**
* Validator for the endtime field.
* Execute this after DbDateValidator
*
* @param string $attribute attribute name
* @param array $params parameters
* @throws \Exception
*/
public function validateEndTime($attribute, $params)
{
if (new DateTime($this->start_datetime ?? 'now') >= new DateTime($this->end_datetime ?? 'now')) {
$this->addError($attribute, Yii::t('CalendarModule.base', "End time must be after start time!"));
}
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => Yii::t('CalendarModule.base', 'ID'),
'title' => Yii::t('CalendarModule.base', 'Title'),
'type_id' => Yii::t('CalendarModule.base', 'Event Type'),
'description' => Yii::t('CalendarModule.base', 'Description'),
'all_day' => Yii::t('CalendarModule.base', 'All Day'),
'allow_decline' => Yii::t('CalendarModule.base', 'Allow option \'Decline\''),
'allow_maybe' => Yii::t('CalendarModule.base', 'Allow option \'Undecided\''),
'participation_mode' => Yii::t('CalendarModule.base', 'Mode'),
'max_participants' => Yii::t('CalendarModule.base', 'Maximum number of participants'),
'location' => Yii::t('CalendarModule.base', 'Location'),
];
}
/**
* @inheritdoc
*/
public function getSearchAttributes()
{
return [
'title' => $this->title,
'description' => $this->description,
];
}
public function beforeSave($insert)
{
$start = new DateTime($this->start_datetime ?? 'now');
$end = new DateTime($this->end_datetime ?? 'now');
// Make sure end and start time is set right for all_day events
if ($this->all_day) {
CalendarUtils::ensureAllDay($start, $end);
$this->start_datetime = CalendarUtils::toDBDateFormat($start);
$this->end_datetime = CalendarUtils::toDBDateFormat($end);
}
if ($this->participation_mode === null) {
$this->participation->setDefautls();
}
if(RecurrenceHelper::isRecurrentRoot($this)) {
$this->streamChannel = null;
}
return parent::beforeSave($insert);
}
/**
* @return mixed
* @throws \Throwable
* @throws \yii\db\StaleObjectException
*/
public function beforeDelete()
{
$this->participation->deleteAll();
return parent::beforeDelete();
}
public function setExdate($exdateStr)
{
$this->exdate = $exdateStr;
}
public function getRecurrenceInstances()
{
return $this->hasMany(CalendarEntry::class, ['parent_event_id' => 'id']);
}
/**
* @throws \Throwable
* @throws \yii\base\InvalidConfigException
*/
public function toggleClosed()
{
$this->closed = $this->closed ? 0 : 1;
$this->saveEvent();
if ($this->closed) {
$this->participation->sendUpdateNotification(CanceledEvent::class);
} else {
$this->participation->sendUpdateNotification(ReopenedEvent::class);
}
}
/**
* Returns the related CalendarEntryType relation if given.
*
* @return CalendarEntryType
*/
public function getEventType()
{
$type = CalendarEntryType::findByContent($this->content)->one();
return $type ? $type : new CalendarEntryType();
}
/**
* Sets the clanedarentry type.
* @param $type
*/
public function setType($type)
{
if(empty($type)) {
$this->removeType();
return;
}
$type = ($type instanceof ContentTag) ? $type : ContentTag::findOne(['id' => $type]);
if ($type->is(CalendarEntryType::class)) {
$this->removeType();
$this->content->addTag($type);
}
}
/**
* Removes the calendar entry type.
* @param $type
*/
public function removeType()
{
CalendarEntryType::deleteContentRelations($this->content);
}
/**
* @return ActiveQueryUser
*/
public function getReminderUserQuery()
{
if ($this->content->container instanceof Space) {
switch ($this->participation_mode) {
case static::PARTICIPATION_MODE_NONE:
return Membership::getSpaceMembersQuery($this->content->container);
case static::PARTICIPATION_MODE_ALL:
case static::PARTICIPATION_MODE_INVITE:
return $this->participation->findParticipants([
CalendarEntryParticipant::PARTICIPATION_STATE_ACCEPTED,
CalendarEntryParticipant::PARTICIPATION_STATE_MAYBE]);
}
} elseif ($this->content->container instanceof User) {
switch ($this->participation_mode) {
case static::PARTICIPATION_MODE_NONE:
return User::find()->where(['id' => $this->content->container->id]);
case static::PARTICIPATION_MODE_INVITE:
case static::PARTICIPATION_MODE_ALL:
return $this->participation->findParticipants([
CalendarEntryParticipant::PARTICIPATION_STATE_ACCEPTED,
CalendarEntryParticipant::PARTICIPATION_STATE_MAYBE])
->union(User::find()->where(['id' => $this->content->container->id]));
}
}
// Fallback should only happen for global events, which are not supported
return User::find()->where(['id' => $this->content->createdBy->id]);
}
public function getUrl()
{
return Url::toEntry($this);
}
/**
* Checks if the event is currently running.
*/
public function isRunning()
{
return $this->formatter->isRunning();
}
/**
* @inheritdoc
*/
public function getTimezone()
{
return ($this->isAllDay()) ? 'UTC' : $this->time_zone;
}
/**
* @return DateTime|\DateTimeInterface
* @throws \Exception
*/
public function getStartDateTime()
{
return new DateTime($this->start_datetime ?? 'now', CalendarUtils::getSystemTimeZone());
}
/**
* @return DateTime|\DateTimeInterface
* @throws \Exception
*/
public function getEndDateTime()
{
return new DateTime($this->end_datetime ?? 'now', CalendarUtils::getSystemTimeZone());
}
/**
* @param string $format
* @return string
*/
public function getFormattedTime($format = 'long')
{
return $this->formatter->getFormattedTime($format);
}
/**
* @return boolean weather or not this item spans exactly over a whole day
*/
public function isAllDay()
{
if ($this->all_day === null) {
$this->all_day = 1;
}
return (boolean)$this->all_day;
}
/**
* Access url of the source content or other view
*
* @return string the timezone this item was originally saved, note this is
*/
public function getTitle()
{
return $this->title;
}
/**
* Returns a badge for the snippet
*
* @return string the timezone this item was originally saved, note this is
* @throws \Throwable
*/
public function getBadge()
{
if($this->closed) {
return Label::danger(Yii::t('CalendarModule.base', 'canceled'))->right();
}
if ($this->participation->isEnabled()) {
$status = $this->getParticipationStatus(Yii::$app->user->identity);
switch ($status) {
case CalendarEntryParticipant::PARTICIPATION_STATE_ACCEPTED:
return Label::success(Yii::t('CalendarModule.base', 'Attending'))->right();
case CalendarEntryParticipant::PARTICIPATION_STATE_MAYBE:
if ($this->allow_maybe) {
return Label::success(Yii::t('CalendarModule.base', 'Interested'))->right();
}
}
}
return null;
}
public function generateIcs()
{
$timezone = Yii::$app->settings->get('defaultTimeZone');
$ics = new ICS($this->title, $this->description, $this->start_datetime, $this->end_datetime, null, null, $timezone, $this->all_day);
return $ics;
}
public function afterMove(ContentContainerActiveRecord $container = null)
{
$this->participation->afterMove($container);
}
/**
* @return string
*/
public function getUid()
{
return $this->uid;
}
public function setUid($uid)
{
$this->uid = $uid;
}
/**
* Check if this calendar entry has a filled location attribute
*
* @return bool true if location is filled, otherwise false
*/
public function hasLocation()
{
return isset($this->location) && $this->location !== '';
}
/**
* Get location of this calendar entry
*
* @return string
*/
public function getLocation()
{
return $this->location;
}
public function getDescription()
{
return $this->description;
}
/**
* Check if this calendar entry is editable, for example by checking `$this->content->isEditable()`.
*
* @return bool true if this entry is editable, false
*/
public function isUpdatable()
{
return !RecurrenceHelper::isRecurrent($this) && $this->content->canEdit();
}
/**
* @return string hex color string e.g: '#44B5F6'
*/
public function getColor()
{
return $this->color;
}
/**
* @return mixed
*/
public function getEventStatus()
{
if ($this->closed) {
return CalendarEventStatusIF::STATUS_CANCELLED;
}
return CalendarEventStatusIF::STATUS_CONFIRMED;
}
/**
* @return Content
*/
public function getContentRecord()
{
return $this->content;
}
/**
* Additional configuration options
* @return array
*/
public function getCalendarOptions()
{
return [];
}
/**
* Access url of the source content or other view
*
* @return string the timezone this item was originally saved, note this is
*/
public function getCalendarViewUrl()
{
return Url::toEntry($this, 1);
}
/**
* @return string view mode 'modal', 'blank', 'redirect'
*/
public function getCalendarViewMode()
{
if(empty($this->getCalendarViewUrl())) {
return static::VIEW_MODE_REDIRECT;
}
return static::VIEW_MODE_MODAL;
}
/**
* Returns the participation state for a given user or guests if $user is null.
*
* @param User $user
* @return int
*/
public function getParticipationStatus(User $user = null)
{
return $this->participation->getParticipationStatus($user);
}
/**
* @inheritDoc
* @throws \Throwable
*/
public function setParticipationStatus(User $user, $status = self::PARTICIPATION_STATUS_ACCEPTED)
{
$this->participation->setParticipationStatus($user, $status);
}
/**
* @inheritDoc
*/
public function getExternalParticipants($status = [])
{
return $this->participation->getExternalParticipants($status);
}
/**
* @inheritDoc
*/
public function getParticipants($status = [])
{
return $this->participation->getParticipants($status);
}
/**
* @inheritDoc
*/
public function getParticipantCount($status = [])
{
return $this->participation->getParticipantCount($status);
}
/**
* CalendarEntryParticipant relation.
*
* Important: Do not remove, since its used in `via()` queries.
*/
public function getParticipantEntries()
{
return $this->participation->getParticipantEntries();
}
/**
* @param User|null $user
* @return mixed
* @throws \Throwable
*/
public function canRespond(User $user = null)
{
return $this->participation->canRespond($user);
}
public function getId()
{
return $this->id;
}
public function getRecurrenceId()
{
return $this->recurrence_id;
}
public function setRecurrenceId($recurrenceId)
{
$this->recurrence_id = $recurrenceId;
}
public function setRecurrenceRootId($rootId)
{
$this->parent_event_id = $rootId;
}
public function getRecurring(): bool
{
$entry = $this->recurrenceRoot ?? $this;
return !empty($entry->rrule);
}
public function isRecurringEnabled(): bool
{
return $this->recurring;
}
public function getRrule()
{
return $this->rrule;
}
public function setRrule($rrule)
{
$this->rrule = $rrule;
}
public function getReminder(): bool
{
if (!isset($this->_reminder)) {
$entry = $this->recurrenceRoot ?? $this;
if (empty($entry->content->id)) {
$this->_reminder = false;
} else {
$reminder = CalendarReminder::findOne(['content_id' => $entry->content->id]);
// Default or Custom reminder is selected for this Calendar Entry
$this->_reminder = empty($reminder) || !$reminder->isDisabled();
}
}
return $this->_reminder;
}
public function getExdate()
{
return $this->exdate;
}
public function getRecurrenceRootId()
{
return $this->parent_event_id;
}
public function getRecurrenceRoot(): ?self
{
if ($this->_recurrenceRoot === false) {
$this->_recurrenceRoot = empty($this->parent_event_id)
? null
: self::findOne(['id' => $this->parent_event_id]);
}
return $this->_recurrenceRoot;
}
/**
* @param $start
* @param $end
* @param $recurrenceId
* @return Content|mixed
* @throws \yii\base\Exception
*/
public function createRecurrence($start, $end)
{
$instance = new self($this->content->container, $this->content->visibility);
$instance->start_datetime = $start;
$instance->end_datetime = $end;
// Currently we do not support notifications and wall entries for recurring instances
$instance->silentContentCreation = true;
$instance->content->stream_channel = null;
return $instance;
}
/**
* @param static $root
* @param static $original
* @return mixed
*/
public function syncEventData($root, $original = null)
{
$this->content->created_by = $root->content->created_by;
$this->content->visibility = $root->content->visibility;
if (!$original || empty($this->description) || $original->description === $this->description) {
$this->description = $root->description;
}
if (!$original || empty($this->participant_info) || $original->participant_info === $this->participant_info) {
$this->participant_info = $root->participant_info;
}
$this->title = $root->title;
$this->color = $root->color;
$this->time_zone = $root->time_zone;
$this->participation_mode = $root->participation_mode;
$this->max_participants = $root->max_participants;
$this->all_day = $root->all_day;
$this->allow_decline = $root->allow_decline;
$this->allow_maybe = $root->allow_maybe;
$this->location = $root->location;
}
/**
* @return AbstractRecurrenceQuery
*/
public function getRecurrenceQuery()
{
return $this->query;
}
public function getSequence()
{
return $this->sequence;
}
public function setSequence($sequence)
{
$this->sequence = $sequence;
}
/**
* @param array $status
* @return ActiveQueryUser
*/
public function findParticipants($status = [])
{
return $this->participation->findParticipants($status);
}
/**
* @return User
*/
public function getOrganizer()
{
return $this->participation->getOrganizer();
}
/**
* @return DateTime|null
* @throws \Exception
*/
public function getLastModified()
{
if (is_string($this->content->updated_at)) {
return new DateTime($this->content->updated_at);
}
return null;
}
/**
* Adds additional options supported by fullcalendar: https://fullcalendar.io/docs/event-object
* @return array
*/
public function getFullCalendarOptions()
{
return [];
}
/**
* @return string
* @deprecated since 1.0 use CalendarUtils::generateUUid()
*/
public static function createUUid()
{
return CalendarUtils::generateUUid();
}
/**
* @inheritDoc
*/
public function updateTime(DateTime $start, DateTime $end)
{
$this->start_datetime = CalendarUtils::toDBDateFormat($start);
$this->end_datetime = CalendarUtils::toDBDateFormat($end);
return $this->save();
}
/**
* The timezone string of the end date.
* In case the start and end timezone is the same, this function can return null.
*
* @return string
*/
public function getEndTimezone()
{
return null;
}
/**
* Should update all data used by the event interface setter.
*
* @return bool|int
*/
public function saveEvent()
{
return $this->save();
}
/**
* @inheritDoc
*
*/
public function canMove(ContentContainerActiveRecord $container = null)
{
if(!$container) {
return true;
}
return $container->getPermissionManager($this->content->createdBy)->can(CreateEntry::class);
}
public function canInvite(?User $user = null): bool
{
return $this->isOwner($user);
}
public function isPast(): bool
{
$timeZone = CalendarUtils::getEndTimeZone($this);
return new DateTime('now', $timeZone) > new DateTime($this->end_datetime ?? 'now', $timeZone);
}
}