HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /home/timetracker.panomity.com/tm.old/Entity/Task.php
<?php

/*
 * This file is part of the TaskManagementBundle for Kimai 2.
 * All rights reserved by Kevin Papst (www.kevinpapst.de).
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace KimaiPlugin\TaskManagementBundle\Entity;

use App\Entity\Activity;
use App\Entity\Project;
use App\Entity\Tag;
use App\Entity\Team;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Export\Annotation as Exporter;
use DateTime;
use DateTimeZone;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as Serializer;
use Swagger\Annotations as SWG;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Table(name="kimai2_tasks",
 *     indexes={
 *          @ORM\Index(columns={"user_id", "status", "start_time", "end_time"}),
 *          @ORM\Index(columns={"team_id", "status", "start_time", "end_time"})
 *     }
 * )
 * @ORM\Entity(repositoryClass="KimaiPlugin\TaskManagementBundle\Repository\TaskRepository")
 * @Exporter\Order({"id", "title", "status", "end", "todo", "estimation", "user", "team", "customer", "project", "activity", "description"})
 * @Exporter\Expose("customer", label="label.customer", exp="object.getProject() === null ? null : object.getProject().getCustomer().getName()")
 * @Exporter\Expose("project", label="label.project", exp="object.getProject() === null ? null : object.getProject().getName()")
 * @Exporter\Expose("activity", label="label.activity", exp="object.getActivity() === null ? null : object.getActivity().getName()")
 * @Exporter\Expose("user", label="label.user", exp="object.getUser() === null ? null : object.getUser().getDisplayName()")
 * @Exporter\Expose("team", label="label.team", exp="object.getTeam() === null ? null : object.getTeam().getName()")
 *
 * @Serializer\ExclusionPolicy("all")
 * @Serializer\VirtualProperty(
 *      "TagsAsArray",
 *      exp="object.getTagsAsArray()",
 *      options={
 *          @Serializer\SerializedName("tags"),
 *          @Serializer\Type(name="array<string>"),
 *          @Serializer\Groups({"Default"})
 *      }
 * )
 */
class Task
{
    public const STATUS_PENDING = 'pending';
    public const STATUS_IN_PROGRESS = 'progress';
    public const STATUS_DONE = 'closed';

