<?php
namespace MedBrief\MSR\Entity;
use DH\Auditor\Provider\Doctrine\Auditing\Annotation as Audit;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use MedBrief\MSR\Repository\CollectionRepository;
/**
* Collection
*
* @ORM\Table(name="collection", indexes={@ORM\Index(name="slug_index", columns={"slug"}), @ORM\Index(name="root_index", columns={"root"})})
*
* @ORM\Entity(repositoryClass=CollectionRepository::class)
*
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
*
* @Gedmo\Tree(type="nested")
*
* @Audit\Auditable
*
* @Audit\Security(view={"ROLE_ALLOWED_TO_AUDIT"})
*/
class Collection
{
public const DISPLAY_NAME_MEDICAL_RECORDS = 'Records';
public const DISPLAY_NAME_PRIVATE = 'Case Documentation';
public const DISPLAY_NAME_PRIVATE_SANDBOX = 'Workspaces'; // Renamed from private folder to workspaces MSR-5393
public const DISPLAY_NAME_UNSORTED_RECORDS = 'Unsorted Records';
public const DISPLAY_NAME_MEDBRIEF_INTERNAL = '# MedBrief';
/**
* @var int
*
* @ORM\Column(name="id", type="bigint", nullable=false)
*
* @ORM\Id
*
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255, nullable=false)
*/
private $name;
/**
* @var int
*
* @ORM\Column(name="lft", type="integer")
*
* @Gedmo\TreeLeft
*/
private $lft;
/**
* @var int
*
* @ORM\Column(name="rgt", type="integer")
*
* @Gedmo\TreeRight
*/
private $rgt;
/**
* @var int|null
*
* @ORM\Column(name="root", type="integer", nullable=true)
*
* @Gedmo\TreeRoot
*/
private $root;
/**
* @var int
*
* @ORM\Column(name="lvl", type="integer")
*
* @Gedmo\TreeLevel
*/
private $lvl;
/**
* @var string
*
* @ORM\Column(name="slug", type="string")
*
* @Gedmo\Slug(fields={"name"}, unique=false, updatable=false, separator="_")
*/
private $slug;
/**
* @var \DateTime
*
* @ORM\Column(name="created", type="datetime")
*
* @Gedmo\Timestampable(on="create")
*/
private $created;
/**
* @var \DateTime
*
* @ORM\Column(name="updated", type="datetime")
*
* @Gedmo\Timestampable(on="update")
*/
private $updated;
/**
* @var \DateTime|null
*
* @ORM\Column(name="deletedAt", type="datetime", nullable=true)
*/
private $deletedAt;
/**
* @var Project
*
* @ORM\OneToOne(targetEntity="MedBrief\MSR\Entity\Project", mappedBy="medicalRecordsCollection")
*/
private $projectMedicalRecords;
/**
* @var Project
*
* @ORM\OneToOne(targetEntity="MedBrief\MSR\Entity\Project", mappedBy="unsortedRecordsCollection")
*/
private $projectUnsortedRecords;
/**
* @var Project
*
* @ORM\OneToOne(targetEntity="MedBrief\MSR\Entity\Project", mappedBy="privateCollection")
*/
private $projectPrivate;
/**
* @var Project
*
* @ORM\OneToOne(targetEntity="MedBrief\MSR\Entity\Project", mappedBy="privateSandboxCollection")
*/
private $projectPrivateSandbox;
/**
* @var Disc
*
* @ORM\OneToOne(targetEntity="MedBrief\MSR\Entity\Disc", mappedBy="collection")
*/
private $disc;
/**
* @var ProjectUser
*
* @ORM\OneToOne(targetEntity="MedBrief\MSR\Entity\ProjectUser", mappedBy="privateCollection", cascade={"persist","detach","merge","refresh"})
*/
private $userPrivateCollection;
/**
* @var ProjectUser
*
* @ORM\OneToOne(targetEntity="MedBrief\MSR\Entity\ProjectUser", mappedBy="privateSandboxCollection", cascade={"persist","detach","merge","refresh"})
*/
private $userPrivateSandboxCollection;
/**
* @var \Doctrine\Common\Collections\Collection
*
* @ORM\OneToMany(targetEntity="MedBrief\MSR\Entity\Collection", mappedBy="parent")
*
* @ORM\OrderBy({
* "name"="ASC"
* })
*/
private $children;
/**
* @var Collection
*
* @ORM\ManyToOne(targetEntity="MedBrief\MSR\Entity\Collection", inversedBy="children")
*
* @ORM\JoinColumns({
*
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
* })
*
* @Gedmo\TreeParent
*/
private $parent;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="MedBrief\MSR\Entity\User")
*
* @ORM\JoinColumns({
*
* @ORM\JoinColumn(name="creator_id", referencedColumnName="id")
* })
*/
private $creator;
/**
* @var \Doctrine\Common\Collections\Collection
*
* @ORM\ManyToMany(targetEntity="MedBrief\MSR\Entity\Document", inversedBy="collections")
*
* @ORM\JoinTable(name="collection_document",
* joinColumns={
*
* @ORM\JoinColumn(name="collection_id", referencedColumnName="id", onDelete="CASCADE")
* },
* inverseJoinColumns={
* @ORM\JoinColumn(name="document_id", referencedColumnName="id", onDelete="CASCADE")
* }
* )
*
* @ORM\OrderBy({
* "filename"="ASC"
* })
*/
private $documents;
/**
* Constructor
*/
public function __construct()
{
$this->children = new \Doctrine\Common\Collections\ArrayCollection();
$this->documents = new \Doctrine\Common\Collections\ArrayCollection();
}
public function __toString()
{
return $this->getName();
}
/**
* Get id
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
*
* @return Collection
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Get exportFriendlyName
*
* @return string
*/
public function getExportFriendly()
{
return str_replace('/', '-', $this->name);
}
/**
* Set lft
*
* @param int $lft
*
* @return Collection
*/
public function setLft($lft)
{
$this->lft = $lft;
return $this;
}
/**
* Get lft
*
* @return int
*/
public function getLft()
{
return $this->lft;
}
/**
* Set rgt
*
* @param int $rgt
*
* @return Collection
*/
public function setRgt($rgt)
{
$this->rgt = $rgt;
return $this;
}
/**
* Get rgt
*
* @return int
*/
public function getRgt()
{
return $this->rgt;
}
/**
* Set root
*
* @param int $root
*
* @return Collection
*/
public function setRoot($root)
{
$this->root = $root;
return $this;
}
/**
* Get root
*
* @return int
*/
public function getRoot()
{
return $this->root;
}
/**
* Set lvl
*
* @param int $lvl
*
* @return Collection
*/
public function setLvl($lvl)
{
$this->lvl = $lvl;
return $this;
}
/**
* Get lvl
*
* @return int
*/
public function getLvl()
{
return $this->lvl;
}
/**
* Set slug
*
* @param string $slug
*
* @return Collection
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* Get slug
*
* @return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* Set created
*
* @param \DateTime $created
*
* @return Collection
*/
public function setCreated($created)
{
$this->created = $created;
return $this;
}
/**
* Get created
*
* @return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* Set updated
*
* @param \DateTime $updated
*
* @return Collection
*/
public function setUpdated($updated)
{
$this->updated = $updated;
return $this;
}
/**
* Get updated
*
* @return \DateTime
*/
public function getUpdated()
{
return $this->updated;
}
/**
* Add children
*
* @param Collection $children
*
* @return Collection
*/
public function addChild(Collection $children)
{
$this->children[] = $children;
return $this;
}
/**
* Remove children
*
* @param Collection $children
*/
public function removeChild(Collection $children)
{
$this->children->removeElement($children);
}
/**
* Get children
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getChildren()
{
return $this->children;
}
/**
* Set parent
*
* @param Collection $parent
*
* @return Collection
*/
public function setParent(?Collection $parent = null)
{
$this->parent = $parent;
return $this;
}
/**
* Get parent
*
* @return Collection
*/
public function getParent()
{
return $this->parent;
}
/**
* Returns this collection as a jsTree formatted Array - as per
* http://www.jstree.com/docs/json/
* The results of this function should be json_encoded
*
* @param string $replacementRootNodeText If this is set, the text of the root node
* will be replaced with this text
* @param bool $includeChildren
* @param bool $includeDocumentChildren
* @param array $additionalLiAttributes
*
* @return array
*/
public function getJsTreeFormattedTree($replacementRootNodeText = null, $includeChildren = false, $includeDocumentChildren = false, $additionalLiAttributes = [])
{
return [$this->getJsTreeFormattedNode($replacementRootNodeText, $includeChildren, $includeDocumentChildren, $additionalLiAttributes)];
}
/**
* Returns this collection as a jsTree formatted node
*
* @param string $replacementNodeText
* @param bool $includeChildren
* @param bool $includeDocumentChildren
* @param array $additionalLiAttributes
*
* @return array
*/
public function getJsTreeFormattedNode($replacementNodeText = null, $includeChildren = false, $includeDocumentChildren = false, $additionalLiAttributes = [])
{
$node = [
'text' => $replacementNodeText ?: $this->getName(),
'type' => 'folder', // we give the node a type because we will be distinguishing between node types on the JS side
// Not passing through an icon will make jstree use it's own
'icon' => 'mb-icon mb-icon--folder',
'li_attr' => [
'data-collection-id' => $this->getId(),
'data-controller' => 'fullrow-foldertree',
'data-creator-id' => $this->getCreator() ? $this->getCreator()->getId() : '',
'title' => $replacementNodeText ?: $this->getName(),
],
];
// any additional attributes that have been passed through should be assigned to the node
foreach ($additionalLiAttributes as $key => $value) {
$node['li_attr'][$key] = $value;
}
// only include the children of this node if we want to
if ($includeChildren) {
$node['children'] = $this->getChildrenAsJsTreeFormattedNodes($includeChildren, $includeDocumentChildren, $additionalLiAttributes);
}
return $node;
}
/**
* Returns this collection as a ContextMenu formatted Tree so we can include it as a submenu structure in a context
* menu
*
* @param string $keyPrefix
* @param null|mixed $replacementNodeText
*
* @return array
*/
public function getContextMenuFormattedTree($keyPrefix, $replacementNodeText = null)
{
return ['sub-' . $this->getId() => $this->getContextMenuFormattedNode($keyPrefix, $replacementNodeText)];
}
/**
* Returns all this Collection's Children in ContextMenu items format
*
* @param bool $includeDocumentChildren
* @param bool $includeChildren
* @param array $additionalLiAttributes
*
* @return array
*/
public function getChildrenAsJsTreeFormattedNodes($includeChildren = false, $includeDocumentChildren = false, $additionalLiAttributes = [])
{
// order the children alphabetically
$criteria = Criteria::create()
->orderBy(['name' => Criteria::ASC])
;
$children = null;
foreach ($this->getChildren()->matching($criteria) as $childCollection) {
$children[] = $childCollection->getJsTreeFormattedNode(null, $includeChildren, $includeDocumentChildren, $additionalLiAttributes);
}
// if we want to include the document children
if ($includeDocumentChildren) {
// all document children also get a collection id sent through, so we know what collection
// the document belongs to
$additionalLiAttributes['data-collection-id'] = $this->getId();
foreach ($this->getDocuments() as $document) {
$children[] = $document->getJsTreeFormattedNode(null, $additionalLiAttributes);
}
}
return $children;
}
/**
* Returns this node formatted as a Context Menu item.
*
* @param string $keyPrefix
* @param null|mixed $replacementNodeText
*
* @return array
*/
public function getContextMenuFormattedNode($keyPrefix, $replacementNodeText = null)
{
// create an array which represents the node in the format required by jQueryContextMenu
$node = [
'name' => $replacementNodeText ?: $this->getName(),
// we always have one sub item which with be an clickable endpoint with the name being the key prefix and the word 'Here'
// this is to facilitate the 'Copy Here' and 'Move Here' functionality that this all exists for
'items' => [$keyPrefix . '-' . $this->getId() => ['name' => ucwords($keyPrefix) . ' Here']],
];
//if we have some children
if (!$this->getChildren()->isEmpty()) {
// add a separator @todo it might become a problem with these separators all having the same key? 'sep'
$node['items']['sep'] = '------';
// add all the children
$node['items'] = array_merge($node['items'], $this->getChildrenAsContextMenuFormattedNodes($keyPrefix));
}
return $node;
}
/**
* Returns all this Collection's Children in jsTreeNode format
*
* @param mixed $keyPrefix
*
* @return array
*/
public function getChildrenAsContextMenuFormattedNodes($keyPrefix)
{
// order the children alphabetically
$criteria = Criteria::create()
->orderBy(['name' => Criteria::ASC])
;
$children = null;
foreach ($this->getChildren()->matching($criteria) as $childCollection) {
$children['sub-' . $childCollection->getId()] = $childCollection->getContextMenuFormattedNode($keyPrefix);
}
return $children;
}
/**
* Recursive function to return all the documents in a collection in the
* vertical order that they appear in the folder tree.
* We start with an empty array then add to it as we recurse.
*
* @param array $documents
*
* @return array
*/
public function getVerticallyOrderedDocumentList($documents = [])
{
$children = $this->getChildren();
// process children first, then get the documents for the current node.
foreach ($children as $child) {
$documents = $child->getVerticallyOrderedDocumentList($documents);
}
foreach ($this->getDocuments() as $doc) {
// add all the documents attached to the collection if the collection
// is not a root collection.
// if it is a root collection only add documents that are ONLY in
// the root collection. If this is not done we will have duplicates
if ($this->getParent() === null && (is_countable($doc->getCollections()) ? count($doc->getCollections()) : 0) == 1) {
$documents[] = [
'collectionId' => $this->getId(),
'document' => $doc,
];
} elseif ($this->getParent() !== null) {
$documents[] = [
'collectionId' => $this->getId(),
'document' => $doc,
];
}
}
return $documents;
}
/**
* Add documents
*
* @param Document $documents
*
* @return Collection
*/
public function addDocument(Document $documents)
{
$this->documents[] = $documents;
return $this;
}
/**
* Remove documents
*
* @param Document $documents
*/
public function removeDocument(Document $documents)
{
$this->documents->removeElement($documents);
}
/**
* Get documents
*
* @return \Doctrine\Common\Collections\Collection|Document[]
*/
public function getDocuments()
{
return $this->documents;
}
/**
* Get project
*
* @throws \Exception
*
* @return Project
*/
public function getProject()
{
// if this Collection has a Medical Records project, then it is a Medical Records collection
$project = $this->getProjectMedicalRecords();
if (!$project) {
$project = $this->getProjectUnsortedRecords();
}
// else we have to assume it is a private collection
if (!$project) {
$project = $this->getProjectPrivate();
}
// Else, it must be a private sandbox collection
if (!$project) {
$project = $this->getProjectPrivateSandbox();
}
return $project;
}
/**
* Set deletedAt
*
* @param \DateTime $deletedAt
*
* @return Collection
*/
public function setDeletedAt($deletedAt)
{
$this->deletedAt = $deletedAt;
return $this;
}
/**
* Get deletedAt
*
* @return \DateTime
*/
public function getDeletedAt()
{
return $this->deletedAt;
}
/**
* Set disc
*
* @param Disc $disc
*
* @return Collection
*/
public function setDisc(?Disc $disc = null)
{
$this->disc = $disc;
return $this;
}
/**
* Get disc
*
* @return Disc
*/
public function getDisc()
{
return $this->disc;
}
/**
* Set userPrivateCollection
*
* @param User $userPrivateCollection
*
* @return Collection
*/
public function setUserPrivateCollection(?User $userPrivateCollection = null)
{
$this->userPrivateCollection = $userPrivateCollection;
return $this;
}
/**
* Get userPrivateCollection
*
* @return ProjectUser
*/
public function getUserPrivateCollection()
{
return $this->userPrivateCollection;
}
/**
* Set projectMedicalRecords
*
* @param Project $projectMedicalRecords
*
* @return Collection
*/
public function setProjectMedicalRecords(?Project $projectMedicalRecords = null)
{
$this->projectMedicalRecords = $projectMedicalRecords;
return $this;
}
/**
* Get projectMedicalRecords
*
* @return Project
*/
public function getProjectMedicalRecords()
{
return $this->projectMedicalRecords;
}
/**
* Set projectPrivate
*
* @param Project $projectPrivate
*
* @return Collection
*/
public function setProjectPrivate(?Project $projectPrivate = null)
{
$this->projectPrivate = $projectPrivate;
return $this;
}
/**
* Get projectPrivate
*
* @return Project
*/
public function getProjectPrivate()
{
return $this->projectPrivate;
}
/**
* Checks to see if this collection recursively contains the given collection
* or is the given collection itself
*
* @param Collection $collection
*
* @return bool
*/
public function containsCollectionRecursive(Collection $collection)
{
// if this collection is the gien one
if ($this->getId() == $collection->getId()) {
// then return true
return true;
}
// else if the given collection is one of this collection's children
if ($this->getChildren()->contains($collection)) {
// then return true
return true;
}
// otherwise go through each of this Collection's children
foreach ($this->getChildren() as $childCollection) {
// and check if they contain the given collection recursively
if ($childCollection->containsCollectionRecursive($collection)) {
return true;
}
}
// if we get here then the given collection does not reside anywhere within
// this collection
return false;
}
/**
* Checks to see if this collection or any of its children contain this document.
*
* @param Document $document
*
* @return bool
*/
public function containsDocumentRecursive(Document $document)
{
// if this collection contains this document
if ($this->getDocuments()->contains($document)) {
return true;
// else loop through each child and return true if any one of them contain it
}
foreach ($this->getChildren() as $child) {
if ($child->containsDocumentRecursive($document)) {
return true;
}
}
// if we get here then this document is not contained in this collection
// or any of its children
return false;
}
/**
* Checks if this collection, or any of it's children, contains documents.
*
* @return bool
*/
public function isEmpty(): bool
{
// If this collection contains at least 1 document
if ($this->getDocuments()->count() > 0) {
// It is not empty
return false;
}
// Loop through each child and return false if any one of them contains a document
foreach ($this->getChildren() as $child) {
if (!$child->isEmpty()) {
return false;
}
}
// If we get here and we still haven't found any documents, the collection and all its children are empty.
return true;
}
/**
* Set projectPrivateSandbox
*
* @param Project $projectPrivateSandbox
*
* @return Collection
*/
public function setProjectPrivateSandbox(?Project $projectPrivateSandbox = null)
{
$this->projectPrivateSandbox = $projectPrivateSandbox;
return $this;
}
/**
* Get projectPrivateSandbox
*
* @return Project
*/
public function getProjectPrivateSandbox()
{
return $this->projectPrivateSandbox;
}
/**
* Set creator
*
* @param User $creator
*
* @return Collection
*/
public function setCreator(User $creator)
{
$this->creator = $creator;
return $this;
}
/**
* Get creator
*
* @return User
*/
public function getCreator()
{
return $this->creator;
}
/**
* Set userPrivateSandboxCollection
*
* @param ProjectUser $userPrivateSandboxCollection
*
* @return Collection
*/
public function setUserPrivateSandboxCollection(?ProjectUser $userPrivateSandboxCollection = null)
{
$this->userPrivateSandboxCollection = $userPrivateSandboxCollection;
return $this;
}
/**
* Get userPrivateSandboxCollection
*
* @return ProjectUser
*/
public function getUserPrivateSandboxCollection()
{
return $this->userPrivateSandboxCollection;
}
/**
* Set projectUnsortedRecords.
*
* @param Project|null $projectUnsortedRecords
*
* @return Collection
*/
public function setProjectUnsortedRecords(?Project $projectUnsortedRecords = null)
{
$this->projectUnsortedRecords = $projectUnsortedRecords;
return $this;
}
/**
* Get projectUnsortedRecords.
*
* @return Project|null
*/
public function getProjectUnsortedRecords()
{
return $this->projectUnsortedRecords;
}
/**
* Returns the display name for the collection.
*
* @return string
*/
public function getDisplayName(): string
{
if ($this->getProject()) {
switch (true) {
case $this->getProject()->getMedicalRecordsCollection() === $this:
return self::DISPLAY_NAME_MEDICAL_RECORDS;
case $this->getProject()->getPrivateCollection() === $this:
return self::DISPLAY_NAME_PRIVATE;
case $this->getProject()->getPrivateSandboxCollection() === $this:
return self::DISPLAY_NAME_PRIVATE_SANDBOX;
case $this->getProject()->getUnsortedRecordsCollection() === $this:
return self::DISPLAY_NAME_UNSORTED_RECORDS;
}
}
return $this->getName();
}
}