Plugin: External notification connect - refs BT#20443

pull/4505/head
Angel Fernando Quiroz Campos 3 years ago
parent f062a1e98c
commit e25066ac46
  1. 3
      plugin/externalnotificationconnect/README.md
  2. 5
      plugin/externalnotificationconnect/install.php
  3. 18
      plugin/externalnotificationconnect/lang/english.php
  4. 18
      plugin/externalnotificationconnect/lang/spanish.php
  5. 5
      plugin/externalnotificationconnect/plugin.php
  6. 71
      plugin/externalnotificationconnect/src/Entity/AccessToken.php
  7. 234
      plugin/externalnotificationconnect/src/ExternalNotificationConnectPlugin.php
  8. 21
      plugin/externalnotificationconnect/src/Hook/ExternalNotificationConnectHookObserver.php
  9. 55
      plugin/externalnotificationconnect/src/Hook/ExternalNotificationConnectLearningPathCreatedHookObserver.php
  10. 50
      plugin/externalnotificationconnect/src/Hook/ExternalNotificationConnectPortfolioItemAddedHookObserver.php
  11. 33
      plugin/externalnotificationconnect/src/Hook/ExternalNotificationConnectPortfolioItemDeletedHookObserver.php
  12. 50
      plugin/externalnotificationconnect/src/Hook/ExternalNotificationConnectPortfolioItemEditedHookObserver.php
  13. 149
      plugin/externalnotificationconnect/src/Traits/RequestTrait.php
  14. 5
      plugin/externalnotificationconnect/uninstall.php

@ -0,0 +1,3 @@
# External Notification Connect
Activate external notification system

@ -0,0 +1,5 @@
<?php
/* For licensing terms, see /license.txt */
ExternalNotificationConnectPlugin::create()->install();

@ -0,0 +1,18 @@
<?php
/* For licensing terms, see /license.txt */
$strings['plugin_title'] = 'External Notification Connect';
$strings['plugin_comment'] = 'Activate external notification system';
$strings['tool_enable'] = 'Enabled';
$strings['auth_url'] = 'Authentication endpoint';
$strings['auth_url_help'] = 'URL for authentication API';
$strings['auth_username'] = 'Username for authentication';
$strings['auth_password'] = 'Password for authentication';
$strings['notification_url'] = 'Notification endpoint';
$strings['notification_url_help'] = 'URL for notification API';
$strings['notify_portfolio'] = 'Portfolio';
$strings['notify_portfolio_help'] = 'Put it to <i>Yes</i> to activate external notification for Portfolio tool actions';
$strings['notify_learnpath'] = 'Learning path';
$strings['notify_learnpath_help'] = 'Put it to <i>Yes</i> to activate external notification for learning path creation';

@ -0,0 +1,18 @@
<?php
/* For licensing terms, see /license.txt */
$strings['plugin_title'] = 'Conexión de notificación externa';
$strings['plugin_comment'] = 'Activar un sistema de notificaciones externo';
$strings['tool_enable'] = 'Activado';
$strings['auth_url'] = 'Punto de conexión de autenticación';
$strings['auth_url_help'] = 'URL para API de autenticación';
$strings['auth_username'] = 'Nombre de usuario para autenticación';
$strings['auth_password'] = 'Contraseña para autenticación';
$strings['notification_url'] = 'Punto de conexión de la notificación';
$strings['notification_url_help'] = 'URL para la API de notificación';
$strings['notify_portfolio'] = 'Portafolio';
$strings['notify_portfolio_help'] = 'Ponerlo a <i></i> para activar la notificación externa para las acciones de la herramienta Portafolio';
$strings['notify_learnpath'] = 'Lecciones';
$strings['notify_learnpath_help'] = 'Ponerlo a <i></i> para activar la notificación externa para la creación de lecciones';

@ -0,0 +1,5 @@
<?php
/* For licensing terms, see /license.txt */
$plugin_info = ExternalNotificationConnectPlugin::create()->get_info();