    /**
     * @var int|null
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Default"})
     *
     * @Exporter\Expose(label="label.id", type="integer")
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;
    /**
     * @var string
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Default"})
     *
     * @Exporter\Expose(label="label.title")
     *
     * @ORM\Column(name="title", type="string", length=100, nullable=false)
     * @Assert\NotBlank()
     * @Assert\Length(min=2, max=100)
     */
    private $title;
    /**
     * @var string
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Default"})
     *
     * @Exporter\Expose(label="task.status")
     *
     * @ORM\Column(name="status", type="string", length=20, nullable=false)
     * @Assert\NotBlank()
     * @Assert\Length(max=20)
     */
    private $status = self::STATUS_PENDING;
    /**
     * @var string|null
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Default"})
     *
     * @Exporter\Expose(label="label.task_todo")
     *
     * @ORM\Column(name="todo", type="text", nullable=true)
     */
    private $todo;
    /**
     * @var string|null
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Default"})
     *
     * @Exporter\Expose(label="label.description")
     *
     * @ORM\Column(name="description", type="text", nullable=true)
     */
    private $description;
    /**
     * @var Project
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Subresource", "Expanded"})
     * @SWG\Property(ref="#/definitions/ProjectExpanded")
     *
     * @ORM\ManyToOne(targetEntity="App\Entity\Project")
     * @ORM\JoinColumn(onDelete="CASCADE", nullable=false)
     * @Assert\NotNull()
     */
    private $project;
    /**
     * @var Activity
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Subresource", "Expanded"})
     * @SWG\Property(ref="#/definitions/ActivityExpanded")
     *
     * @ORM\ManyToOne(targetEntity="App\Entity\Activity")
     * @ORM\JoinColumn(onDelete="CASCADE", nullable=false)
     * @Assert\NotNull()
     */
    private $activity;
    /**
     * Assigned user that is working on that task.
     *
     * @var User
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Subresource", "Expanded"})
     * @SWG\Property(ref="#/definitions/User")
     *
     * @ORM\ManyToOne(targetEntity="App\Entity\User")
     * @ORM\JoinColumn(onDelete="CASCADE", nullable=true)
     *
     * @SWG\Property(ref="#/definitions/User")
     */
    private $user;
    /**
     * If set, only this team can see the task.
     *
     * @var Team
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Subresource", "Expanded"})
     * @SWG\Property(ref="#/definitions/Team")
     *
     * @ORM\ManyToOne(targetEntity="App\Entity\Team")
     * @ORM\JoinColumn(onDelete="CASCADE", nullable=true)
     */
    private $team;
    /**
     * @var DateTime
     *
     * @ORM\Column(name="start_time", type="datetime", nullable=true)
     */
    private $begin;
    /**
     * @var DateTime
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Default"})
     * @Serializer\Type(name="DateTime")
     * @Serializer\Accessor(getter="getEnd")
     *
     * Attention: Accessor MUST be used, otherwise date will be serialized in UTC.
     *
     * @Exporter\Expose(label="label.end", type="datetime")
     *
     * @ORM\Column(name="end_time", type="datetime", nullable=true)
     */
    private $end;
    /**
     * @var string
     *
     * @ORM\Column(name="timezone", type="string", length=64, nullable=true)
     */
    private $timezone;
    /**
     * @var bool
     */
    private $localized = false;
    /**
     * @var Collection<Tag>
     *
     * @ORM\ManyToMany(targetEntity="App\Entity\Tag", cascade={"persist"})
     * @ORM\JoinTable(
     *  name="kimai2_task_tags",
     *  joinColumns={
     *      @ORM\JoinColumn(name="task_id", referencedColumnName="id", onDelete="CASCADE")
     *  },
     *  inverseJoinColumns={
     *      @ORM\JoinColumn(name="tag_id", referencedColumnName="id", onDelete="CASCADE")
     *  }
     * )
     */
    private $tags;
    /**
     * @var Collection<Timesheet>
     *
     * @ORM\ManyToMany(targetEntity="App\Entity\Timesheet", cascade={"persist"})
     * @ORM\JoinTable(
     *  name="kimai2_task_timesheets",
     *  joinColumns={
     *      @ORM\JoinColumn(name="task_id", referencedColumnName="id", onDelete="CASCADE")
     *  },
     *  inverseJoinColumns={
     *      @ORM\JoinColumn(name="timesheet_id", referencedColumnName="id", onDelete="CASCADE", unique=true)
     *  }
     * )
     * @ORM\OrderBy({"begin" = "DESC","id" = "DESC"})
     */
    private $timesheets;
    /**
     * Estimated duration for the task in seconds or null if no estimation was added
     *
     * @var int|null
     *
     * @Serializer\Expose()
     * @Serializer\Groups({"Default"})
     *
     * @Exporter\Expose(label="label.estimation", type="integer")
     *
     * @ORM\Column(name="estimation", type="integer", nullable=true)
     * @Assert\GreaterThanOrEqual(0)
     */
    private $estimation;

    public function __construct()
    {
        $this->tags = new ArrayCollection();
        $this->timesheets = new ArrayCollection();
    }

    /**
     * Get entry id, returns null for new entities which were not persisted.
     *
     * @return int|null
     */
    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * Make sure begin and end date have the correct timezone.
     * This will be called once for each item after being loaded from the database.
     */
    protected function localizeDates(): void
    {
        if ($this->localized) {
            return;
        }

        if (null !== $this->begin) {
            $this->begin->setTimeZone(new DateTimeZone($this->timezone));
        }

        if (null !== $this->end) {
            $this->end->setTimeZone(new DateTimeZone($this->timezone));
        }

        $this->localized = true;
    }

    public function getBegin(): ?DateTime
    {
        $this->localizeDates();

        return $this->begin;
    }

    public function setBegin(?DateTime $begin): Task
    {
        $this->begin = $begin;

        if (null !== $begin) {
            $this->timezone = $begin->getTimezone()->getName();
        }

        return $this;
    }

    public function getEnd(): ?DateTime
    {
        $this->localizeDates();

        return $this->end;
    }

    public function setEnd(?DateTime $end): Task
    {
        $this->end = $end;

        if (null !== $end) {
            $this->timezone = $end->getTimezone()->getName();
        }

        return $this;
    }

    public function setUser(?User $user): Task
    {
        $this->user = $user;

        return $this;
    }

    public function getUser(): ?User
    {
        return $this->user;
    }

    public function setActivity(Activity $activity): Task
    {
        $this->activity = $activity;

        return $this;
    }

    public function getActivity(): ?Activity
    {
        return $this->activity;
    }

    public function setProject(Project $project): Task
    {
        $this->project = $project;

        return $this;
    }

    public function getProject(): ?Project
    {
        return $this->project;
    }

