Portfolio: Allow comment in items - refs BT#18201

pull/3744/head
Angel Fernando Quiroz Campos 5 years ago
parent 054d4d2004
commit dcc7e3a65e
  1. 20
      app/Resources/public/css/base.css
  2. 150
      main/inc/lib/PortfolioController.php
  3. 5
      main/install/configuration.dist.php
  4. 12
      main/portfolio/index.php
  5. 59
      main/template/default/portfolio/items.html.twig
  6. 103
      main/template/default/portfolio/view.html.twig
  7. 18
      src/Chamilo/CoreBundle/Entity/Portfolio.php
  8. 239
      src/Chamilo/CoreBundle/Entity/PortfolioComment.php

@ -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;
}

@ -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' => '<ul class="media-list">',
'rootClose' => '</ul>',
'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 '<li class="media">
<div class="media-left">
<img class="media-object thumbnail" src="'.$userPicture.'" alt="'.$author->getCompleteName().'">
</div>
<div class="media-body">';
},
'childClose' => '</div></li>',
'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 '<p class="h4 media-heading">'.$comment->getAuthor()->getCompleteName().PHP_EOL.'<small>'
.$clockIcon.PHP_EOL.Display::dateToStringAgoAndLongDate($comment->getDate()).'</small>'
.'</p><div class="pull-right">'.$commentActions.'</div>'.$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();
}
}

@ -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

@ -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();

@ -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 ~ '&' : '') %}
<div class="row">
<div class="row portfolio-items">
{% for item in items %}
<div class="col-sm-6 col-md-4" id="portfolio-item-{{ item.id }}">
<div class="col-md-6" id="portfolio-item-{{ item.id }}">
<div class="thumbnail">
<div class="caption">
<h3>{{ item.title }}</h3>
{% if _u.id == item.user.id %}
<div class="portfolio-actions pull-right">
<a href="{{ baseurl ~ {'action':'edit_item', 'id':item.id}|url_encode }}">
{{ 'edit.png'|img(22, 'Edit'|get_lang) }}
</a>
{% if item.isVisible %}
<a href="{{ baseurl ~ {'action':'hide_item', 'id':item.id}|url_encode }}">
{{ 'visible.png'|img(22, 'Invisible'|get_lang) }}
</a>
{% else %}
<a href="{{ baseurl ~ {'action':'show_item', 'id':item.id}|url_encode }}">
{{ 'invisible.png'|img(22, 'Visible'|get_lang) }}
</a>
{% endif %}
<a href="{{ baseurl ~ {'action':'delete_item', 'id':item.id}|url_encode }}" class="btn-delete">
{{ 'delete.png'|img(22, 'Delete'|get_lang) }}
</a>
</div>
{% endif %}
<h3>
<a href="{{ baseurl ~ {'action':'view', 'id':item.id}|url_encode }}">{{ item.title }}</a>
</h3>
<ul class="fa-ul">
{% if _c is empty %}
@ -43,37 +65,16 @@
{{ 'UpdateDate'|get_lang ~ ': ' ~ item.updateDate|date_to_time_ago }}
</li>
{% endif %}
<li>
<span class="fa-li fa fa-comment-o" aria-hidden="true"></span>
{{ 'XComments'|get_lang|format(item.comments.count) }}
</li>
</ul>
<hr>
{{ item.content }}
{% if _u.id == item.user.id %}
<hr>
<p>
<a href="{{ baseurl ~ {'action':'edit_item', 'id':item.id}|url_encode }}" class="btn btn-primary btn-sm">
<span class="fa fa-edit fa-fw" aria-hidden="true"></span>
{{ 'Edit'|get_lang }}
</a>
{% if item.isVisible %}
<a href="{{ baseurl ~ {'action':'hide_item', 'id':item.id}|url_encode }}" class="btn btn-info btn-sm">
<span class="fa fa-eye-slash fa-fw" aria-hidden="true"></span>
{{ 'Invisible'|get_lang }}
</a>
{% else %}
<a href="{{ baseurl ~ {'action':'show_item', 'id':item.id}|url_encode }}" class="btn btn-info btn-sm">
<span class="fa fa-eye fa-fw" aria-hidden="true"></span>
{{ 'Visible'|get_lang }}
</a>
{% endif %}
<a href="{{ baseurl ~ {'action':'delete_item', 'id':item.id}|url_encode }}" class="btn btn-danger btn-sm btn-delete">
<span class="fa fa-remove fa-fw" aria-hidden="true"></span>
{{ 'Delete'|get_lang }}
</a>
</p>
{% endif %}
</div>
</div>
</div>

