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; $form = new FormValidator('add_portfolio', 'post', $this->baseUrl.'action=add_item'); 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); foreach ($userIdListToSend as $userIdToSend) { $messageContent = sprintf( get_lang('PortfolioAlertNewPostContent'), $this->owner->getCompleteName(), $messageCourseTitle, $this->baseUrl.http_build_query(['action' => 'view', 'id' => $portfolio->getId()]) ); MessageManager::send_message_simple($userIdToSend, $messageSubject, $messageContent, 0, false, false, [], false); } } Display::addFlash( Display::return_message(get_lang('PortfolioItemAdded'), 'success') ); header("Location: $this->baseUrl"); exit; } $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 ); $actions[] = ''. Display::return_icon('expand.png', get_lang('Expand'), ['id' => 'expand'], ICON_SIZE_MEDIUM). Display::return_icon('contract.png', get_lang('Collapse'), ['id' => 'contract', 'class' => 'hide'], ICON_SIZE_MEDIUM).''; $js = ''; $content = '
'.PHP_EOL .$comment->getAuthor()->getCompleteName().PHP_EOL.''.$clockIcon.PHP_EOL .Display::dateToStringAgoAndLongDate($comment->getDate()).''.PHP_EOL; if ($comment->isImportant() && ($this->itemBelongToOwner($comment->getItem()) || $isAllowedToEdit) ) { $nodeHtml .= '' .get_lang('CommentMarkedAsImportant') .''.PHP_EOL; } $nodeHtml .= '
'.PHP_EOL .''.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); $pdfContent .= Display::page_subheader2(get_lang('PortfolioItems')); if (count($itemsHtml) > 0) { $pdfContent .= implode(PHP_EOL, $itemsHtml); } else { $pdfContent .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning'); } $pdfContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade')); if (count($commentsHtml) > 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; } /** * @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(PHP_EOL, $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); $frmTagList = new FormValidator( 'frm_tag_list', 'get', $this->baseUrl.($listByUser ? 'user='.$this->owner->getId() : ''), '', [], FormValidator::LAYOUT_BOX ); $frmTagList->addDatePicker('date', get_lang('CreationDate')); /** @var SelectAjax $txtTags */ $txtTags = $frmTagList->addSelectAjax( 'tags', get_lang('Tags'), [], [ 'multiple' => 'multiple', 'url' => api_get_path(WEB_AJAX_PATH)."extra_field.ajax.php?a=search_tags&field_id=29&type=portfolio&byid=1", ] ); $selectedTags = $txtTags->getValue(); if (!empty($selectedTags)) { foreach ($tagTepo->findById($selectedTags) as $tag) { $txtTags->addOption($tag->getTag(), $tag->getId()); } } $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->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(); 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 = '$originContent