    public function setDescription(?string $description): Task
    {
        $this->description = $description;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function getTimezone(): string
    {
        return $this->timezone;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): Task
    {
        $this->title = $title;

        return $this;
    }

    public function getTeam(): ?Team
    {
        return $this->team;
    }

    public function setTeam(?Team $team): Task
    {
        $this->team = $team;

        return $this;
    }

    public function getStatus(): string
    {
        return $this->status;
    }

    public function isPending(): bool
    {
        return $this->status === self::STATUS_PENDING;
    }

    public function isInProgress(): bool
    {
        return $this->status === self::STATUS_IN_PROGRESS;
    }

    public function setStatus(string $status): Task
    {
        if (!\in_array($status, [self::STATUS_PENDING, self::STATUS_IN_PROGRESS, self::STATUS_DONE])) {
            throw new \InvalidArgumentException(sprintf('Unknown task status: %s', $status));
        }
        $this->status = $status;

        return $this;
    }

    public function getActiveRecord(?User $user): ?Timesheet
    {
        /** @var Timesheet $timesheet */
        foreach ($this->timesheets as $timesheet) {
            if (null === $timesheet->getEnd() && ($user === null || $timesheet->getUser()->getId() === $user->getId())) {
                return $timesheet;
            }
        }

        return null;
    }

    /**
     * @Serializer\VirtualProperty
     * @Serializer\Expose
     * @Serializer\SerializedName("activeTimesheets"),
     * @SWG\Property(type="array", @SWG\Items(ref="#/definitions/TimesheetEntityExpanded")),
     * @Serializer\Groups({"Subresource", "Expanded"})
     *
     * @return array
     */
    public function getActiveRecords(): array
    {
        $all = [];
        /** @var Timesheet $timesheet */
        foreach ($this->timesheets as $timesheet) {
            if (null === $timesheet->getEnd()) {
                $all[] = $timesheet;
            }
        }

        return $all;
    }

    public function hasActiveRecord(User $user): bool
    {
        /** @var Timesheet $timesheet */
        foreach ($this->timesheets as $timesheet) {
            if (null === $timesheet->getEnd() && $timesheet->getUser()->getId() === $user->getId()) {
                return true;
            }
        }

        return false;
    }

    public function addTimesheet(Timesheet $timesheet): Task
    {
        $this->timesheets->add($timesheet);

        return $this;
    }

    /**
     * @return Collection<Timesheet>
     */
    public function getTimesheets(): Collection
    {
        return $this->timesheets;
    }

    public function getDuration(): ?int
    {
        if ($this->timesheets->count() === 0) {
            return null;
        }

        $fullDuration = 0;

        /** @var Timesheet $timesheet */
        foreach ($this->timesheets as $timesheet) {
            $duration = $timesheet->getDuration();

            if (null === $timesheet->getEnd()) {
                $now = new DateTime('now', $timesheet->getBegin()->getTimezone());
                $duration = $now->getTimestamp() - $timesheet->getBegin()->getTimestamp();
            }

            $fullDuration += $duration;
        }

        return $fullDuration;
    }

    public function isOverdue(): bool
    {
        if ($this->end !== null) {
            $now = new DateTime('now', $this->end->getTimezone());
            if ($now->getTimestamp() > $this->end->getTimestamp()) {
                return true;
            }
        }

        if ($this->estimation !== null) {
            $duration = $this->getDuration();
            if ($duration === null) {
                return false;
            }
            if ($duration > $this->estimation) {
                return true;
            }
        }

        return false;
    }

    public function isClosed(): bool
    {
        return $this->status === self::STATUS_DONE;
    }

    public function addTag(Tag $tag): Task
    {
        if ($this->tags->contains($tag)) {
            return $this;
        }
        $this->tags->add($tag);

        return $this;
    }

    public function removeTag(Tag $tag): void
    {
        if (!$this->tags->contains($tag)) {
            return;
        }
        $this->tags->removeElement($tag);
    }

    /**
     * @return Collection<Tag>
     */
    public function getTags(): Collection
    {
        return $this->tags;
    }

    /**
     * @return string[]
     */
    public function getTagsAsArray(): array
    {
        return array_map(
            function (Tag $element) {
                return $element->getName();
            },
            $this->getTags()->toArray()
        );
    }

    public function getEstimation(): ?int
    {
        return $this->estimation;
    }

    public function setEstimation(?int $estimation): Task
    {
        $this->estimation = $estimation;

        return $this;
    }

    public function getTodo(): ?string
    {
        return $this->todo;
    }

    public function setTodo(?string $todo): void
    {
        $this->todo = $todo;
    }

    public function __clone()
    {
        if ($this->id) {
            $this->id = null;
        }

        $this->timesheets = new ArrayCollection();

        $previousTags = $this->tags;
        $this->tags = new ArrayCollection();
        foreach ($previousTags as $tag) {
            $this->addTag($tag);
        }
    }
}