@ -0,0 +1,103 @@
<article class="thumbnail portfolio-item">
<div class="caption">
{% if _u.id == item.user.id %}
<div class="portfolio-actions pull-right">
<a href="{{ baseurl ~ {'action':'edit_item', 'id':item.id}|url_encode }}">
{{ 'edit.png'|img(22, 'Edit'|get_lang) }}
</a>
{% if item.isVisible %}
<a href="{{ baseurl ~ {'action':'hide_item', 'id':item.id}|url_encode }}">
{{ 'visible.png'|img(22, 'Invisible'|get_lang) }}
</a>
{% else %}
<a href="{{ baseurl ~ {'action':'show_item', 'id':item.id}|url_encode }}">
{{ 'invisible.png'|img(22, 'Visible'|get_lang) }}
</a>
{% endif %}
<a href="{{ baseurl ~ {'action':'delete_item', 'id':item.id}|url_encode }}" class="btn-delete">
{{ 'delete.png'|img(22, 'Delete'|get_lang) }}
</a>
</div>
{% endif %}
<h4 class="h3">{{ item.title }}</h4>
<ul class="fa-ul list-inline">
{% if _c is empty %}
{% if item.session %}
<li>
<span class="fa-li fa fa-book" aria-hidden="true"></span>
{{ 'Course'|get_lang ~ ': ' ~ item.session.name ~ ' (' ~ item.course.title ~ ')' }}
</li>
{% elseif not item.session and item.course %}
<li>
<span class="fa-li fa fa-book" aria-hidden="true"></span>
{{ 'Course'|get_lang ~ ': ' ~ item.course.title }}
</li>
{% endif %}
{% else %}
<li>
<span class="fa-li fa fa-user" aria-hidden="true"></span>
{{ item.user.completeName }}
</li>
{% endif %}
<li>
<span class="fa-li fa fa-clock-o" aria-hidden="true"></span>
{{ 'CreationDate'|get_lang ~ ': ' ~ item.creationDate|date_to_time_ago }}
</li>
{% if item.creationDate != item.updateDate %}
<li>
<span class="fa-li fa fa-clock-o" aria-hidden="true"></span>
{{ 'UpdateDate'|get_lang ~ ': ' ~ item.updateDate|date_to_time_ago }}
</li>
{% endif %}
<li>
<span class="fa-li fa fa-comment-o" aria-hidden="true"></span>
{{ 'XComments'|get_lang|format(item.comments.count) }}
</li>
</ul>
<hr>
{{ item.content }}
</div>
</article>
<hr>
<h5 class="h4">{{ 'XComments'|get_lang|format(item.comments.count) }}</h5>
{{ comments }}
{{ form }}
<script>
$(function () {
var frmCommentTop = $("#frm_comment").offset().top;
$('.btn-reply-to').on('click', function (e) {
e.preventDefault();
var comment = $.extend(
{},
{'id': 0},
$(this).data('comment')
);
if (!comment.id) {
return;
}
var $frm = $('form#frm_comment');
$frm.find('#frm_comment_parent').val(comment.id);
CKEDITOR.instances.content.setData('');
$('html, body').animate({scrollTop: frmCommentTop});
});
});
</script>

@ -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;
}
}

@ -0,0 +1,239 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Entity;
use Chamilo\UserBundle\Entity\User;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* Class PortfolioComment.
*
* @package Chamilo\CoreBundle\Entity
*
* @Gedmo\Tree(type="nested")
* @ORM\Table(name="portfolio_comment")
* Add @ to the next line if api_get_configuration_value('allow_portfolio_tool') is true
* ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
*/
class PortfolioComment
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue
*/
private $id;
/**
* @var \Chamilo\UserBundle\Entity\User
*
* @ORM\ManyToOne(targetEntity="Chamilo\UserBundle\Entity\User")
* @ORM\JoinColumn(name="author_id", referencedColumnName="id", nullable=false)
*/
private $author;
/**
* @var \Chamilo\CoreBundle\Entity\Portfolio
*
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\Portfolio", inversedBy="comments")
* @ORM\JoinColumn(name="item_id", referencedColumnName="id", nullable=false)
*/
private $item;
/**
* @var string
*
* @ORM\Column(name="content", type="text")
*/
private $content;
/**
* @var \DateTime
*
* @ORM\Column(name="date", type="datetime")
*/
private $date;
/**
* @var int
*
* @Gedmo\TreeLeft()
* @ORM\Column(name="lft", type="integer")
*/
private $lft;
/**
* @var int
*
* @Gedmo\TreeLevel()
* @ORM\Column(name="lvl", type="integer")
*/
private $lvl;
/**
* @var int
*
* @Gedmo\TreeRight()
* @ORM\Column(name="rgt", type="integer")
*/
private $rgt;
/**
* @var \Chamilo\CoreBundle\Entity\PortfolioComment
*
* @Gedmo\TreeRoot()
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\PortfolioComment")
* @ORM\JoinColumn(name="tree_root", referencedColumnName="id", onDelete="CASCADE")
*/
private $root;
/**
* @var \Chamilo\CoreBundle\Entity\PortfolioComment|null
*
* @Gedmo\TreeParent()
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\PortfolioComment", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;
/**
* @var \Doctrine\Common\Collections\ArrayCollection
*
* @ORM\OneToMany(targetEntity="Chamilo\CoreBundle\Entity\PortfolioComment", mappedBy="parent")
* @ORM\OrderBy({"lft"="DESC"})
*/
private $children;
/**
* PortfolioComment constructor.
*/
public function __construct()
{
$this->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;
}
}
Loading…
Cancel
Save