@ -0,0 +1,71 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\ExternalNotificationConnect\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="plugin_ext_notif_conn_access_token")
* @ORM\Entity()
*/
class AccessToken
{
/**
* @var int
*
* @ORM\Id()
* @ORM\GeneratedValue(strategy="IDENTITY")
* @ORM\Column(name="id", type="integer")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="access_token", type="text")
*/
private $token;
/**
* @var bool
*
* @ORM\Column(name="is_valid", type="boolean")
*/
private $isValid;
public function getId(): int
{
return $this->id;
}
public function setId(int $id): AccessToken
{
$this->id = $id;
return $this;
}
public function getToken(): string
{
return $this->token;
}
public function setToken(string $token): AccessToken
{
$this->token = $token;
return $this;
}
public function isValid(): bool
{
return $this->isValid;
}
public function setIsValid(bool $isValid): AccessToken
{
$this->isValid = $isValid;
return $this;
}
}

@ -0,0 +1,234 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\PluginBundle\ExternalNotificationConnect\Entity\AccessToken;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\ToolsException;
use Firebase\JWT\JWT;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\ServerException;
class ExternalNotificationConnectPlugin extends Plugin implements HookPluginInterface
{
public const SETTING_AUTH_URL = 'auth_url';
public const SETTING_AUTH_USERNAME = 'auth_username';
public const SETTING_AUTH_PASSWORD = 'auth_password';
public const SETTING_NOTIFICATION_URL = 'notification_url';
public const SETTING_NOTIFY_PORTFOLIO = 'notify_portfolio';
public const SETTING_NOTIFY_LEARNPATH = 'notify_learnpath';
protected function __construct()
{
$author = [
'Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>',
];
$settings = [
'tool_enable' => 'boolean',
self::SETTING_AUTH_URL => 'text',
self::SETTING_AUTH_USERNAME => 'text',
self::SETTING_AUTH_PASSWORD => 'text',
self::SETTING_NOTIFICATION_URL => 'text',
self::SETTING_NOTIFY_PORTFOLIO => 'boolean',
self::SETTING_NOTIFY_LEARNPATH => 'boolean',
];
parent::__construct(
'1.0',
implode('; ', $author),
$settings
);
}
public static function create(): ?ExternalNotificationConnectPlugin
{
static $result = null;
return $result ?: $result = new self();
}
public function performActionsAfterConfigure(): ExternalNotificationConnectPlugin
{
$portfolioItemAddedEvent = HookPortfolioItemAdded::create();
$portfolioItemEditedEvent = HookPortfolioItemEdited::create();
$portfolioItemDeletedEvent = HookPortfolioItemDeleted::create();
$portfolioItemAddedObserver = ExternalNotificationConnectPortfolioItemAddedHookObserver::create();
$portfolioItemEditedObserver = ExternalNotificationConnectPortfolioItemEditedHookObserver::create();
$portfolioItemDeletedObserver = ExternalNotificationConnectPortfolioItemDeletedHookObserver::create();
if ('true' === $this->get(self::SETTING_NOTIFY_PORTFOLIO)) {
$portfolioItemAddedEvent->attach($portfolioItemAddedObserver);
$portfolioItemEditedEvent->attach($portfolioItemEditedObserver);
$portfolioItemDeletedEvent->attach($portfolioItemDeletedObserver);
} else {
$portfolioItemAddedEvent->detach($portfolioItemAddedObserver);
$portfolioItemEditedEvent->detach($portfolioItemEditedObserver);
$portfolioItemDeletedEvent->detach($portfolioItemDeletedObserver);
}
$lpCreatedEvent = HookLearningPathCreated::create();
$lpCreatedObserver = ExternalNotificationConnectLearningPathCreatedHookObserver::create();
if ('true' === $this->get(self::SETTING_NOTIFY_LEARNPATH)) {
$lpCreatedEvent->attach($lpCreatedObserver);
} else {
$lpCreatedEvent->detach($lpCreatedObserver);
}
return $this;
}
public function installHook()
{
}
public function uninstallHook()
{
$portfolioItemAddedEvent = HookPortfolioItemAdded::create();
$portfolioItemEditedEvent = HookPortfolioItemEdited::create();
$portfolioItemDeletedEvent = HookPortfolioItemDeleted::create();
$lpCreatedEvent = HookLearningPathCreated::create();
$portfolioItemAddedObserver = ExternalNotificationConnectPortfolioItemAddedHookObserver::create();
$portfolioItemEditedObserver = ExternalNotificationConnectPortfolioItemEditedHookObserver::create();
$portfolioItemDeletedObserver = ExternalNotificationConnectPortfolioItemDeletedHookObserver::create();
$lpCreatedObserver = ExternalNotificationConnectLearningPathCreatedHookObserver::create();
$portfolioItemAddedEvent->detach($portfolioItemAddedObserver);
$portfolioItemEditedEvent->detach($portfolioItemEditedObserver);
$portfolioItemDeletedEvent->detach($portfolioItemDeletedObserver);
$lpCreatedEvent->detach($lpCreatedObserver);
}
public function install()
{
$em = Database::getManager();
$schemaManager = $em->getConnection()->getSchemaManager();
$tableExists = $schemaManager->tablesExist(['plugin_ext_notif_conn_access_token']);
if ($tableExists) {
return;
}
$this->installDBTables();
$this->installHook();
}
public function uninstall()
{
$this->uninstallHook();
$this->uninstallDBTables();
}
/**
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\ORMException
* @throws Exception
*/
public function getAccessToken()
{
$em = Database::getManager();
$tokenRepository = $em->getRepository(AccessToken::class);
$accessToken = $tokenRepository->findOneBy(['isValid' => true]);
if (!$accessToken) {
$newToken = $this->requestAuthToken();
$accessToken = (new AccessToken())
->setToken($newToken)
->setIsValid(true);
$em->persist($accessToken);
$em->flush();
} else {
$tks = explode('.', $accessToken->getToken());
$payload = json_decode(JWT::urlsafeB64Decode($tks[1]), true);
if (time() >= $payload['exp']) {
$accessToken->setIsValid(false);
$newToken = $this->requestAuthToken();
$accessToken = (new AccessToken())
->setToken($newToken)
->setIsValid(true);
$em->persist($accessToken);
$em->flush();
}
}
return $accessToken->getToken();
}
private function installDBTables()
{
$em = Database::getManager();
try {
(new SchemaTool($em))
->createSchema([
$em->getClassMetadata(AccessToken::class),
]);
} catch (ToolsException $e) {
return;
}
}
private function uninstallDBTables()
{
$em = Database::getManager();
(new SchemaTool($em))
->dropSchema([
$em->getClassMetadata(AccessToken::class),
]);
}
/**
* @throws Exception
*/
private function requestAuthToken(): string
{
$client = new Client();
try {
$response = $client->request(
'POST',
$this->get(ExternalNotificationConnectPlugin::SETTING_AUTH_URL),
[
'json' => [
'email' => $this->get(ExternalNotificationConnectPlugin::SETTING_AUTH_USERNAME),
'password' => $this->get(ExternalNotificationConnectPlugin::SETTING_AUTH_PASSWORD),
],
]
);
} catch (ClientException|ServerException $e) {
if (!$e->hasResponse()) {
throw new Exception($e->getMessage());
}
$response = $e->getResponse();
} catch (GuzzleException $e) {
throw new Exception($e->getMessage());
}
$json = json_decode((string) $response->getBody(), true);
if (201 !== $json['status']) {
throw new Exception($json['message']);
}
return $json['data']['data']['token'];
}
}

