em = Database::getManager();
$this->owner = api_get_user_entity(api_get_user_id());
$this->course = api_get_course_entity(api_get_course_int_id());
$this->session = api_get_session_entity(api_get_session_id());
$cidreq = api_get_cidreq();
$this->baseUrl = api_get_self().'?'.($cidreq ? $cidreq.'&' : '');
}
/**
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function translateCategory($category, $languages, $languageId)
{
global $interbreadcrumb;
$originalName = $category->getTitle();
$variableLanguage = '$'.$this->getLanguageVariable($originalName);
$translateUrl = api_get_path(WEB_AJAX_PATH).'lang.ajax.php?a=translate_portfolio_category&sec_token='.Security::get_token();
$form = new FormValidator('new_lang_variable', 'POST', $translateUrl);
$form->addHeader(get_lang('AddWordForTheSubLanguage'));
$form->addText('variable_language', get_lang('LanguageVariable'), false);
$form->addText('original_name', get_lang('OriginalName'), false);
$languagesOptions = [0 => get_lang('None')];
foreach ($languages as $language) {
$languagesOptions[$language->getId()] = $language->getOriginalName();
}
$form->addSelect(
'sub_language',
[get_lang('SubLanguage'), get_lang('OnlyActiveSubLanguagesAreListed')],
$languagesOptions
);
if ($languageId) {
$languageInfo = api_get_language_info($languageId);
$form->addText(
'new_language',
[get_lang('Translation'), get_lang('IfThisTranslationExistsThisWillReplaceTheTerm')]
);
$form->addHidden('category_id', $category->getId());
$form->addHidden('id', $languageInfo['parent_id']);
$form->addHidden('sub', $languageInfo['id']);
$form->addHidden('sub_language_id', $languageInfo['id']);
$form->addHidden('redirect', true);
$form->addButtonSave(get_lang('Save'));
}
$form->setDefaults([
'variable_language' => $variableLanguage,
'original_name' => $originalName,
'sub_language' => $languageId,
]);
$form->addRule('sub_language', get_lang('Required'), 'required');
$form->freeze(['variable_language', 'original_name']);
$interbreadcrumb[] = [
'name' => get_lang('Portfolio'),
'url' => $this->baseUrl,
];
$interbreadcrumb[] = [
'name' => get_lang('Categories'),
'url' => $this->baseUrl.'action=list_categories&parent_id='.$category->getParentId(),
];
$interbreadcrumb[] = [
'name' => Security::remove_XSS($category->getTitle()),
'url' => $this->baseUrl.'action=edit_category&id='.$category->getId(),
];
$actions = [];
$actions[] = Display::url(
Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
$this->baseUrl.'action=edit_category&id='.$category->getId()
);
$js = '';
$content = $form->returnForm();
$this->renderView($content.$js, get_lang('TranslateCategory'), $actions);
}
/**
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function listCategories()
{
global $interbreadcrumb;
$parentId = isset($_REQUEST['parent_id']) ? (int) $_REQUEST['parent_id'] : 0;
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
$headers = [
get_lang('Title'),
get_lang('Description'),
];
if ($parentId === 0) {
$headers[] = get_lang('SubCategories');
}
$headers[] = get_lang('Actions');
$column = 0;
foreach ($headers as $header) {
$table->setHeaderContents(0, $column, $header);
$column++;
}
$currentUserId = api_get_user_id();
$row = 1;
$categories = $this->getCategoriesForIndex(null, $parentId);
foreach ($categories as $category) {
$column = 0;
$subcategories = $this->getCategoriesForIndex(null, $category->getId());
$linkSubCategories = $category->getTitle();
if (count($subcategories) > 0) {
$linkSubCategories = Display::url(
$category->getTitle(),
$this->baseUrl.'action=list_categories&parent_id='.$category->getId()
);
}
$table->setCellContents($row, $column++, $linkSubCategories);
$table->setCellContents($row, $column++, strip_tags($category->getDescription()));
if ($parentId === 0) {
$table->setCellContents($row, $column++, count($subcategories));
}
// Actions
$links = null;
// Edit action
$url = $this->baseUrl.'action=edit_category&id='.$category->getId();
$links .= Display::url(Display::return_icon('edit.png', get_lang('Edit')), $url).' ';
// Visible action : if active
if ($category->isVisible() != 0) {
$url = $this->baseUrl.'action=hide_category&id='.$category->getId();
$links .= Display::url(Display::return_icon('visible.png', get_lang('Hide')), $url).' ';
} else { // else if not active
$url = $this->baseUrl.'action=show_category&id='.$category->getId();
$links .= Display::url(Display::return_icon('invisible.png', get_lang('Show')), $url).' ';
}
// Delete action
$url = $this->baseUrl.'action=delete_category&id='.$category->getId();
$links .= Display::url(Display::return_icon('delete.png', get_lang('Delete')), $url, ['onclick' => 'javascript:if(!confirm(\''.get_lang('AreYouSureToDeleteJS').'\')) return false;']);
$table->setCellContents($row, $column++, $links);
$row++;
}
$interbreadcrumb[] = [
'name' => get_lang('Portfolio'),
'url' => $this->baseUrl,
];
if ($parentId > 0) {
$interbreadcrumb[] = [
'name' => get_lang('Categories'),
'url' => $this->baseUrl.'action=list_categories',
];
}
$actions = [];
$actions[] = Display::url(
Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
$this->baseUrl.($parentId > 0 ? 'action=list_categories' : '')
);
if ($currentUserId == $this->owner->getId() && $parentId === 0) {
$actions[] = Display::url(
Display::return_icon('new_folder.png', get_lang('AddCategory'), [], ICON_SIZE_MEDIUM),
$this->baseUrl.'action=add_category'
);
}
$content = $table->toHtml();
$pageTitle = get_lang('Categories');
if ($parentId > 0) {
$em = Database::getManager();
$parentCategory = $em->find('ChamiloCoreBundle:PortfolioCategory', $parentId);
$pageTitle = $parentCategory->getTitle().' : '.get_lang('SubCategories');
}
$this->renderView($content, $pageTitle, $actions);
}
/**
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function addCategory()
{
global $interbreadcrumb;
Display::addFlash(
Display::return_message(get_lang('PortfolioCategoryFieldHelp'), 'info')
);
$form = new FormValidator('add_category', 'post', "{$this->baseUrl}&action=add_category");
if (api_get_configuration_value('save_titles_as_html')) {
$form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
} else {
$form->addText('title', get_lang('Title'));
$form->applyFilter('title', 'trim');
}
$form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
$parentSelect = $form->addSelect(
'parent_id',
get_lang('ParentCategory')
);
$parentSelect->addOption(get_lang('Level0'), 0);
$currentUserId = api_get_user_id();
$categories = $this->getCategoriesForIndex(null, 0);
foreach ($categories as $category) {
$parentSelect->addOption($category->getTitle(), $category->getId());
}
$form->addButtonCreate(get_lang('Create'));
if ($form->validate()) {
$values = $form->exportValues();
$category = new PortfolioCategory();
$category
->setTitle($values['title'])
->setDescription($values['description'])
->setParentId($values['parent_id'])
->setUser($this->owner);
$this->em->persist($category);
$this->em->flush();
Display::addFlash(
Display::return_message(get_lang('CategoryAdded'), 'success')
);
header("Location: {$this->baseUrl}action=list_categories");
exit;
}
$interbreadcrumb[] = [
'name' => get_lang('Portfolio'),
'url' => $this->baseUrl,
];
$interbreadcrumb[] = [
'name' => get_lang('Categories'),
'url' => $this->baseUrl.'action=list_categories',
];
$actions = [];
$actions[] = Display::url(
Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
$this->baseUrl.'action=list_categories'
);
$content = $form->returnForm();
$this->renderView($content, get_lang('AddCategory'), $actions);
}
/**
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Exception
*/
public function editCategory(PortfolioCategory $category)
{
global $interbreadcrumb;
if (!api_is_platform_admin()) {
api_not_allowed(true);
}
Display::addFlash(
Display::return_message(get_lang('PortfolioCategoryFieldHelp'), 'info')
);
$form = new FormValidator(
'edit_category',
'post',
$this->baseUrl."action=edit_category&id={$category->getId()}"
);
if (api_get_configuration_value('save_titles_as_html')) {
$form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
} else {
$translateUrl = $this->baseUrl.'action=translate_category&id='.$category->getId();
$translateButton = Display::toolbarButton(get_lang('TranslateThisTerm'), $translateUrl, 'language', 'link');
$form->addText(
'title',
[get_lang('Title'), $translateButton]
);
$form->applyFilter('title', 'trim');
}
$form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
$form->addButtonUpdate(get_lang('Update'));
$form->setDefaults(
[
'title' => $category->getTitle(),
'description' => $category->getDescription(),
]
);
if ($form->validate()) {
$values = $form->exportValues();
$category
->setTitle($values['title'])
->setDescription($values['description']);
$this->em->persist($category);
$this->em->flush();
Display::addFlash(
Display::return_message(get_lang('Updated'), 'success')
);
header("Location: {$this->baseUrl}action=list_categories&parent_id=".$category->getParentId());
exit;
}
$interbreadcrumb[] = [
'name' => get_lang('Portfolio'),
'url' => $this->baseUrl,
];
$interbreadcrumb[] = [
'name' => get_lang('Categories'),
'url' => $this->baseUrl.'action=list_categories',
];
if ($category->getParentId() > 0) {
$em = Database::getManager();
$parentCategory = $em->find('ChamiloCoreBundle:PortfolioCategory', $category->getParentId());
$pageTitle = $parentCategory->getTitle().' : '.get_lang('SubCategories');
$interbreadcrumb[] = [
'name' => Security::remove_XSS($pageTitle),
'url' => $this->baseUrl.'action=list_categories&parent_id='.$category->getParentId(),
];
}
$actions = [];
$actions[] = Display::url(
Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
$this->baseUrl.'action=list_categories&parent_id='.$category->getParentId()
);
$content = $form->returnForm();
$this->renderView($content, get_lang('EditCategory'), $actions);
}
/**
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function showHideCategory(PortfolioCategory $category)
{
if (!$this->categoryBelongToOwner($category)) {
api_not_allowed(true);
}
$category->setIsVisible(!$category->isVisible());
$this->em->persist($category);
$this->em->flush();
Display::addFlash(
Display::return_message(get_lang('VisibilityChanged'), 'success')
);
header("Location: {$this->baseUrl}action=list_categories");
exit;
}
/**
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function deleteCategory(PortfolioCategory $category)
{
if (!api_is_platform_admin()) {
api_not_allowed(true);
}
$this->em->remove($category);
$this->em->flush();
Display::addFlash(
Display::return_message(get_lang('CategoryDeleted'), 'success')
);
header("Location: {$this->baseUrl}action=list_categories");
exit;
}
/**
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
* @throws \Exception
*/
public function addItem()
{
global $interbreadcrumb;
$templates = $this->em
->getRepository(Portfolio::class)
->findBy(
[
'isTemplate' => true,
'course' => $this->course,
'session' => $this->session,
]
);
$form = new FormValidator('add_portfolio', 'post', $this->baseUrl.'action=add_item');
$form->addSelectFromCollection(
'template',
[
get_lang('Template'),
null,
'',
],
$templates,
[],
true,
'getTitle'
);
if (api_get_configuration_value('save_titles_as_html')) {
$form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
} else {
$form->addText('title', get_lang('Title'));
$form->applyFilter('title', 'trim');
}
$editorConfig = [
'ToolbarSet' => 'NotebookStudent',
'Width' => '100%',
'Height' => '400',
'cols-size' => [2, 10, 0],
];
$form->addHtmlEditor('content', get_lang('Content'), true, false, $editorConfig);
$categoriesSelect = $form->addSelect(
'category',
[get_lang('Category'), get_lang('PortfolioCategoryFieldHelp')]
);
$categoriesSelect->addOption(get_lang('SelectACategory'), 0);
$parentCategories = $this->getCategoriesForIndex(null, 0);
foreach ($parentCategories as $parentCategory) {
$categoriesSelect->addOption($this->translateDisplayName($parentCategory->getTitle()), $parentCategory->getId());
$subCategories = $this->getCategoriesForIndex(null, $parentCategory->getId());
if (count($subCategories) > 0) {
foreach ($subCategories as $subCategory) {
$categoriesSelect->addOption(' — '.$this->translateDisplayName($subCategory->getTitle()), $subCategory->getId());
}
}
}
$extraField = new ExtraField('portfolio');
$extra = $extraField->addElements($form);
$this->addAttachmentsFieldToForm($form);
$form->addButtonCreate(get_lang('Create'));
if ($form->validate()) {
$values = $form->exportValues();
$currentTime = new DateTime(
api_get_utc_datetime(),
new DateTimeZone('UTC')
);
$portfolio = new Portfolio();
$portfolio
->setTitle($values['title'])
->setContent($values['content'])
->setUser($this->owner)
->setCourse($this->course)
->setSession($this->session)
->setCategory(
$this->em->find('ChamiloCoreBundle:PortfolioCategory', $values['category'])
)
->setCreationDate($currentTime)
->setUpdateDate($currentTime);
$this->em->persist($portfolio);
$this->em->flush();
$values['item_id'] = $portfolio->getId();
$extraFieldValue = new ExtraFieldValue('portfolio');
$extraFieldValue->saveFieldValues($values);
$this->processAttachments(
$form,
$portfolio->getUser(),
$portfolio->getId(),
PortfolioAttachment::TYPE_ITEM
);
$hook = HookPortfolioItemAdded::create();
$hook->setEventData(['portfolio' => $portfolio]);
$hook->notifyItemAdded();
if (1 == api_get_course_setting('email_alert_teachers_new_post')) {
if ($this->session) {
$messageCourseTitle = "{$this->course->getTitle()} ({$this->session->getName()})";
$teachers = SessionManager::getCoachesByCourseSession(
$this->session->getId(),
$this->course->getId()
);
$userIdListToSend = array_values($teachers);
} else {
$messageCourseTitle = $this->course->getTitle();
$teachers = CourseManager::get_teacher_list_from_course_code($this->course->getCode());
$userIdListToSend = array_keys($teachers);
}
$messageSubject = sprintf(get_lang('PortfolioAlertNewPostSubject'), $messageCourseTitle);
$messageContent = sprintf(
get_lang('PortfolioAlertNewPostContent'),
$this->owner->getCompleteName(),
$messageCourseTitle,
$this->baseUrl.http_build_query(['action' => 'view', 'id' => $portfolio->getId()])
);
$messageContent .= '
'.get_lang('Course').': '; if ($this->session) { $pdfContent .= $this->session->getName().' ('.$this->course->getTitle().')'; } else { $pdfContent .= $this->course->getTitle(); } $pdfContent .= '
'; } $visibility = []; if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) { $visibility[] = Portfolio::VISIBILITY_VISIBLE; $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER; } $items = $this->em ->getRepository(Portfolio::class) ->findItemsByUser( $this->owner, $this->course, $this->session, null, $visibility ); $comments = $this->em ->getRepository(PortfolioComment::class) ->findCommentsByUser($this->owner, $this->course, $this->session); $itemsHtml = $this->getItemsInHtmlFormatted($items); $commentsHtml = $this->getCommentsInHtmlFormatted($comments); $totalNumberOfItems = count($itemsHtml); $totalNumberOfComments = count($commentsHtml); $requiredNumberOfItems = (int) api_get_course_setting('portfolio_number_items'); $requiredNumberOfComments = (int) api_get_course_setting('portfolio_number_comments'); $itemsSubtitle = ''; $commentsSubtitle = ''; if ($requiredNumberOfItems > 0) { $itemsSubtitle = sprintf( get_lang('XAddedYRequired'), $totalNumberOfItems, $requiredNumberOfItems ); } if ($requiredNumberOfComments > 0) { $commentsSubtitle = sprintf( get_lang('XAddedYRequired'), $totalNumberOfComments, $requiredNumberOfComments ); } $pdfContent .= Display::page_subheader2( get_lang('PortfolioItems'), $itemsSubtitle ); if ($totalNumberOfItems > 0) { $pdfContent .= implode(PHP_EOL, $itemsHtml); } else { $pdfContent .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning'); } $pdfContent .= Display::page_subheader2( get_lang('PortfolioCommentsMade'), $commentsSubtitle ); if ($totalNumberOfComments > 0) { $pdfContent .= implode(PHP_EOL, $commentsHtml); } else { $pdfContent .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning'); } $pdfName = $this->owner->getCompleteName() .($this->course ? '_'.$this->course->getCode() : '') .'_'.get_lang('Portfolio'); $pdf = new PDF(); $pdf->content_to_pdf( $pdfContent, null, $pdfName, $this->course ? $this->course->getCode() : null, 'D', false, null, false, true ); } public function exportZip(HttpRequest $httpRequest) { $currentUserId = api_get_user_id(); $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit(); if ($isAllowedToFilterStudent) { if ($httpRequest->query->has('user')) { $this->owner = api_get_user_entity($httpRequest->query->getInt('user')); if (empty($this->owner)) { api_not_allowed(true); } } } $itemsRepo = $this->em->getRepository(Portfolio::class); $commentsRepo = $this->em->getRepository(PortfolioComment::class); $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class); $visibility = []; if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) { $visibility[] = Portfolio::VISIBILITY_VISIBLE; $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER; } $items = $itemsRepo->findItemsByUser( $this->owner, $this->course, $this->session, null, $visibility ); $comments = $commentsRepo->findCommentsByUser($this->owner, $this->course, $this->session); $itemsHtml = $this->getItemsInHtmlFormatted($items); $commentsHtml = $this->getCommentsInHtmlFormatted($comments); $sysArchivePath = api_get_path(SYS_ARCHIVE_PATH); $tempPortfolioDirectory = $sysArchivePath."portfolio/{$this->owner->getId()}"; $userDirectory = UserManager::getUserPathById($this->owner->getId(), 'system'); $attachmentsDirectory = $userDirectory.'portfolio_attachments/'; $tblItemsHeaders = []; $tblItemsHeaders[] = get_lang('Title'); $tblItemsHeaders[] = get_lang('CreationDate'); $tblItemsHeaders[] = get_lang('LastUpdate'); $tblItemsHeaders[] = get_lang('Category'); $tblItemsHeaders[] = get_lang('Category'); $tblItemsHeaders[] = get_lang('Score'); $tblItemsHeaders[] = get_lang('Course'); $tblItemsHeaders[] = get_lang('Session'); $tblItemsData = []; $tblCommentsHeaders = []; $tblCommentsHeaders[] = get_lang('Resume'); $tblCommentsHeaders[] = get_lang('Date'); $tblCommentsHeaders[] = get_lang('PortfolioItemTitle'); $tblCommentsHeaders[] = get_lang('Score'); $tblCommentsData = []; $filenames = []; $fs = new Filesystem(); /** * @var int $i * @var Portfolio $item */ foreach ($items as $i => $item) { $itemCategory = $item->getCategory(); $itemCourse = $item->getCourse(); $itemSession = $item->getSession(); $itemDirectory = $item->getCreationDate()->format('Y-m-d-H-i-s'); $itemFilename = sprintf('%s/items/%s/item.html', $tempPortfolioDirectory, $itemDirectory); $itemFileContent = $this->fixImagesSourcesToHtml($itemsHtml[$i]); $fs->dumpFile($itemFilename, $itemFileContent); $filenames[] = $itemFilename; $attachments = $attachmentsRepo->findFromItem($item); /** @var PortfolioAttachment $attachment */ foreach ($attachments as $attachment) { $attachmentFilename = sprintf( '%s/items/%s/attachments/%s', $tempPortfolioDirectory, $itemDirectory, $attachment->getFilename() ); $fs->copy( $attachmentsDirectory.$attachment->getPath(), $attachmentFilename ); $filenames[] = $attachmentFilename; } $tblItemsData[] = [ Display::url( Security::remove_XSS($item->getTitle()), sprintf('items/%s/item.html', $itemDirectory) ), api_convert_and_format_date($item->getCreationDate()), api_convert_and_format_date($item->getUpdateDate()), $itemCategory ? $itemCategory->getTitle() : null, $item->getComments()->count(), $item->getScore(), $itemCourse->getTitle(), $itemSession ? $itemSession->getName() : null, ]; } /** * @var int $i * @var PortfolioComment $comment */ foreach ($comments as $i => $comment) { $commentDirectory = $comment->getDate()->format('Y-m-d-H-i-s'); $commentFileContent = $this->fixImagesSourcesToHtml($commentsHtml[$i]); $commentFilename = sprintf('%s/comments/%s/comment.html', $tempPortfolioDirectory, $commentDirectory); $fs->dumpFile($commentFilename, $commentFileContent); $filenames[] = $commentFilename; $attachments = $attachmentsRepo->findFromComment($comment); /** @var PortfolioAttachment $attachment */ foreach ($attachments as $attachment) { $attachmentFilename = sprintf( '%s/comments/%s/attachments/%s', $tempPortfolioDirectory, $commentDirectory, $attachment->getFilename() ); $fs->copy( $attachmentsDirectory.$attachment->getPath(), $attachmentFilename ); $filenames[] = $attachmentFilename; } $tblCommentsData[] = [ Display::url( $comment->getExcerpt(), sprintf('comments/%s/comment.html', $commentDirectory) ), api_convert_and_format_date($comment->getDate()), Security::remove_XSS($comment->getItem()->getTitle()), $comment->getScore(), ]; } $tblItems = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']); $tblItems->setHeaders($tblItemsHeaders); $tblItems->setData($tblItemsData); $tblComments = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']); $tblComments->setHeaders($tblCommentsHeaders); $tblComments->setData($tblCommentsData); $itemFilename = sprintf('%s/index.html', $tempPortfolioDirectory); $filenames[] = $itemFilename; $fs->dumpFile( $itemFilename, $this->formatZipIndexFile($tblItems, $tblComments) ); $zipName = $this->owner->getCompleteName() .($this->course ? '_'.$this->course->getCode() : '') .'_'.get_lang('Portfolio'); $tempZipFile = $sysArchivePath."portfolio/$zipName.zip"; $zip = new PclZip($tempZipFile); foreach ($filenames as $filename) { $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $tempPortfolioDirectory); } DocumentManager::file_send_for_download($tempZipFile, true, "$zipName.zip"); $fs->remove($tempPortfolioDirectory); $fs->remove($tempZipFile); } public function qualifyItem(Portfolio $item) { global $interbreadcrumb; $em = Database::getManager(); $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'item' => $item->getId()]); $form = new FormValidator('frm_qualify', 'post', $formAction); $form->addUserAvatar('user', get_lang('Author')); $form->addLabel(get_lang('Title'), $item->getTitle()); $itemContent = $this->generateItemContent($item); $form->addLabel(get_lang('Content'), $itemContent); $form->addNumeric( 'score', [get_lang('QualifyNumeric'), null, ' / '.api_get_course_setting('portfolio_max_score')] ); $form->addButtonSave(get_lang('QualifyThisPortfolioItem')); if ($form->validate()) { $values = $form->exportValues(); $item->setScore($values['score']); $em->persist($item); $em->flush(); Display::addFlash( Display::return_message(get_lang('PortfolioItemGraded'), 'success') ); header("Location: $formAction"); exit(); } $form->setDefaults( [ 'user' => $item->getUser(), 'score' => (float) $item->getScore(), ] ); $interbreadcrumb[] = [ 'name' => get_lang('Portfolio'), 'url' => $this->baseUrl, ]; $interbreadcrumb[] = [ 'name' => $item->getTitle(true), 'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]), ]; $actions = []; $actions[] = Display::url( Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM), $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]) ); $this->renderView($form->returnForm(), get_lang('Qualify'), $actions); } public function qualifyComment(PortfolioComment $comment) { global $interbreadcrumb; $em = Database::getManager(); $item = $comment->getItem(); $commentPath = $em->getRepository(PortfolioComment::class)->getPath($comment); $template = new Template('', false, false, false, true, false, false); $template->assign('item', $item); $template->assign('comments_path', $commentPath); $commentContext = $template->fetch( $template->get_template('portfolio/comment_context.html.twig') ); $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'comment' => $comment->getId()]); $form = new FormValidator('frm_qualify', 'post', $formAction); $form->addHtml($commentContext); $form->addUserAvatar('user', get_lang('Author')); $form->addLabel(get_lang('Comment'), $comment->getContent()); $form->addNumeric( 'score', [get_lang('QualifyNumeric'), null, '/ '.api_get_course_setting('portfolio_max_score')] ); $form->addButtonSave(get_lang('QualifyThisPortfolioComment')); if ($form->validate()) { $values = $form->exportValues(); $comment->setScore($values['score']); $em->persist($comment); $em->flush(); Display::addFlash( Display::return_message(get_lang('PortfolioCommentGraded'), 'success') ); header("Location: $formAction"); exit(); } $form->setDefaults( [ 'user' => $comment->getAuthor(), 'score' => (float) $comment->getScore(), ] ); $interbreadcrumb[] = [ 'name' => get_lang('Portfolio'), 'url' => $this->baseUrl, ]; $interbreadcrumb[] = [ 'name' => $item->getTitle(true), 'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]), ]; $actions = []; $actions[] = Display::url( Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM), $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]) ); $this->renderView($form->returnForm(), get_lang('Qualify'), $actions); } public function downloadAttachment(HttpRequest $httpRequest) { $path = $httpRequest->query->get('file'); if (empty($path)) { api_not_allowed(true); } $em = Database::getManager(); $attachmentRepo = $em->getRepository(PortfolioAttachment::class); $attachment = $attachmentRepo->findOneByPath($path); if (empty($attachment)) { api_not_allowed(true); } $originOwnerId = 0; if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) { $item = $em->find(Portfolio::class, $attachment->getOrigin()); $originOwnerId = $item->getUser()->getId(); } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) { $comment = $em->find(PortfolioComment::class, $attachment->getOrigin()); $originOwnerId = $comment->getAuthor()->getId(); } else { api_not_allowed(true); } $userDirectory = UserManager::getUserPathById($originOwnerId, 'system'); $attachmentsDirectory = $userDirectory.'portfolio_attachments/'; $attachmentFilename = $attachmentsDirectory.$attachment->getPath(); if (!Security::check_abs_path($attachmentFilename, $attachmentsDirectory)) { api_not_allowed(true); } $downloaded = DocumentManager::file_send_for_download( $attachmentFilename, true, $attachment->getFilename() ); if (!$downloaded) { api_not_allowed(true); } } public function deleteAttachment(HttpRequest $httpRequest) { $currentUserId = api_get_user_id(); $path = $httpRequest->query->get('file'); if (empty($path)) { api_not_allowed(true); } $em = Database::getManager(); $fs = new Filesystem(); $attachmentRepo = $em->getRepository(PortfolioAttachment::class); $attachment = $attachmentRepo->findOneByPath($path); if (empty($attachment)) { api_not_allowed(true); } $originOwnerId = 0; $itemId = 0; if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) { $item = $em->find(Portfolio::class, $attachment->getOrigin()); $originOwnerId = $item->getUser()->getId(); $itemId = $item->getId(); } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) { $comment = $em->find(PortfolioComment::class, $attachment->getOrigin()); $originOwnerId = $comment->getAuthor()->getId(); $itemId = $comment->getItem()->getId(); } if ($currentUserId !== $originOwnerId) { api_not_allowed(true); } $em->remove($attachment); $em->flush(); $userDirectory = UserManager::getUserPathById($originOwnerId, 'system'); $attachmentsDirectory = $userDirectory.'portfolio_attachments/'; $attachmentFilename = $attachmentsDirectory.$attachment->getPath(); $fs->remove($attachmentFilename); if ($httpRequest->isXmlHttpRequest()) { echo Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success'); } else { Display::addFlash( Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success') ); header('Location: '.$this->baseUrl.http_build_query(['action' => 'view', 'id' => $itemId])); } exit; } /** * @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\ORMException */ public function markAsHighlighted(Portfolio $item) { if ($item->getCourse()->getId() !== (int) api_get_course_int_id()) { api_not_allowed(true); } $item->setIsHighlighted( !$item->isHighlighted() ); Database::getManager()->flush(); Display::addFlash( Display::return_message( $item->isHighlighted() ? get_lang('MarkedAsHighlighted') : get_lang('UnmarkedAsHighlighted'), 'success' ) ); header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()])); exit; } public function markAsTemplate(Portfolio $item) { if (!$this->itemBelongToOwner($item)) { api_not_allowed(true); } $item->setIsTemplate( !$item->isTemplate() ); Database::getManager()->flush($item); Display::addFlash( Display::return_message( $item->isTemplate() ? get_lang('PortfolioItemSetAsTemplate') : get_lang('PortfolioItemUnsetAsTemplate'), 'success' ) ); header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()])); exit; } /** * @param bool $showHeader */ 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); if ($showHeader) { $view->assign('header', $toolName); } $actionsStr = ''; if ($this->course) { $actionsStr .= Display::return_introduction_section(TOOL_PORTFOLIO); } if ($actions) { $actions = implode('', $actions); $actionsStr .= Display::toolbarAction('portfolio-toolbar', [$actions]); } $view->assign('baseurl', $this->baseUrl); $view->assign('actions', $actionsStr); $view->assign('content', $content); $view->display_one_col_template(); } private function categoryBelongToOwner(PortfolioCategory $category): bool { if ($category->getUser()->getId() != $this->owner->getId()) { return false; } return true; } private function addAttachmentsFieldToForm(FormValidator $form) { $form->addButton('add_attachment', get_lang('AddAttachment'), 'plus'); $form->addHtml(' '); $script = "$(function () { var attachmentsTemplate = $('#container-attachments').html(); var \$btnAdd = $('[name=\"add_attachment\"]'); var \$reference = \$btnAdd.parents('.form-group'); \$btnAdd.on('click', function (e) { e.preventDefault(); $(attachmentsTemplate).insertBefore(\$reference); }); })"; $form->addHtml(""); } private function processAttachments( FormValidator $form, User $user, int $originId, int $originType ) { $em = Database::getManager(); $fs = new Filesystem(); $comments = $form->getSubmitValue('attachment_comment'); foreach ($_FILES['attachment_file']['error'] as $i => $attachmentFileError) { if ($attachmentFileError != UPLOAD_ERR_OK) { continue; } $_file = [ 'name' => $_FILES['attachment_file']['name'][$i], 'type' => $_FILES['attachment_file']['type'][$i], 'tmp_name' => $_FILES['attachment_file']['tmp_name'][$i], 'size' => $_FILES['attachment_file']['size'][$i], ]; if (empty($_file['type'])) { $_file['type'] = DocumentManager::file_get_mime_type($_file['name']); } $newFileName = add_ext_on_mime(stripslashes($_file['name']), $_file['type']); if (!filter_extension($newFileName)) { Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFileFilteredExtension'), 'error')); continue; } $newFileName = uniqid(); $attachmentsDirectory = UserManager::getUserPathById($user->getId(), 'system').'portfolio_attachments/'; if (!$fs->exists($attachmentsDirectory)) { $fs->mkdir($attachmentsDirectory, api_get_permissions_for_new_directories()); } $attachmentFilename = $attachmentsDirectory.$newFileName; if (is_uploaded_file($_file['tmp_name'])) { $moved = move_uploaded_file($_file['tmp_name'], $attachmentFilename); if (!$moved) { Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFile'), 'error')); continue; } } $attachment = new PortfolioAttachment(); $attachment ->setFilename($_file['name']) ->setComment($comments[$i]) ->setPath($newFileName) ->setOrigin($originId) ->setOriginType($originType) ->setSize($_file['size']); $em->persist($attachment); $em->flush(); } } private function itemBelongToOwner(Portfolio $item): bool { if ($item->getUser()->getId() != $this->owner->getId()) { return false; } return true; } private function createFormTagFilter(bool $listByUser = false): FormValidator { $em = Database::getManager(); $tagTepo = $em->getRepository(Tag::class); $extraField = new ExtraField('portfolio'); $tagFieldInfo = $extraField->get_handler_field_info_by_tags('tags'); $selectTagOptions = array_map( function (array $tagOption) { return $tagOption['tag']; }, $tagFieldInfo['options'] ?? [] ); $frmTagList = new FormValidator( 'frm_tag_list', 'get', $this->baseUrl.($listByUser ? 'user='.$this->owner->getId() : ''), '', [], FormValidator::LAYOUT_BOX ); $frmTagList->addDatePicker('date', get_lang('CreationDate')); $frmTagList->addSelect( 'tags', get_lang('Tags'), $selectTagOptions, ['multiple' => 'multiple'] ); $frmTagList->addText('text', get_lang('Search'), false)->setIcon('search'); $frmTagList->applyFilter('text', 'trim'); $frmTagList->addHtml('$link
"); if ($listHighlighted) { $link = Display::url( get_lang('BackToMainPortfolio'), $this->baseUrl ); } else { $link = Display::url( get_lang('SeeHighlights'), $this->baseUrl.http_build_query(['list_highlighted' => true]) ); } $frmStudentList->addHtml("$link
"); return $frmStudentList; } private function getCategoriesForIndex(?int $currentUserId = null, ?int $parentId = null): array { $categoriesCriteria = []; if (isset($currentUserId)) { $categoriesCriteria['user'] = $this->owner; } if (!api_is_platform_admin() && $currentUserId !== $this->owner->getId()) { $categoriesCriteria['isVisible'] = true; } if (isset($parentId)) { $categoriesCriteria['parentId'] = $parentId; } return $this->em ->getRepository(PortfolioCategory::class) ->findBy($categoriesCriteria); } private function getHighlightedItems() { $queryBuilder = $this->em->createQueryBuilder(); $queryBuilder ->select('pi') ->from(Portfolio::class, 'pi') ->where('pi.course = :course') ->andWhere('pi.isHighlighted = TRUE') ->setParameter('course', $this->course); if ($this->session) { $queryBuilder->andWhere('pi.session = :session'); $queryBuilder->setParameter('session', $this->session); } else { $queryBuilder->andWhere('pi.session IS NULL'); } $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE]; if (api_is_allowed_to_edit()) { $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER; } $queryBuilder ->andWhere( $queryBuilder->expr()->orX( 'pi.user = :current_user', $queryBuilder->expr()->andX( 'pi.user != :current_user', $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria) ) ) ) ->setParameter('current_user', api_get_user_id()); $queryBuilder->orderBy('pi.creationDate', 'DESC'); return $queryBuilder->getQuery()->getResult(); } private function getItemsForIndex( bool $listByUser = false, FormValidator $frmFilterList = null ) { $currentUserId = api_get_user_id(); if ($this->course) { $queryBuilder = $this->em->createQueryBuilder(); $queryBuilder ->select('pi') ->from(Portfolio::class, 'pi') ->where('pi.course = :course'); $queryBuilder->setParameter('course', $this->course); if ($this->session) { $queryBuilder->andWhere('pi.session = :session'); $queryBuilder->setParameter('session', $this->session); } else { $queryBuilder->andWhere('pi.session IS NULL'); } if ($frmFilterList && $frmFilterList->validate()) { $values = $frmFilterList->exportValues(); if (!empty($values['date'])) { $queryBuilder ->andWhere('pi.creationDate >= :date') ->setParameter(':date', api_get_utc_datetime($values['date'], false, true)) ; } if (!empty($values['tags'])) { $queryBuilder ->innerJoin(ExtraFieldRelTag::class, 'efrt', Join::WITH, 'efrt.itemId = pi.id') ->innerJoin(ExtraFieldEntity::class, 'ef', Join::WITH, 'ef.id = efrt.fieldId') ->andWhere('ef.extraFieldType = :efType') ->andWhere('ef.variable = :variable') ->andWhere('efrt.tagId IN (:tags)'); $queryBuilder->setParameter('efType', ExtraFieldEntity::PORTFOLIO_TYPE); $queryBuilder->setParameter('variable', 'tags'); $queryBuilder->setParameter('tags', $values['tags']); } if (!empty($values['text'])) { $queryBuilder->andWhere( $queryBuilder->expr()->orX( $queryBuilder->expr()->like('pi.title', ':text'), $queryBuilder->expr()->like('pi.content', ':text') ) ); $queryBuilder->setParameter('text', '%'.$values['text'].'%'); } // Filters by category level 0 $searchCategories = []; if (!empty($values['categoryId'])) { $searchCategories[] = $values['categoryId']; $subCategories = $this->getCategoriesForIndex(null, $values['categoryId']); if (count($subCategories) > 0) { foreach ($subCategories as $subCategory) { $searchCategories[] = $subCategory->getId(); } } $queryBuilder->andWhere('pi.category IN('.implode(',', $searchCategories).')'); } // Filters by sub-category, don't show the selected values $diff = []; if (!empty($values['subCategoryIds']) && !('all' === $values['subCategoryIds'])) { $subCategoryIds = explode(',', $values['subCategoryIds']); $diff = array_diff($searchCategories, $subCategoryIds); } else { if (trim($values['subCategoryIds']) === '') { $diff = $searchCategories; } } if (!empty($diff)) { unset($diff[0]); if (!empty($diff)) { $queryBuilder->andWhere('pi.category NOT IN('.implode(',', $diff).')'); } } } if ($listByUser) { $queryBuilder ->andWhere('pi.user = :user') ->setParameter('user', $this->owner); } $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE]; if (api_is_allowed_to_edit()) { $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER; } $queryBuilder ->andWhere( $queryBuilder->expr()->orX( 'pi.user = :current_user', $queryBuilder->expr()->andX( 'pi.user != :current_user', $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria) ) ) ) ->setParameter('current_user', $currentUserId); $queryBuilder->orderBy('pi.creationDate', 'DESC'); $items = $queryBuilder->getQuery()->getResult(); } else { $itemsCriteria = []; $itemsCriteria['category'] = null; $itemsCriteria['user'] = $this->owner; if ($currentUserId !== $this->owner->getId()) { $itemsCriteria['visibility'] = Portfolio::VISIBILITY_VISIBLE; } $items = $this->em ->getRepository(Portfolio::class) ->findBy($itemsCriteria, ['creationDate' => 'DESC']); } return $items; } /** * @throws \Doctrine\ORM\ORMException * @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\TransactionRequiredException */ 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->addHeader(get_lang('AddNewComment')); $form->addHtmlEditor('content', get_lang('Comments'), true, false, ['ToolbarSet' => 'Minimal']); $form->addHidden('item', $item->getId()); $form->addHidden('parent', 0); $form->applyFilter('content', 'trim'); $this->addAttachmentsFieldToForm($form); $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(); $this->processAttachments( $form, $comment->getAuthor(), $comment->getId(), PortfolioAttachment::TYPE_COMMENT ); $hook = HookPortfolioItemCommented::create(); $hook->setEventData(['comment' => $comment]); $hook->notifyItemCommented(); PortfolioNotifier::notifyTeachersAndAuthor($comment); Display::addFlash( Display::return_message(get_lang('CommentAdded'), 'success') ); header("Location: $formAction"); exit; } return $form->returnForm(); } private function generateAttachmentList($post, bool $includeHeader = true): string { $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class); $postOwnerId = 0; if ($post instanceof Portfolio) { $attachments = $attachmentsRepo->findFromItem($post); $postOwnerId = $post->getUser()->getId(); } elseif ($post instanceof PortfolioComment) { $attachments = $attachmentsRepo->findFromComment($post); $postOwnerId = $post->getAuthor()->getId(); } if (empty($attachments)) { return ''; } $currentUserId = api_get_user_id(); $listItems = ''.Security::remove_XSS($attachment->getComment()).'
'; } $listItems .= '$originContent