Load chat tool as a resource

pull/3124/head
Julio Montoya 5 years ago
parent 45d6994726
commit 0f570fd0b2
  1. 6
      assets/js/vendor.js
  2. 2
      composer.json
  3. 2
      package.json
  4. 26
      public/main/chat/chat.php
  5. 85
      public/main/inc/ajax/course_chat.ajax.php
  6. 258
      public/main/inc/lib/CourseChatUtils.php
  7. 0
      public/sound/notification.mp3
  8. 0
      public/sound/notification.ogg
  9. 0
      public/sound/notification.wav
  10. 35
      src/CoreBundle/Controller/AbstractResourceController.php
  11. 145
      src/CoreBundle/Controller/ChatController.php
  12. 30
      src/CoreBundle/Controller/ResourceController.php
  13. 3
      src/CoreBundle/Repository/ResourceFactory.php
  14. 63
      src/CoreBundle/Repository/ResourceNodeRepository.php
  15. 71
      src/CoreBundle/Repository/ResourceRepository.php
  16. 4
      src/CoreBundle/Resources/config/repositories.yml
  17. 6
      src/CoreBundle/Resources/config/tools.yml
  18. 81
      src/CourseBundle/Entity/CChatConversation.php
  19. 54
      src/CourseBundle/Repository/CChatConversationRepository.php
  20. 1
      src/CourseBundle/Repository/CForumPostRepository.php
  21. 4
      src/CourseBundle/Resources/config/services.yml
  22. 107
      src/ThemeBundle/Resources/views/Chat/chat.html.twig
  23. 0
      src/ThemeBundle/Resources/views/Chat/video.html.twig
  24. 29
      yarn.lock

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

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

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

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

@ -1,85 +0,0 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Responses to AJAX calls for course chat.
*/
require_once __DIR__.'/../global.inc.php';
if (!api_protect_course_script(false)) {
exit;
}
$courseId = api_get_course_int_id();
$userId = api_get_user_id();
$sessionId = api_get_session_id();
$groupId = api_get_group_id();
$json = ['status' => 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);

@ -1,14 +1,22 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\CourseRelUser;
use Chamilo\CoreBundle\Entity\Resource\ResourceLink;
use Chamilo\CoreBundle\Entity\Resource\ResourceNode;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
use Chamilo\CoreBundle\Repository\ResourceNodeRepository;
use Chamilo\CoreBundle\Repository\ResourceRepository;
use Chamilo\CourseBundle\Entity\CChatConnected;
use Chamilo\CourseBundle\Entity\CChatConversation;
use Chamilo\UserBundle\Entity\User;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Michelf\MarkdownExtra;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Class CourseChat
@ -20,6 +28,7 @@ class CourseChatUtils
private $courseId;
private $sessionId;
private $userId;
private $resourceNode;
/**
* CourseChat constructor.
@ -29,12 +38,14 @@ class CourseChatUtils
* @param int $sessionId
* @param int $groupId
*/
public function __construct($courseId, $userId, $sessionId = 0, $groupId = 0)
public function __construct($courseId, $userId, $sessionId = 0, $groupId = 0, ResourceNode $resourceNode, ResourceRepository $repository)
{
$this->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
'<a href="http://$1" target="_blank">',
$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()

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

@ -0,0 +1,145 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Controller;
use Chamilo\CoreBundle\Repository\ResourceNodeRepository;
use Chamilo\CourseBundle\Controller\CourseControllerInterface;
use Chamilo\CourseBundle\Controller\CourseControllerTrait;
use Chamilo\CourseBundle\Repository\CChatConversationRepository;
use Event;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class ChatController.
*/
class ChatController extends AbstractResourceController implements CourseControllerInterface
{
use CourseControllerTrait;
/**
* @Route("/resources/chat/", name="chat_home", options={"expose"=true})
*/
public function indexAction(Request $request): Response
{
Event::event_access_tool(TOOL_CHAT);
$logInfo = [
'tool' => 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);
}
}

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

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

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

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

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

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

@ -0,0 +1,81 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\CourseBundle\Entity;
use Chamilo\CoreBundle\Entity\Resource\AbstractResource;
use Chamilo\CoreBundle\Entity\Resource\ResourceInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* CChatConversation.
*
* @ORM\Table(name="c_chat_conversation")
* @ORM\Entity
*/
class CChatConversation extends AbstractResource implements ResourceInterface
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255, nullable=true)
*/
protected $name;
public function __toString(): string
{
return $this->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();
}
}

@ -0,0 +1,54 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\CourseBundle\Repository;
use APY\DataGridBundle\Grid\Column\Column;
use APY\DataGridBundle\Grid\Grid;
use Chamilo\CoreBundle\Component\Utils\ResourceSettings;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Resource\ResourceNode;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Repository\ResourceRepository;
use Chamilo\CoreBundle\Repository\ResourceRepositoryInterface;
use Chamilo\CourseBundle\Entity\CGroupInfo;
use Chamilo\UserBundle\Entity\User;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
final class CChatConversationRepository extends ResourceRepository implements ResourceRepositoryInterface
{
public function getResourceSettings(): ResourceSettings
{
$settings = new ResourceSettings();
$settings
->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');
}
}

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

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