@ -0,0 +1,21 @@
<?php
/* For licensing terms, see /license.txt */
abstract class ExternalNotificationConnectHookObserver extends HookObserver
{
/**
* @var ExternalNotificationConnectPlugin
*/
protected $plugin;
protected function __construct()
{
parent::__construct(
'plugin/externalnotificationconnect/src/ExternalNotificationConnectPlugin.php',
'externalnotificationconnect'
);
$this->plugin = ExternalNotificationConnectPlugin::create();
}
}

@ -0,0 +1,55 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\PluginBundle\ExternalNotificationConnect\Traits\RequestTrait\RequestTrait;
class ExternalNotificationConnectLearningPathCreatedHookObserver extends ExternalNotificationConnectHookObserver implements HookLearningPathCreatedObserverInterface
{
use RequestTrait;
public function hookCreated(HookLearningPathCreatedEventInterface $hookEvent)
{
/** @var learnpath $lp */
$lp = $hookEvent->getEventData()['lp'];
$userId = api_get_user_id();
$courseCode = api_get_course_id();
$cidreq = api_get_cidreq();
$url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?';
$url .= ($cidreq ? $cidreq.'&' : '');
$url .= http_build_query(
[
'action' => 'view',
'lp_id' => $lp->lp_id,
'isStudentView' => 'true',
]
);
try {
$json = $this->doCreateRequest(
[
'user_id' => $userId,
'course_code' => $courseCode,
'content_id' => $lp->get_id(),
'content_type' => 'lp',
'content_url' => $url,
'post_title' => $lp->get_name(),
]
);
} catch (Exception $e) {
Display::addFlash(
Display::return_message($e->getMessage(), 'error')
);
return;
}
if (empty($json)) {
return;
}
error_log('ExtNotifConn: Learning path created: ID '.$json['data']['notification_id']);
}
}

