diff --git a/assets/js/vendor.js b/assets/js/vendor.js index f46c1c696b..4ca7a74ac2 100644 --- a/assets/js/vendor.js +++ b/assets/js/vendor.js @@ -35,6 +35,12 @@ require('@fancyapps/fancybox/dist/jquery.fancybox.js'); require('@fancyapps/fancybox/src/js/media.js'); require('jquery-contextmenu/dist/jquery.contextMenu.js'); +var hljs = require('highlight.js'); +global.hljs = hljs; + +var textcomplete = require('textcomplete'); +global.textcomplete = textcomplete; + require('chart.js'); require('./annotation.js'); diff --git a/composer.json b/composer.json index 33d082f5b4..01e692dd5f 100755 --- a/composer.json +++ b/composer.json @@ -176,7 +176,7 @@ "symfony/browser-kit": "^4.0|^5.0", "symfony/css-selector": "^4.0|^5.0", "symfony/debug-pack": "*", - "symfony/phpunit-bridge": "^4.0|^5.0", + "symfony/phpunit-bridge": "^5.0", "symfony/profiler-pack": "*", "symfony/test-pack": "*", "symplify/easy-coding-standard": "^7.0", diff --git a/package.json b/package.json index f76d887a52..67148e6600 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "fs": "0.0.1-security", "fullcalendar": "^3.0", "highlight.js": "^9.12.0", + "hljs": "^6.2.3", "image-map-resizer": "^1.0.10", "jquery-contextmenu": "^2.9.0", "jquery-ui": "^1.12.1", @@ -60,6 +61,7 @@ "readmore-js": "^2.2.1", "select2": "^4.0", "sweetalert2": "^9.5.3", + "textcomplete": "^0.18.1", "timeago": "^1.6.7", "timepicker": "^1.11.14", "uglifyjs-webpack-plugin": "^1.3.0", diff --git a/public/main/chat/chat.php b/public/main/chat/chat.php index e70d93e369..4c7acec3e7 100644 --- a/public/main/chat/chat.php +++ b/public/main/chat/chat.php @@ -19,10 +19,10 @@ Event::registerLog($logInfo); // View $externalCSS = [ - 'jquery-emojiarea/jquery.emojiarea.css', - 'jquery-textcomplete/jquery.textcomplete.css', - 'emojione/css/emojione.min.css', - 'emojione/css/autocomplete.css', + //'jquery-emojiarea/jquery.emojiarea.css', + //'jquery-textcomplete/jquery.textcomplete.css', + //'emojione/css/emojione.min.css', + //'emojione/css/autocomplete.css', 'highlight/styles/github.css', ]; @@ -35,9 +35,9 @@ $htmlHeadXtra[] = api_get_css(api_get_path(WEB_CSS_PATH).'markdown.css'); $externalJS = [ 'highlight/highlight.pack.js', - 'jquery-textcomplete/jquery.textcomplete.js', - 'emojione/js/emojione.min.js', - 'jquery-emojiarea/jquery.emojiarea.js', + //'jquery-textcomplete/jquery.textcomplete.js', + //'emojione/js/emojione.min.js', + //'jquery-emojiarea/jquery.emojiarea.js', ]; foreach ($externalJS as $js) { @@ -46,20 +46,12 @@ foreach ($externalJS as $js) { $iconList = []; -foreach (Emojione\Emojione::$shortcode_replace as $key => $icon) { - if (!in_array($key, CourseChatUtils::getEmojisToInclude())) { - continue; - } - - $iconList[$key] = strtoupper($icon).'.png'; -} - $view = new Template(get_lang('Chat'), false, false, false, true, false); $view->assign('icons', $iconList); $view->assign('emoji_strategy', CourseChatUtils::getEmojiStrategy()); -$view->assign('emoji_smile', \Emojione\Emojione::toImage(':smile:')); +//$view->assign('emoji_smile', \Emojione\Emojione::toImage(':smile:')); $view->assign('restrict_to_coach', api_get_configuration_value('course_chat_restrict_to_coach')); - +$view->assign('user', api_get_user_info()); $template = $view->get_template('chat/chat.tpl'); $content = $view->fetch($template); diff --git a/public/main/inc/ajax/course_chat.ajax.php b/public/main/inc/ajax/course_chat.ajax.php deleted file mode 100644 index 9f6473cfa5..0000000000 --- a/public/main/inc/ajax/course_chat.ajax.php +++ /dev/null @@ -1,85 +0,0 @@ - false]; - -$courseChatUtils = new CourseChatUtils($courseId, $userId, $sessionId, $groupId); - -switch ($_REQUEST['action']) { - case 'chat_logout': - $logInfo = [ - 'tool' => TOOL_CHAT, - 'tool_id' => 0, - 'tool_id_detail' => 0, - 'action' => 'exit', - 'action_details' => 'exit-chat', - 'info' => '', - ]; - Event::registerLog($logInfo); - break; - case 'track': - $courseChatUtils->keepUserAsConnected(); - $courseChatUtils->disconnectInactiveUsers(); - - $friend = isset($_REQUEST['friend']) ? (int) $_REQUEST['friend'] : 0; - $filePath = $courseChatUtils->getFileName(true, $friend); - $newFileSize = file_exists($filePath) ? filesize($filePath) : 0; - $oldFileSize = isset($_GET['size']) ? (int) $_GET['size'] : -1; - $newUsersOnline = $courseChatUtils->countUsersOnline(); - $oldUsersOnline = isset($_GET['users_online']) ? (int) $_GET['users_online'] : 0; - - $json = [ - 'status' => true, - 'data' => [ - 'oldFileSize' => file_exists($filePath) ? filesize($filePath) : 0, - 'history' => $newFileSize !== $oldFileSize ? $courseChatUtils->readMessages(false, $friend) : null, - 'usersOnline' => $newUsersOnline, - 'userList' => $newUsersOnline != $oldUsersOnline ? $courseChatUtils->listUsersOnline() : null, - 'currentFriend' => $friend, - ], - ]; - - break; - case 'preview': - $json = [ - 'status' => true, - 'data' => [ - 'message' => CourseChatUtils::prepareMessage($_REQUEST['message']), - ], - ]; - break; - case 'reset': - $friend = isset($_REQUEST['friend']) ? (int) $_REQUEST['friend'] : 0; - - $json = [ - 'status' => true, - 'data' => $courseChatUtils->readMessages(true, $friend), - ]; - break; - case 'write': - $friend = isset($_REQUEST['friend']) ? (int) $_REQUEST['friend'] : 0; - $writed = $courseChatUtils->saveMessage($_POST['message'], $friend); - - $json = [ - 'status' => $writed, - 'data' => [ - 'writed' => $writed, - ], - ]; - break; -} - -header('Content-Type: application/json'); -echo json_encode($json); diff --git a/public/main/inc/lib/CourseChatUtils.php b/public/main/inc/lib/CourseChatUtils.php index ea430b6453..ea94730849 100644 --- a/public/main/inc/lib/CourseChatUtils.php +++ b/public/main/inc/lib/CourseChatUtils.php @@ -1,14 +1,22 @@ courseId = (int) $courseId; $this->userId = (int) $userId; $this->sessionId = (int) $sessionId; $this->groupId = (int) $groupId; + $this->resourceNode = $resourceNode; + $this->repository = $repository; } /** @@ -50,8 +61,8 @@ class CourseChatUtils return ''; } - Emojione\Emojione::$imagePathPNG = api_get_path(WEB_LIBRARY_PATH).'javascript/emojione/png/'; - Emojione\Emojione::$ascii = true; + //Emojione\Emojione::$imagePathPNG = api_get_path(WEB_LIBRARY_PATH).'javascript/emojione/png/'; + //Emojione\Emojione::$ascii = true; $message = trim($message); $message = nl2br($message); @@ -69,8 +80,9 @@ class CourseChatUtils '', $message ); + // Parsing emojis - $message = Emojione\Emojione::toImage($message); + //$message = Emojione\Emojione::toImage($message); // Parsing text to understand markdown (code highlight) $message = MarkdownExtra::defaultTransform($message); @@ -95,22 +107,6 @@ class CourseChatUtils $user = api_get_user_entity($this->userId); $courseInfo = api_get_course_info_by_id($this->courseId); $isMaster = api_is_course_admin(); - $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'; - $basepath_chat = '/chat_files'; - $group_info = []; - if ($this->groupId) { - $group_info = GroupManager::get_group_properties($this->groupId); - $basepath_chat = $group_info['directory'].'/chat_files'; - } - - $chat_path = $document_path.$basepath_chat.'/'; - - if (!is_dir($chat_path)) { - if (is_file($chat_path)) { - @unlink($chat_path); - } - } - $date_now = date('Y-m-d'); $timeNow = date('d/m/y H:i:s'); $basename_chat = 'messages-'.$date_now; @@ -129,30 +125,10 @@ class CourseChatUtils $message = self::prepareMessage($message); - $fileTitle = $basename_chat.'.log.html'; - $filePath = $basepath_chat.'/'.$fileTitle; - $absoluteFilePath = $chat_path.$fileTitle; - - if (!file_exists($absoluteFilePath)) { - $doc_id = DocumentManager::addDocument( - $courseInfo, - $filePath, - 'file', - 0, - $fileTitle, - null, - 0, - true, - 0, - 0, - 0, - false - ); - } else { - $doc_id = DocumentManager::get_document_id($courseInfo, $filePath); - } + $fileTitle = $basename_chat.'-log.html'; + + - $fp = fopen($absoluteFilePath, 'a'); $userPhoto = UserManager::getUserPicture($this->userId, USER_IMAGE_SIZE_MEDIUM); if ($isMaster) { @@ -181,11 +157,26 @@ class CourseChatUtils '; } - fputs($fp, $fileContent); + $criteria = [ + 'slug' => $fileTitle, + 'parent' => $this->resourceNode, + ]; + + $resourceNode = $this->repository->getResourceNodeRepository()->findOneBy($criteria); + if ($resourceNode) { + $resource = $this->repository->getResourceFromResourceNode($resourceNode->getId()); + if ($resource) { + $content = $this->repository->getResourceNodeFileContent($resourceNode); + $this->repository->updateResourceFileContent($resource, $content.$fileContent); + } + } + + + /*fputs($fp, $fileContent); fclose($fp); $size = filesize($absoluteFilePath); update_existing_document($courseInfo, $doc_id, $size); - item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId); + item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);*/ return true; } @@ -266,17 +257,18 @@ class CourseChatUtils $extraCondition = null; if ($this->groupId) { - $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId); + $extraCondition = 'AND ccc.toGroupId = '.$this->groupId; } else { - $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId); + $extraCondition = 'AND ccc.sessionId = '.$this->sessionId; } $currentTime = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC')); + /** @var CChatConnected $connection */ $connection = $em ->createQuery(" SELECT ccc FROM ChamiloCourseBundle:CChatConnected ccc - WHERE ccc.userId = :user AND ccc.cId = :course $extraCondition + WHERE ccc.userId = :user AND ccc.cId = :course $extraCondition ") ->setParameters([ 'user' => $this->userId, @@ -286,7 +278,7 @@ class CourseChatUtils if ($connection) { $connection->setLastConnection($currentTime); - $em->merge($connection); + $em->persist($connection); $em->flush(); return; @@ -309,10 +301,10 @@ class CourseChatUtils * * @return array */ - public static function getEmojiStrategy() + /*public static function getEmojiStrategy() { return require_once api_get_path(SYS_CODE_PATH).'chat/emoji_strategy.php'; - } + }*/ /** * Get the emoji list to include in chat. @@ -416,8 +408,9 @@ class CourseChatUtils return $base; } - $courseInfo = api_get_course_info_by_id($this->courseId); - $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'; + //$courseInfo = api_get_course_info_by_id($this->courseId); + + $document_path = '/document'; $chatPath = $document_path.'/chat_files/'; if ($this->groupId) { @@ -441,119 +434,72 @@ class CourseChatUtils $courseInfo = api_get_course_info_by_id($this->courseId); $date_now = date('Y-m-d'); $isMaster = (bool) api_is_course_admin(); - $basepath_chat = '/chat_files'; - $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'; - $group_info = []; + //$basepath_chat = '/chat_files'; + //$document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'; if ($this->groupId) { $group_info = GroupManager:: get_group_properties($this->groupId); - $basepath_chat = $group_info['directory'].'/chat_files'; + //$basepath_chat = $group_info['directory'].'/chat_files'; } - $chat_path = $document_path.$basepath_chat.'/'; - - if (!is_dir($chat_path)) { - if (is_file($chat_path)) { - @unlink($chat_path); - } - - if (!api_is_anonymous()) { - @mkdir($chat_path, api_get_permissions_for_new_directories()); - // Save chat files document for group into item property - if ($this->groupId) { - DocumentManager::addDocument( - $courseInfo, - $basepath_chat, - 'folder', - 0, - 'chat_files', - null, - 0, - true, - 0, - 0, - 0, - false - ); - } - } - } + //$chat_path = $document_path.$basepath_chat.'/'; - $filename_chat = 'messages-'.$date_now.'.log.html'; + $filename_chat = 'messages-'.$date_now.'-log.html'; if ($this->groupId && !$friendId) { - $filename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId.'.log.html'; + $filename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId.'-log.html'; } elseif ($this->sessionId && !$friendId) { - $filename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId.'.log.html'; + $filename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId.'-log.html'; } elseif ($friendId) { if ($this->userId < $friendId) { - $filename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId.'.log.html'; + $filename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId.'-log.html'; } else { - $filename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId.'.log.html'; + $filename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId.'-log.html'; } } - if (!file_exists($chat_path.$filename_chat)) { - @fclose(fopen($chat_path.$filename_chat, 'w')); - if (!api_is_anonymous()) { - DocumentManager::addDocument( - $courseInfo, - $basepath_chat.'/'.$filename_chat, - 'file', - 0, - $filename_chat, - null, - 0, - true, - 0, - 0, - 0, - false - ); - } + $criteria = [ + 'slug' => $filename_chat, + 'parent' => $this->resourceNode, + ]; + + $resourceNode = $this->repository->getResourceNodeRepository()->findOneBy($criteria); + + /** @var ResourceNode $resourceNode */ + //$resourceNode = $this->repository->findOneBy($criteria); + //var_dump($filename_chat, $this->resourceNode->getId());exit; + + if (null === $resourceNode) { + $em = Database::getManager(); + $resource = new CChatConversation(); + $resource->setName($filename_chat); + + $handle = tmpfile(); + fwrite($handle, ''); + $meta = stream_get_meta_data($handle); + $file = new UploadedFile($meta['uri'], $filename_chat, 'text/html', null, true); + + $this->repository->addResourceToCourse( + $resource, + ResourceLink::VISIBILITY_PUBLISHED, + api_get_user_entity(api_get_user_id()), + api_get_course_entity(), + api_get_session_entity(), + api_get_group_entity(), + $file + ); + $em->flush(); + + $resourceNode = $resource->getResourceNode(); } - $basename_chat = 'messages-'.$date_now; - if ($this->groupId && !$friendId) { - $basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId; - } elseif ($this->sessionId && !$friendId) { - $basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId; - } elseif ($friendId) { - if ($this->userId < $friendId) { - $basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId; - } else { - $basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId; - } + if ($resourceNode->hasResourceFile()) { + //$resourceFile = $resourceNode->getResourceFile(); + //$fileName = $this->getFilename($resourceFile); + return $this->repository->getResourceNodeFileContent($resourceNode); } - if ($reset && $isMaster) { - $i = 1; - while (file_exists($chat_path.$basename_chat.'-'.$i.'.log.html')) { - $i++; - } + return ''; - @rename($chat_path.$basename_chat.'.log.html', $chat_path.$basename_chat.'-'.$i.'.log.html'); - @fclose(fopen($chat_path.$basename_chat.'.log.html', 'w')); - - $doc_id = DocumentManager::addDocument( - $courseInfo, - $basepath_chat.'/'.$basename_chat.'-'.$i.'.log.html', - 'file', - filesize($chat_path.$basename_chat.'-'.$i.'.log.html'), - $basename_chat.'-'.$i.'.log.html', - null, - 0, - true, - 0, - 0, - 0, - false - ); - $doc_id = DocumentManager::get_document_id( - $courseInfo, - $basepath_chat.'/'.$basename_chat.'.log.html' - ); - update_existing_document($courseInfo, $doc_id, 0); - } $remove = 0; $content = []; @@ -605,9 +551,9 @@ class CourseChatUtils $date->modify('-5 seconds'); if ($this->groupId) { - $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId); + $extraCondition = 'AND ccc.toGroupId = '.$this->groupId; } else { - $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId); + $extraCondition = 'AND ccc.sessionId = '.$this->sessionId; } $number = Database::getManager() @@ -627,10 +573,6 @@ class CourseChatUtils /** * Get the users online data. * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * * @return array */ public function listUsersOnline() @@ -686,11 +628,7 @@ class CourseChatUtils /** * Get the users subscriptions (SessionRelCourseRelUser array or CourseRelUser array) for chat. * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * - * @return \Doctrine\Common\Collections\ArrayCollection + * @return ArrayCollection */ private function getUsersSubscriptions() { @@ -772,9 +710,9 @@ class CourseChatUtils $date->modify('-5 seconds'); if ($this->groupId) { - $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId); + $extraCondition = 'AND ccc.toGroupId = '.$this->groupId; } else { - $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId); + $extraCondition = 'AND ccc.sessionId = '.$this->sessionId; } $number = Database::getManager() diff --git a/public/main/chat/sound/notification.mp3 b/public/sound/notification.mp3 similarity index 100% rename from public/main/chat/sound/notification.mp3 rename to public/sound/notification.mp3 diff --git a/public/main/chat/sound/notification.ogg b/public/sound/notification.ogg similarity index 100% rename from public/main/chat/sound/notification.ogg rename to public/sound/notification.ogg diff --git a/public/main/chat/sound/notification.wav b/public/sound/notification.wav similarity index 100% rename from public/main/chat/sound/notification.wav rename to public/sound/notification.wav diff --git a/src/CoreBundle/Controller/AbstractResourceController.php b/src/CoreBundle/Controller/AbstractResourceController.php index 41bb376a55..bae99b6d06 100644 --- a/src/CoreBundle/Controller/AbstractResourceController.php +++ b/src/CoreBundle/Controller/AbstractResourceController.php @@ -6,11 +6,13 @@ namespace Chamilo\CoreBundle\Controller; use Chamilo\CoreBundle\Component\Utils\Glide; use Chamilo\CoreBundle\Entity\Resource\AbstractResource; +use Chamilo\CoreBundle\Entity\Resource\ResourceNode; use Chamilo\CoreBundle\Repository\ResourceFactory; use Chamilo\CoreBundle\Repository\ResourceRepository; +use Chamilo\UserBundle\Entity\User; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Vich\UploaderBundle\Storage\FlysystemStorage; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; /** * Class AbstractResourceController. @@ -29,6 +31,11 @@ abstract class AbstractResourceController extends BaseController $tool = $request->get('tool'); $type = $request->get('type'); + return $this->getRepository($tool, $type); + } + + public function getRepository($tool, $type): ResourceRepository + { return $this->resourceRepositoryFactory->createRepository($tool, $type); } @@ -54,6 +61,32 @@ abstract class AbstractResourceController extends BaseController } } + protected function getParentResourceNode(Request $request): ResourceNode + { + $parentNodeId = $request->get('id'); + + $parentResourceNode = null; + if (empty($parentNodeId)) { + if ($this->hasCourse()) { + $parentResourceNode = $this->getCourse()->getResourceNode(); + } else { + if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + /** @var User $user */ + $parentResourceNode = $this->getUser()->getResourceNode(); + } + } + } else { + $repo = $this->getDoctrine()->getRepository('ChamiloCoreBundle:Resource\ResourceNode'); + $parentResourceNode = $repo->find($parentNodeId); + } + + if (null === $parentResourceNode) { + throw new AccessDeniedException(); + } + + return $parentResourceNode; + } + /** * @return Glide */ diff --git a/src/CoreBundle/Controller/ChatController.php b/src/CoreBundle/Controller/ChatController.php new file mode 100644 index 0000000000..80f8434e24 --- /dev/null +++ b/src/CoreBundle/Controller/ChatController.php @@ -0,0 +1,145 @@ + TOOL_CHAT, + 'action' => 'start', + 'action_details' => 'start-chat', + ]; + Event::registerLog($logInfo); + + return $this->render( + '@ChamiloTheme/Chat/chat.html.twig', + [ + 'restrict_to_coach' => api_get_configuration_value('course_chat_restrict_to_coach'), + 'user' => api_get_user_info(), + ] + ); + } + + /** + * @Route("/resources/chat/conversations/", name="chat_ajax", options={"expose"=true}) + */ + public function ajaxAction(Request $request, ResourceNodeRepository $repo): Response + { + if (!api_protect_course_script(false)) { + exit; + } + + /** @var CChatConversationRepository $resourceRepo */ + $resourceRepo = $this->getRepository('chat', 'conversations'); + + $courseId = api_get_course_int_id(); + $userId = api_get_user_id(); + $sessionId = api_get_session_id(); + $groupId = api_get_group_id(); + $json = ['status' => false]; + $parentResourceNode = $this->getParentResourceNode($request); + + $courseChatUtils = new \CourseChatUtils( + $courseId, + $userId, + $sessionId, + $groupId, + $parentResourceNode, + $resourceRepo + ); + + $action = $request->get('action'); + + switch ($action) { + case 'chat_logout': + $logInfo = [ + 'tool' => TOOL_CHAT, + 'action' => 'exit', + 'action_details' => 'exit-chat', + ]; + Event::registerLog($logInfo); + + break; + case 'track': + $courseChatUtils->keepUserAsConnected(); + $courseChatUtils->disconnectInactiveUsers(); + + $friend = isset($_REQUEST['friend']) ? (int) $_REQUEST['friend'] : 0; + //$filePath = $courseChatUtils->getFileName(true, $friend); + //$newFileSize = file_exists($filePath) ? filesize($filePath) : 0; + //$oldFileSize = isset($_GET['size']) ? (int) $_GET['size'] : -1; + $newUsersOnline = $courseChatUtils->countUsersOnline(); + $oldUsersOnline = isset($_GET['users_online']) ? (int) $_GET['users_online'] : 0; + + $json = [ + 'status' => true, + 'data' => [ + //'oldFileSize' => file_exists($filePath) ? filesize($filePath) : 0, + 'oldFileSize' => false, + 'history' => $courseChatUtils->readMessages(false, $friend), + 'usersOnline' => $newUsersOnline, + 'userList' => $newUsersOnline != $oldUsersOnline ? $courseChatUtils->listUsersOnline() : null, + 'currentFriend' => $friend, + ], + ]; + + break; + case 'preview': + $json = [ + 'status' => true, + 'data' => [ + 'message' => CourseChatUtils::prepareMessage($_REQUEST['message']), + ], + ]; + + break; + case 'reset': + $friend = isset($_REQUEST['friend']) ? (int) $_REQUEST['friend'] : 0; + + $json = [ + 'status' => true, + 'data' => $courseChatUtils->readMessages(true, $friend), + ]; + + break; + case 'write': + $friend = isset($_REQUEST['friend']) ? (int) $_REQUEST['friend'] : 0; + $status = $courseChatUtils->saveMessage($_REQUEST['message'], $friend); + + $json = [ + 'status' => $status, + 'data' => [ + 'writed' => $status, + ], + ]; + + break; + } + + return new JsonResponse($json); + } +} diff --git a/src/CoreBundle/Controller/ResourceController.php b/src/CoreBundle/Controller/ResourceController.php index c21bee3cd6..4eba437784 100644 --- a/src/CoreBundle/Controller/ResourceController.php +++ b/src/CoreBundle/Controller/ResourceController.php @@ -23,7 +23,6 @@ use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter; use Chamilo\CourseBundle\Controller\CourseControllerInterface; use Chamilo\CourseBundle\Controller\CourseControllerTrait; use Chamilo\CourseBundle\Entity\CDocument; -use Chamilo\UserBundle\Entity\User; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\QueryBuilder; @@ -40,7 +39,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Router; use Symfony\Component\Routing\RouterInterface; -use Symfony\Component\Security\Core\Exception\AccessDeniedException; use ZipStream\Option\Archive; use ZipStream\ZipStream; @@ -1074,32 +1072,6 @@ class ResourceController extends AbstractResourceController implements CourseCon } } - private function getParentResourceNode(Request $request): ResourceNode - { - $parentNodeId = $request->get('id'); - - $parentResourceNode = null; - if (empty($parentNodeId)) { - if ($this->hasCourse()) { - $parentResourceNode = $this->getCourse()->getResourceNode(); - } else { - if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { - /** @var User $user */ - $parentResourceNode = $this->getUser()->getResourceNode(); - } - } - } else { - $repo = $this->getDoctrine()->getRepository('ChamiloCoreBundle:Resource\ResourceNode'); - $parentResourceNode = $repo->find($parentNodeId); - } - - if (null === $parentResourceNode) { - throw new AccessDeniedException(); - } - - return $parentResourceNode; - } - /** * @param string $mode * @param string $filter @@ -1150,7 +1122,7 @@ class ResourceController extends AbstractResourceController implements CourseCon $params['crop'] = $crop; } - $fileName = $repo->getFilename($resourceFile); + $fileName = $repo->getResourceNodeRepository()->getFilename($resourceFile); return $server->getImageResponse($fileName, $params); } diff --git a/src/CoreBundle/Repository/ResourceFactory.php b/src/CoreBundle/Repository/ResourceFactory.php index 4a20deee7c..ec24afe59d 100644 --- a/src/CoreBundle/Repository/ResourceFactory.php +++ b/src/CoreBundle/Repository/ResourceFactory.php @@ -18,7 +18,6 @@ class ResourceFactory { protected $mountManager; protected $toolChain; - protected $fs; protected $slugify; protected $entityManager; protected $authorizationChecker; @@ -33,8 +32,6 @@ class ResourceFactory Container $container ) { $this->mountManager = $mountManager; - // @todo create a service to remove hardcode value of "resources_fs" - $this->fs = $mountManager->getFilesystem('resources_fs'); $this->toolChain = $toolChain; $this->slugify = $slugify; $this->entityManager = $entityManager; diff --git a/src/CoreBundle/Repository/ResourceNodeRepository.php b/src/CoreBundle/Repository/ResourceNodeRepository.php index eada2917f1..5a12aa532e 100644 --- a/src/CoreBundle/Repository/ResourceNodeRepository.php +++ b/src/CoreBundle/Repository/ResourceNodeRepository.php @@ -5,17 +5,80 @@ namespace Chamilo\CoreBundle\Repository; use Chamilo\CoreBundle\Entity\Course; +use Chamilo\CoreBundle\Entity\Resource\ResourceFile; use Chamilo\CoreBundle\Entity\Resource\ResourceLink; use Chamilo\CoreBundle\Entity\Resource\ResourceNode; use Chamilo\CoreBundle\Entity\Resource\ResourceType; use Chamilo\CoreBundle\Entity\Session; +use Doctrine\ORM\EntityManagerInterface; use Gedmo\Tree\Entity\Repository\MaterializedPathRepository; +use League\Flysystem\MountManager; +use Symfony\Component\Filesystem\Exception\FileNotFoundException; +use Vich\UploaderBundle\Storage\FlysystemStorage; /** * Class ResourceNodeRepository. */ class ResourceNodeRepository extends MaterializedPathRepository { + protected $mountManager; + protected $storage; + + public function __construct(EntityManagerInterface $manager, FlysystemStorage $storage, MountManager $mountManager) + { + parent::__construct($manager, $manager->getClassMetadata(ResourceNode::class)); + $this->storage = $storage; + $this->mountManager = $mountManager; + } + + public function getFilename(ResourceFile $resourceFile) + { + return $this->storage->resolveUri($resourceFile); + } + + /** + * @return \League\Flysystem\FilesystemInterface + */ + public function getFileSystem() + { + // Flysystem mount name is saved in config/packages/oneup_flysystem.yaml @todo add it as a service. + $this->fs = $this->mountManager->getFilesystem('resources_fs'); + + return $this->fs; + } + + public function getResourceNodeFileContent(ResourceNode $resourceNode): string + { + try { + if ($resourceNode->hasResourceFile()) { + $resourceFile = $resourceNode->getResourceFile(); + $fileName = $this->getFilename($resourceFile); + + return $this->getFileSystem()->read($fileName); + } + + return ''; + } catch (\Throwable $exception) { + throw new FileNotFoundException($resourceNode); + } + } + + public function getResourceNodeFileStream(ResourceNode $resourceNode) + { + try { + if ($resourceNode->hasResourceFile()) { + $resourceFile = $resourceNode->getResourceFile(); + $fileName = $this->getFilename($resourceFile); + + return $this->getFileSystem()->readStream($fileName); + } + + return ''; + } catch (\Throwable $exception) { + throw new FileNotFoundException($resourceNode); + } + } + /** * @todo filter files, check status */ diff --git a/src/CoreBundle/Repository/ResourceRepository.php b/src/CoreBundle/Repository/ResourceRepository.php index e4df51e639..055fb896d0 100644 --- a/src/CoreBundle/Repository/ResourceRepository.php +++ b/src/CoreBundle/Repository/ResourceRepository.php @@ -29,7 +29,6 @@ use Doctrine\ORM\EntityRepository as BaseEntityRepository; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use League\Flysystem\FilesystemInterface; -use League\Flysystem\MountManager; use Symfony\Component\Filesystem\Exception\FileNotFoundException; use Symfony\Component\Form\FormFactory; use Symfony\Component\Form\FormInterface; @@ -37,7 +36,6 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -use Vich\UploaderBundle\Storage\FlysystemStorage; /** * Class ResourceRepository. @@ -78,40 +76,28 @@ class ResourceRepository extends BaseEntityRepository */ protected $authorizationChecker; - /** @var MountManager */ - protected $mountManager; - /** @var SlugifyInterface */ protected $slugify; /** @var ToolChain */ protected $toolChain; - /** @var FlysystemStorage */ - protected $storage; - /** * ResourceRepository constructor. */ public function __construct( AuthorizationCheckerInterface $authorizationChecker, EntityManager $entityManager, - MountManager $mountManager, RouterInterface $router, SlugifyInterface $slugify, ToolChain $toolChain, - FlysystemStorage $storage, + ResourceNodeRepository $resourceNodeRepository, string $className ) { $this->authorizationChecker = $authorizationChecker; $this->repository = $entityManager->getRepository($className); - - // Flysystem mount name is saved in config/packages/oneup_flysystem.yaml @todo add it as a service. - $this->fs = $mountManager->getFilesystem('resources_fs'); - $this->storage = $storage; - $this->mountManager = $mountManager; $this->router = $router; - $this->resourceNodeRepository = $entityManager->getRepository('ChamiloCoreBundle:Resource\ResourceNode'); + $this->resourceNodeRepository = $resourceNodeRepository; $this->slugify = $slugify; $this->toolChain = $toolChain; } @@ -139,14 +125,6 @@ class ResourceRepository extends BaseEntityRepository return $this->resourceNodeRepository; } - /** - * @return FilesystemInterface - */ - public function getFileSystem() - { - return $this->fs; - } - public function getEntityManager(): EntityManager { return $this->getRepository()->getEntityManager(); @@ -737,14 +715,8 @@ class ResourceRepository extends BaseEntityRepository { try { $resourceNode = $resource->getResourceNode(); - if ($resourceNode->hasResourceFile()) { - $resourceFile = $resourceNode->getResourceFile(); - $fileName = $this->getFilename($resourceFile); - - return $this->getFileSystem()->read($fileName); - } - return ''; + return $this->resourceNodeRepository->getResourceNodeFileContent($resourceNode); } catch (\Throwable $exception) { throw new FileNotFoundException($resource); } @@ -752,39 +724,12 @@ class ResourceRepository extends BaseEntityRepository public function getResourceNodeFileContent(ResourceNode $resourceNode): string { - try { - if ($resourceNode->hasResourceFile()) { - $resourceFile = $resourceNode->getResourceFile(); - $fileName = $this->getFilename($resourceFile); - - return $this->getFileSystem()->read($fileName); - } - - return ''; - } catch (\Throwable $exception) { - throw new FileNotFoundException($resourceNode); - } + return $this->resourceNodeRepository->getResourceNodeFileContent($resourceNode); } public function getResourceNodeFileStream(ResourceNode $resourceNode) { - try { - if ($resourceNode->hasResourceFile()) { - $resourceFile = $resourceNode->getResourceFile(); - $fileName = $this->getFilename($resourceFile); - - return $this->getFileSystem()->readStream($fileName); - } - - return ''; - } catch (\Throwable $exception) { - throw new FileNotFoundException($resourceNode); - } - } - - public function getFilename(ResourceFile $resourceFile) - { - return $this->storage->resolveUri($resourceFile); + return $this->resourceNodeRepository->getResourceNodeFileStream($resourceNode); } public function getResourceFileUrl(AbstractResource $resource, array $extraParams = [], $referenceType = null): string @@ -837,9 +782,9 @@ class ResourceRepository extends BaseEntityRepository if ($resourceNode->hasResourceFile()) { $resourceFile = $resourceNode->getResourceFile(); if ($resourceFile) { - $fileName = $this->getFilename($resourceFile); - $this->getFileSystem()->update($fileName, $content); - $size = $this->getFileSystem()->getSize($fileName); + $fileName = $this->getResourceNodeRepository()->getFilename($resourceFile); + $this->getResourceNodeRepository()->getFileSystem()->update($fileName, $content); + $size = $this->getResourceNodeRepository()->getSize($fileName); if ($resource instanceof CDocument) { $resource->setSize($size); diff --git a/src/CoreBundle/Resources/config/repositories.yml b/src/CoreBundle/Resources/config/repositories.yml index 489544d36f..47c287e1f4 100644 --- a/src/CoreBundle/Resources/config/repositories.yml +++ b/src/CoreBundle/Resources/config/repositories.yml @@ -4,12 +4,14 @@ services: public: true autoconfigure: true + Vich\UploaderBundle\Storage\FlysystemStorage: ~ Chamilo\CoreBundle\Repository\ResourceFactory: ~ + Chamilo\CoreBundle\Repository\ResourceNodeRepository: ~ # Classic entity repositories Chamilo\CoreBundle\Repository\: resource: '../../Repository' - exclude: '../../Repository/{BranchSyncRepository.php,ResourceRepository.php,ResourceNodeRepository.php}' + exclude: '../../Repository/{BranchSyncRepository.php,ResourceRepository.php}' tags: ['doctrine.repository_service'] # Resource repositories diff --git a/src/CoreBundle/Resources/config/tools.yml b/src/CoreBundle/Resources/config/tools.yml index 8d4253d254..e8877f3bb6 100644 --- a/src/CoreBundle/Resources/config/tools.yml +++ b/src/CoreBundle/Resources/config/tools.yml @@ -145,9 +145,11 @@ services: arguments: - 'chat' - 'interaction' - - '/main/chat/chat.php' + - '/resources/chat/' - '@chamilo_course.settings.chat' - - ~ + - + conversations: + repository: Chamilo\CourseBundle\Repository\CChatConversationRepository tags: - {name: chamilo_core.tool} diff --git a/src/CourseBundle/Entity/CChatConversation.php b/src/CourseBundle/Entity/CChatConversation.php new file mode 100644 index 0000000000..70a2d6ade8 --- /dev/null +++ b/src/CourseBundle/Entity/CChatConversation.php @@ -0,0 +1,81 @@ +getName(); + } + + /** + * @return int + */ + public function getId(): int + { + return $this->id; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $name + * + * @return CChatConversation + */ + public function setName(string $name): CChatConversation + { + $this->name = $name; + + return $this; + } + + /** + * Resource identifier. + */ + public function getResourceIdentifier(): int + { + return $this->getId(); + } + + public function getResourceName(): string + { + return $this->getName(); + } + +} diff --git a/src/CourseBundle/Repository/CChatConversationRepository.php b/src/CourseBundle/Repository/CChatConversationRepository.php new file mode 100644 index 0000000000..327344c89e --- /dev/null +++ b/src/CourseBundle/Repository/CChatConversationRepository.php @@ -0,0 +1,54 @@ +setAllowNodeCreation(false) + ->setAllowResourceCreation(false) + ->setAllowResourceUpload(false) + ; + + return $settings; + } + + public function getResources(User $user, ResourceNode $parentNode, Course $course = null, Session $session = null, CGroupInfo $group = null): QueryBuilder + { + return $this->getResourcesByCourse($course, $session, $group, $parentNode); + } + + public function saveUpload(UploadedFile $file) + { + throw new AccessDeniedException(); + } + + public function saveResource(FormInterface $form, $course, $session, $fileType) + { + } + + public function getTitleColumn(Grid $grid): Column + { + return $grid->getColumn('name'); + } +} diff --git a/src/CourseBundle/Repository/CForumPostRepository.php b/src/CourseBundle/Repository/CForumPostRepository.php index a7938369d2..c4be49589d 100644 --- a/src/CourseBundle/Repository/CForumPostRepository.php +++ b/src/CourseBundle/Repository/CForumPostRepository.php @@ -72,6 +72,7 @@ class CForumPostRepository extends ResourceRepository public function delete(AbstractResource $resource) { + /** @var CForumPost $resource */ $attachments = $resource->getAttachments(); if (!empty($attachments)) { foreach ($attachments as $attachment) { diff --git a/src/CourseBundle/Resources/config/services.yml b/src/CourseBundle/Resources/config/services.yml index 30cf3de1bc..e40fa51580 100644 --- a/src/CourseBundle/Resources/config/services.yml +++ b/src/CourseBundle/Resources/config/services.yml @@ -75,6 +75,10 @@ services: arguments: $className: 'Chamilo\CourseBundle\Entity\CCalendarEvent' + Chamilo\CourseBundle\Repository\CChatConversationRepository: + arguments: + $className: 'Chamilo\CourseBundle\Entity\CChatConversation' + Chamilo\CourseBundle\Repository\CDocumentRepository: arguments: $className: 'Chamilo\CourseBundle\Entity\CDocument' diff --git a/public/main/template/default/chat/chat.html.twig b/src/ThemeBundle/Resources/views/Chat/chat.html.twig similarity index 81% rename from public/main/template/default/chat/chat.html.twig rename to src/ThemeBundle/Resources/views/Chat/chat.html.twig index 420f1e30eb..c4c4a87e20 100644 --- a/public/main/template/default/chat/chat.html.twig +++ b/src/ThemeBundle/Resources/views/Chat/chat.html.twig @@ -1,3 +1,7 @@ +{% extends "@ChamiloTheme/Layout/no_layout.html.twig" %} + +{% block content %} + {% autoescape false %}
@@ -60,14 +64,14 @@
+ +{% endautoescape %} +{% endblock %} \ No newline at end of file diff --git a/public/main/template/default/chat/video.html.twig b/src/ThemeBundle/Resources/views/Chat/video.html.twig similarity index 100% rename from public/main/template/default/chat/video.html.twig rename to src/ThemeBundle/Resources/views/Chat/video.html.twig diff --git a/yarn.lock b/yarn.lock index 4532cee8db..d2b0ebe798 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3094,6 +3094,11 @@ ev-emitter@^1.0.0: resolved "https://registry.yarnpkg.com/ev-emitter/-/ev-emitter-1.1.1.tgz#8f18b0ce5c76a5d18017f71c0a795c65b9138f2a" integrity sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q== +eventemitter3@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" + integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= + eventemitter3@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" @@ -3886,6 +3891,11 @@ highlight.js@^9.12.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c" integrity sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg== +hljs@^6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/hljs/-/hljs-6.2.3.tgz#d4d6208fa2a84f294956bc50f2c812e9cbd49bcc" + integrity sha1-1NYgj6KoTylJVrxQ8sgS6cvUm8w= + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -7571,6 +7581,20 @@ terser@^4.1.2: source-map "~0.6.1" source-map-support "~0.5.12" +textarea-caret@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.1.0.tgz#5d5a35bb035fd06b2ff0e25d5359e97f2655087f" + integrity sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q== + +textcomplete@^0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/textcomplete/-/textcomplete-0.18.1.tgz#43a3eb545275b5f5714669220a4725b69f13b490" + integrity sha512-ukWti83oL4rBAnj6HVwl76mhKRScAvjJps90EeT5qVYm6dwwa0XCytLCfSGQOYbj4CyjgN//oIfq+Gnb29KCPg== + dependencies: + eventemitter3 "^2.0.3" + textarea-caret "^3.0.1" + undate "^0.2.3" + through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -7749,6 +7773,11 @@ uglifyjs-webpack-plugin@^1.3.0: webpack-sources "^1.1.0" worker-farm "^1.5.2" +undate@^0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/undate/-/undate-0.2.4.tgz#ccb2a8cf38edc035d1006fcb2909c4c6024a8400" + integrity sha512-k1WTRVhI076HYqP6e3pZPzS7K2xNAcuhCcWOPjHmR8SwU3byyKYvyNZ4XTEAd7Ofe40+wrEjNq6WmmO8WoUVNg== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"