diff --git a/app/Resources/public/css/base.css b/app/Resources/public/css/base.css
index 95c7a13113..703a5b46d6 100644
--- a/app/Resources/public/css/base.css
+++ b/app/Resources/public/css/base.css
@@ -9485,6 +9485,25 @@ ul.dropdown-menu.inner > li > a {
border-color: red;
grid-column: span 2;
}
+
+.portfolio-items.row .thumbnail .caption .h3,
+.portfolio-item.thumbnail .caption .h3 {
+ margin-top: 0;
+}
+
+.portfolio-items .thumbnail .portfolio-actions a > img,
+.portfolio-item.thumbnail .portfolio-actions a > img {
+ display: inline;
+}
+
+.fa-ul.list-inline {
+ margin-left: -15px;
+}
+.fa-ul.list-inline li {
+ margin-left: 45px;
+ padding: 0;
+}
+
/* CSS Responsive */
@media (min-width: 1025px) and (max-width: 1200px) {
.sidebar-scorm {
@@ -10186,6 +10205,7 @@ ul.dropdown-menu.inner > li > a {
clear: none;
}
+ .portfolio-items.row .col-md-6:nth-child(2n+1),
.col-md-3.course-tool:nth-child(4n+1) {
clear: left;
}
diff --git a/main/inc/lib/PortfolioController.php b/main/inc/lib/PortfolioController.php
index f1da18296d..b7f5f11275 100644
--- a/main/inc/lib/PortfolioController.php
+++ b/main/inc/lib/PortfolioController.php
@@ -4,6 +4,7 @@
use Chamilo\CoreBundle\Entity\Portfolio;
use Chamilo\CoreBundle\Entity\PortfolioCategory;
+use Chamilo\CoreBundle\Entity\PortfolioComment;
/**
* Class PortfolioController.
@@ -126,15 +127,19 @@ class PortfolioController
* @param string $content
* @param string $toolName
* @param array $actions
+ * @param bool $showHeader
*/
- private function renderView(string $content, string $toolName, array $actions = [])
+ private function renderView(string $content, string $toolName, array $actions = [], $showHeader = true)
{
global $this_section;
$this_section = $this->course ? SECTION_COURSES : SECTION_SOCIAL;
$view = new Template($toolName);
- $view->assign('header', $toolName);
+
+ if ($showHeader) {
+ $view->assign('header', $toolName);
+ }
$actionsStr = '';
@@ -612,4 +617,145 @@ class PortfolioController
$this->renderView($content, get_lang('Portfolio'), $actions);
}
+
+ /**
+ * @param \Chamilo\CoreBundle\Entity\Portfolio $item
+ *
+ * @throws \Doctrine\ORM\ORMException
+ * @throws \Doctrine\ORM\OptimisticLockException
+ */
+ public function view(Portfolio $item)
+ {
+ global $interbreadcrumb;
+
+ $form = $this->createCommentForm($item);
+
+ $commentsRepo = $this->em->getRepository(PortfolioComment::class);
+
+ $query = $commentsRepo->createQueryBuilder('comment')
+ ->where('comment.item = :item')
+ ->orderBy('comment.root, comment.lft', 'ASC')
+ ->setParameter('item', $item)
+ ->getQuery();
+
+ $clockIcon = Display::returnFontAwesomeIcon('clock-o', '', true);
+
+ $commentsHtml = $commentsRepo->buildTree(
+ $query->getArrayResult(),
+ [
+ 'decorate' => true,
+ 'rootOpen' => '
',
+ 'childOpen' => function ($node) use ($commentsRepo) {
+ /** @var PortfolioComment $comment */
+ $comment = $commentsRepo->find($node['id']);
+ $author = $comment->getAuthor();
+
+ $userPicture = UserManager::getUserPicture(
+ $comment->getAuthor()->getId(),
+ USER_IMAGE_SIZE_SMALL,
+ null,
+ [
+ 'picture_uri' => $author->getPictureUri(),
+ 'email' => $author->getEmail(),
+ ]
+ );
+
+ return '
+
+ ';
+ },
+ 'childClose' => '
',
+ 'nodeDecorator' => function ($node) use ($commentsRepo, $clockIcon) {
+ /** @var PortfolioComment $comment */
+ $comment = $commentsRepo->find($node['id']);
+
+ $commentActions = Display::toolbarButton(
+ get_lang('ReplyToThisComment'),
+ '#',
+ 'reply',
+ 'default',
+ [
+ 'data-comment' => htmlspecialchars(
+ json_encode(['id' => $comment->getId()])
+ ),
+ 'role' => 'button',
+ 'class' => 'btn-reply-to'
+ ],
+ false
+ );
+
+ return ''.$comment->getAuthor()->getCompleteName().PHP_EOL.''
+ .$clockIcon.PHP_EOL.Display::dateToStringAgoAndLongDate($comment->getDate()).''
+ .'
'.$commentActions.'
'.$comment->getContent().PHP_EOL;
+ },
+ ]
+ );
+
+ $template = new Template(null, false, false, false, false, false, false);
+ $template->assign('item', $item);
+ $template->assign('comments', $commentsHtml);
+ $template->assign('form', $form);
+
+ $layout = $template->get_template('portfolio/view.html.twig');
+ $content = $template->fetch($layout);
+
+ $interbreadcrumb[] = ['name' => get_lang('Portfolio'), 'url' => $this->baseUrl];
+
+ $actions = [];
+ $actions[] = Display::url(
+ Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
+ $this->baseUrl
+ );
+
+ $this->renderView($content, $item->getTitle(), $actions, false);
+ }
+
+ /**
+ * @param \Chamilo\CoreBundle\Entity\Portfolio $item
+ *
+ * @throws \Doctrine\ORM\ORMException
+ * @throws \Doctrine\ORM\OptimisticLockException
+ *
+ * @return string
+ */
+ private function createCommentForm(Portfolio $item): string
+ {
+ $formAction = $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]);
+
+ $form = new FormValidator('frm_comment', 'post', $formAction);
+ $form->addHtmlEditor('content', get_lang('Comments'), true, false, ['ToolbarSet' => 'Minimal']);
+ $form->addHidden('item', $item->getId());
+ $form->addHidden('parent', 0);
+ $form->applyFilter('content', 'trim');
+ $form->addButtonSave(get_lang('Save'));
+
+ if ($form->validate()) {
+ $values = $form->exportValues();
+
+ $parentComment = $this->em->find(PortfolioComment::class, $values['parent']);
+
+ $comment = new PortfolioComment();
+ $comment
+ ->setAuthor($this->owner)
+ ->setParent($parentComment)
+ ->setContent($values['content'])
+ ->setDate(api_get_utc_datetime(null, false, true))
+ ->setItem($item);
+
+ $this->em->persist($comment);
+ $this->em->flush();
+
+ Display::addFlash(
+ Display::return_message(get_lang('CommentAdded'), 'success')
+ );
+
+ header("Location: $formAction");
+ exit;
+ }
+
+ return $form->returnForm();
+ }
}
diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php
index edbcc22f5f..14f626708c 100755
--- a/main/install/configuration.dist.php
+++ b/main/install/configuration.dist.php
@@ -920,6 +920,11 @@ ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED1062613FECDF FOREIGN KEY (session_id
ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED106212469DE2 FOREIGN KEY (category_id) REFERENCES portfolio_category (id) ON DELETE SET NULL;
ALTER TABLE portfolio_category ADD CONSTRAINT FK_7AC64359A76ED395 FOREIGN KEY (user_id) REFERENCES user (id);
INSERT INTO settings_current(variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url_changeable) VALUES('course_create_active_tools','portfolio','checkbox','Tools','true','CourseCreateActiveToolsTitle','CourseCreateActiveToolsComment',NULL,'Portfolio', 0);
+CREATE TABLE portfolio_comment (id INT AUTO_INCREMENT NOT NULL, author_id INT NOT NULL, item_id INT NOT NULL, tree_root INT DEFAULT NULL, parent_id INT DEFAULT NULL, content LONGTEXT NOT NULL, date DATETIME NOT NULL, lft INT NOT NULL, lvl INT NOT NULL, rgt INT NOT NULL, INDEX IDX_C2C17DA2F675F31B (author_id), INDEX IDX_C2C17DA2126F525E (item_id), INDEX IDX_C2C17DA2A977936C (tree_root), INDEX IDX_C2C17DA2727ACA70 (parent_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB;
+ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2F675F31B FOREIGN KEY (author_id) REFERENCES user (id);
+ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2126F525E FOREIGN KEY (item_id) REFERENCES portfolio (id);
+ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2A977936C FOREIGN KEY (tree_root) REFERENCES portfolio_comment (id) ON DELETE CASCADE;
+ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2727ACA70 FOREIGN KEY (parent_id) REFERENCES portfolio_comment (id) ON DELETE CASCADE;
*/
// In 1.11.8, before enabling this feature, you also need to:
// - edit src/Chamilo/CoreBundle/Entity/Portfolio.php and PortfolioCategory.php
diff --git a/main/portfolio/index.php b/main/portfolio/index.php
index 616c6eb9f4..ed8f892aad 100755
--- a/main/portfolio/index.php
+++ b/main/portfolio/index.php
@@ -104,6 +104,18 @@ switch ($action) {
$controller->deleteItem($item);
return;
+ case 'view':
+ $id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
+
+ /** @var Portfolio $item */
+ $item = $em->find('ChamiloCoreBundle:Portfolio', $id);
+
+ if (empty($item)) {
+ break;
+ }
+
+ $controller->view($item);
+ return;
case 'list':
default:
$controller->index();
diff --git a/main/template/default/portfolio/items.html.twig b/main/template/default/portfolio/items.html.twig
index 208e3de33d..ca88205942 100644
--- a/main/template/default/portfolio/items.html.twig
+++ b/main/template/default/portfolio/items.html.twig
@@ -5,12 +5,34 @@
{% set delete_img = 'delete.png'|img(22, 'Delete'|get_lang) %}
{% set baseurl = _p.web_self ~ '?' ~ (_p.web_cid_query ? _p.web_cid_query ~ '&' : '') %}
-
+
{% for item in items %}
-
+
diff --git a/main/template/default/portfolio/view.html.twig b/main/template/default/portfolio/view.html.twig
new file mode 100644
index 0000000000..b26c49e67e
--- /dev/null
+++ b/main/template/default/portfolio/view.html.twig
@@ -0,0 +1,103 @@
+
+
+ {% if _u.id == item.user.id %}
+
+ {% endif %}
+
+
{{ item.title }}
+
+
+ {% if _c is empty %}
+ {% if item.session %}
+ -
+
+ {{ 'Course'|get_lang ~ ': ' ~ item.session.name ~ ' (' ~ item.course.title ~ ')' }}
+
+ {% elseif not item.session and item.course %}
+ -
+
+ {{ 'Course'|get_lang ~ ': ' ~ item.course.title }}
+
+ {% endif %}
+ {% else %}
+ -
+
+ {{ item.user.completeName }}
+
+ {% endif %}
+
+ -
+
+ {{ 'CreationDate'|get_lang ~ ': ' ~ item.creationDate|date_to_time_ago }}
+
+
+ {% if item.creationDate != item.updateDate %}
+ -
+
+ {{ 'UpdateDate'|get_lang ~ ': ' ~ item.updateDate|date_to_time_ago }}
+
+ {% endif %}
+
+ -
+
+ {{ 'XComments'|get_lang|format(item.comments.count) }}
+
+
+
+
+
+ {{ item.content }}
+
+
+
+
+
+
{{ 'XComments'|get_lang|format(item.comments.count) }}
+
+{{ comments }}
+
+{{ form }}
+
+
\ No newline at end of file
diff --git a/src/Chamilo/CoreBundle/Entity/Portfolio.php b/src/Chamilo/CoreBundle/Entity/Portfolio.php
index 3059c7b93f..f868304eba 100644
--- a/src/Chamilo/CoreBundle/Entity/Portfolio.php
+++ b/src/Chamilo/CoreBundle/Entity/Portfolio.php
@@ -4,6 +4,8 @@
namespace Chamilo\CoreBundle\Entity;
use Chamilo\UserBundle\Entity\User;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
@@ -100,12 +102,20 @@ class Portfolio
*/
protected $category;
+ /**
+ * @var \Doctrine\Common\Collections\ArrayCollection
+ *
+ * @ORM\OneToMany(targetEntity="Chamilo\CoreBundle\Entity\PortfolioComment", mappedBy="item")
+ */
+ private $comments;
+
/**
* Portfolio constructor.
*/
public function __construct()
{
$this->category = new PortfolioCategory();
+ $this->comments = new ArrayCollection();
}
/**
@@ -321,4 +331,12 @@ class Portfolio
return $this;
}
+
+ /**
+ * @return \Doctrine\Common\Collections\Collection
+ */
+ public function getComments(): Collection
+ {
+ return $this->comments;
+ }
}
diff --git a/src/Chamilo/CoreBundle/Entity/PortfolioComment.php b/src/Chamilo/CoreBundle/Entity/PortfolioComment.php
new file mode 100644
index 0000000000..c98a60e586
--- /dev/null
+++ b/src/Chamilo/CoreBundle/Entity/PortfolioComment.php
@@ -0,0 +1,239 @@
+children = new ArrayCollection();
+ }
+
+ /**
+ * @return int
+ */
+ public function getId(): int
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return \Chamilo\UserBundle\Entity\User
+ */
+ public function getAuthor(): User
+ {
+ return $this->author;
+ }
+
+ /**
+ * @param \Chamilo\UserBundle\Entity\User $author
+ *
+ * @return PortfolioComment
+ */
+ public function setAuthor(User $author): PortfolioComment
+ {
+ $this->author = $author;
+
+ return $this;
+ }
+
+ /**
+ * @return \Chamilo\CoreBundle\Entity\Portfolio
+ */
+ public function getItem(): Portfolio
+ {
+ return $this->item;
+ }
+
+ /**
+ * @param \Chamilo\CoreBundle\Entity\Portfolio $item
+ *
+ * @return PortfolioComment
+ */
+ public function setItem(Portfolio $item): PortfolioComment
+ {
+ $this->item = $item;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getContent(): string
+ {
+ return $this->content;
+ }
+
+ /**
+ * @param string $content
+ *
+ * @return PortfolioComment
+ */
+ public function setContent(string $content): PortfolioComment
+ {
+ $this->content = $content;
+
+ return $this;
+ }
+
+ /**
+ * @return \DateTime
+ */
+ public function getDate(): DateTime
+ {
+ return $this->date;
+ }
+
+ /**
+ * @param \DateTime $date
+ *
+ * @return PortfolioComment
+ */
+ public function setDate(DateTime $date): PortfolioComment
+ {
+ $this->date = $date;
+
+ return $this;
+ }
+
+ /**
+ * @return \Chamilo\CoreBundle\Entity\PortfolioComment|null
+ */
+ public function getParent(): ?PortfolioComment
+ {
+ return $this->parent;
+ }
+
+ /**
+ * @param \Chamilo\CoreBundle\Entity\PortfolioComment|null $parent
+ *
+ * @return PortfolioComment
+ */
+ public function setParent(?PortfolioComment $parent): PortfolioComment
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * @return \Doctrine\Common\Collections\ArrayCollection
+ */
+ public function getChildren(): ArrayCollection
+ {
+ return $this->children;
+ }
+
+ /**
+ * @param \Doctrine\Common\Collections\ArrayCollection $children
+ *
+ * @return PortfolioComment
+ */
+ public function setChildren(ArrayCollection $children): PortfolioComment
+ {
+ $this->children = $children;
+
+ return $this;
+ }
+}