@ -0,0 +1,50 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Portfolio;
use Chamilo\PluginBundle\ExternalNotificationConnect\Traits\RequestTrait\RequestTrait;
class ExternalNotificationConnectPortfolioItemAddedHookObserver extends ExternalNotificationConnectHookObserver implements HookPortfolioItemAddedObserverInterface
{
use RequestTrait;
public function hookItemAdded(HookPortfolioItemAddedEventInterface $hookEvent)
{
/** @var Portfolio $item */
$item = $hookEvent->getEventData()['portfolio'];
$userId = api_get_user_id();
$courseCode = api_get_course_id();
$cidreq = api_get_cidreq();
$url = api_get_path(WEB_CODE_PATH).'portfolio/index.php?';
$url .= ($cidreq ? $cidreq.'&' : '');
$url .= http_build_query(['action' => 'view', 'id' => $item->getId()]);
try {
$json = $this->doCreateRequest(
[
'user_id' => $userId,
'course_code' => $courseCode,
'content_id' => $item->getId(),
'content_type' => 'eportfolio',
'content_url' => $url,
'post_title' => $item->getTitle(),
]
);
} catch (Exception $e) {
Display::addFlash(
Display::return_message($e->getMessage(), 'error')
);
return;
}
if (empty($json)) {
return;
}
error_log('ExtNotifConn: Portfolio item created: ID '.$json['data']['notification_id']);
}
}

@ -0,0 +1,33 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Portfolio;
use Chamilo\PluginBundle\ExternalNotificationConnect\Traits\RequestTrait\RequestTrait;
class ExternalNotificationConnectPortfolioItemDeletedHookObserver extends ExternalNotificationConnectHookObserver implements HookPortfolioItemDeletedHookObserverInterface
{
use RequestTrait;
public function hookItemDeleted(HookPortfolioItemDeletedEventInterface $hookEvent)
{
/** @var Portfolio $item */
$item = $hookEvent->getEventData()['item'];
try {
$json = $this->doDeleteRequest($item->getId(), 'eportfolio');
} catch (Exception $e) {
Display::addFlash(
Display::return_message($e->getMessage(), 'error')
);
return;
}
if (empty($json)) {
return;
}
error_log('ExtNotifConn: Portfolio item deleted: Status '.((int) $json['status']));
}
}