@ -1,3 +1,7 @@
{% extends "@ChamiloTheme/Layout/no_layout.html.twig" %}
{% block content %}
{% autoescape false %}
<div class="page-chat">
<div class="row">
<div class="col-sm-4 col-md-5 col-lg-4">
@ -60,14 +64,14 @@
</div>
</div>
<audio id="chat-alert" class="skip">
<source src="{{ _p.web_main }}/chat/sound/notification.wav" type="audio/wav"></source>
<source src="{{ _p.web_main }}chat/sound/notification.ogg" type="audio/ogg"></source>
<source src="{{ _p.web_main }}chat/sound/notification.mp3" type="audio/mpeg"></source>
<source src="{{ asset('sound/notification.wav') }}" type="audio/wav"></source>
<source src="{{ asset('chat/sound/notification.ogg') }}" type="audio/ogg"></source>
<source src="{{ asset('chat/sound/notification.mp3') }}" type="audio/mpeg"></source>
</audio>
<script>
$(function () {
var ChChat = {
_ajaxUrl: '{{ _p.web_ajax }}course_chat.ajax.php?{{ _p.web_cid_query }}',
_ajaxUrl: '{{ url('chat_ajax') ~ '?' ~ course_url_params }}',
_historySize: -1,
usersOnline: 0,
currentFriend: 0,
@ -100,7 +104,7 @@
.prop('scrollTop', function () {
return this.scrollHeight;
});
$('#chat-alert').get(0).play();
//$('#chat-alert').get(0).play();
},
setConnectedUsers: function (userList) {
var html = '';
@ -115,7 +119,7 @@
' <ul class="list-unstyled">' +
' <li>' + user.complete_name;
if (user.id != {{ _u.user_id }}) {
if (user.id != {{ user.id }}) {
html += ' <button type="button" class="btn btn-link btn-xs" title="' + buttonTitle + '" data-name="' + user.complete_name + '" data-user="' + user.id + '">' +
' <i class="fa fa-comments text-' + buttonStatus + '"></i><span class="sr-only">' + buttonTitle + '</span>' +
' </button>';
@ -134,18 +138,17 @@
$('#chat-users').html(html);
},
onPreviewListener: function () {
$
.post(ChChat._ajaxUrl, {
action: 'preview',
'message': $('textarea#chat-writer').val()
})
.done(function (response) {
if (!response.status) {
return;
}
$.post(ChChat._ajaxUrl, {
action: 'preview',
'message': $('textarea#chat-writer').val()
})
.done(function (response) {
if (!response.status) {
return;
}
$('#html-preview').html(response.data.message);
});
$('#html-preview').html(response.data.message);
});
},
onSendMessageListener: function (e) {
e.preventDefault();
@ -157,40 +160,39 @@
var self = this;
self.disabled = true;
$
.post(ChChat._ajaxUrl, {
action: 'write',
message: $('textarea#chat-writer').val(),
friend: ChChat.currentFriend
})
.done(function (response) {
self.disabled = false;
$.get(ChChat._ajaxUrl, {
action: 'write',
message: $('textarea#chat-writer').val(),
friend: ChChat.currentFriend
})
.done(function (response) {
self.disabled = false;
if (!response.status) {
return;
}
if (!response.status) {
return;
}
$('textarea#chat-writer').val('');
$(".emoji-wysiwyg-editor").html('');
});
$('textarea#chat-writer').val('');
$(".emoji-wysiwyg-editor").html('');
});
},
onResetListener: function (e) {
if (!confirm("{{ 'ConfirmReset'|get_lang }}")) {
e.preventDefault();
return;
}
$
.get(ChChat._ajaxUrl, {
action: 'reset',
friend: ChChat.currentFriend
})
.done(function (response) {
if (!response.status) {
return;
}
ChChat.setHistory(response.data);
});
$.get(ChChat._ajaxUrl, {
action: 'reset',
friend: ChChat.currentFriend
})
.done(function (response) {
if (!response.status) {
return;
}
ChChat.setHistory(response.data);
});
},
init: function () {
ChChat.track().done(function () {
@ -201,27 +203,17 @@
hljs.initHighlightingOnLoad();
emojione.ascii = true;
emojione.imagePathPNG = '{{ _p.web_lib }}javascript/emojione/png/';
emojione.imagePathSVG = '{{ _p.web_lib }}javascript/emojione/svg/';
emojione.imagePathSVGSprites = '{{ _p.web_lib }}javascript/emojione/sprites/';
var emojiStrategy = {{ emoji_strategy|json_encode }};
$.emojiarea.path = '{{ _p.web_lib }}javascript/emojione/png/';
$.emojiarea.icons = {{ icons|json_encode }};
$('body').on('click', '#chat-reset', ChChat.onResetListener);
$('#preview').on('click', ChChat.onPreviewListener);
$('#emojis').on('click', function () {
$('[data-toggle="tab"][href="#tab1"]').show().tab('show');
});
$('textarea#chat-writer').emojiarea({
/*$('textarea#chat-writer').emojiarea({
button: '#emojis'
});
});*/
$('body').delay(1500).find('.emoji-wysiwyg-editor').textcomplete([{
/*$('body').delay(1500).find('.emoji-wysiwyg-editor').textcomplete([{
match: /\B:([\-+\w]*)$/,
search: function (term, callback) {
var results = [];
@ -263,7 +255,7 @@
index: 1,
maxCount: 10
}], {});
*/
$('button#chat-send-message').on('click', ChChat.onSendMessageListener);
$('#chat-users').on('click', 'div.chat-user', function (e) {
e.preventDefault();
@ -328,3 +320,6 @@
ChChat.init();
});
</script>
{% endautoescape %}
{% endblock %}

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

Loading…
Cancel
Save