@ -0,0 +1,50 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Portfolio;
use Chamilo\PluginBundle\ExternalNotificationConnect\Traits\RequestTrait\RequestTrait;
class ExternalNotificationConnectPortfolioItemEditedHookObserver extends ExternalNotificationConnectHookObserver implements HookPortfolioItemEditedObserverInterface
{
use RequestTrait;
public function hookItemEdited(HookPortfolioItemEditedEventInterface $hookEvent)
{
/** @var Portfolio $item */
$item = $hookEvent->getEventData()['item'];
$userId = api_get_user_id();
$courseCode = api_get_course_id();
$cidreq = api_get_cidreq();
$url = api_get_path(WEB_CODE_PATH).'portfolio/index.php?';
$url .= ($cidreq ? $cidreq.'&' : '');
$url .= http_build_query(['action' => 'view', 'id' => $item->getId()]);
try {
$json = $this->doEditRequest(
[
'user_id' => $userId,
'course_code' => $courseCode,
'content_id' => $item->getId(),
'content_type' => 'eportfolio',
'content_url' => $url,
'post_title' => $item->getTitle(),
]
);
} catch (Exception $e) {
Display::addFlash(
Display::return_message($e->getMessage(), 'error')
);
return;
}
if (empty($json)) {
return;
}
error_log('ExtNotifConn: Portfolio item edited. Status'.((int) $json['status']));
}
}

@ -0,0 +1,149 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\ExternalNotificationConnect\Traits\RequestTrait;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Exception;
use ExternalNotificationConnectPlugin;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
trait RequestTrait
{
/**
* @throws Exception
*/
protected function doCreateRequest($json): ?array
{
try {
$token = $this->plugin->getAccessToken();
} catch (OptimisticLockException|ORMException|Exception $e) {
throw new Exception($e->getMessage());
}
$options = [
'headers' => [
'Authorization' => "Bearer $token",
],
'json' => $json,
];
$client = new Client();
try {
$response = $client->post(
$this->plugin->get(ExternalNotificationConnectPlugin::SETTING_NOTIFICATION_URL),
$options
);
} catch (ClientException|ServerException $e) {
if (!$e->hasResponse()) {
throw new Exception($e->getMessage());
}
$response = $e->getResponse();
}
$json = json_decode((string) $response->getBody(), true);
if (isset($json['status']) && 500 === $json['status']) {
throw new Exception($json['message']);
}
if (isset($json['validation_errors']) && $json['validation_errors']) {
$messageError = implode(
'<br>',
array_column($json['errors'], 'message')
);
throw new Exception($messageError);
}
return $json;
}
/**
* @throws Exception
*/
protected function doEditRequest(array $json): array
{
try {
$token = $this->plugin->getAccessToken();
} catch (OptimisticLockException|ORMException|Exception $e) {
throw new Exception($e->getMessage());
}
$url = $this->plugin->get(ExternalNotificationConnectPlugin::SETTING_NOTIFICATION_URL)
.'/'.$json['content_id'].'/'.$json['content_type'];
$options = [
'headers' => [
'Authorization' => "Bearer $token",
],
'json' => $json,
];
$client = new Client();
try {
$response = $client->post($url, $options);
} catch (ClientException|ServerException $e) {
if (!$e->hasResponse()) {
throw new Exception($e->getMessage());
}
$response = $e->getResponse();
}
$json = json_decode((string) $response->getBody(), true);
if (isset($json['status']) && 500 === $json['status']) {
throw new Exception($json['message']);
}
return $json;
}
/**
* @throws Exception
*/
protected function doDeleteRequest(int $contentId, string $contentType): array
{
try {
$token = $this->plugin->getAccessToken();
} catch (OptimisticLockException|ORMException|Exception $e) {
throw new Exception($e->getMessage());
}
$url = $this->plugin->get(ExternalNotificationConnectPlugin::SETTING_NOTIFICATION_URL)."/$contentId/$contentType";
$options = [
'headers' => [
'Authorization' => "Bearer $token",
],
];
$client = new Client();
try {
$response = $client->delete($url, $options);
} catch (ClientException|ServerException $e) {
if (!$e->hasResponse()) {
throw new Exception($e->getMessage());
}
$response = $e->getResponse();
}
$json = json_decode((string) $response->getBody(), true);
if (isset($json['status']) && 500 === $json['status']) {
throw new Exception($json['message']);
}
return $json;
}
}

@ -0,0 +1,5 @@
<?php
/* For licensing terms, see /license.txt */
ExternalNotificationConnectPlugin::create()->uninstall();
Loading…
Cancel
Save