Internal: Update from 1.11.x

pull/3451/head
Julio Montoya 5 years ago
parent 59decff895
commit 5f79f57ca2
  1. 60
      public/main/inc/lib/plugin.class.php
  2. 49
      public/main/inc/lib/social.lib.php
  3. 45
      public/main/inc/lib/tracking.lib.php
  4. 11
      public/plugin/azure_active_directory/README.md
  5. 5
      public/plugin/azure_active_directory/index.php
  6. 1
      public/plugin/azure_active_directory/install.php
  7. 10
      public/plugin/azure_active_directory/lang/dutch.php
  8. 1
      public/plugin/azure_active_directory/lang/english.php
  9. 9
      public/plugin/azure_active_directory/layout/login_form.tpl
  10. 3
      public/plugin/azure_active_directory/login.php
  11. 3
      public/plugin/azure_active_directory/plugin.php
  12. 9
      public/plugin/azure_active_directory/src/AzureActiveDirectory.php
  13. 78
      public/plugin/azure_active_directory/src/callback.php
  14. 11
      public/plugin/azure_active_directory/view/login.tpl
  15. 2
      public/plugin/bbb/listing.php
  16. 3
      public/plugin/customcertificate/config.php
  17. 5
      public/plugin/customcertificate/database.php
  18. 1
      public/plugin/customcertificate/index.php
  19. 3
      public/plugin/customcertificate/install.php
  20. 146
      public/plugin/customcertificate/lang/english.php
  21. 148
      public/plugin/customcertificate/lang/spanish.php
  22. 3
      public/plugin/customcertificate/plugin.php
  23. 21
      public/plugin/customcertificate/src/CustomCertificatePlugin.php
  24. 7
      public/plugin/customcertificate/src/customcertificate.ajax.php
  25. 708
      public/plugin/customcertificate/src/export_pdf_all_in_one.php
  26. 93
      public/plugin/customcertificate/src/index.php
  27. 244
      public/plugin/customcertificate/src/print_certificate.php
  28. 4
      public/plugin/customcertificate/start.php
  29. 3
      public/plugin/customcertificate/uninstall.php
  30. 3
      public/plugin/customcertificate/update.php
  31. 274
      public/plugin/pausetraining/PauseTraining.php
  32. 1
      public/plugin/pausetraining/cron.php
  33. 7
      public/plugin/pausetraining/cronTest.php
  34. 5
      public/plugin/pausetraining/lang/english.php
  35. 5
      public/plugin/pausetraining/lang/french.php
  36. 608
      public/plugin/zoom/Entity/Meeting.php
  37. 208
      public/plugin/zoom/Entity/MeetingActivity.php
  38. 193
      public/plugin/zoom/Entity/Recording.php
  39. 265
      public/plugin/zoom/Entity/Registrant.php
  40. 70
      public/plugin/zoom/README.code.md
  41. 37
      public/plugin/zoom/README.md
  42. 55
      public/plugin/zoom/activity.php
  43. 5
      public/plugin/zoom/config.php
  44. 151
      public/plugin/zoom/endpoint.php
  45. 13
      public/plugin/zoom/global.php
  46. 1
      public/plugin/zoom/index.php
  47. 8
      public/plugin/zoom/install.php
  48. 65
      public/plugin/zoom/join_meeting.php
  49. 144
      public/plugin/zoom/lang/english.php
  50. 138
      public/plugin/zoom/lang/french.php
  51. 138
      public/plugin/zoom/lang/spanish.php
  52. 32
      public/plugin/zoom/lib/API/BaseMeetingTrait.php
  53. 56
      public/plugin/zoom/lib/API/Client.php
  54. 42
      public/plugin/zoom/lib/API/CreatedRegistration.php
  55. 18
      public/plugin/zoom/lib/API/CustomQuestion.php
  56. 39
      public/plugin/zoom/lib/API/GlobalDialInNumber.php
  57. 94
      public/plugin/zoom/lib/API/JWTClient.php
  58. 122
      public/plugin/zoom/lib/API/JsonDeserializableTrait.php
  59. 84
      public/plugin/zoom/lib/API/Meeting.php
  60. 34
      public/plugin/zoom/lib/API/MeetingInfo.php
  61. 148
      public/plugin/zoom/lib/API/MeetingInfoGet.php
  62. 50
      public/plugin/zoom/lib/API/MeetingInstance.php
  63. 53
      public/plugin/zoom/lib/API/MeetingInstances.php
  64. 58
      public/plugin/zoom/lib/API/MeetingList.php
  65. 44
      public/plugin/zoom/lib/API/MeetingListItem.php
  66. 105
      public/plugin/zoom/lib/API/MeetingRegistrant.php
  67. 53
      public/plugin/zoom/lib/API/MeetingRegistrantList.php
  68. 29
      public/plugin/zoom/lib/API/MeetingRegistrantListItem.php
  69. 143
      public/plugin/zoom/lib/API/MeetingSettings.php
  70. 68
      public/plugin/zoom/lib/API/Pagination.php
  71. 71
      public/plugin/zoom/lib/API/PaginationToken.php
  72. 57
      public/plugin/zoom/lib/API/ParticipantList.php
  73. 22
      public/plugin/zoom/lib/API/ParticipantListItem.php
  74. 74
      public/plugin/zoom/lib/API/PastMeeting.php
  75. 115
      public/plugin/zoom/lib/API/RecordingFile.php
  76. 65
      public/plugin/zoom/lib/API/RecordingList.php
  77. 95
      public/plugin/zoom/lib/API/RecordingMeeting.php
  78. 29
      public/plugin/zoom/lib/API/TrackingField.php
  79. 204
      public/plugin/zoom/lib/MeetingRepository.php
  80. 65
      public/plugin/zoom/lib/RecordingRepository.php
  81. 33
      public/plugin/zoom/lib/RegistrantRepository.php
  82. 1400
      public/plugin/zoom/lib/ZoomPlugin.php
  83. 94
      public/plugin/zoom/meeting.php
  84. 28
      public/plugin/zoom/meetings.php
  85. 4
      public/plugin/zoom/plugin.php
  86. 118
      public/plugin/zoom/start.php
  87. 8
      public/plugin/zoom/uninstall.php
  88. 57
      public/plugin/zoom/view/activity.tpl
  89. 116
      public/plugin/zoom/view/meeting.tpl
  90. 62
      public/plugin/zoom/view/meetings.tpl
  91. 56
      public/plugin/zoom/view/start.tpl

@ -275,6 +275,7 @@ class Plugin
$selectedValue = 'checked';
}
}
$element = $result->createElement(
$type,
$name,
@ -294,6 +295,21 @@ class Plugin
$attributes
);
break;
case 'user':
$options = [];
if (!empty($value)) {
$userInfo = api_get_user_info($value);
if ($userInfo) {
$options[$value] = $userInfo['complete_name'];
}
}
$result->addSelectAjax(
$name,
[$this->get_lang($name), $help],
$options,
['url' => api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_like']
);
break;
}
}
@ -301,7 +317,7 @@ class Plugin
$result->addGroup(
$checkboxGroup,
null,
[$this->get_lang('sms_types'), $help]
['', $help]
);
}
$result->setDefaults($defaults);
@ -459,7 +475,7 @@ class Plugin
*
* @return bool|null False on error, null otherwise
*/
public function install_course_fields($courseId, $add_tool_link = true)
public function install_course_fields($courseId, $add_tool_link = true, $iconName = '')
{
$plugin_name = $this->get_name();
$t_course = Database::get_course_table(TABLE_COURSE_SETTING);
@ -478,6 +494,11 @@ class Plugin
$value = $setting['init_value'];
}
$pluginGlobalValue = api_get_plugin_setting($plugin_name, $variable);
if (null !== $pluginGlobalValue) {
$value = 1;
}
$type = 'textfield';
if (isset($setting['type'])) {
$type = $setting['type'];
@ -531,7 +552,7 @@ class Plugin
}
// Add an icon in the table tool list
$this->createLinkToCourseTool($plugin_name, $courseId);
$this->createLinkToCourseTool($plugin_name, $courseId, $iconName);
}
/**
@ -585,14 +606,14 @@ class Plugin
*
* @param bool $add_tool_link Whether we want to add a plugin link on the course homepage
*/
public function install_course_fields_in_all_courses($add_tool_link = true)
public function install_course_fields_in_all_courses($add_tool_link = true, $iconName = '')
{
// Update existing courses to add plugin settings
$table = Database::get_main_table(TABLE_MAIN_COURSE);
$sql = "SELECT id FROM $table ORDER BY id";
$res = Database::query($sql);
while ($row = Database::fetch_assoc($res)) {
$this->install_course_fields($row['id'], $add_tool_link);
$this->install_course_fields($row['id'], $add_tool_link, $iconName);
}
}
@ -619,6 +640,10 @@ class Plugin
$settings = [];
if (is_array($this->course_settings)) {
foreach ($this->course_settings as $item) {
// Skip html type
if ('html' === $item['type']) {
continue;
}
if (isset($item['group'])) {
if (!in_array($item['group'], $settings)) {
$settings[] = $item['group'];
@ -846,9 +871,11 @@ class Plugin
/**
* Returns true if the plugin is installed, false otherwise.
*
* @param bool $checkEnabled Also check if enabled (instead of only installed)
*
* @return bool True if plugin is installed/enabled, false otherwise
*/
public function isEnabled()
public function isEnabled($checkEnabled = false)
{
$settings = api_get_settings_params_simple(
[
@ -859,7 +886,26 @@ class Plugin
],
]
);
if (is_array($settings) && isset($settings['selected_value']) && 'installed' == $settings['selected_value']) {
if (is_array($settings) && isset($settings['selected_value']) && $settings['selected_value'] == 'installed') {
// The plugin is installed
// If we need a check on whether it is enabled, also check for
// *plugin*_tool_enable and make sure it is *NOT* false
if ($checkEnabled) {
$enabled = api_get_settings_params_simple(
[
"variable = ? AND subkey = ? AND category = 'Plugins' " => [
$this->get_name().'_tool_enable',
$this->get_name(),
],
]
);
if (is_array($enabled) && isset($enabled['selected_value']) && $enabled['selected_value'] == 'false') {
// Only return false if the setting exists and it is
// *specifically* set to false
return false;
}
}
return true;
}

@ -3111,6 +3111,55 @@ class SocialManager extends UserManager
return $social_group_block;
}
/**
* @param string $selected
*
* @return string
*/
public static function getHomeProfileTabs($selected = 'home')
{
$headers = [
[
'url' => api_get_path(WEB_CODE_PATH).'auth/profile.php',
'content' => get_lang('Profile'),
],
];
$allowJustification = api_get_plugin_setting('justification', 'tool_enable') === 'true';
if ($allowJustification) {
$plugin = Justification::create();
$headers[] = [
'url' => api_get_path(WEB_CODE_PATH).'auth/justification.php',
'content' => $plugin->get_lang('Justification'),
];
}
$allowPauseTraining = api_get_plugin_setting('pausetraining', 'tool_enable') === 'true';
$allowEdit = api_get_plugin_setting('pausetraining', 'allow_users_to_edit_pause_formation') === 'true';
if ($allowPauseTraining && $allowEdit) {
$plugin = PauseTraining::create();
$headers[] = [
'url' => api_get_path(WEB_CODE_PATH).'auth/pausetraining.php',
'content' => $plugin->get_lang('PauseTraining'),
];
}
$selectedItem = 1;
foreach ($headers as $header) {
$info = pathinfo($header['url']);
if ($selected === $info['filename']) {
break;
}
$selectedItem++;
}
$tabs = '';
if (count($headers) > 1) {
$tabs = Display::tabsOnlyLink($headers, $selectedItem);
}
return $tabs;
}
/**
* Returns the formatted header message post.
*

@ -2112,6 +2112,34 @@ class Tracking
return false;
}
/**
* Get last course access by course/session.
*/
public static function getLastConnectionDateByCourse($courseId, $sessionId = 0)
{
$courseId = (int) $courseId;
$sessionId = (int) $sessionId;
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
$sql = "SELECT logout_course_date
FROM $table
WHERE
c_id = $courseId AND
session_id = $sessionId
ORDER BY logout_course_date DESC
LIMIT 0,1";
$result = Database::query($sql);
if (Database::num_rows($result)) {
$row = Database::fetch_array($result);
if ($row) {
return $row['logout_course_date'];
}
}
return '';
}
/**
* Get count of the connections to the course during a specified period.
*
@ -4452,9 +4480,24 @@ class Tracking
}
$rs = Database::query($sql);
$allow = api_get_plugin_setting('pausetraining', 'tool_enable') === 'true';
$allowPauseFormation = api_get_plugin_setting('pausetraining', 'allow_users_to_edit_pause_formation') === 'true';
$extraFieldValue = new ExtraFieldValue('user');
$users = [];
while ($user = Database::fetch_array($rs)) {
$users[] = $user['user_id'];
$userId = $user['user_id'];
if ($allow && $allowPauseFormation) {
$pause = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'pause_formation');
if (!empty($pause) && isset($pause['value']) && 1 == $pause['value']) {
// Skip user because he paused his formation.
continue;
}
}
$users[] = $userId;
}
return $users;

@ -1,6 +1,10 @@
# The Azure Active Directory Plugin
Allow authentication (with OAuth2) with Microsoft's Azure Active Directory.
This plugin add two extra fields for users:
- `organisationemail`, the email registered in Azure Active Directory for each user.
- `azure_id`, to save the internal ID for each user in Azure.
> This plugin use the [`thenetworg/oauth2-azure`](https://github.com/TheNetworg/oauth2-azure) package.
### To configure Azure Active Directory
@ -17,5 +21,10 @@ Allow authentication (with OAuth2) with Microsoft's Azure Active Directory.
* _Management login_: (Optional) Disable the chamilo login and enable an alternative login page for users.
You will need copy the `/plugin/azure_active_directory/layout/login_form.tpl` file to `/main/template/overrides/layout/` directory.
* _Name for the management login_: A name for the management login. By default is "Management Login".
* And assign a region. Preferably `login_bottom`.
And assign a region. Preferably `login_bottom`.
Also, you can configure the external login to work with the classic Chamilo form login.
Adding this line in `configuration.php` file.
```php
$extAuthSource["azure"]["login"] = $_configuration['root_sys']."main/auth/external_login/login.azure.php";
```

@ -1,14 +1,15 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
*
* @package chamilo.plugin.azure_active_directory
*/
/** @var AzureActiveDirectory $activeDirectoryPlugin */
$activeDirectoryPlugin = AzureActiveDirectory::create();
if ('true' === $activeDirectoryPlugin->get(AzureActiveDirectory::SETTING_ENABLE)) {
if ($activeDirectoryPlugin->get(AzureActiveDirectory::SETTING_ENABLE) === 'true') {
$_template['block_title'] = $activeDirectoryPlugin->get(AzureActiveDirectory::SETTING_BLOCK_NAME);
$_template['signin_url'] = $activeDirectoryPlugin->getUrl(AzureActiveDirectory::URL_TYPE_AUTHORIZE);

@ -1,5 +1,4 @@
<?php
/* For licensing terms, see /license.txt */
if (!api_is_platform_admin()) {

@ -0,0 +1,10 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Strings to dutch L10n.
*
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
*
* @package chamilo.plugin.azure_active_directory
*/
$strings['InvalidId'] = 'Deze identificatie is niet geldig (verkeerde log-in of wachtwoord). Errocode: AZMNF';

@ -24,3 +24,4 @@ $strings['management_login_name_help'] = 'By default is "Management Login".';
$strings['OrganisationEmail'] = 'Organisation e-mail';
$strings['AzureId'] = 'Azure ID (mailNickname)';
$strings['ManagementLogin'] = 'Management Login';
$strings['InvalidId'] = 'Login failed - incorrect login or password. Errocode: AZMNF';

@ -24,8 +24,15 @@
{% endif %}
{% if "allow_lostpassword"|api_get_setting == 'true' %}
{% set pass_reminder_link = 'pass_reminder_custom_link'|api_get_configuration_value %}
{% set lost_password_link = _p.web_main ~ 'auth/lostPassword.php' %}
{% if not pass_reminder_link is empty %}
{% set lost_password_link = pass_reminder_link %}
{% endif %}
<li>
<a href="{{ _p.web_main }}auth/lostPassword.php">{{ 'LostPassword'|get_lang }}</a>
<a href="{{ lost_password_link }}"> {{ 'LostPassword' | get_lang }} </a>
</li>
{% endif %}
</ul>

@ -1,5 +1,4 @@
<?php
/* For license terms, see /license.txt */
require __DIR__.'/../../main/inc/global.inc.php';
@ -17,7 +16,7 @@ if ('true' !== $pluginEnabled || 'true' !== $managementLoginEnabled) {
$userId = api_get_user_id();
if (!$userId || api_is_anonymous($userId)) {
if (!($userId) || api_is_anonymous($userId)) {
$managementLoginName = $plugin->get(AzureActiveDirectory::SETTING_MANAGEMENT_LOGIN_NAME);
if (empty($managementLoginName)) {

@ -1,8 +1,9 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
*
* @package chamilo.plugin.azure_active_directory
*/
$plugin_info = AzureActiveDirectory::create()->get_info();

@ -1,5 +1,4 @@
<?php
/* For license terms, see /license.txt */
use TheNetworg\OAuth2\Client\Provider\Azure;
@ -8,6 +7,8 @@ use TheNetworg\OAuth2\Client\Provider\Azure;
* AzureActiveDirectory plugin class.
*
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
*
* @package chamilo.plugin.azure_active_directory
*/
class AzureActiveDirectory extends Plugin
{
@ -54,7 +55,7 @@ class AzureActiveDirectory extends Plugin
{
static $result = null;
return $result ?: $result = new self();
return $result ? $result : $result = new self();
}
/**
@ -70,11 +71,13 @@ class AzureActiveDirectory extends Plugin
*/
public function getProvider()
{
return new Azure([
$provider = new Azure([
'clientId' => $this->get(self::SETTING_APP_ID),
'clientSecret' => $this->get(self::SETTING_APP_SECRET),
'redirectUri' => api_get_path(WEB_PLUGIN_PATH).'azure_active_directory/src/callback.php',
]);
return $provider;
}
/**

@ -1,5 +1,4 @@
<?php
/* For license terms, see /license.txt */
require __DIR__.'/../../../main/inc/global.inc.php';
@ -35,39 +34,67 @@ $me = null;
try {
$me = $provider->get('me', $token);
} catch (Exception $e) {
exit;
}
$userInfo = [];
if (empty($me)) {
throw new Exception('Token not found.');
}
if (!empty($me['email'])) {
$userInfo = api_get_user_info_from_email($me['email']);
}
if (empty($me['mail']) || empty($me['mailNickname'])) {
throw new Exception('Mail empty');
}
if (empty($userInfo) && !empty($me['email'])) {
$extraFieldValue = new ExtraFieldValue('user');
$itemValue = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
'organisationemail',
$me['email']
$organisationValue = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
AzureActiveDirectory::EXTRA_FIELD_ORGANISATION_EMAIL,
$me['mail']
);
$userInfo = api_get_user_info($itemValue['item_id']);
}
if (empty($userInfo) && !empty($me['mailNickname'])) {
$extraFieldValue = new ExtraFieldValue('user');
$itemValue = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
'azure_id',
$azureValue = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
AzureActiveDirectory::EXTRA_FIELD_AZURE_ID,
$me['mailNickname']
);
$userInfo = api_get_user_info($itemValue['item_id']);
}
$userId = null;
// Check EXTRA_FIELD_ORGANISATION_EMAIL
if (!empty($organisationValue) && isset($organisationValue['item_id'])) {
$userId = $organisationValue['item_id'];
}
if (empty($userId)) {
// Check EXTRA_FIELD_AZURE_ID
if (!empty($azureValue) && isset($azureValue['item_id'])) {
$userId = $azureValue['item_id'];
}
}
/*$emptyValues = empty($organisationValue['item_id']) || empty($azureValue['item_id']);
$differentValues = !$emptyValues && $organisationValue['item_id'] != $azureValue['item_id'];
if ($emptyValues || $differentValues) {
throw new Exception('Empty values');
}*/
if (empty($userId)) {
throw new Exception('User not found when checking the extra fields.');
}
$userInfo = api_get_user_info($userId);
if (empty($userInfo)) {
throw new Exception('User not found');
}
if ($userInfo['active'] != '1') {
throw new Exception('account_inactive');
}
} catch (Exception $exception) {
$message = Display::return_message($plugin->get_lang('InvalidId'), 'error');
if (empty($userInfo)) {
header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=user_password_incorrect');
if ($exception->getMessage() === 'account_inactive') {
$message = Display::return_message(get_lang('AccountInactive'), 'error');
}
Display::addFlash($message);
header('Location: '.api_get_path(WEB_PATH));
exit;
}
@ -77,5 +104,4 @@ $_user['uidReset'] = true;
ChamiloSession::write('_user', $_user);
ChamiloSession::write('_user_auth_source', 'azure_active_directory');
header('Location: '.api_get_path(WEB_PATH));
exit;
Redirect::session_request_uri(true, $userInfo['user_id']);

@ -10,8 +10,15 @@
<li><a href="{{ _p.web_main }}auth/inscription.php">{{ 'SignUp'|get_lang }}</a></li>
{% endif %}
{% if "allow_lostpassword"|api_get_setting == 'true' %}
<li><a href="{{ _p.web_main }}auth/lostPassword.php">{{ 'LostPassword'|get_lang }}</a></li>
{% if "allow_lostpassword" | api_get_setting == 'true' %}
{% set pass_reminder_link = 'pass_reminder_custom_link'|api_get_configuration_value %}
{% set lost_password_link = _p.web_main ~ 'auth/lostPassword.php' %}
{% if not pass_reminder_link is empty %}
{% set lost_password_link = pass_reminder_link %}
{% endif %}
<li><a href="{{ lost_password_link }}"> {{ 'LostPassword' | get_lang }} </a></li>
{% endif %}
</ul>
{% endif %}

@ -353,7 +353,7 @@ if ($bbb->isGlobalConference() === false &&
) {
$url = api_get_self().'?'.api_get_cidreq(true, false).'&gidReq=';
$htmlHeadXtra[] = '<script>
$(document).ready(function() {
$(function() {
$("#group_select").on("change", function() {
var groupId = $(this).find("option:selected").val();
var url = "'.$url.'";

@ -1,10 +1,11 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Config the plugin.
*
* @package chamilo.plugin.customcertificate
*
* @author Jose Angel Ruiz <desarrollo@nosolored.com>
*/
require_once __DIR__.'/../../main/inc/global.inc.php';

@ -1,16 +1,15 @@
<?php
/* For license terms, see /license.txt */
use Doctrine\DBAL\Types\Type;
/*
/**
* Plugin database installation script. Can only be executed if included
* inside another script loading global.inc.php.
*
* @package chamilo.plugin.customcertificate
*/
/*
/**
* Check if script can be called.
*/
if (!function_exists('api_get_path')) {

@ -1,4 +1,3 @@
<?php
/* For license terms, see /license.txt */
require_once 'config.php';

@ -1,9 +1,10 @@
<?php
/* For license terms, see /license.txt */
/**
* This script is included by main/admin/settings.lib.php and generally
* includes things to execute in the main database (settings_current table).
*
* @package chamilo.plugin.customcertificate
*/
require_once __DIR__.'/config.php';

@ -1,77 +1,77 @@
<?php
$strings['plugin_title'] = 'Custom certificate';
$strings['plugin_comment'] = 'This plugin allows you to create custom certificates for each course.';
$strings['enable_plugin_customcertificate'] = 'Enable plugin';
$strings['customcertificate_course_enable'] = 'Custom certificate enable in course';
$strings['use_certificate_default'] = 'Use default custom certificate';
$strings['ToolDisabled'] = 'The tool is disabled from the administration';
$strings['OnlyAdminPlatform'] = 'Tool only for administrators';
$strings['OnlyAdminPlatformOrTeacher'] = 'Tool only for administrators and teachers';
$strings['TrainingEntity'] = 'Training entity';
$strings['DescriptionFront'] = 'Description front';
$strings['DescriptionRear'] = 'Description rear';
$strings['Certify'] = 'Certify';
$strings['CertificateType'] = 'Certificate type';
$strings['CertifyThat'] = 'CERTIFY THAT';
$strings['plugin_title'] = "Custom certificate";
$strings['plugin_comment'] = "This plugin allows you to create custom certificates for each course.";
$strings['enable_plugin_customcertificate'] = "Enable plugin";
$strings['customcertificate_course_enable'] = "Custom certificate enable in course";
$strings['use_certificate_default'] = "Use default custom certificate";
$strings['ToolDisabled'] = "The tool is disabled from the administration";
$strings['OnlyAdminPlatform'] = "Tool only for administrators";
$strings['OnlyAdminPlatformOrTeacher'] = "Tool only for administrators and teachers";
$strings['TrainingEntity'] = "Training entity";
$strings['DescriptionFront'] = "Description front";
$strings['DescriptionRear'] = "Description rear";
$strings['Certify'] = "Certify";
$strings['CertificateType'] = "Certificate type";
$strings['CertifyThat'] = "CERTIFY THAT";
$strings['StudentCourseInfo'] = 'Student and Course data';
$strings['StudentData'] = 'Student data';
$strings['CourseData'] = 'Course data';
$strings['Modality'] = 'Modality';
$strings['Contents'] = 'Contents';
$strings['ContentsToShow'] = 'Contents to show';
$strings['StudentData'] = "Student data";
$strings['CourseData'] = "Course data";
$strings['Modality'] = "Modality";
$strings['Contents'] = "Contents";
$strings['ContentsToShow'] = "Contents to show";
$strings['ContentsCourseDescription'] = 'Use section "Course description"> "Contents"';
$strings['ContentsIndexLearnpath'] = 'Use learnpath index';
$strings['ContentsCustom'] = 'Use custom content';
$strings['ContentsHide'] = 'No show contents';
$strings['Dates'] = 'Dates';
$strings['CourseDeliveryDates'] = 'Course delivery dates';
$strings['Custom'] = 'Custom';
$strings['UseDateSessionAccess'] = 'Use access dates to the session';
$strings['ExpectionPlace'] = 'Expection place';
$strings['DateExpediction'] = 'Expediction date';
$strings['UseDateEndAccessSession'] = 'Use end date of session access';
$strings['UseDateDownloadCertificate'] = 'Use certificate download date';
$strings['UseDateGenerationCertificate'] = 'Use certificate generation date';
$strings['UseCustomDate'] = 'Use custom date';
$strings['LogosSeal'] = 'Logos / Seals';
$strings['LogoLeft'] = 'Logo left';
$strings['LogoCenter'] = 'Logo center';
$strings['LogoRight'] = 'Logo right';
$strings['Seal'] = 'Seal';
$strings['Signature1'] = 'Signature 1';
$strings['Signature2'] = 'Signature 2';
$strings['Signature3'] = 'Signature 3';
$strings['Signature4'] = 'Signature 4';
$strings['SignatureText1'] = 'Signature text 1';
$strings['SignatureText2'] = 'Signature text 2';
$strings['SignatureText3'] = 'Signature text 3';
$strings['SignatureText4'] = 'Signature text 4';
$strings['OtherOptions'] = 'Others options';
$strings['MarginRight'] = 'Margin right';
$strings['MarginLeft'] = 'Margin left';
$strings['SetDefaultTemplate'] = 'Set template by default';
$strings['MessageDefaultTemplate'] = 'Save this default customization for courses and sessions without
defined certificates';
$strings['None'] = 'None';
$strings['ErrorTemplateCertificate'] = 'There is no template defined for the certificate.
There is no template by default.';
$strings['DateStartEnd'] = 'With Start date and End date: ';
$strings['ExpedictionIn'] = 'Expediction in';
$strings['Signatures'] = 'Signatures';
$strings['BackgroundCertificate'] = 'Background image of the certificate';
$strings['Background'] = 'Background';
$strings['CertificateSetting'] = 'Certificate setting';
$strings['ToolDisabledCourse'] = 'Tool disabled in course setting';
$strings['ToolUseDefaultSettingCourse'] = 'Tool configured to use the default certificate. <br>
$strings['ContentsIndexLearnpath'] = "Use learnpath index";
$strings['ContentsCustom'] = "Use custom content";
$strings['ContentsHide'] = "No show contents";
$strings['Dates'] = "Dates";
$strings['CourseDeliveryDates'] = "Course delivery dates";
$strings['Custom'] = "Custom";
$strings['UseDateSessionAccess'] = "Use access dates to the session";
$strings['ExpectionPlace'] = "Expection place";
$strings['DateExpediction'] = "Expediction date";
$strings['UseDateEndAccessSession'] = "Use end date of session access";
$strings['UseDateDownloadCertificate'] = "Use certificate download date";
$strings['UseDateGenerationCertificate'] = "Use certificate generation date";
$strings['UseCustomDate'] = "Use custom date";
$strings['LogosSeal'] = "Logos / Seals";
$strings['LogoLeft'] = "Logo left";
$strings['LogoCenter'] = "Logo center";
$strings['LogoRight'] = "Logo right";
$strings['Seal'] = "Seal";
$strings['Signature1'] = "Signature 1";
$strings['Signature2'] = "Signature 2";
$strings['Signature3'] = "Signature 3";
$strings['Signature4'] = "Signature 4";
$strings['SignatureText1'] = "Signature text 1";
$strings['SignatureText2'] = "Signature text 2";
$strings['SignatureText3'] = "Signature text 3";
$strings['SignatureText4'] = "Signature text 4";
$strings['OtherOptions'] = "Others options";
$strings['MarginRight'] = "Margin right";
$strings['MarginLeft'] = "Margin left";
$strings['SetDefaultTemplate'] = "Set template by default";
$strings['MessageDefaultTemplate'] = "Save this default customization for courses and sessions without
defined certificates";
$strings['None'] = "None";
$strings['ErrorTemplateCertificate'] = "There is no template defined for the certificate.
There is no template by default.";
$strings['DateStartEnd'] = "With Start date and End date: ";
$strings['ExpedictionIn'] = "Expediction in";
$strings['Signatures'] = "Signatures";
$strings['BackgroundCertificate'] = "Background image of the certificate";
$strings['Background'] = "Background";
$strings['CertificateSetting'] = "Certificate setting";
$strings['ToolDisabledCourse'] = "Tool disabled in course setting";
$strings['ToolUseDefaultSettingCourse'] = "Tool configured to use the default certificate. <br>
You can edit it from the Administration screen -> Plugins -> Custom Certificate. <br>
Or if you want you can disable the option to use certificate by default in the plugin configuration in the course';
$strings['CertificateSettingDefault'] = 'Default certificate settings';
$strings['InfoFromDefaultCertificate'] = 'The content of the certificate is based on the default certificate.
The modifications you make will not affect the default certificate.';
$strings['to'] = ' to ';
$strings['formatDownloadDate'] = ' to %sth %s, %s';
$strings['PrintCertificate'] = 'Print certificate';
$strings['QuestionDelete'] = 'Do you want to delete the specific diploma and use the default certificate?';
$strings['SuccessDelete'] = 'Successfully deleted';
$strings['ProblemDelete'] = 'Problem deleting the certificate';
Or if you want you can disable the option to use certificate by default in the plugin configuration in the course";
$strings['CertificateSettingDefault'] = "Default certificate settings";
$strings['InfoFromDefaultCertificate'] = "The content of the certificate is based on the default certificate.
The modifications you make will not affect the default certificate.";
$strings['to'] = " to ";
$strings['formatDownloadDate'] = " to %sth %s, %s";
$strings['PrintCertificate'] = "Print certificate";
$strings['QuestionDelete'] = "Do you want to delete the specific diploma and use the default certificate?";
$strings['SuccessDelete'] = "Successfully deleted";
$strings['ProblemDelete'] = "Problem deleting the certificate";
$strings['OnlyCustomCertificates'] = "Only courses with a personalized certificate are exported";

@ -1,79 +1,79 @@
<?php
$strings['plugin_title'] = 'Certificado personalizado';
$strings['plugin_comment'] = 'Este plugin permite crear certificados personalizados por curso.';
$strings['enable_plugin_customcertificate'] = 'Activar plugin';
$strings['customcertificate_course_enable'] = 'Habilitar en el curso el certificado alternativo';
$strings['use_certificate_default'] = 'Usar el certificado personalizado por defecto';
$strings['ToolDisabled'] = 'La herramienta está deshabilitada desde la administración';
$strings['OnlyAdminPlatform'] = 'Herramienta exclusiva para administradores';
$strings['OnlyAdminPlatformOrTeacher'] = 'Herramienta para administradores y profesores';
$strings['TrainingEntity'] = 'Entidad formadora';
$strings['DescriptionFront'] = 'Descripción principal';
$strings['DescriptionRear'] = 'Descripción trasera';
$strings['Certify'] = 'Certifica';
$strings['CertificateType'] = 'Tipo certificado';
$strings['CertifyThat'] = 'CERTIFICA QUE';
$strings['plugin_title'] = "Certificado personalizado";
$strings['plugin_comment'] = "Este plugin permite crear certificados personalizados por curso.";
$strings['enable_plugin_customcertificate'] = "Activar plugin";
$strings['customcertificate_course_enable'] = "Habilitar en el curso el certificado alternativo";
$strings['use_certificate_default'] = "Usar el certificado personalizado por defecto";
$strings['ToolDisabled'] = "La herramienta está deshabilitada desde la administración";
$strings['OnlyAdminPlatform'] = "Herramienta exclusiva para administradores";
$strings['OnlyAdminPlatformOrTeacher'] = "Herramienta para administradores y profesores";
$strings['TrainingEntity'] = "Entidad formadora";
$strings['DescriptionFront'] = "Descripción principal";
$strings['DescriptionRear'] = "Descripción trasera";
$strings['Certify'] = "Certifica";
$strings['CertificateType'] = "Tipo certificado";
$strings['CertifyThat'] = "CERTIFICA QUE";
$strings['StudentCourseInfo'] = 'Datos del alumno y curso';
$strings['StudentData'] = 'Datos estudiante';
$strings['CourseData'] = 'Datos curso';
$strings['Modality'] = 'Modalidad';
$strings['Contents'] = 'Contenidos';
$strings['ContentsToShow'] = 'Contenidos a mostrar';
$strings['StudentData'] = "Datos estudiante";
$strings['CourseData'] = "Datos curso";
$strings['Modality'] = "Modalidad";
$strings['Contents'] = "Contenidos";
$strings['ContentsToShow'] = "Contenidos a mostrar";
$strings['ContentsCourseDescription'] = 'Usar apartado "Descripcion del curso" > "Contenidos"';
$strings['ContentsIndexLearnpath'] = 'Usar indice de lecciones';
$strings['ContentsCustom'] = 'Usar contenido personalizado';
$strings['ContentsHide'] = 'No mostrar contenidos';
$strings['Dates'] = 'Fechas';
$strings['CourseDeliveryDates'] = 'Fechas de impartición del curso';
$strings['Custom'] = 'Personalizado';
$strings['UseDateSessionAccess'] = 'Usar fechas de acceso a la sesión';
$strings['ExpectionPlace'] = 'Lugar expedición';
$strings['DateExpediction'] = 'Fecha expedición';
$strings['UseDateEndAccessSession'] = 'Usar fecha fin de acceso de la sesión';
$strings['UseDateDownloadCertificate'] = 'Usar fecha de descarga del certificado';
$strings['UseDateGenerationCertificate'] = 'Usar fecha de generación del certificado';
$strings['UseCustomDate'] = 'Usar fechas personalizadas';
$strings['LogosSeal'] = 'Logos / Sellos';
$strings['LogoLeft'] = 'Logo izquierda';
$strings['LogoCenter'] = 'Logo central';
$strings['LogoRight'] = 'Logo derecha';
$strings['Seal'] = 'Sello';
$strings['Signature1'] = 'Firma 1';
$strings['Signature2'] = 'Firma 2';
$strings['Signature3'] = 'Firma 3';
$strings['Signature4'] = 'Firma 4';
$strings['SignatureText1'] = 'Texto firma 1';
$strings['SignatureText2'] = 'Texto firma 2';
$strings['SignatureText3'] = 'Texto firma 3';
$strings['SignatureText4'] = 'Texto firma 4';
$strings['OtherOptions'] = 'Otras opciones';
$strings['MarginRight'] = 'Margen derecho';
$strings['MarginLeft'] = 'Margen izquierdo';
$strings['SetDefaultTemplate'] = 'Establecer plantilla por defecto';
$strings['MessageDefaultTemplate'] = 'Guardar esta personalización por defecto para cursos y
sesiones sin certificados definidos';
$strings['None'] = 'Ninguno';
$strings['ErrorTemplateCertificate'] = 'No hay una plantilla definida para el certificado.
No existe plantilla por defecto.';
$strings['DateStartEnd'] = 'Con Fecha de inicio y Fecha fin: ';
$strings['ExpedictionIn'] = 'Expedido en';
$strings['Signatures'] = 'Firmas';
$strings['BackgroundCertificate'] = 'Imagen de fondo del certificado';
$strings['Background'] = 'Fondo';
$strings['CertificateSetting'] = 'Configuración Certificado';
$strings['ToolDisabledCourse'] = 'Herramienta desabilitada en el curso';
$strings['ToolUseDefaultSettingCourse'] = 'Herramienta configurada para usar el certificado por defecto.<br>
$strings['ContentsIndexLearnpath'] = "Usar indice de lecciones";
$strings['ContentsCustom'] = "Usar contenido personalizado";
$strings['ContentsHide'] = "No mostrar contenidos";
$strings['Dates'] = "Fechas";
$strings['CourseDeliveryDates'] = "Fechas de impartición del curso";
$strings['Custom'] = "Personalizado";
$strings['UseDateSessionAccess'] = "Usar fechas de acceso a la sesión";
$strings['ExpectionPlace'] = "Lugar expedición";
$strings['DateExpediction'] = "Fecha expedición";
$strings['UseDateEndAccessSession'] = "Usar fecha fin de acceso de la sesión";
$strings['UseDateDownloadCertificate'] = "Usar fecha de descarga del certificado";
$strings['UseDateGenerationCertificate'] = "Usar fecha de generación del certificado";
$strings['UseCustomDate'] = "Usar fechas personalizadas";
$strings['LogosSeal'] = "Logos / Sellos";
$strings['LogoLeft'] = "Logo izquierda";
$strings['LogoCenter'] = "Logo central";
$strings['LogoRight'] = "Logo derecha";
$strings['Seal'] = "Sello";
$strings['Signature1'] = "Firma 1";
$strings['Signature2'] = "Firma 2";
$strings['Signature3'] = "Firma 3";
$strings['Signature4'] = "Firma 4";
$strings['SignatureText1'] = "Texto firma 1";
$strings['SignatureText2'] = "Texto firma 2";
$strings['SignatureText3'] = "Texto firma 3";
$strings['SignatureText4'] = "Texto firma 4";
$strings['OtherOptions'] = "Otras opciones";
$strings['MarginRight'] = "Margen derecho";
$strings['MarginLeft'] = "Margen izquierdo";
$strings['SetDefaultTemplate'] = "Establecer plantilla por defecto";
$strings['MessageDefaultTemplate'] = "Guardar esta personalización por defecto para cursos y
sesiones sin certificados definidos";
$strings['None'] = "Ninguno";
$strings['ErrorTemplateCertificate'] = "No hay una plantilla definida para el certificado.
No existe plantilla por defecto.";
$strings['DateStartEnd'] = "Con Fecha de inicio y Fecha fin: ";
$strings['ExpedictionIn'] = "Expedido en";
$strings['Signatures'] = "Firmas";
$strings['BackgroundCertificate'] = "Imagen de fondo del certificado";
$strings['Background'] = "Fondo";
$strings['CertificateSetting'] = "Configuración Certificado";
$strings['ToolDisabledCourse'] = "Herramienta desabilitada en el curso";
$strings['ToolUseDefaultSettingCourse'] = "Herramienta configurada para usar el certificado por defecto.<br>
Podrá editarlo desde la pantalla de Administración -> Plugins -> Certificado personalizado.<br>
O si lo desea puede desactivar la opción de usar certificado por defecto en la configuración del
plugin en el curso';
$strings['CertificateSettingDefault'] = 'Configuración del certificado por defecto';
$strings['InfoFromDefaultCertificate'] = 'El contenido del certificado está basado en el certificado por defecto.
Las modificaciones que realice no afectará al certificado por defecto.';
$strings['to'] = ' a ';
$strings['formatDownloadDate'] = ' a %s de %s de %s';
$strings['MessageUpdate'] = 'El proceso de actualización ha terminado';
$strings['PrintCertificate'] = 'Imprimir certificado';
$strings['QuestionDelete'] = '¿Desea eliminar el diploma específico y volver a usar el certificado por defecto?';
$strings['SuccessDelete'] = 'Borrado con éxito';
$strings['ProblemDelete'] = 'Hubo un problema al borrar el certificado';
plugin en el curso";
$strings['CertificateSettingDefault'] = "Configuración del certificado por defecto";
$strings['InfoFromDefaultCertificate'] = "El contenido del certificado está basado en el certificado por defecto.
Las modificaciones que realice no afectará al certificado por defecto.";
$strings['to'] = " a ";
$strings['formatDownloadDate'] = " a %s de %s de %s";
$strings['MessageUpdate'] = "El proceso de actualización ha terminado";
$strings['PrintCertificate'] = "Imprimir certificado";
$strings['QuestionDelete'] = "¿Desea eliminar el diploma específico y volver a usar el certificado por defecto?";
$strings['SuccessDelete'] = "Borrado con éxito";
$strings['ProblemDelete'] = "Hubo un problema al borrar el certificado";
$strings['OnlyCustomCertificates'] = "Solo se exportan los diplomas de cursos con certificado personalizado";

@ -1,11 +1,12 @@
<?php
/* For license terms, see /license.txt */
/**
* This script is a configuration file for the date plugin.
* You can use it as a master for other platform plugins (course plugins are slightly different).
* These settings will be used in the administration interface for plugins (Chamilo configuration settings->Plugins).
*
* @package chamilo.plugin.customcertificate
*/
/**

@ -1,10 +1,11 @@
<?php
/* For license terms, see /license.txt */
/**
* Plugin class for the CustomCertificate plugin.
*
* @package chamilo.plugin.customcertificate
*
* @author Jose Angel Ruiz <desarrollo@nosolored.com>
*/
class CustomCertificatePlugin extends Plugin
@ -51,7 +52,7 @@ class CustomCertificatePlugin extends Plugin
{
static $result = null;
return $result ?: $result = new self();
return $result ? $result : $result = new self();
}
/**
@ -99,7 +100,7 @@ class CustomCertificatePlugin extends Plugin
{
$oldCertificateTable = 'gradebook_certificate_alternative';
$base = api_get_path(WEB_UPLOAD_PATH);
if (1 == Database::num_rows(Database::query("SHOW TABLES LIKE '$oldCertificateTable'"))) {
if (Database::num_rows(Database::query("SHOW TABLES LIKE '$oldCertificateTable'")) == 1) {
$sql = "SELECT * FROM $oldCertificateTable";
$res = Database::query($sql);
while ($row = Database::fetch_assoc($res)) {
@ -109,13 +110,13 @@ class CustomCertificatePlugin extends Plugin
'c_id' => $row['c_id'],
'session_id' => $row['session_id'],
'content_course' => $row['content_course'],
'contents_type' => (int) ($row['contents_type']),
'contents_type' => intval($row['contents_type']),
'contents' => $row['contents'],
'date_change' => (int) ($row['date_change']),
'date_change' => intval($row['date_change']),
'date_start' => $row['date_start'],
'date_end' => $row['date_end'],
'place' => $row['place'],
'type_date_expediction' => (int) ($row['type_date_expediction']),
'type_date_expediction' => intval($row['type_date_expediction']),
'day' => $row['day'],
'month' => $row['month'],
'year' => $row['year'],
@ -132,7 +133,7 @@ class CustomCertificatePlugin extends Plugin
'signature_text3' => $row['signature_text3'],
'signature_text4' => $row['signature_text4'],
'background' => $row['background'],
'margin_left' => (int) ($row['margin']),
'margin_left' => intval($row['margin']),
'margin_right' => 0,
'certificate_default' => 0,
];
@ -166,7 +167,7 @@ class CustomCertificatePlugin extends Plugin
}
}
if (1 == $row['certificate_default']) {
if ($row['certificate_default'] == 1) {
$params['c_id'] = 0;
$params['session_id'] = 0;
$params['certificate_default'] = 1;
@ -232,7 +233,7 @@ class CustomCertificatePlugin extends Plugin
$courseCode = $row['course_code'];
$sessionId = $row['session_id'];
$userId = $row['user_id'];
if (1 == api_get_course_setting('customcertificate_course_enable', api_get_course_info($courseCode))) {
if (api_get_course_setting('customcertificate_course_enable', api_get_course_info($courseCode)) == 1) {
return [
'course_code' => $courseCode,
'session_id' => $sessionId,
@ -256,7 +257,7 @@ class CustomCertificatePlugin extends Plugin
$certId = (int) $certId;
$userId = !empty($userId) ? $userId : api_get_user_id();
if ('true' === api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate')) {
if (api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate') === 'true') {
$infoCertificate = self::getCertificateData($certId, $userId);
if (!empty($infoCertificate)) {
if ($certificate->user_id == api_get_user_id() && !empty($certificate->certificate_data)) {

@ -1,9 +1,10 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Responses to AJAX calls.
*
* @package chamilo.plugin.customcertificate
*/
$cidReset = true;
@ -12,9 +13,9 @@ require_once __DIR__.'/../../../main/inc/global.inc.php';
api_block_anonymous_users();
$plugin = CustomCertificatePlugin::create();
$enable = 'true' == $plugin->get('enable_plugin_customcertificate');
$enable = $plugin->get('enable_plugin_customcertificate') == 'true';
if (false === $enable) {
if ($enable === false) {
api_not_allowed();
}

@ -0,0 +1,708 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CourseBundle\Entity\CLpCategory;
$course_plugin = 'customcertificate';
require_once __DIR__.'/../config.php';
api_block_anonymous_users();
$plugin = CustomCertificatePlugin::create();
$enable = $plugin->get('enable_plugin_customcertificate') === 'true';
$tblProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
$tblCourse = Database::get_main_table(TABLE_MAIN_COURSE);
$tblSessionRelCourse = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
$tblSessionRelAccessUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
define('NO_DATE_FILTER', 0);
define('DATE_BEGIN_FILTER', 1);
define('DATE_END_FILTER', 2);
define('ALL_DATE_FILTER', 3);
if (!$enable) {
api_not_allowed(true, $plugin->get_lang('ToolDisabled'));
}
$currentLocalTime = api_get_local_time();
$accessUrlId = api_get_current_access_url_id();
$sessionId = isset($_GET['session_id']) ? (int) $_GET['session_id'] : null;
$dateBegin = isset($_GET['date_begin']) ? strtotime($_GET['date_begin']) : null;
$dateEnd = isset($_GET['date_end']) ? strtotime($_GET['date_end'].' 23:59:59') : null;
if (api_is_multiple_url_enabled()) {
if ($accessUrlId != -1) {
$result = Database::select(
'*',
"$tblSessionRelAccessUrl",
[
'where' => [
"access_url_id = ? AND session_id = ?" => [$accessUrlId, $sessionId],
],
]
);
if (empty($result)) {
api_not_allowed();
}
}
}
$exportAllInOne = isset($_GET['export_pdf']) ? (int) $_GET['export_pdf'] : false;
$exportZip = isset($_GET['export_zip']) ? (int) $_GET['export_zip'] : false;
$filterDate = 0;
if (!empty($dateBegin)) {
$filterDate += DATE_BEGIN_FILTER;
}
if (!empty($dateEnd)) {
$filterDate += DATE_END_FILTER;
}
$filterCheckList = [];
$extraField = new ExtraField('user');
$extraFieldsAll = $extraField->get_all(['filter = ?' => 1], 'option_order');
foreach ($extraFieldsAll as $field) {
if (!empty($_GET['extra_'.$field['variable']])) {
$filterCheckList[$field['id']] = $field;
}
}
$result = Database::select(
'c.id, c.code',
"$tblCourse c INNER JOIN $tblSessionRelCourse r ON c.id = r.c_id",
[
'where' => [
"r.session_id = ? " => [$sessionId],
],
]
);
foreach ($result as $value) {
$courseId = $value['id'];
$courseCode = $value['code'];
$cats = Category::load(
null,
null,
$courseCode,
null,
null,
$sessionId,
'ORDER BY id'
);
if (empty($cats)) {
// first time
$cats = Category::load(
0,
null,
$courseCode,
null,
null,
$sessionId,
'ORDER BY id'
);
}
$selectCat = (int) $cats[0]->get_id();
$certificateList = [];
$certificateListAux = GradebookUtils::get_list_users_certificates($selectCat);
foreach ($certificateListAux as $value) {
$created_at = strtotime(api_get_local_time($value['created_at']));
$value['category_id'] = $selectCat;
$value['c_id'] = $courseId;
$value['course_code'] = $courseCode;
switch ($filterDate) {
case NO_DATE_FILTER:
$certificateList[] = $value;
break;
case DATE_BEGIN_FILTER:
if ($created_at >= $dateBegin) {
$certificateList[] = $value;
}
break;
case DATE_END_FILTER:
if ($created_at <= $dateEnd) {
$certificateList[] = $value;
}
break;
case ALL_DATE_FILTER:
if ($created_at >= $dateBegin && $created_at <= $dateEnd) {
$certificateList[] = $value;
}
break;
}
}
// Filter extra field
foreach ($certificateList as $key => $value) {
foreach ($filterCheckList as $fieldId => $field) {
$extraFieldValue = new ExtraFieldValue('user');
$extraFieldValueData = $extraFieldValue->get_values_by_handler_and_field_id(
$value['user_id'],
$fieldId
);
if (empty($extraFieldValueData)) {
unset($certificateList[$key]);
break;
}
switch ($field['field_type']) {
case ExtraField::FIELD_TYPE_TEXT:
case ExtraField::FIELD_TYPE_ALPHANUMERIC:
$pos = stripos($extraFieldValueData['value'], $_GET['extra_'.$field['variable']]);
if ($pos === false) {
unset($certificateList[$key]);
}
break;
case ExtraField::FIELD_TYPE_RADIO:
$valueRadio = $_GET['extra_'.$field['variable']]['extra_'.$field['variable']];
if ($extraFieldValueData['value'] != $valueRadio) {
unset($certificateList[$key]);
}
break;
case ExtraField::FIELD_TYPE_SELECT:
if ($extraFieldValueData['value'] != $_GET['extra_'.$field['variable']]) {
unset($certificateList[$key]);
}
break;
}
}
}
}
$userList = [];
foreach ($certificateList as $index => $value) {
$infoUser = api_get_user_info($value['user_id']);
$infoUser['category_id'] = $value['category_id'];
$infoUser['c_id'] = $value['c_id'];
$infoUser['course_code'] = $value['course_code'];
$userList[] = $infoUser;
}
$sessionInfo = [];
if ($sessionId > 0) {
$sessionInfo = SessionManager::fetch($sessionId);
}
$path = api_get_path(WEB_UPLOAD_PATH).'certificates/';
$htmlList = [];
foreach ($userList as $userInfo) {
$courseId = $userInfo['c_id'];
$courseCode = $userInfo['course_code'];
$studentId = $userInfo['user_id'];
$courseInfo = api_get_course_info($courseCode);
$allowCustomCertificate = api_get_course_setting('customcertificate_course_enable', $courseInfo);
if (!$allowCustomCertificate) {
continue;
}
// Get info certificate
$infoCertificate = CustomCertificatePlugin::getInfoCertificate($courseId, $sessionId, $accessUrlId);
if (!is_array($infoCertificate)) {
$infoCertificate = [];
}
if (empty($infoCertificate)) {
$infoCertificate = CustomCertificatePlugin::getInfoCertificateDefault($accessUrlId);
if (empty($infoCertificate)) {
Display::display_header($plugin->get_lang('PrintCertificate'));
echo Display::return_message($plugin->get_lang('ErrorTemplateCertificate'), 'error');
Display::display_footer();
exit;
}
}
$workSpace = intval(297 - $infoCertificate['margin_left'] - $infoCertificate['margin_right']);
$widthCell = intval($workSpace / 6);
$htmlText = '';
if (!$exportAllInOne) {
$htmlText = '<html>';
$htmlText .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_PLUGIN_PATH).'customcertificate/resources/css/certificate.css">';
$htmlText .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_CSS_PATH).'document.css">';
$htmlText .= '<body>';
}
$studentId = $userInfo['user_id'];
if (empty($infoCertificate['background'])) {
$htmlText .= '<div class="caraA" style="page-break-before:always; margin:0px; padding:0px;">';
} else {
$urlBackground = $path.$infoCertificate['background'];
$htmlText .= ' <div
class="caraA"
style="background-image:url('.$urlBackground.') no-repeat;
background-image-resize:6; margin:0px; padding:0px;">';
}
if (!empty($infoCertificate['logo_left'])) {
$logoLeft = '
<img
style="max-height: 150px; max-width: '.(2 * $widthCell).'mm;"
src="'.$path.$infoCertificate['logo_left'].'" />';
} else {
$logoLeft = '';
}
$logoCenter = '';
if (!empty($infoCertificate['logo_center'])) {
$logoCenter = '
<img
style="max-height: 150px; max-width: '.intval($workSpace - (2 * $widthCell)).'mm;"
src="'.$path.$infoCertificate['logo_center'].'" />';
}
$logoRight = '';
if (!empty($infoCertificate['logo_right'])) {
$logoRight = '
<img
style="max-height: 150px; max-width: '.(2 * $widthCell).'mm;"
src="'.$path.$infoCertificate['logo_right'].'" />';
}
$htmlText .= '<table
width="'.$workSpace.'mm"
style="
margin-left:'.$infoCertificate['margin_left'].'mm;
margin-right:'.$infoCertificate['margin_right'].'mm;
"
border="0">';
$htmlText .= '<tr>';
$htmlText .= '<td style="width:'.intval($workSpace / 3).'mm" class="logo">'.$logoLeft.'</td>';
$htmlText .= '<td style="width:'.intval($workSpace / 3).'mm; text-align:center;" class="logo">';
$htmlText .= $logoCenter;
$htmlText .= '</td>';
$htmlText .= '<td style="width:'.intval($workSpace / 3).'mm; text-align:right;" class="logo">'.$logoRight.'</td>';
$htmlText .= '</tr>';
$htmlText .= '</table>';
$allUserInfo = DocumentManager::get_all_info_to_certificate(
$studentId,
$courseCode,
$sessionId,
false
);
$myContentHtml = $infoCertificate['content_course'];
$myContentHtml = str_replace(chr(13).chr(10).chr(13).chr(10), chr(13).chr(10), $myContentHtml);
$infoToBeReplacedInContentHtml = $allUserInfo[0];
$infoToReplaceInContentHtml = $allUserInfo[1];
$myContentHtml = str_replace(
$infoToBeReplacedInContentHtml,
$infoToReplaceInContentHtml,
$myContentHtml
);
$startDate = '';
$endDate = '';
switch ($infoCertificate['date_change']) {
case 0:
if (!empty($sessionInfo['access_start_date'])) {
$startDate = date("d/m/Y", strtotime(api_get_local_time($sessionInfo['access_start_date'])));
}
if (!empty($sessionInfo['access_end_date'])) {
$endDate = date("d/m/Y", strtotime(api_get_local_time($sessionInfo['access_end_date'])));
}
break;
case 1:
$startDate = date("d/m/Y", strtotime($infoCertificate['date_start']));
$endDate = date("d/m/Y", strtotime($infoCertificate['date_end']));
break;
}
$myContentHtml = str_replace(
'((start_date))',
$startDate,
$myContentHtml
);
$myContentHtml = str_replace(
'((end_date))',
$endDate,
$myContentHtml
);
$dateExpediction = '';
if ($infoCertificate['type_date_expediction'] != 3) {
$dateExpediction .= $plugin->get_lang('ExpedictionIn').' '.$infoCertificate['place'];
if ($infoCertificate['type_date_expediction'] == 1) {
$dateExpediction .= $plugin->get_lang('to').api_format_date(time(), DATE_FORMAT_LONG);
} elseif ($infoCertificate['type_date_expediction'] == 2) {
$dateFormat = $plugin->get_lang('formatDownloadDate');
if (!empty($infoCertificate['day']) &&
!empty($infoCertificate['month']) &&
!empty($infoCertificate['year'])
) {
$dateExpediction .= sprintf(
$dateFormat,
$infoCertificate['day'],
$infoCertificate['month'],
$infoCertificate['year']
);
} else {
$dateExpediction .= sprintf(
$dateFormat,
'......',
'....................',
'............'
);
}
} elseif ($infoCertificate['type_date_expediction'] == 4) {
$dateExpediction .= $plugin->get_lang('to').$infoToReplaceInContentHtml[9]; //date_certificate_no_time
} else {
if (!empty($sessionInfo)) {
$dateInfo = api_get_local_time($sessionInfo['access_end_date']);
$dateExpediction .= $plugin->get_lang('to').api_format_date($dateInfo, DATE_FORMAT_LONG);
}
}
}
$myContentHtml = str_replace(
'((date_expediction))',
$dateExpediction,
$myContentHtml
);
$myContentHtml = strip_tags(
$myContentHtml,
'<p><b><strong><table><tr><td><th><tbody><span><i><li><ol><ul>
<dd><dt><dl><br><hr><img><a><div><h1><h2><h3><h4><h5><h6>'
);
$htmlText .= '<div style="
height: 480px;
width:'.$workSpace.'mm;
margin-left:'.$infoCertificate['margin_left'].'mm;
margin-right:'.$infoCertificate['margin_right'].'mm;
">';
$htmlText .= $myContentHtml;
$htmlText .= '</div>';
$htmlText .= '<table
width="'.$workSpace.'mm"
style="
margin-left:'.$infoCertificate['margin_left'].'mm;
margin-right:'.$infoCertificate['margin_right'].'mm;
"
border="0">';
$htmlText .= '<tr>';
$htmlText .= '<td colspan="2" class="seals" style="width:'.$widthCell.'mm">'.
((!empty($infoCertificate['signature_text1'])) ? $infoCertificate['signature_text1'] : '').
'</td>
<td colspan="2" class="seals" style="width:'.$widthCell.'mm">'.
((!empty($infoCertificate['signature_text2'])) ? $infoCertificate['signature_text2'] : '').
'</td>
<td colspan="2" class="seals" style="width:'.$widthCell.'mm">'.
((!empty($infoCertificate['signature_text3'])) ? $infoCertificate['signature_text3'] : '').
'</td>
<td colspan="2" class="seals" style="width:'.$widthCell.'mm">'.
((!empty($infoCertificate['signature_text4'])) ? $infoCertificate['signature_text4'] : '').
'</td>
<td colspan="4" class="seals" style="width:'.(2 * $widthCell).'mm">
'.((!empty($infoCertificate['seal'])) ? $plugin->get_lang('Seal') : '').
'</td>';
$htmlText .= '</tr>';
$htmlText .= '<tr>';
$htmlText .= '<td colspan="2" class="logo-seals" style="width:'.$widthCell.'mm">'.
((!empty($infoCertificate['signature1']))
? '<img style="max-height: 100px; max-width: '.$widthCell.'mm;"
src="'.$path.$infoCertificate['signature1'].'" />'
: '').
'</td>
<td colspan="2" class="logo-seals" style="width:'.$widthCell.'mm">'.
((!empty($infoCertificate['signature2']))
? '<img style="max-height: 100px; '.$widthCell.'mm;"
src="'.$path.$infoCertificate['signature2'].'" />'
: '').
'</td>
<td colspan="2" class="logo-seals" style="width:'.$widthCell.'mm">'.
((!empty($infoCertificate['signature3']))
? '<img style="max-height: 100px; '.$widthCell.'mm;"
src="'.$path.$infoCertificate['signature3'].'" />'
: '').
'</td>
<td colspan="2" class="logo-seals" style="width:'.$widthCell.'mm">'.
((!empty($infoCertificate['signature4']))
? '<img style="max-height: 100px; '.$widthCell.'mm;"
src="'.$path.$infoCertificate['signature4'].'" />'
: '').
'</td>
<td colspan="4" class="logo-seals" style="width:'.(2 * $widthCell).'mm">'.
((!empty($infoCertificate['seal']))
? '<img style="max-height: 100px; '.(2 * $widthCell).'mm;"
src="'.$path.$infoCertificate['seal'].'" />'
: '').
'</td>';
$htmlText .= '</tr>';
$htmlText .= '</table>';
$htmlText .= '</div>';
// Rear certificate
if ($infoCertificate['contents_type'] != 3) {
$htmlText .= '<div class="caraB" style="page-break-before:always; margin:0px; padding:0px;">';
if ($infoCertificate['contents_type'] == 0) {
$courseDescription = new CourseDescription();
$contentDescription = $courseDescription->get_data_by_description_type(3, $courseId, 0);
$domd = new DOMDocument();
libxml_use_internal_errors(true);
if (isset($contentDescription['description_content'])) {
$domd->loadHTML($contentDescription['description_content']);
}
libxml_use_internal_errors(false);
$domx = new DOMXPath($domd);
$items = $domx->query("//li[@style]");
foreach ($items as $item) {
$item->removeAttribute("style");
}
$items = $domx->query("//span[@style]");
foreach ($items as $item) {
$item->removeAttribute("style");
}
$output = $domd->saveHTML();
$htmlText .= getIndexFiltered($output);
}
if ($infoCertificate['contents_type'] == 1) {
$items = [];
$categoriesTempList = learnpath::getCategories($courseId);
$categoryTest = new CLpCategory();
$categoryTest->setId(0);
$categoryTest->setName($plugin->get_lang('WithOutCategory'));
$categoryTest->setPosition(0);
$categories = [$categoryTest];
if (!empty($categoriesTempList)) {
$categories = array_merge($categories, $categoriesTempList);
}
foreach ($categories as $item) {
$categoryId = $item->getId();
if (!learnpath::categoryIsVisibleForStudent($item, api_get_user_entity($studentId))) {
continue;
}
$sql = "SELECT 1
FROM $tblProperty
WHERE tool = 'learnpath_category'
AND ref = $categoryId
AND visibility = 0
AND (session_id = $sessionId OR session_id IS NULL)";
$res = Database::query($sql);
if (Database::num_rows($res) > 0) {
continue;
}
$list = new LearnpathList(
$studentId,
$courseCode,
$sessionId,
null,
false,
$categoryId
);
$flat_list = $list->get_flat_list();
if (empty($flat_list)) {
continue;
}
if (count($categories) > 1 && count($flat_list) > 0) {
if ($item->getName() != $plugin->get_lang('WithOutCategory')) {
$items[] = '<h4 style="margin:0">'.$item->getName().'</h4>';
}
}
foreach ($flat_list as $learnpath) {
$lpId = $learnpath['lp_old_id'];
$sql = "SELECT 1
FROM $tblProperty
WHERE tool = 'learnpath'
AND ref = $lpId AND visibility = 0
AND (session_id = $sessionId OR session_id IS NULL)";
$res = Database::query($sql);
if (Database::num_rows($res) > 0) {
continue;
}
$lpName = $learnpath['lp_name'];
$items[] = $lpName.'<br>';
}
$items[] = '<br />';
}
if (count($items) > 0) {
$htmlText .= '<table width="100%" class="contents-learnpath">';
$htmlText .= '<tr>';
$htmlText .= '<td>';
$i = 0;
foreach ($items as $value) {
if ($i == 50) {
$htmlText .= '</td><td>';
}
$htmlText .= $value;
$i++;
}
$htmlText .= '</td>';
$htmlText .= '</tr>';
$htmlText .= '</table>';
}
$htmlText .= '</td></table>';
}
if ($infoCertificate['contents_type'] == 2) {
$htmlText .= '<table width="100%" class="contents-learnpath">';
$htmlText .= '<tr>';
$htmlText .= '<td>';
$myContentHtml = strip_tags(
$infoCertificate['contents'],
'<p><b><strong><table><tr><td><th><span><i><li><ol><ul>'.
'<dd><dt><dl><br><hr><img><a><div><h1><h2><h3><h4><h5><h6>'
);
$htmlText .= $myContentHtml;
$htmlText .= '</td>';
$htmlText .= '</tr>';
$htmlText .= '</table>';
}
$htmlText .= '</div>';
}
if (!$exportAllInOne) {
$htmlText .= '</body></html>';
}
$fileName = 'certificate_'.$userInfo['course_code'].'_'.$userInfo['complete_name'].'_'.$currentLocalTime;
$htmlList[$fileName] = $htmlText;
}
$fileList = [];
$archivePath = api_get_path(SYS_ARCHIVE_PATH).'certificates/';
if (!is_dir($archivePath)) {
mkdir($archivePath, api_get_permissions_for_new_directories());
}
if ($exportAllInOne) {
$params = [
'pdf_title' => 'Certificate',
'pdf_description' => '',
'format' => 'A4-L',
'orientation' => 'L',
'left' => 15,
'top' => 15,
'bottom' => 0,
];
$pdf = new PDF($params['format'], $params['orientation'], $params);
$contentAllCertificate = '';
foreach ($htmlList as $fileName => $content) {
$contentAllCertificate .= $content;
}
if (!empty($contentAllCertificate)) {
$certificateContent = '<html>';
$certificateContent .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_PLUGIN_PATH).'customcertificate/resources/css/certificate.css">';
$certificateContent .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_CSS_PATH).'document.css">';
$certificateContent .= '<body>';
$certificateContent .= $contentAllCertificate;
$certificateContent .= '</body></html>';
$pdf->content_to_pdf(
$certificateContent,
'',
'certificate'.date('Y_m_d_His'),
null,
'D',
false,
null,
false,
false,
false
);
}
} else {
foreach ($htmlList as $fileName => $content) {
$fileName = api_replace_dangerous_char($fileName);
$params = [
'filename' => $fileName,
'pdf_title' => 'Certificate',
'pdf_description' => '',
'format' => 'A4-L',
'orientation' => 'L',
'left' => 15,
'top' => 15,
'bottom' => 0,
];
$pdf = new PDF($params['format'], $params['orientation'], $params);
if ($exportZip) {
$filePath = $archivePath.$fileName.'.pdf';
$pdf->content_to_pdf($content, '', $fileName, null, 'F', true, $filePath, false, false, false);
$fileList[] = $filePath;
} else {
$pdf->content_to_pdf($content, '', $fileName, null, 'D', false, null, false, false, false);
}
}
if (!empty($fileList)) {
$zipFile = $archivePath.'certificates_'.api_get_unique_id().'.zip';
$zipFolder = new PclZip($zipFile);
foreach ($fileList as $file) {
$zipFolder->add($file, PCLZIP_OPT_REMOVE_ALL_PATH);
}
$name = 'certificates_'.$currentLocalTime.'.zip';
DocumentManager::file_send_for_download($zipFile, true, $name);
exit;
}
}
function getIndexFiltered($index)
{
$txt = strip_tags($index, "<b><strong><i>");
$txt = str_replace(chr(13).chr(10).chr(13).chr(10), chr(13).chr(10), $txt);
$lines = explode(chr(13).chr(10), $txt);
$text1 = '';
for ($x = 0; $x < 47; $x++) {
if (isset($lines[$x])) {
$text1 .= $lines[$x].chr(13).chr(10);
}
}
$text2 = '';
for ($x = 47; $x < 94; $x++) {
if (isset($lines[$x])) {
$text2 .= $lines[$x].chr(13).chr(10);
}
}
$showLeft = str_replace(chr(13).chr(10), "<br/>", $text1);
$showRight = str_replace(chr(13).chr(10), "<br/>", $text2);
$result = '<table width="100%">';
$result .= '<tr>';
$result .= '<td style="width:50%;vertical-align:top;padding-left:15px; font-size:12px;">'.$showLeft.'</td>';
$result .= '<td style="vertical-align:top; font-size:12px;">'.$showRight.'</td>';
$result .= '<tr>';
$result .= '</table>';
return $result;
}

@ -1,11 +1,10 @@
<?php
/* For licensing terms, see /license.txt */
$useDefault = false;
$isDefault = isset($_GET['default']) ? (int) $_GET['default'] : null;
if (1 === $isDefault) {
if ($isDefault === 1) {
$cidReset = true;
}
@ -17,11 +16,11 @@ $_setting['student_view_enabled'] = 'false';
$userId = api_get_user_id();
$plugin = CustomCertificatePlugin::create();
$nameTools = $plugin->get_lang('CertificateSetting');
$enable = 'true' == $plugin->get('enable_plugin_customcertificate');
$enable = $plugin->get('enable_plugin_customcertificate') == 'true';
$accessUrlId = api_get_current_access_url_id();
$course_info = api_get_course_info();
if (1 === $isDefault) {
if ($isDefault === 1) {
$courseId = 0;
$courseCode = '';
$sessionId = 0;
@ -34,8 +33,8 @@ if (1 === $isDefault) {
$courseId = api_get_course_int_id();
$courseCode = api_get_course_id();
$sessionId = api_get_session_id();
$enableCourse = 1 == api_get_course_setting('customcertificate_course_enable', $course_info) ? true : false;
$useDefault = 1 == api_get_course_setting('use_certificate_default', $course_info) ? true : false;
$enableCourse = api_get_course_setting('customcertificate_course_enable', $course_info) == 1 ? true : false;
$useDefault = api_get_course_setting('use_certificate_default', $course_info) == 1 ? true : false;
$defaultCertificate = 0;
$urlParams = '?'.api_get_cidreq();
}
@ -72,7 +71,7 @@ $htmlHeadXtra[] = '<script>
e.preventDefault();
e.stopPropagation();
if (confirm("'.$plugin->get_lang('QuestionDelete').'")) {
if (confirm("'.$plugin->get_lang("QuestionDelete").'")) {
var courseId = '.$courseId.';
var sessionId = '.$sessionId.';
var accessUrlId = '.$accessUrlId.';
@ -121,9 +120,9 @@ if ($form->validate()) {
'content_course' => $formValues['content_course'],
'contents_type' => (int) $formValues['contents_type'],
'contents' => $contents,
'date_change' => (int) ($formValues['date_change']),
'date_start' => date('Y-m-d', strtotime($date_start)),
'date_end' => date('Y-m-d', strtotime($date_end)),
'date_change' => intval($formValues['date_change']),
'date_start' => date("Y-m-d", strtotime($date_start)),
'date_end' => date("Y-m-d", strtotime($date_end)),
'place' => $formValues['place'],
'type_date_expediction' => (int) $formValues['type_date_expediction'],
'day' => $formValues['day'],
@ -138,7 +137,7 @@ if ($form->validate()) {
'certificate_default' => 0,
];
if ((int) (1 == $formValues['default_certificate'])) {
if (intval($formValues['default_certificate'] == 1)) {
$params['certificate_default'] = 1;
}
@ -191,7 +190,7 @@ if ($form->validate()) {
}
// Certificate Default
if ((int) (1 == $formValues['use_default'])) {
if (intval($formValues['use_default'] == 1)) {
$infoCertificateDefault = CustomCertificatePlugin::getInfoCertificateDefault($accessUrlId);
if (!empty($infoCertificateDefault)) {
foreach ($fieldList as $field) {
@ -205,7 +204,7 @@ if ($form->validate()) {
}
}
Display::addFlash(Display::return_message(get_lang('Saved.')));
Display::addFlash(Display::return_message(get_lang('Saved')));
Security::clear_token();
header('Location: '.api_get_self().$urlParams);
@ -295,7 +294,7 @@ $strInfo .= '((start_date))<br />';
$strInfo .= '((end_date))<br />';
$strInfo .= '((date_expediction))';
$createCertificate = get_lang('Create your certificate copy-pasting the following tags. They will be replaced in the document by their student-specific value:');
$createCertificate = get_lang('CreateCertificateWithTags');
$form->addElement(
'html',
Display::return_message($createCertificate.': <br />'.$strInfo, 'normal', false)
@ -387,7 +386,7 @@ $form->addHtmlEditor(
$form->addHtml('</div>');
// Dates section
$form->addHtml('<fieldset><legend>'.strtoupper(get_lang('Dates')).'</legend>');
$form->addHtml('<fieldset><legend>'.strtoupper(get_lang("Dates")).'</legend>');
$group = [];
$option1 = &$form->createElement(
@ -404,7 +403,7 @@ $option2 = &$form->createElement(
'radio',
'date_change',
'',
get_lang('none'),
get_lang('None'),
2,
['id' => 'date_change_2', 'onclick' => 'javascript: dateCertificateSwitchRadioButton2();']
);
@ -439,10 +438,10 @@ $form->addHtml('<div class="form-group" style="padding-top: 10px;">
name="date_start"
id="date_start"
type="text"
value="'.('1' == $infoCertificate['date_change']
? date('d/m/Y', strtotime($infoCertificate['date_start']))
value="'.(($infoCertificate['date_change'] == '1')
? date("d/m/Y", strtotime($infoCertificate['date_start']))
: '').'"
'.('' == $infoCertificate['date_change'] ? 'disabled' : '').'
'.(($infoCertificate['date_change'] == '') ? 'disabled' : '').'
>
<span style="margin: 0 10px; font-style: italic;">'.get_lang('Until').'</span>
<input
@ -451,10 +450,10 @@ $form->addHtml('<div class="form-group" style="padding-top: 10px;">
name="date_end"
id="date_end"
type="text"
value="'.('1' == $infoCertificate['date_change']
? date('d/m/Y', strtotime($infoCertificate['date_end']))
value="'.(($infoCertificate['date_change'] == '1')
? date("d/m/Y", strtotime($infoCertificate['date_end']))
: '').'"
'.('0' == $infoCertificate['date_change'] ? 'disabled' : '').'
'.(($infoCertificate['date_change'] == "0") ? 'disabled' : '').'
>
</div>
</div>
@ -477,7 +476,7 @@ $option = &$form->createElement(
[
'id' => 'type_date_expediction_0',
'onclick' => 'javascript: dateCertificateSwitchRadioButton0();',
(0 == $sessionId ? 'disabled' : ''),
(($sessionId == 0) ? 'disabled' : ''),
]
);
$group[] = $option;
@ -512,7 +511,7 @@ $option = &$form->createElement(
'radio',
'type_date_expediction',
'',
get_lang('none'),
get_lang('None'),
3,
[
'id' => 'type_date_expediction_3',
@ -556,7 +555,7 @@ $form->addHtml(
id="day"
type="text"
value="'.$infoCertificate['day'].'"
'.('2' != $infoCertificate['type_date_expediction'] ? 'disabled' : '').'
'.(($infoCertificate['type_date_expediction'] != '2') ? 'disabled' : '').'
>
<span class="certificado-text-label">de</span>
<input
@ -567,7 +566,7 @@ $form->addHtml(
id="month"
type="text"
value="'.$infoCertificate['month'].'"
'.('2' != $infoCertificate['type_date_expediction'] ? 'disabled' : '').'
'.(($infoCertificate['type_date_expediction'] != '2') ? 'disabled' : '').'
>
<span class="certificado-text-label">de</span>
<input
@ -578,7 +577,7 @@ $form->addHtml(
id="year"
type="text"
value="'.$infoCertificate['year'].'"
'.('2' != $infoCertificate['type_date_expediction'] ? 'disabled' : '').'
'.(($infoCertificate['type_date_expediction'] != '2') ? 'disabled' : '').'
>
</div>
</div>
@ -604,7 +603,7 @@ $form->addFile(
);
$form->addProgress();
if (!empty($infoCertificate['logo_left'])) {
$form->addElement('checkbox', 'remove_logo_left', null, get_lang('Remove picture'));
$form->addElement('checkbox', 'remove_logo_left', null, get_lang('DelImage'));
$form->addElement(
'html',
'<label class="col-sm-2">&nbsp;</label>
@ -615,7 +614,7 @@ if (!empty($infoCertificate['logo_left'])) {
$allowedPictureTypes = api_get_supported_image_extensions(false);
$form->addRule(
'logo_left',
get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(', ', $allowedPictureTypes).')',
get_lang('OnlyImagesAllowed').' ('.implode(', ', $allowedPictureTypes).')',
'filetype',
$allowedPictureTypes
);
@ -634,7 +633,7 @@ $form->addFile(
);
$form->addProgress();
if (!empty($infoCertificate['logo_center'])) {
$form->addElement('checkbox', 'remove_logo_center', null, get_lang('Remove picture'));
$form->addElement('checkbox', 'remove_logo_center', null, get_lang('DelImage'));
$form->addElement(
'html',
'<label class="col-sm-2">&nbsp;</label>
@ -645,7 +644,7 @@ if (!empty($infoCertificate['logo_center'])) {
$allowedPictureTypes = api_get_supported_image_extensions(false);
$form->addRule(
'logo_center',
get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(', ', $allowedPictureTypes).')',
get_lang('OnlyImagesAllowed').' ('.implode(', ', $allowedPictureTypes).')',
'filetype',
$allowedPictureTypes
);
@ -664,7 +663,7 @@ $form->addFile(
);
$form->addProgress();
if (!empty($infoCertificate['logo_right'])) {
$form->addElement('checkbox', 'remove_logo_right', null, get_lang('Remove picture'));
$form->addElement('checkbox', 'remove_logo_right', null, get_lang('DelImage'));
$form->addElement(
'html',
'<label class="col-sm-2">&nbsp;</label>
@ -675,7 +674,7 @@ if (!empty($infoCertificate['logo_right'])) {
$tblProperty = api_get_supported_image_extensions(false);
$form->addRule(
'logo_right',
get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(', ', $allowedPictureTypes).')',
get_lang('OnlyImagesAllowed').' ('.implode(', ', $allowedPictureTypes).')',
'filetype',
$allowedPictureTypes
);
@ -693,7 +692,7 @@ $form->addFile(
);
$form->addProgress();
if (!empty($infoCertificate['seal'])) {
$form->addElement('checkbox', 'remove_seal', null, get_lang('Remove picture'));
$form->addElement('checkbox', 'remove_seal', null, get_lang('DelImage'));
$form->addElement(
'html',
'<label class="col-sm-2">&nbsp;</label>
@ -704,7 +703,7 @@ if (!empty($infoCertificate['seal'])) {
$allowedPictureTypes = api_get_supported_image_extensions(false);
$form->addRule(
'seal',
get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(', ', $allowedPictureTypes).')',
get_lang('OnlyImagesAllowed').' ('.implode(', ', $allowedPictureTypes).')',
'filetype',
$allowedPictureTypes
);
@ -731,7 +730,7 @@ $form->addFile(
);
$form->addProgress();
if (!empty($infoCertificate['signature1'])) {
$form->addElement('checkbox', 'remove_signature1', null, get_lang('Remove picture'));
$form->addElement('checkbox', 'remove_signature1', null, get_lang('DelImage'));
$form->addElement(
'html',
'<label class="col-sm-2">&nbsp;</label>
@ -742,7 +741,7 @@ if (!empty($infoCertificate['signature1'])) {
$allowedPictureTypes = api_get_supported_image_extensions(false);
$form->addRule(
'signature1',
get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(', ', $allowedPictureTypes).')',
get_lang('OnlyImagesAllowed').' ('.implode(', ', $allowedPictureTypes).')',
'filetype',
$allowedPictureTypes
);
@ -767,7 +766,7 @@ $form->addFile(
);
$form->addProgress();
if (!empty($infoCertificate['signature2'])) {
$form->addElement('checkbox', 'remove_signature2', null, get_lang('Remove picture'));
$form->addElement('checkbox', 'remove_signature2', null, get_lang('DelImage'));
$form->addElement(
'html',
'<label class="col-sm-2">&nbsp;</label>
@ -778,7 +777,7 @@ if (!empty($infoCertificate['signature2'])) {
$allowedPictureTypes = api_get_supported_image_extensions(false);
$form->addRule(
'signature2',
get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(', ', $allowedPictureTypes).')',
get_lang('OnlyImagesAllowed').' ('.implode(', ', $allowedPictureTypes).')',
'filetype',
$allowedPictureTypes
);
@ -803,7 +802,7 @@ $form->addFile(
);
$form->addProgress();
if (!empty($infoCertificate['signature3'])) {
$form->addElement('checkbox', 'remove_signature3', null, get_lang('Remove picture'));
$form->addElement('checkbox', 'remove_signature3', null, get_lang('DelImage'));
$form->addElement(
'html',
'<label class="col-sm-2">&nbsp;</label>
@ -814,7 +813,7 @@ if (!empty($infoCertificate['signature3'])) {
$allowedPictureTypes = api_get_supported_image_extensions(false);
$form->addRule(
'signature3',
get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(', ', $allowedPictureTypes).')',
get_lang('OnlyImagesAllowed').' ('.implode(', ', $allowedPictureTypes).')',
'filetype',
$allowedPictureTypes
);
@ -839,7 +838,7 @@ $form->addFile(
);
$form->addProgress();
if (!empty($infoCertificate['signature4'])) {
$form->addElement('checkbox', 'remove_signature4', null, get_lang('Remove picture'));
$form->addElement('checkbox', 'remove_signature4', null, get_lang('DelImage'));
$form->addElement(
'html',
'<label class="col-sm-2">&nbsp;</label>
@ -850,7 +849,7 @@ if (!empty($infoCertificate['signature4'])) {
$allowedPictureTypes = api_get_supported_image_extensions(false);
$form->addRule(
'signature4',
get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(', ', $allowedPictureTypes).')',
get_lang('OnlyImagesAllowed').' ('.implode(', ', $allowedPictureTypes).')',
'filetype',
$allowedPictureTypes
);
@ -871,7 +870,7 @@ $form->addFile(
);
$form->addProgress();
if (!empty($infoCertificate['background'])) {
$form->addElement('checkbox', 'remove_background', null, get_lang('Remove picture'));
$form->addElement('checkbox', 'remove_background', null, get_lang('DelImage'));
$form->addElement(
'html',
'<label class="col-sm-2">&nbsp;</label>
@ -882,7 +881,7 @@ if (!empty($infoCertificate['background'])) {
$allowedPictureTypes = api_get_supported_image_extensions(false);
$form->addRule(
'background',
get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(', ', $allowedPictureTypes).')',
get_lang('OnlyImagesAllowed').' ('.implode(', ', $allowedPictureTypes).')',
'filetype',
$allowedPictureTypes
);
@ -916,7 +915,7 @@ $form->addHtml('<div class="clearfix"></div>');
$form->addButton(
'submit',
get_lang('Save certificate'),
get_lang('SaveCertificate'),
'check',
'primary',
null,
@ -969,7 +968,7 @@ function checkInstanceImage($certificateId, $imagePath, $field, $type = 'certifi
$sql = "SELECT * FROM $table WHERE $field = '$imagePath'";
$res = Database::query($sql);
if (1 == Database::num_rows($res)) {
if (Database::num_rows($res) == 1) {
api_remove_uploaded_file($type, $imagePath);
}

@ -1,12 +1,11 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CourseBundle\Entity\CLpCategory;
$default = isset($_GET['default']) ? (int) $_GET['default'] : null;
if (1 === $default) {
if ($default === 1) {
$cidReset = true;
}
@ -15,14 +14,15 @@ require_once __DIR__.'/../config.php';
api_block_anonymous_users();
$plugin = CustomCertificatePlugin::create();
$enable = 'true' == $plugin->get('enable_plugin_customcertificate');
$enable = $plugin->get('enable_plugin_customcertificate') == 'true';
$tblProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
$categoryId = isset($_GET['cat_id']) ? (int) $_GET['cat_id'] : 0;
if (!$enable) {
api_not_allowed(true, $plugin->get_lang('ToolDisabled'));
}
if (1 == $default) {
if ($default == 1) {
$courseId = 0;
$courseCode = '';
$sessionId = 0;
@ -32,8 +32,8 @@ if (1 == $default) {
$courseId = api_get_course_int_id();
$courseCode = api_get_course_id();
$sessionId = api_get_session_id();
$enableCourse = 1 == api_get_course_setting('customcertificate_course_enable') ? true : false;
$useDefault = 1 == api_get_course_setting('use_certificate_default') ? true : false;
$enableCourse = api_get_course_setting('customcertificate_course_enable') == 1 ? true : false;
$useDefault = api_get_course_setting('use_certificate_default') == 1 ? true : false;
}
if (empty($courseCode)) {
@ -53,24 +53,25 @@ if (empty($sessionId)) {
$accessUrlId = api_get_current_access_url_id();
$userList = [];
if (empty($_GET['export_all'])) {
$exportZip = false;
$exportAllInOne = false;
if (empty($_GET['export_all']) && empty($_GET['export_all_in_one'])) {
if (!isset($_GET['student_id'])) {
$studentId = api_get_user_id();
} else {
$studentId = (int) ($_GET['student_id']);
$studentId = intval($_GET['student_id']);
}
$userList[] = api_get_user_info($studentId);
} else {
$certificateTable = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
$categoryTable = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
$sql = "SELECT cer.user_id AS user_id
FROM $certificateTable cer
INNER JOIN $categoryTable cat
ON (cer.cat_id = cat.id)
WHERE cat.course_code = '$courseCode' AND cat.session_id = $sessionId";
$rs = Database::query($sql);
while ($row = Database::fetch_assoc($rs)) {
$userList[] = api_get_user_info($row['user_id']);
if (!empty($_GET['export_all'])) {
$exportZip = true;
}
if (!empty($_GET['export_all_in_one'])) {
$exportAllInOne = true;
}
$certificate_list = GradebookUtils::get_list_users_certificates($categoryId);
foreach ($certificate_list as $index => $value) {
$userList[] = api_get_user_info($value['user_id']);
}
}
@ -81,7 +82,7 @@ if ($sessionId > 0) {
$table = Database::get_main_table(CustomCertificatePlugin::TABLE_CUSTOMCERTIFICATE);
$useDefault = false;
$path = api_get_path(SYS_UPLOAD_PATH).'certificates/';
$path = api_get_path(WEB_UPLOAD_PATH).'certificates/';
// Get info certificate
$infoCertificate = CustomCertificatePlugin::getInfoCertificate($courseId, $sessionId, $accessUrlId);
@ -103,37 +104,41 @@ if (empty($infoCertificate)) {
}
}
$workSpace = (int) (297 - $infoCertificate['margin_left'] - $infoCertificate['margin_right']);
$widthCell = (int) ($workSpace / 6);
$workSpace = intval(297 - $infoCertificate['margin_left'] - $infoCertificate['margin_right']);
$widthCell = intval($workSpace / 6);
$htmlList = [];
$currentLocalTime = api_get_local_time();
foreach ($userList as $userInfo) {
$htmlText = '<html>';
$htmlText .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_PLUGIN_PATH).'customcertificate/resources/css/certificate.css">';
$htmlText .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_CSS_PATH).'document.css">';
$htmlText .= '<body>';
$htmlText = '';
if (!$exportAllInOne) {
$htmlText = '<html>';
$htmlText .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_PLUGIN_PATH).'customcertificate/resources/css/certificate.css">';
$htmlText .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_CSS_PATH).'document.css">';
$htmlText .= '<body>';
}
$studentId = $userInfo['user_id'];
if (empty($infoCertificate['background'])) {
$htmlText .= '<div class="caraA" style="page-break-before:always; margin:0px; padding:0px;">';
} else {
$urlBackground = $path.$infoCertificate['background'];
$htmlText .= ' <div
class = "caraA"
style = "background-image:url('.$urlBackground.') no-repeat; background-image-resize:6; margin:0px; padding:0px;">';
$htmlText .= ' <div
class="caraA"
style="background-image:url('.$urlBackground.') no-repeat;
background-image-resize:6; margin:0px; padding:0px;">';
}
if (!empty($infoCertificate['logo_left'])) {
$logoLeft = '
<img
<img
style="max-height: 150px; max-width: '.(2 * $widthCell).'mm;"
src="'.$path.$infoCertificate['logo_left'].'" />';
} else {
@ -143,8 +148,8 @@ foreach ($userList as $userInfo) {
$logoCenter = '';
if (!empty($infoCertificate['logo_center'])) {
$logoCenter = '
<img
style="max-height: 150px; max-width: '.(int) ($workSpace - (2 * $widthCell)).'mm;"
<img
style="max-height: 150px; max-width: '.intval($workSpace - (2 * $widthCell)).'mm;"
src="'.$path.$infoCertificate['logo_center'].'" />';
}
@ -156,7 +161,7 @@ foreach ($userList as $userInfo) {
src="'.$path.$infoCertificate['logo_right'].'" />';
}
$htmlText .= '<table
$htmlText .= '<table
width="'.$workSpace.'mm"
style="
margin-left:'.$infoCertificate['margin_left'].'mm;
@ -164,15 +169,18 @@ foreach ($userList as $userInfo) {
"
border="0">';
$htmlText .= '<tr>';
$htmlText .= '<td style="width:'.(int) ($workSpace / 3).'mm" class="logo">'.$logoLeft.'</td>';
$htmlText .= '<td style="width:'.(int) ($workSpace / 3).'mm; text-align:center;" class="logo">'.$logoCenter.'</td>';
$htmlText .= '<td style="width:'.(int) ($workSpace / 3).'mm; text-align:right;" class="logo">'.$logoRight.'</td>';
$htmlText .= '<td style="width:'.intval($workSpace / 3).'mm" class="logo">'.$logoLeft.'</td>';
$htmlText .= '<td style="width:'.intval($workSpace / 3).'mm; text-align:center;" class="logo">';
$htmlText .= $logoCenter;
$htmlText .= '</td>';
$htmlText .= '<td style="width:'.intval($workSpace / 3).'mm; text-align:right;" class="logo">'.$logoRight.'</td>';
$htmlText .= '</tr>';
$htmlText .= '</table>';
$allUserInfo = DocumentManager::get_all_info_to_certificate(
$studentId,
$courseCode,
$sessionId,
false
);
@ -191,17 +199,15 @@ foreach ($userList as $userInfo) {
switch ($infoCertificate['date_change']) {
case 0:
if (!empty($sessionInfo['access_start_date'])) {
$startDate = date('d/m/Y', strtotime(api_get_local_time($sessionInfo['access_start_date'])));
$startDate = date("d/m/Y", strtotime(api_get_local_time($sessionInfo['access_start_date'])));
}
if (!empty($sessionInfo['access_end_date'])) {
$endDate = date('d/m/Y', strtotime(api_get_local_time($sessionInfo['access_end_date'])));
$endDate = date("d/m/Y", strtotime(api_get_local_time($sessionInfo['access_end_date'])));
}
break;
case 1:
$startDate = date('d/m/Y', strtotime($infoCertificate['date_start']));
$endDate = date('d/m/Y', strtotime($infoCertificate['date_end']));
$startDate = date("d/m/Y", strtotime($infoCertificate['date_start']));
$endDate = date("d/m/Y", strtotime($infoCertificate['date_end']));
break;
}
@ -218,11 +224,11 @@ foreach ($userList as $userInfo) {
);
$dateExpediction = '';
if (3 != $infoCertificate['type_date_expediction']) {
if ($infoCertificate['type_date_expediction'] != 3) {
$dateExpediction .= $plugin->get_lang('ExpedictionIn').' '.$infoCertificate['place'];
if (1 == $infoCertificate['type_date_expediction']) {
if ($infoCertificate['type_date_expediction'] == 1) {
$dateExpediction .= $plugin->get_lang('to').api_format_date(time(), DATE_FORMAT_LONG);
} elseif (2 == $infoCertificate['type_date_expediction']) {
} elseif ($infoCertificate['type_date_expediction'] == 2) {
$dateFormat = $plugin->get_lang('formatDownloadDate');
if (!empty($infoCertificate['day']) &&
!empty($infoCertificate['month']) &&
@ -242,7 +248,7 @@ foreach ($userList as $userInfo) {
'............'
);
}
} elseif (4 == $infoCertificate['type_date_expediction']) {
} elseif ($infoCertificate['type_date_expediction'] == 4) {
$dateExpediction .= $plugin->get_lang('to').$infoToReplaceInContentHtml[9]; //date_certificate_no_time
} else {
if (!empty($sessionInfo)) {
@ -283,48 +289,48 @@ foreach ($userList as $userInfo) {
$htmlText .= '<tr>';
$htmlText .= '<td colspan="2" class="seals" style="width:'.$widthCell.'mm">'.
(!empty($infoCertificate['signature_text1']) ? $infoCertificate['signature_text1'] : '').
((!empty($infoCertificate['signature_text1'])) ? $infoCertificate['signature_text1'] : '').
'</td>
<td colspan="2" class="seals" style="width:'.$widthCell.'mm">'.
(!empty($infoCertificate['signature_text2']) ? $infoCertificate['signature_text2'] : '').
((!empty($infoCertificate['signature_text2'])) ? $infoCertificate['signature_text2'] : '').
'</td>
<td colspan="2" class="seals" style="width:'.$widthCell.'mm">'.
(!empty($infoCertificate['signature_text3']) ? $infoCertificate['signature_text3'] : '').
((!empty($infoCertificate['signature_text3'])) ? $infoCertificate['signature_text3'] : '').
'</td>
<td colspan="2" class="seals" style="width:'.$widthCell.'mm">'.
(!empty($infoCertificate['signature_text4']) ? $infoCertificate['signature_text4'] : '').
((!empty($infoCertificate['signature_text4'])) ? $infoCertificate['signature_text4'] : '').
'</td>
<td colspan="4" class="seals" style="width:'.(2 * $widthCell).'mm">
'.(!empty($infoCertificate['seal']) ? $plugin->get_lang('Seal') : '').
'.((!empty($infoCertificate['seal'])) ? $plugin->get_lang('Seal') : '').
'</td>';
$htmlText .= '</tr>';
$htmlText .= '<tr>';
$htmlText .= '<td colspan="2" class="logo-seals" style="width:'.$widthCell.'mm">'.
(!empty($infoCertificate['signature1'])
((!empty($infoCertificate['signature1']))
? '<img style="max-height: 100px; max-width: '.$widthCell.'mm;"
src="'.$path.$infoCertificate['signature1'].'" />'
: '').
'</td>
<td colspan="2" class="logo-seals" style="width:'.$widthCell.'mm">'.
(!empty($infoCertificate['signature2'])
((!empty($infoCertificate['signature2']))
? '<img style="max-height: 100px; '.$widthCell.'mm;"
src="'.$path.$infoCertificate['signature2'].'" />'
: '').
'</td>
<td colspan="2" class="logo-seals" style="width:'.$widthCell.'mm">'.
(!empty($infoCertificate['signature3'])
((!empty($infoCertificate['signature3']))
? '<img style="max-height: 100px; '.$widthCell.'mm;"
src="'.$path.$infoCertificate['signature3'].'" />'
: '').
'</td>
<td colspan="2" class="logo-seals" style="width:'.$widthCell.'mm">'.
(!empty($infoCertificate['signature4'])
((!empty($infoCertificate['signature4']))
? '<img style="max-height: 100px; '.$widthCell.'mm;"
src="'.$path.$infoCertificate['signature4'].'" />'
: '').
'</td>
<td colspan="4" class="logo-seals" style="width:'.(2 * $widthCell).'mm">'.
(!empty($infoCertificate['seal'])
((!empty($infoCertificate['seal']))
? '<img style="max-height: 100px; '.(2 * $widthCell).'mm;"
src="'.$path.$infoCertificate['seal'].'" />'
: '').
@ -334,9 +340,9 @@ foreach ($userList as $userInfo) {
$htmlText .= '</div>';
// Rear certificate
if (3 != $infoCertificate['contents_type']) {
if ($infoCertificate['contents_type'] != 3) {
$htmlText .= '<div class="caraB" style="page-break-before:always; margin:0px; padding:0px;">';
if (0 == $infoCertificate['contents_type']) {
if ($infoCertificate['contents_type'] == 0) {
$courseDescription = new CourseDescription();
$contentDescription = $courseDescription->get_data_by_description_type(3, $courseId, 0);
$domd = new DOMDocument();
@ -346,26 +352,26 @@ foreach ($userList as $userInfo) {
}
libxml_use_internal_errors(false);
$domx = new DOMXPath($domd);
$items = $domx->query('//li[@style]');
$items = $domx->query("//li[@style]");
foreach ($items as $item) {
$item->removeAttribute('style');
$item->removeAttribute("style");
}
$items = $domx->query('//span[@style]');
$items = $domx->query("//span[@style]");
foreach ($items as $item) {
$item->removeAttribute('style');
$item->removeAttribute("style");
}
$output = $domd->saveHTML();
$htmlText .= getIndexFiltered($output);
}
if (1 == $infoCertificate['contents_type']) {
if ($infoCertificate['contents_type'] == 1) {
$items = [];
$categoriesTempList = learnpath::getCategories($courseId);
$categoryTest = new CLpCategory();
$categoryTest->setId(0);
$categoryTest->setName($plugin->get_lang('Without category'));
$categoryTest->setName($plugin->get_lang('WithOutCategory'));
$categoryTest->setPosition(0);
$categories = [$categoryTest];
@ -407,7 +413,7 @@ foreach ($userList as $userInfo) {
}
if (count($categories) > 1 && count($flat_list) > 0) {
if ($item->getName() != $plugin->get_lang('Without category')) {
if ($item->getName() != $plugin->get_lang('WithOutCategory')) {
$items[] = '<h4 style="margin:0">'.$item->getName().'</h4>';
}
}
@ -435,7 +441,7 @@ foreach ($userList as $userInfo) {
$htmlText .= '<td>';
$i = 0;
foreach ($items as $value) {
if (50 == $i) {
if ($i == 50) {
$htmlText .= '</td><td>';
}
$htmlText .= $value;
@ -448,7 +454,7 @@ foreach ($userList as $userInfo) {
$htmlText .= '</td></table>';
}
if (2 == $infoCertificate['contents_type']) {
if ($infoCertificate['contents_type'] == 2) {
$htmlText .= '<table width="100%" class="contents-learnpath">';
$htmlText .= '<tr>';
$htmlText .= '<td>';
@ -464,7 +470,10 @@ foreach ($userList as $userInfo) {
}
$htmlText .= '</div>';
}
$htmlText .= '</body></html>';
if (!$exportAllInOne) {
$htmlText .= '</body></html>';
}
$fileName = 'certificate_'.$courseInfo['code'].'_'.$userInfo['complete_name'].'_'.$currentLocalTime;
$htmlList[$fileName] = $htmlText;
}
@ -475,10 +484,8 @@ if (!is_dir($archivePath)) {
mkdir($archivePath, api_get_permissions_for_new_directories());
}
foreach ($htmlList as $fileName => $content) {
$fileName = api_replace_dangerous_char($fileName);
if ($exportAllInOne) {
$params = [
'filename' => $fileName,
'pdf_title' => 'Certificate',
'pdf_description' => '',
'format' => 'A4-L',
@ -488,30 +495,77 @@ foreach ($htmlList as $fileName => $content) {
'bottom' => 0,
];
$pdf = new PDF($params['format'], $params['orientation'], $params);
if (1 == count($htmlList)) {
$pdf->content_to_pdf($content, '', $fileName, null, 'D', false, null, false, false, false);
exit;
} else {
$filePath = $archivePath.$fileName.'.pdf';
$pdf->content_to_pdf($content, '', $fileName, null, 'F', true, $filePath, false, false, false);
$fileList[] = $filePath;
$contentAllCertificate = '';
foreach ($htmlList as $fileName => $content) {
$contentAllCertificate .= $content;
}
}
if (!empty($fileList)) {
$zipFile = $archivePath.'certificates_'.api_get_unique_id().'.zip';
$zipFolder = new PclZip($zipFile);
foreach ($fileList as $file) {
$zipFolder->add($file, PCLZIP_OPT_REMOVE_ALL_PATH);
if (!empty($contentAllCertificate)) {
$certificateContent = '<html>';
$certificateContent .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_PLUGIN_PATH).'customcertificate/resources/css/certificate.css">';
$certificateContent .= '
<link rel="stylesheet"
type="text/css"
href="'.api_get_path(WEB_CSS_PATH).'document.css">';
$certificateContent .= '<body>';
$certificateContent .= $contentAllCertificate;
$certificateContent .= '</body></html>';
$pdf->content_to_pdf(
$certificateContent,
'',
'certificate'.date("Y_m_d_His"),
null,
'D',
false,
null,
false,
false,
false
);
}
} else {
foreach ($htmlList as $fileName => $content) {
$fileName = api_replace_dangerous_char($fileName);
$params = [
'filename' => $fileName,
'pdf_title' => 'Certificate',
'pdf_description' => '',
'format' => 'A4-L',
'orientation' => 'L',
'left' => 15,
'top' => 15,
'bottom' => 0,
];
$pdf = new PDF($params['format'], $params['orientation'], $params);
if ($exportZip) {
$filePath = $archivePath.$fileName.'.pdf';
$pdf->content_to_pdf($content, '', $fileName, null, 'F', true, $filePath, false, false, false);
$fileList[] = $filePath;
} else {
$pdf->content_to_pdf($content, '', $fileName, null, 'D', false, null, false, false, false);
}
}
if (!empty($fileList)) {
$zipFile = $archivePath.'certificates_'.api_get_unique_id().'.zip';
$zipFolder = new PclZip($zipFile);
foreach ($fileList as $file) {
$zipFolder->add($file, PCLZIP_OPT_REMOVE_ALL_PATH);
}
$name = 'certificates_'.$courseInfo['code'].'_'.$currentLocalTime.'.zip';
DocumentManager::file_send_for_download($zipFile, true, $name);
exit;
}
$name = 'certificates_'.$courseInfo['code'].'_'.$currentLocalTime.'.zip';
DocumentManager::file_send_for_download($zipFile, true, $name);
exit;
}
function getIndexFiltered($index)
{
$txt = strip_tags($index, '<b><strong><i>');
$txt = strip_tags($index, "<b><strong><i>");
$txt = str_replace(chr(13).chr(10).chr(13).chr(10), chr(13).chr(10), $txt);
$lines = explode(chr(13).chr(10), $txt);
$text1 = '';
@ -528,8 +582,8 @@ function getIndexFiltered($index)
}
}
$showLeft = str_replace(chr(13).chr(10), '<br/>', $text1);
$showRight = str_replace(chr(13).chr(10), '<br/>', $text2);
$showLeft = str_replace(chr(13).chr(10), "<br/>", $text1);
$showRight = str_replace(chr(13).chr(10), "<br/>", $text2);
$result = '<table width="100%">';
$result .= '<tr>';
$result .= '<td style="width:50%;vertical-align:top;padding-left:15px; font-size:12px;">'.$showLeft.'</td>';

@ -1,12 +1,14 @@
<?php
/**
* This script initiates a customcertificate plugin.
*
* @package chamilo.plugin.customcertificate
*/
$course_plugin = 'customcertificate';
require_once __DIR__.'/config.php';
$plugin = CustomCertificatePlugin::create();
$enable = 'true' == $plugin->get('enable_plugin_customcertificate');
$enable = $plugin->get('enable_plugin_customcertificate') == 'true';
if ($enable) {
if (api_is_platform_admin() || api_is_teacher()) {

@ -1,11 +1,12 @@
<?php
/* For license terms, see /license.txt */
/**
* This script is included by main/admin/settings.lib.php when unselecting a plugin
* and is meant to remove things installed by the install.php script in both
* the global database and the courses tables.
*
* @package chamilo.plugin.customcertificate
*/
require_once __DIR__.'/config.php';
CustomCertificatePlugin::create()->uninstall();

@ -1,8 +1,9 @@
<?php
/* For licensing terms, see /license.txt */
/**
* This script is meant to update previous version the plugin.
*
* @package chamilo.plugin.customcertificate
*/
require_once __DIR__.'/config.php';

@ -4,8 +4,6 @@
class PauseTraining extends Plugin
{
public $isCoursePlugin = false;
protected function __construct()
{
parent::__construct(
@ -15,6 +13,7 @@ class PauseTraining extends Plugin
'tool_enable' => 'boolean',
'allow_users_to_edit_pause_formation' => 'boolean',
'cron_alert_users_if_inactive_days' => 'text', // Example: "5" or "5,10,15"
'sender_id' => 'user',
]
);
}
@ -37,7 +36,7 @@ class PauseTraining extends Plugin
'pause_formation',
'start_pause_date',
'end_pause_date',
'allow_notifications',
'disable_emails',
];
$valuesToUpdate = [
@ -62,12 +61,12 @@ class PauseTraining extends Plugin
$valuesToUpdate['extra_pause_formation']['extra_pause_formation'] = $pause;
}
$notification = (int) $valuesToUpdate['extra_allow_notifications'];
$notification = (int) $valuesToUpdate['extra_disable_emails'];
if (empty($notification)) {
$valuesToUpdate['extra_allow_notifications'] = 0;
$valuesToUpdate['extra_disable_emails'] = 0;
} else {
$valuesToUpdate['extra_allow_notifications'] = [];
$valuesToUpdate['extra_allow_notifications']['extra_allow_notifications'] = $notification;
$valuesToUpdate['extra_disable_emails'] = [];
$valuesToUpdate['extra_disable_emails']['extra_disable_emails'] = $notification;
}
$check = DateTime::createFromFormat('Y-m-d H:i', $valuesToUpdate['extra_start_pause_date']);
@ -91,72 +90,229 @@ class PauseTraining extends Plugin
return (int) $userId;
}
public function runCron()
public function runCron($date = '', $isTest = false)
{
$enable = $this->get('tool_enable');
$senderId = $this->get('sender_id');
$enableDays = $this->get('cron_alert_users_if_inactive_days');
if ($enable && !empty($enableDays)) {
$enableDaysList = explode(',', $enableDays);
rsort($enableDaysList);
$loginTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
$userTable = Database::get_main_table(TABLE_MAIN_USER);
$now = api_get_utc_datetime();
$usersNotificationPerDay = [];
$users = [];
foreach ($enableDaysList as $day) {
$day = (int) $day;
$sql = "SELECT
stats_login.user_id,
MAX(stats_login.login_course_date) max_date
FROM $loginTable stats_login
INNER JOIN $userTable u
ON (u.id = stats_login.user_id)
WHERE
u.status <> ".ANONYMOUS." AND
u.active = 1
GROUP BY stats_login.user_id
HAVING DATE_SUB('$now', INTERVAL '$day' DAY) > max_date ";
$rs = Database::query($sql);
while ($user = Database::fetch_array($rs)) {
$userId = $user['user_id'];
if (in_array($userId, $users)) {
continue;
}
$users[] = $userId;
$usersNotificationPerDay[$day][] = $userId;
if ('true' !== $enable) {
echo 'Plugin not enabled';
return false;
}
if (empty($senderId)) {
echo 'Sender id not configured';
return false;
}
$senderInfo = api_get_user_info($senderId);
if (empty($senderInfo)) {
echo "Sender #$senderId not found in Chamilo";
return false;
}
$enableDaysList = explode(',', $enableDays);
rsort($enableDaysList);
$track = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
$login = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
$userTable = Database::get_main_table(TABLE_MAIN_USER);
$usersNotificationPerDay = [];
$now = api_get_utc_datetime();
if (!empty($date)) {
$now = $date;
}
if ($isTest) {
echo "-------------------------------------------".PHP_EOL;
echo "----- Testing date $now ----".PHP_EOL;
echo "-------------------------------------------".PHP_EOL;
}
$extraFieldValue = new ExtraFieldValue('user');
$sql = "SELECT u.id
FROM $userTable u
WHERE u.status <> ".ANONYMOUS." AND u.active = 1";
$rs = Database::query($sql);
$users = [];
while ($user = Database::fetch_array($rs)) {
$userId = $user['id'];
$sql = "SELECT
MAX(t.logout_course_date) max_course_date,
MAX(l.logout_date) max_login_date
FROM $userTable u
LEFT JOIN $track t
ON (u.id = t.user_id)
LEFT JOIN $login l
ON (u.id = l.login_user_id)
WHERE
u.id = $userId
LIMIT 1
";
$result = Database::query($sql);
$data = Database::fetch_array($result);
$maxCourseDate = '';
$maxLoginDate = '';
// Take max date value.
if ($data) {
$maxCourseDate = $data['max_course_date'];
$maxLoginDate = $data['max_login_date'];
}
$maxEndPause = null;
$pause = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'pause_formation');
if (!empty($pause) && isset($pause['value']) && 1 == $pause['value']) {
$endDate = $extraFieldValue->get_values_by_handler_and_field_variable(
$userId,
'end_pause_date'
);
if (!empty($endDate) && isset($endDate['value']) && !empty($endDate['value'])) {
$maxEndPause = $endDate['value'];
}
}
if (!empty($usersNotificationPerDay)) {
ksort($usersNotificationPerDay);
$extraFieldValue = new ExtraFieldValue('user');
foreach ($usersNotificationPerDay as $day => $userList) {
$template = new Template();
$title = sprintf($this->get_lang('InactivityXDays'), $day);
foreach ($userList as $userId) {
$userInfo = api_get_user_info($userId);
$pause = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'pause_formation');
if (!empty($pause) && isset($pause['value']) && 1 == $pause['value']) {
// Skip user because he paused his formation.
$maxDate = $maxCourseDate;
if ($maxLoginDate > $maxCourseDate) {
$maxDate = $maxLoginDate;
}
if ($maxEndPause > $maxDate) {
$maxDate = $maxEndPause;
}
if (empty($maxDate)) {
// Nothing found for that user, skip.
continue;
}
$users[$userId] = $maxDate;
}
$extraFieldValue = new ExtraFieldValue('user');
foreach ($enableDaysList as $day) {
$day = (int) $day;
if (0 === $day) {
echo 'Day = 0 avoided '.PHP_EOL;
continue;
}
$dayToCheck = $day + 1;
$hourStart = $dayToCheck * 24;
$hourEnd = ($dayToCheck - 1) * 24;
$date = new DateTime($now);
$date->sub(new DateInterval('PT'.$hourStart.'H'));
$hourStart = $date->format('Y-m-d H:i:s');
$date = new DateTime($now);
$date->sub(new DateInterval('PT'.$hourEnd.'H'));
$hourEnd = $date->format('Y-m-d H:i:s');
echo "Processing day $day: $hourStart - $hourEnd ".PHP_EOL.PHP_EOL;
foreach ($users as $userId => $maxDate) {
if (!($maxDate > $hourStart && $maxDate < $hourEnd)) {
//echo "Message skipped for user #$userId because max date found: $maxDate not in range $hourStart - $hourEnd ".PHP_EOL;
continue;
}
// Check if user has selected to pause formation.
$pause = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'pause_formation');
if (!empty($pause) && isset($pause['value']) && 1 == $pause['value']) {
$startDate = $extraFieldValue->get_values_by_handler_and_field_variable(
$userId,
'start_pause_date'
);
$endDate = $extraFieldValue->get_values_by_handler_and_field_variable(
$userId,
'end_pause_date'
);
if (
!empty($startDate) && isset($startDate['value']) && !empty($startDate['value']) &&
!empty($endDate) && isset($endDate['value']) && !empty($endDate['value'])
) {
$startDate = $startDate['value'];
$endDate = $endDate['value'];
if ($startDate > $hourStart && $startDate < $hourStart) {
//echo "Message skipped for user #$userId because process date $hourStart is in start pause in $startDate - $endDate ".PHP_EOL;
continue;
}
if ($endDate > $hourEnd && $endDate < $hourEnd) {
//echo "Message skipped for user #$userId because process date $hourEnd is in start pause in $startDate - $endDate ".PHP_EOL;
continue;
}
}
}
echo "User #$userId added to message queue because latest login is $maxDate between $hourStart AND $hourEnd".PHP_EOL;
$users[] = $userId;
$usersNotificationPerDay[$day][] = $userId;
}
}
$template->assign('days', $day);
$template->assign('user', $userInfo);
$content = $template->fetch('pausetraining/view/notification_content.tpl');
MessageManager::send_message($userId, $title, $content);
if (!empty($usersNotificationPerDay)) {
echo PHP_EOL.'Now processing messages ...'.PHP_EOL;
ksort($usersNotificationPerDay);
foreach ($usersNotificationPerDay as $day => $userList) {
$template = new Template(
'',
true,
true,
false,
false,
true,
false
);
$title = sprintf($this->get_lang('InactivityXDays'), $day);
foreach ($userList as $userId) {
$userInfo = api_get_user_info($userId);
$template->assign('days', $day);
$template->assign('user', $userInfo);
$content = $template->fetch('pausetraining/view/notification_content.tpl');
echo 'Ready to send a message "'.$title.'" to user #'.$userId.' '.$userInfo['complete_name'].PHP_EOL;
if (false === $isTest) {
MessageManager::send_message_simple($userId, $title, $content, $senderId);
} else {
echo 'Message not send because is in test mode.'.PHP_EOL;
}
}
}
}
}
public function runCronTest()
{
$now = api_get_utc_datetime();
$days = 15;
$before = new DateTime($now);
$before->sub(new DateInterval('P'.$days.'D'));
$after = new DateTime($now);
$after->add(new DateInterval('P'.$days.'D'));
$period = new DatePeriod(
$before,
new DateInterval('P1D'),
$after
);
foreach ($period as $key => $value) {
self::runCron($value->format('Y-m-d H:i:s'), true);
}
}
public function install()
{
UserManager::create_extra_field(
@ -181,9 +337,9 @@ class PauseTraining extends Plugin
);
UserManager::create_extra_field(
'allow_notifications',
'disable_emails',
ExtraField::FIELD_TYPE_CHECKBOX,
$this->get_lang('AllowEmailNotification'),
$this->get_lang('DisableEmails'),
''
);
}

@ -1,4 +1,5 @@
<?php
/* For licensing terms, see /license.txt */
require_once __DIR__.'/../../main/inc/global.inc.php';

@ -0,0 +1,7 @@
<?php
/* For licensing terms, see /license.txt */
require_once __DIR__.'/../../main/inc/global.inc.php';
PauseTraining::create()->runCronTest();

@ -1,5 +1,7 @@
<?php
/* For licensing terms, see /license.txt */
$strings['plugin_title'] = "Pause training";
$strings['plugin_comment'] = "";
$strings['tool_enable'] = 'Enable plugin';
@ -10,8 +12,9 @@ $strings['cron_alert_users_if_inactive_days'] = 'Alert users if inactive days (v
$strings['PauseFormation'] = 'Pause my formation';
$strings['StartPauseDateTime'] = 'Start pause date';
$strings['EndPauseDateTime'] = 'End pause date';
$strings['AllowEmailNotification'] = 'Allow email notifications';
$strings['DisableEmails'] = 'Disable email notifications';
$strings['InactivityXDays'] = 'Inactivity for %s days';
$strings['YouAreConnectedInPlatformXLinkXSinceXDays'] = '
We have noticed that you have not connected to the platform %s (%s) for %s days.
This is an automatic message to remind you of your activity.';
$strings['sender_id'] = 'User that will send the cron emails';

@ -1,4 +1,7 @@
<?php
/* For licensing terms, see /license.txt */
$strings['plugin_title'] = "Pause training";
$strings['plugin_comment'] = "";
$strings['tool_enable'] = 'Enable plugin';
@ -9,7 +12,7 @@ $strings['cron_alert_users_if_inactive_days'] = 'Alert users if inactive days (v
$strings['PauseFormation'] = 'Mettre en pause ma formation';
$strings['StartPauseDateTime'] = 'Date de début de pause';
$strings['EndPauseDateTime'] = 'Date de fin de pause';
$strings['AllowEmailNotification'] = 'Continuer à recevoir les mails de la plateforme';
$strings['DisableEmails'] = 'Désactiver les e-mails ';
$strings['InactivityXDays'] = 'Inactivité sur la plateforme depuis %s jours';
$strings['YouAreConnectedInPlatformXLinkXSinceXDays'] = '
Nous avons remarqué que vous ne vous êtes pas connecté à la plateforme %s (%s) depuis %s jours.

@ -0,0 +1,608 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\CourseRelUser;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
use Chamilo\CourseBundle\Entity\CGroupInfo;
use Chamilo\PluginBundle\Zoom\API\MeetingInfoGet;
use Chamilo\PluginBundle\Zoom\API\MeetingListItem;
use Chamilo\PluginBundle\Zoom\API\MeetingSettings;
use Chamilo\UserBundle\Entity\User;
use Database;
use DateInterval;
use DateTime;
use DateTimeZone;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Exception;
/**
* Class Meeting.
*
* @ORM\Entity(repositoryClass="Chamilo\PluginBundle\Zoom\MeetingRepository")
* @ORM\Table(
* name="plugin_zoom_meeting",
* indexes={
* @ORM\Index(name="user_id_index", columns={"user_id"}),
* @ORM\Index(name="course_id_index", columns={"course_id"}),
* @ORM\Index(name="session_id_index", columns={"session_id"})
* }
* )
* @ORM\HasLifecycleCallbacks
*/
class Meeting
{
/** @var string meeting type name */
public $typeName;
/** @var DateTime meeting start time as a DateTime instance */
public $startDateTime;
/** @var string meeting formatted start time */
public $formattedStartTime;
/** @var DateInterval meeting duration as a DateInterval instance */
public $durationInterval;
/** @var string meeting formatted duration */
public $formattedDuration;
/** @var string */
public $statusName;
/**
* @var int
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue()
*/
protected $id;
/**
* @var int the remote zoom meeting identifier
* @ORM\Column(name="meeting_id", type="string")
*/
protected $meetingId;
/**
* @var User
* @ORM\ManyToOne(targetEntity="Chamilo\UserBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
protected $user;
/**
* @var Course
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\Course")
* @ORM\JoinColumn(name="course_id", referencedColumnName="id", nullable=true)
*/
protected $course;
/**
* @var CGroupInfo
* @ORM\ManyToOne(targetEntity="Chamilo\CourseBundle\Entity\CGroupInfo")
* @ORM\JoinColumn(name="group_id", referencedColumnName="iid", nullable=true)
*/
protected $group;
/**
* @var Session
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\Session")
* @ORM\JoinColumn(name="session_id", referencedColumnName="id", nullable=true)
*/
protected $session;
/**
* @var string
* @ORM\Column(type="text", name="meeting_list_item_json", nullable=true)
*/
protected $meetingListItemJson;
/**
* @var string
* @ORM\Column(type="text", name="meeting_info_get_json", nullable=true)
*/
protected $meetingInfoGetJson;
/** @var MeetingListItem */
protected $meetingListItem;
/** @var MeetingInfoGet */
protected $meetingInfoGet;
/**
* @var MeetingActivity[]|ArrayCollection
* @ORM\OrderBy({"createdAt" = "DESC"})
* @ORM\OneToMany(targetEntity="MeetingActivity", mappedBy="meeting", cascade={"persist", "remove"})
*/
protected $activities;
/**
* @var Registrant[]|ArrayCollection
*
* @ORM\OneToMany(targetEntity="Registrant", mappedBy="meeting", cascade={"persist", "remove"})
*/
protected $registrants;
/**
* @var Recording[]|ArrayCollection
*
* @ORM\OneToMany(targetEntity="Recording", mappedBy="meeting", cascade={"persist"}, orphanRemoval=true)
*/
protected $recordings;
public function __construct()
{
$this->registrants = new ArrayCollection();
$this->recordings = new ArrayCollection();
$this->activities = new ArrayCollection();
}
/**
* @return string
*/
public function __toString()
{
return sprintf('Meeting %d', $this->id);
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return int
*/
public function getMeetingId()
{
return $this->meetingId;
}
/**
* @param int $meetingId
*
* @return Meeting
*/
public function setMeetingId($meetingId)
{
$this->meetingId = $meetingId;
return $this;
}
/**
* @return User
*/
public function getUser()
{
return $this->user;
}
/**
* @return Course
*/
public function getCourse()
{
return $this->course;
}
/**
* @return Session
*/
public function getSession()
{
return $this->session;
}
/**
* @return Registrant[]|ArrayCollection
*/
public function getRegistrants()
{
return $this->registrants;
}
/**
* @return Recording[]|ArrayCollection
*/
public function getRecordings()
{
return $this->recordings;
}
/**
* @return MeetingActivity[]|ArrayCollection
*/
public function getActivities()
{
return $this->activities;
}
public function addActivity(MeetingActivity $activity)
{
$activity->setMeeting($this);
$this->activities[] = $activity;
}
/**
* @param MeetingActivity[]|ArrayCollection $activities
*
* @return Meeting
*/
public function setActivities($activities)
{
$this->activities = $activities;
return $this;
}
/**
* @ORM\PostLoad
*
* @throws Exception
*/
public function postLoad()
{
if (null !== $this->meetingListItemJson) {
$this->meetingListItem = MeetingListItem::fromJson($this->meetingListItemJson);
}
if (null !== $this->meetingInfoGetJson) {
$this->meetingInfoGet = MeetingInfoGet::fromJson($this->meetingInfoGetJson);
}
$this->initializeDisplayableProperties();
}
/**
* @ORM\PostUpdate
*
* @throws Exception
*/
public function postUpdate()
{
$this->initializeDisplayableProperties();
}
/**
* @ORM\PreFlush
*/
public function preFlush()
{
if (null !== $this->meetingListItem) {
$this->meetingListItemJson = json_encode($this->meetingListItem);
}
if (null !== $this->meetingInfoGet) {
$this->meetingInfoGetJson = json_encode($this->meetingInfoGet);
}
}
/**
* @return MeetingListItem
*/
public function getMeetingListItem()
{
return $this->meetingListItem;
}
/**
* @return MeetingInfoGet
*/
public function getMeetingInfoGet()
{
return $this->meetingInfoGet;
}
/**
* @param User $user
*
* @return $this
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* @param Course $course
*
* @return $this
*/
public function setCourse($course)
{
$this->course = $course;
return $this;
}
/**
* @param Session $session
*
* @return $this
*/
public function setSession($session)
{
$this->session = $session;
return $this;
}
/**
* @return CGroupInfo
*/
public function getGroup()
{
return $this->group;
}
/**
* @param CGroupInfo $group
*
* @return Meeting
*/
public function setGroup($group)
{
$this->group = $group;
return $this;
}
/**
* @param MeetingListItem $meetingListItem
*
* @throws Exception
*
* @return Meeting
*/
public function setMeetingListItem($meetingListItem)
{
if (null === $this->meetingId) {
$this->meetingId = $meetingListItem->id;
} elseif ($this->meetingId != $meetingListItem->id) {
throw new Exception('the Meeting identifier differs from the MeetingListItem identifier');
}
$this->meetingListItem = $meetingListItem;
return $this;
}
/**
* @param MeetingInfoGet $meetingInfoGet
*
* @throws Exception
*
* @return Meeting
*/
public function setMeetingInfoGet($meetingInfoGet)
{
if (null === $this->meetingId) {
$this->meetingId = $meetingInfoGet->id;
} elseif ($this->meetingId != $meetingInfoGet->id) {
throw new Exception('the Meeting identifier differs from the MeetingInfoGet identifier');
}
$this->meetingInfoGet = $meetingInfoGet;
$this->initializeDisplayableProperties();
return $this;
}
/**
* @return bool
*/
public function isCourseMeeting()
{
return null !== $this->course;
}
/**
* @return bool
*/
public function isCourseGroupMeeting()
{
return null !== $this->course && null !== $this->group;
}
/**
* @return bool
*/
public function isUserMeeting()
{
return null !== $this->user && null === $this->course;
}
/**
* @return bool
*/
public function isGlobalMeeting()
{
return null === $this->user && null === $this->course;
}
public function setStatus($status)
{
$this->meetingInfoGet->status = $status;
}
/**
* Builds the list of users that can register into this meeting.
* Zoom requires an email address, therefore users without an email address are excluded from the list.
*
* @return User[] the list of users
*/
public function getRegistrableUsers()
{
$users = [];
if (!$this->isCourseMeeting()) {
$criteria = ['active' => true];
$users = Database::getManager()->getRepository('ChamiloUserBundle:User')->findBy($criteria);
} elseif (null === $this->session) {
if (null !== $this->course) {
/** @var CourseRelUser $courseRelUser */
foreach ($this->course->getUsers() as $courseRelUser) {
$users[] = $courseRelUser->getUser();
}
}
} else {
if (null !== $this->course) {
$subscriptions = $this->session->getUserCourseSubscriptionsByStatus($this->course, Session::STUDENT);
if ($subscriptions) {
/** @var SessionRelCourseRelUser $sessionCourseUser */
foreach ($subscriptions as $sessionCourseUser) {
$users[] = $sessionCourseUser->getUser();
}
}
}
}
$activeUsersWithEmail = [];
foreach ($users as $user) {
if ($user->isActive() && !empty($user->getEmail())) {
$activeUsersWithEmail[] = $user;
}
}
return $activeUsersWithEmail;
}
/**
* @return bool
*/
public function requiresDateAndDuration()
{
return MeetingInfoGet::TYPE_SCHEDULED === $this->meetingInfoGet->type
|| MeetingInfoGet::TYPE_RECURRING_WITH_FIXED_TIME === $this->meetingInfoGet->type;
}
/**
* @return bool
*/
public function requiresRegistration()
{
return
MeetingSettings::APPROVAL_TYPE_AUTOMATICALLY_APPROVE === $this->meetingInfoGet->settings->approval_type;
/*return
MeetingSettings::APPROVAL_TYPE_NO_REGISTRATION_REQUIRED != $this->meetingInfoGet->settings->approval_type;*/
}
/**
* @return bool
*/
public function hasCloudAutoRecordingEnabled()
{
return \ZoomPlugin::RECORDING_TYPE_NONE !== $this->meetingInfoGet->settings->auto_recording;
}
/**
* @param User $user
*
* @return bool
*/
public function hasRegisteredUser($user)
{
return $this->getRegistrants()->exists(
function (Registrant $registrantEntity) use (&$user) {
return $registrantEntity->getUser() === $user;
}
);
}
/**
* @param User $user
*
* @return Registrant|null
*/
public function getRegistrant($user)
{
foreach ($this->getRegistrants() as $registrant) {
if ($registrant->getUser() === $user) {
return $registrant;
}
}
return null;
}
/**
* Generates a short presentation of the meeting for the future participant.
* To be displayed above the "Enter meeting" link.
*
* @return string
*/
public function getIntroduction()
{
$introduction = sprintf('<h1>%s</h1>', $this->meetingInfoGet->topic);
if (!$this->isGlobalMeeting()) {
if (!empty($this->formattedStartTime)) {
$introduction .= $this->formattedStartTime;
if (!empty($this->formattedDuration)) {
$introduction .= ' ('.$this->formattedDuration.')';
}
}
}
if ($this->user) {
$introduction .= sprintf('<p>%s</p>', $this->user->getFullname());
} elseif ($this->isCourseMeeting()) {
if (null === $this->session) {
$introduction .= sprintf('<p class="main">%s</p>', $this->course);
} else {
$introduction .= sprintf('<p class="main">%s (%s)</p>', $this->course, $this->session);
}
}
if (!empty($this->meetingInfoGet->agenda)) {
$introduction .= sprintf('<p>%s</p>', $this->meetingInfoGet->agenda);
}
return $introduction;
}
/**
* @throws Exception on unexpected start_time or duration
*/
private function initializeDisplayableProperties()
{
$zoomPlugin = new \ZoomPlugin();
$typeList = [
API\Meeting::TYPE_INSTANT => $zoomPlugin->get_lang('InstantMeeting'),
API\Meeting::TYPE_SCHEDULED => $zoomPlugin->get_lang('ScheduledMeeting'),
API\Meeting::TYPE_RECURRING_WITH_NO_FIXED_TIME => $zoomPlugin->get_lang('RecurringWithNoFixedTime'),
API\Meeting::TYPE_RECURRING_WITH_FIXED_TIME => $zoomPlugin->get_lang('RecurringWithFixedTime'),
];
$this->typeName = $typeList[$this->meetingInfoGet->type];
if (property_exists($this, 'status')) {
$statusList = [
'waiting' => $zoomPlugin->get_lang('Waiting'),
'started' => $zoomPlugin->get_lang('Started'),
'finished' => $zoomPlugin->get_lang('Finished'),
];
$this->statusName = $statusList[$this->meetingInfoGet->status];
}
$this->startDateTime = null;
$this->formattedStartTime = '';
$this->durationInterval = null;
$this->formattedDuration = '';
if (!empty($this->meetingInfoGet->start_time)) {
$this->startDateTime = new DateTime($this->meetingInfoGet->start_time);
$this->startDateTime->setTimezone(new DateTimeZone(date_default_timezone_get()));
$this->formattedStartTime = $this->startDateTime->format('Y-m-d H:i');
}
if (!empty($this->meetingInfoGet->duration)) {
$now = new DateTime();
$later = new DateTime();
$later->add(new DateInterval('PT'.$this->meetingInfoGet->duration.'M'));
$this->durationInterval = $later->diff($now);
$this->formattedDuration = $this->durationInterval->format($zoomPlugin->get_lang('DurationFormat'));
}
}
}

@ -0,0 +1,208 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Meeting.
*
* @ORM\Entity()
* @ORM\Table(name="plugin_zoom_meeting_activity")
* @ORM\HasLifecycleCallbacks
*/
class MeetingActivity
{
/**
* @var int
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue()
*/
protected $id;
/**
* @var Meeting
*
* @ORM\ManyToOne(targetEntity="Meeting", inversedBy="activities")
* @ORM\JoinColumn(name="meeting_id")
*/
protected $meeting;
/**
* @var Meeting
*
* @ORM\ManyToOne(targetEntity="Chamilo\UserBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* @var string
*
* @ORM\Column(type="string", name="name", length=255, nullable=false)
*/
protected $name;
/**
* @var string
*
* @ORM\Column(type="string", name="type", length=255, nullable=false)
*/
protected $type;
/**
* @var string
*
* @ORM\Column(type="text", name="event", nullable=true)
*/
protected $event;
/**
* @var \DateTime
*
* @ORM\Column(name="created_at", type="datetime", nullable=false)
*/
protected $createdAt;
public function __construct()
{
$this->createdAt = new \DateTime();
}
/**
* @return string
*/
public function __toString()
{
return sprintf('Activity %d', $this->id);
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return Meeting
*/
public function getMeeting()
{
return $this->meeting;
}
/**
* @param Meeting $meeting
*
* @return MeetingActivity
*/
public function setMeeting($meeting)
{
$this->meeting = $meeting;
return $this;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
*
* @return MeetingActivity
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @param string $type
*
* @return MeetingActivity
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* @return DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* @return string
*/
public function getEvent()
{
return $this->event;
}
/**
* @return Meeting
*/
public function getUser()
{
return $this->user;
}
/**
* @param Meeting $user
*
* @return MeetingActivity
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
public function getEventDecoded()
{
if (!empty($this->event)) {
return json_decode($this->event);
}
return '';
}
/**
* @param string $event
*
* @return MeetingActivity
*/
public function setEvent($event)
{
$this->event = $event;
return $this;
}
}

@ -0,0 +1,193 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\PluginBundle\Zoom\API\RecordingMeeting;
use Database;
use DateInterval;
use DateTime;
use DateTimeZone;
use Doctrine\ORM\Mapping as ORM;
use Exception;
/**
* Class RecordingEntity.
*
* @ORM\Entity(repositoryClass="Chamilo\PluginBundle\Zoom\RecordingRepository")
* @ORM\Table(
* name="plugin_zoom_recording",
* indexes={
* @ORM\Index(name="meeting_id_index", columns={"meeting_id"}),
* }
* )
* @ORM\HasLifecycleCallbacks
*/
class Recording
{
/** @var DateTime */
public $startDateTime;
/** @var string */
public $formattedStartTime;
/** @var DateInterval */
public $durationInterval;
/** @var string */
public $formattedDuration;
/**
* @var string
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue()
*/
protected $id;
/**
* @var string
*
* @ORM\Column(type="string")
*/
protected $uuid;
/**
* @var Meeting
*
* @ORM\ManyToOne(targetEntity="Meeting", inversedBy="recordings")
* @ORM\JoinColumn(name="meeting_id")
*/
protected $meeting;
/**
* @var string
*
* @ORM\Column(type="text", name="recording_meeting_json", nullable=true)
*/
protected $recordingMeetingJson;
/** @var RecordingMeeting */
protected $recordingMeeting;
/**
* @param $name
*
* @throws Exception
*
* @return mixed
*/
public function __get($name)
{
$object = $this->getRecordingMeeting();
if (property_exists($object, $name)) {
return $object->$name;
}
throw new Exception(sprintf('%s does not know property %s', $this, $name));
}
/**
* @return string
*/
public function __toString()
{
return sprintf('Recording %d', $this->uuid);
}
/**
* @return Meeting
*/
public function getMeeting()
{
return $this->meeting;
}
/**
* @throws Exception
*
* @return RecordingMeeting
*/
public function getRecordingMeeting()
{
return $this->recordingMeeting;
}
/**
* @param Meeting $meeting
*
* @return $this
*/
public function setMeeting($meeting)
{
$this->meeting = $meeting;
$this->meeting->getRecordings()->add($this);
return $this;
}
/**
* @param RecordingMeeting $recordingMeeting
*
* @throws Exception
*
* @return Recording
*/
public function setRecordingMeeting($recordingMeeting)
{
if (null === $this->uuid) {
$this->uuid = $recordingMeeting->uuid;
} elseif ($this->uuid !== $recordingMeeting->uuid) {
throw new Exception('the RecordingEntity identifier differs from the RecordingMeeting identifier');
}
if (null === $this->meeting) {
$this->meeting = Database::getManager()->getRepository(Meeting::class)->find($recordingMeeting->id);
} elseif ($this->meeting->getMeetingId() != $recordingMeeting->id) {
// $this->meeting remains null when the remote RecordingMeeting refers to a deleted meeting.
throw new Exception('The RecordingEntity meeting id differs from the RecordingMeeting meeting id');
}
$this->recordingMeeting = $recordingMeeting;
return $this;
}
/**
* @ORM\PostLoad
*
* @throws Exception
*/
public function postLoad()
{
if (null !== $this->recordingMeetingJson) {
$this->recordingMeeting = RecordingMeeting::fromJson($this->recordingMeetingJson);
}
$this->initializeExtraProperties();
}
/**
* @ORM\PreFlush
*/
public function preFlush()
{
if (null !== $this->recordingMeeting) {
$this->recordingMeetingJson = json_encode($this->recordingMeeting);
}
}
/**
* @throws Exception
*/
public function initializeExtraProperties()
{
$this->startDateTime = new DateTime($this->recordingMeeting->start_time);
$this->startDateTime->setTimezone(new DateTimeZone(date_default_timezone_get()));
$this->formattedStartTime = $this->startDateTime->format('Y-m-d H:i');
$now = new DateTime();
$later = new DateTime();
$later->add(new DateInterval('PT'.$this->recordingMeeting->duration.'M'));
$this->durationInterval = $later->diff($now);
$this->formattedDuration = $this->durationInterval->format(get_lang('DurationFormat'));
}
}

@ -0,0 +1,265 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\PluginBundle\Zoom\API\CreatedRegistration;
use Chamilo\PluginBundle\Zoom\API\MeetingRegistrant;
use Chamilo\PluginBundle\Zoom\API\MeetingRegistrantListItem;
use Chamilo\UserBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;
use Exception;
/**
* Class RegistrantEntity.
*
* @ORM\Entity(repositoryClass="RegistrantRepository")
* @ORM\Table(
* name="plugin_zoom_registrant",
* indexes={
* @ORM\Index(name="user_id_index", columns={"user_id"}),
* @ORM\Index(name="meeting_id_index", columns={"meeting_id"}),
* }
* )
* @ORM\HasLifecycleCallbacks
*/
class Registrant
{
/** @var string */
public $fullName;
/**
* @var string
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var User
* @ORM\ManyToOne(targetEntity="Chamilo\UserBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
protected $user;
/**
* @var Meeting
* @ORM\ManyToOne(targetEntity="Meeting", inversedBy="registrants")
* @ORM\JoinColumn(name="meeting_id", referencedColumnName="id")
*/
protected $meeting;
/**
* @var string
* @ORM\Column(type="text", name="created_registration_json", nullable=true)
*/
protected $createdRegistrationJson;
/**
* @var string
* @ORM\Column(type="text", name="meeting_registrant_list_item_json", nullable=true)
*/
protected $meetingRegistrantListItemJson;
/**
* @var string
* @ORM\Column(type="text", name="meeting_registrant_json", nullable=true)
*/
protected $meetingRegistrantJson;
/** @var CreatedRegistration */
protected $createdRegistration;
/** @var MeetingRegistrant */
protected $meetingRegistrant;
/** @var MeetingRegistrantListItem */
protected $meetingRegistrantListItem;
/**
* @return string
*/
public function __toString()
{
return sprintf('Registrant %d', $this->id);
}
/**
* @return Meeting
*/
public function getMeeting()
{
return $this->meeting;
}
/**
* @param Meeting $meeting
*
* @return $this
*/
public function setMeeting($meeting)
{
$this->meeting = $meeting;
$this->meeting->getRegistrants()->add($this);
return $this;
}
/**
* @return User
*/
public function getUser()
{
return $this->user;
}
/**
* @param User $user
*
* @return $this
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* @throws Exception
*
* @return MeetingRegistrantListItem
*/
public function getMeetingRegistrantListItem()
{
return $this->meetingRegistrantListItem;
}
/**
* @param MeetingRegistrantListItem $meetingRegistrantListItem
*
* @throws Exception
*
* @return $this
*/
public function setMeetingRegistrantListItem($meetingRegistrantListItem)
{
if (!is_null($this->meeting) && $this->meeting->getId() != $meetingRegistrantListItem->id) {
throw new Exception('RegistrantEntity meeting id differs from MeetingRegistrantListItem id');
}
$this->meetingRegistrantListItem = $meetingRegistrantListItem;
$this->computeFullName();
return $this;
}
public function computeFullName()
{
$this->fullName = api_get_person_name(
$this->meetingRegistrant->first_name,
$this->meetingRegistrant->last_name
);
}
public function getJoinUrl()
{
if (!$this->createdRegistration) {
return '';
}
return $this->createdRegistration->join_url;
}
/**
* @throws Exception
*
* @return CreatedRegistration
*/
public function getCreatedRegistration()
{
return $this->createdRegistration;
}
/**
* @param CreatedRegistration $createdRegistration
*
* @throws Exception
*
* @return $this
*/
public function setCreatedRegistration($createdRegistration)
{
if (null === $this->id) {
$this->id = $createdRegistration->registrant_id;
} elseif ($this->id != $createdRegistration->registrant_id) {
throw new Exception('RegistrantEntity id differs from CreatedRegistration identifier');
}
$this->createdRegistration = $createdRegistration;
return $this;
}
/**
* @throws Exception
*
* @return MeetingRegistrant
*/
public function getMeetingRegistrant()
{
return $this->meetingRegistrant;
}
/**
* @param MeetingRegistrant $meetingRegistrant
*
* @throws Exception
*
* @return $this
*/
public function setMeetingRegistrant($meetingRegistrant)
{
$this->meetingRegistrant = $meetingRegistrant;
$this->computeFullName();
return $this;
}
/**
* @ORM\PostLoad
*
* @throws Exception
*/
public function postLoad()
{
if (null !== $this->meetingRegistrantJson) {
$this->meetingRegistrant = MeetingRegistrant::fromJson($this->meetingRegistrantJson);
}
if (null !== $this->createdRegistrationJson) {
$this->createdRegistration = CreatedRegistration::fromJson($this->createdRegistrationJson);
}
if (null !== $this->meetingRegistrantListItemJson) {
$this->meetingRegistrantListItem = MeetingRegistrantListItem::fromJson(
$this->meetingRegistrantListItemJson
);
}
$this->computeFullName();
}
/**
* @ORM\PreFlush
*/
public function preFlush()
{
if (null !== $this->meetingRegistrant) {
$this->meetingRegistrantJson = json_encode($this->meetingRegistrant);
}
if (null !== $this->createdRegistration) {
$this->createdRegistrationJson = json_encode($this->createdRegistration);
}
if (null !== $this->meetingRegistrantListItem) {
$this->meetingRegistrantListItemJson = json_encode($this->meetingRegistrantListItem);
}
}
}

@ -0,0 +1,70 @@
The Chamilo Zoom plugin class itself is defined in _plugin/zoom/lib/ZoomPlugin.php_
It manipulates both **remote Zoom server objects** and **local database entities**:
# Local database entities
The local entities map the remote objects to Chamilo courses/sessions and users.
They also maintain a cache of the matching remote objects.
_Entity/*Entity.php_ are the local database entity classes.
Doctrine entity manager repository classes are in _lib/*EntityRepository.php_.
# Remote Zoom server objets
_lib/API/*.php_ contains the Zoom API data structure definition classes,
based on Zoom's own API specification:
* https://marketplace.zoom.us/docs/api-reference/zoom-api
* https://marketplace.zoom.us/docs/api-reference/zoom-api/Zoom%20API.oas2.json
These classes provide methods to list, create, update and delete the remote objects.
# JWT Client
API class methods use a JWT client implemented in _lib/API/JWTClient.php_.
The plugin constructor initializes the JWT Client, giving it required API key and secret.
# Event notification handler
_endpoint.php_ is the Zoom API event notification web hook end point.
It handles notifications sent by Zoom servers on useful events :
* meeting start and end,
* registrant join and leave,
* recordings created and deleted
# Administrative interface
_admin.php_ is the administrative interface.
It lists all meetings and recordings.
# Course tool
_start.php_ is the **course** tool target:
* to the course teacher, it shows a course meeting management interface;
* to the course learners, it shows the list of scheduled course meetings.
# Home page's profile block (also on "My Courses" page)
This plugin can add 3 kinds of links to "profile block" :
1. _join_meeting.php?meetingId=…_ links to upcoming meetings accessible to the current user.
_join_meeting.php_ presents the meeting and shows a link to enter the meeting.
2. _user.php_ is the **user**'s own meeting management interface.
3. _global.php_ directs the user to _join_meeting.php_ with the **global** meeting.
# Meeting management page
_admin.php_, _start.php_ and _user.php_ link to _meeting.php_.
_meeting.php_ is the meeting management page, where one can manage
* the meeting properties,
* the list of its registrants and
* its recordings.

@ -0,0 +1,37 @@
This plugin adds Zoom meetings, user registration to meetings and meeting recordings.
## Meetings
A **meeting** can be linked to a local **user** and/or a local **course**/**session**:
* a meeting with a course is a _course meeting_;
* a meeting with a user and no course is a _user meeting_;
* a meeting with no course nor user is a _global meeting_.
## Registrants
A **registrant** is the registration of a local user to a meeting.
Users do not register themselves to meetings.
* They are registered to a course meeting by the course manager.
* They are registered to a user meeting by that user.
* They are registered automatically to the global meeting, when they enter it.
## Recordings
A **recording** is the list of files created during a past meeting instance.
Course meeting files can be copied to the course by the course manager.
# Required Zoom user account
Recordings and user registration are only available to paying Zoom customers.
For a non-paying Zoom user, this plugin still works but participants will join anonymously.
The user that starts the meeting will be identified as the zoom account that is define in the plugin. Socreate a generic account that works for all the users that start meetings.
# Contributing
Read README.code.md for an introduction to the plugin's code.

@ -0,0 +1,55 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Zoom\Meeting;
$course_plugin = 'zoom'; // needed in order to load the plugin lang variables
require_once __DIR__.'/config.php';
$plugin = ZoomPlugin::create();
$tool_name = $plugin->get_lang('ZoomVideoConferences');
$meetingId = isset($_REQUEST['meetingId']) ? (int) $_REQUEST['meetingId'] : 0;
if (empty($meetingId)) {
api_not_allowed(true);
}
$content = '';
/** @var Meeting $meeting */
$meeting = $plugin->getMeetingRepository()->findOneBy(['meetingId' => $meetingId]);
if (null === $meeting) {
api_not_allowed(true);
}
if (!$plugin->userIsConferenceManager($meeting)) {
api_not_allowed(true);
}
$returnURL = 'meetings.php';
$urlExtra = '';
if ($meeting->isCourseMeeting()) {
api_protect_course_script(true);
$urlExtra = api_get_cidreq();
$returnURL = 'start.php?'.$urlExtra;
if (api_is_in_group()) {
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.api_get_cidreq(),
'name' => get_lang('Groups'),
];
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group_space.php?'.api_get_cidreq(),
'name' => get_lang('GroupSpace').' '.$meeting->getGroup()->getName(),
];
}
}
$interbreadcrumb[] = [
'url' => $returnURL,
'name' => $plugin->get_lang('ZoomVideoConferences'),
];
$tpl = new Template($meeting->getMeetingId());
$tpl->assign('actions', $plugin->getToolbar());
$tpl->assign('meeting', $meeting);
$tpl->assign('url_extra', $urlExtra);
$tpl->assign('content', $tpl->fetch('zoom/view/activity.tpl'));
$tpl->display_one_col_template();

@ -0,0 +1,5 @@
<?php
/* For licensing terms, see /license.txt */
require_once __DIR__.'/../../main/inc/global.inc.php';

@ -0,0 +1,151 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Zoom\API\RecordingMeeting;
use Chamilo\PluginBundle\Zoom\Meeting;
use Chamilo\PluginBundle\Zoom\MeetingActivity;
use Chamilo\PluginBundle\Zoom\Recording;
use Symfony\Component\HttpFoundation\Response;
if ('POST' !== $_SERVER['REQUEST_METHOD']) {
http_response_code(Response::HTTP_NOT_FOUND); // Not found
exit;
}
// @todo handle non-apache installations
$authorizationHeaderValue = apache_request_headers()['Authorization'];
require_once __DIR__.'/config.php';
if (api_get_plugin_setting('zoom', 'verificationToken') !== $authorizationHeaderValue) {
http_response_code(Response::HTTP_UNAUTHORIZED);
exit;
}
$body = file_get_contents('php://input');
$decoded = json_decode($body);
if (is_null($decoded) || !is_object($decoded) || !isset($decoded->event) || !isset($decoded->payload->object)) {
error_log(sprintf('Did not recognize event notification: %s', $body));
http_response_code(Response::HTTP_UNPROCESSABLE_ENTITY);
exit;
}
$object = $decoded->payload->object;
list($objectType, $action) = explode('.', $decoded->event);
$em = Database::getManager();
$meetingRepository = $em->getRepository(Meeting::class);
$meeting = null;
if ($object->id) {
/** @var Meeting $meeting */
$meeting = $meetingRepository->findOneBy(['meetingId' => $object->id]);
}
if (null === $meeting) {
error_log("Meeting not found");
error_log(sprintf('Event "%s" on %s was unhandled: %s', $action, $objectType, $body));
http_response_code(Response::HTTP_NOT_FOUND);
exit;
}
$activity = new MeetingActivity();
$activity->setName($action);
$activity->setType($objectType);
$activity->setEvent(json_encode($object));
switch ($objectType) {
case 'meeting':
error_log('Meeting '.$action.' - '.$meeting->getId());
error_log(print_r($object, 1));
switch ($action) {
case 'deleted':
$em->remove($meeting);
break;
case 'ended':
case 'started':
$meeting->setStatus($action);
$meeting->addActivity($activity);
$em->persist($meeting);
break;
default:
$meeting->addActivity($activity);
$em->persist($meeting);
break;
}
$em->flush();
break;
case 'recording':
$recordingRepository = $em->getRepository(Recording::class);
$recordingEntity = null;
if ($object->uuid) {
/** @var Recording $recordingEntity */
$recordingEntity = $recordingRepository->findOneBy(['uuid' => $object->uuid, 'meeting' => $meeting]);
}
error_log("Recording: $action");
error_log(print_r($object, 1));
switch ($action) {
case 'completed':
$recording = new Recording();
$recording->setRecordingMeeting(RecordingMeeting::fromObject($object));
$recording->setMeeting($meeting);
$meeting->addActivity($activity);
$em->persist($meeting);
$em->persist($recording);
$em->flush();
break;
case 'recovered':
/*if (null === $recordingEntity) {
$em->persist(
(new Recording())->setRecordingMeeting(RecordingMeeting::fromObject($object))
);
$em->flush();
}*/
break;
case 'trashed':
case 'deleted':
$meeting->addActivity($activity);
if (null !== $recordingEntity) {
$recordMeeting = $recordingEntity->getRecordingMeeting();
$recordingToDelete = RecordingMeeting::fromObject($object);
$files = [];
if ($recordingToDelete->recording_files) {
foreach ($recordingToDelete->recording_files as $fileToDelete) {
foreach ($recordMeeting->recording_files as $file) {
if ($fileToDelete->id != $file->id) {
$files[] = $file;
}
}
}
}
if (empty($files)) {
$em->remove($recordingEntity);
} else {
$recordMeeting->recording_files = $files;
$recordingEntity->setRecordingMeeting($recordMeeting);
$em->persist($recordingEntity);
}
}
$em->persist($meeting);
$em->flush();
break;
default:
$meeting->addActivity($activity);
$em->persist($meeting);
$em->flush();
//error_log(sprintf('Event "%s" on %s was unhandled: %s', $action, $objectType, $body));
//http_response_code(501); // Not Implemented
break;
}
break;
default:
error_log(sprintf('Event "%s" on %s was unhandled: %s', $action, $objectType, $body));
http_response_code(501); // Not Implemented
break;
}

@ -0,0 +1,13 @@
<?php
/* For license terms, see /license.txt */
$course_plugin = 'zoom'; // needed in order to load the plugin lang variables
require_once __DIR__.'/config.php';
if (!ZoomPlugin::currentUserCanJoinGlobalMeeting()) {
api_not_allowed(true);
}
api_location('join_meeting.php?meetingId='.ZoomPlugin::create()->getGlobalMeeting()->getMeetingId());

@ -0,0 +1,8 @@
<?php
/* For license terms, see /license.txt */
if (!api_is_platform_admin()) {
die('You must have admin permissions to install plugins');
}
ZoomPlugin::create()->install();

@ -0,0 +1,65 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Zoom\Meeting;
require_once __DIR__.'/config.php';
api_block_anonymous_users();
$course_plugin = 'zoom'; // needed in order to load the plugin lang variables
$meetingId = isset($_REQUEST['meetingId']) ? (int) $_REQUEST['meetingId'] : 0;
if (empty($meetingId)) {
api_not_allowed(true);
}
$plugin = ZoomPlugin::create();
$content = '';
/** @var Meeting $meeting */
$meeting = $plugin->getMeetingRepository()->findOneBy(['meetingId' => $meetingId]);
if (null === $meeting) {
api_not_allowed(true, $plugin->get_lang('MeetingNotFound'));
}
if ($meeting->isCourseMeeting()) {
api_protect_course_script(true);
if (api_is_in_group()) {
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.api_get_cidreq(),
'name' => get_lang('Groups'),
];
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group_space.php?'.api_get_cidreq(),
'name' => get_lang('GroupSpace').' '.$meeting->getGroup()->getName(),
];
}
}
try {
$startJoinURL = $plugin->getStartOrJoinMeetingURL($meeting);
$content .= $meeting->getIntroduction();
if (!empty($startJoinURL)) {
$content .= Display::url($plugin->get_lang('EnterMeeting'), $startJoinURL, ['class' => 'btn btn-primary']);
} else {
$content .= Display::return_message($plugin->get_lang('ConferenceNotAvailable'), 'warning');
}
if ($plugin->userIsConferenceManager($meeting)) {
$content .= '&nbsp;'.Display::url(
get_lang('Details'),
api_get_path(WEB_PLUGIN_PATH).'zoom/meeting.php?meetingId='.$meeting->getMeetingId(),
['class' => 'btn btn-default']
);
}
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'warning')
);
}
Display::display_header($plugin->get_title());
echo $plugin->getToolbar();
echo $content;
Display::display_footer();

@ -0,0 +1,144 @@
<?php
/* License: see /license.txt */
// Needed in order to show the plugin title
$strings['plugin_title'] = "Zoom Videoconference";
$strings['plugin_comment'] = "Zoom Videoconference integration in courses and sessions";
$strings['tool_enable'] = 'Zoom videoconference tool enabled';
$strings['apiKey'] = 'API Key';
$strings['apiSecret'] = 'API Secret';
$strings['verificationToken'] = 'Verification Token';
$strings['enableParticipantRegistration'] = 'Enable participant registration';
$strings['enableCloudRecording'] = 'Automatic recording type';
$strings['enableGlobalConference'] = 'Enable global conference';
$strings['enableGlobalConferencePerUser'] = 'Enable global conference per user';
$strings['globalConferenceAllowRoles'] = "Global conference link only visible for these user roles";
$strings['globalConferencePerUserAllowRoles'] = "Global conference per user link only visible for these user roles";
$strings['tool_enable_help'] = "Choose whether you want to enable the Zoom videoconference tool.
Once enabled, it will show as an additional course tool in all courses' homepage :
teachers will be able to <strong>launch</strong> a conference and student to <strong>join</strong> it.
<br/>
This plugin requires a Zoom account to manage meetings.
The Zoom API uses JSON Web Tokens (JWT) to authenticate account-level access.
<br/>
JWT apps provide an <strong>API <em>Key</em> and <em>Secret</em></strong> required to authenticate with JWT.
To get them, create a <em>JWT App</em> :
<br/>1. log into <a href=\"https://zoom.us/profile\">your Zoom profile page</a>
<br/>2. click on <em>Advanced / Application Marketplace</em>
<br/>3. click on <em><a href=\"https://marketplace.zoom.us/develop/create\">Develop / build App</a></em>
<br/>4. choose <em>JWT / Create</em>
<br/>5. Information: fill in fields about your \"App\"
(application and company names, contact name and email address)
<br/>6. Click on <em>Continue</em>
<br/>7. App Credentials: <strong>copy your API Key and Secret to these fields below</strong>
<br/>8. click on <em>Continue</em>
<br/>9. Feature:
enable <em>Event Subscriptions</em> to add a new one with endpoint URL
<code>https://your.chamilo.url/plugin/zoom/endpoint.php</code>
and add these event types:
<br/>- Start Meeting
<br/>- End Meeting
<br/>- Participant/Host joined meeting
<br/>- Participant/Host left meeting
<br/>- All Recordings have completed
<br/>- Recording transcript files have completed
<br/>then click on <em>Done</em> then on <em>Save</em>
and <strong>copy your Verification Token to the field below</strong>.
<br/>10. click on <em>Continue</em>
<br/>
<strong>Attention</strong>:
<br/>Zoom is <em>NOT</em> free software and specific rules apply to personal data protection.
Please check with Zoom and make sure they satisfy you and learning users.";
$strings['enableParticipantRegistration_help'] = "Requires a paying Zoom profile.
Will not work for a <em>basic</em> profile.";
$strings['enableCloudRecording_help'] = "Requires a paying Zoom profile.
Will not work for a <em>basic</em> profile.";
// please keep these lines alphabetically sorted
$strings['AllCourseUsersWereRegistered'] = "All course students were registered";
$strings['Agenda'] = "Agenda";
$strings['CannotRegisterWithoutEmailAddress'] = "Cannot register without email address";
$strings['CopyingJoinURL'] = "Copying join URL";
$strings['CopyJoinAsURL'] = "Copy 'join as' URL";
$strings['CopyToCourse'] = "Copy to course";
$strings['CouldNotCopyJoinURL'] = "Could not copy join URL";
$strings['Course'] = "Cours";
$strings['CreatedAt'] = "Created at";
$strings['CreateLinkInCourse'] = "Create link(s) in course";
$strings['CreateUserVideoConference'] = "Create user conference";
$strings['DateMeetingTitle'] = "%s: %s";
$strings['DeleteMeeting'] = "Delete meeting";
$strings['DeleteFile'] = "Delete file(s)";
$strings['Details'] = "Details";
$strings['DoIt'] = "Do it";
$strings['Duration'] = "Duration";
$strings['DurationFormat'] = "%hh%I";
$strings['DurationInMinutes'] = "Duration (in minutes)";
$strings['EndDate'] = "End Date";
$strings['EnterMeeting'] = "Enter meeting";
$strings['ViewMeeting'] = "View meeting";
$strings['Files'] = "Files";
$strings['Finished'] = "finished";
$strings['FileWasCopiedToCourse'] = "The file was copied to the course";
$strings['FileWasDeleted'] = "The file was deleted";
$strings['GlobalMeeting'] = "Global conference";
$strings['GlobalMeetingPerUser'] = "Global conference per user";
$strings['GroupUsersWereRegistered'] = "Group members were registered";
$strings['InstantMeeting'] = "Instant meeting";
$strings['Join'] = "Join";
$strings['JoinGlobalVideoConference'] = "Join global conference";
$strings['JoinURLCopied'] = "Join URL copied";
$strings['JoinURLToSendToParticipants'] = "Join URL to send to participants";
$strings['LiveMeetings'] = "Live meetings";
$strings['LinkToFileWasCreatedInCourse'] = "A link to the file was added to the course";
$strings['MeetingDeleted'] = "Meeting deleted";
$strings['MeetingsFound'] = "Meetings found";
$strings['MeetingUpdated'] = "Meeting updated";
$strings['NewMeetingCreated'] = "New meeting created";
$strings['Password'] = "Password";
$strings['RecurringWithFixedTime'] = "Recurring with fixed time";
$strings['RecurringWithNoFixedTime'] = "Recurring with no fixed time";
$strings['RegisterAllCourseUsers'] = "Register all course users";
$strings['RegisteredUserListWasUpdated'] = "Registered user list updated";
$strings['RegisteredUsers'] = "Registered users";
$strings['RegisterNoUser'] = "Register no user";
$strings['RegisterTheseGroupMembers'] = "Register these group members";
$strings['ScheduleAMeeting'] = "Schedule a meeting";
$strings['ScheduledMeeting'] = "Scheduled meeting";
$strings['ScheduledMeetings'] = "Scheduled Meetings";
$strings['ScheduleAMeeting'] = "Schedule a meeting";
$strings['SearchMeeting'] = "Search meeting";
$strings['Session'] = "Session";
$strings['StartDate'] = "Start Date";
$strings['Started'] = "started";
$strings['StartInstantMeeting'] = "Start instant meeting";
$strings['StartMeeting'] = "Start meeting";
$strings['StartTime'] = "Start time";
$strings['Topic'] = "Topic";
$strings['TopicAndAgenda'] = "Topic and agenda";
$strings['Type'] = "Type";
$strings['UpcomingMeetings'] = "Upcoming meetings";
$strings['UpdateMeeting'] = "Update meeting";
$strings['UpdateRegisteredUserList'] = "Update registered user list";
$strings['UserRegistration'] = "User registration";
$strings['Y-m-d H:i'] = "Y-m-d H:i";
$strings['Waiting'] = "waiting";
$strings['XRecordingOfMeetingXFromXDurationXDotX'] = "%s recording of meeting %s from %s (%s).%s";
$strings['YouAreNotRegisteredToThisMeeting'] = "You are not registered to this meeting";
$strings['ZoomVideoConferences'] = "Zoom Video Conferences";
$strings['Recordings'] = "Recordings";
$strings['CreateGlobalVideoConference'] = "Create global video conference";
$strings['ConferenceNotStarted'] = "Conference not started";
$strings['MeetingNotFound'] = "Meeting not found";
$strings['JoinURLNotAvailable'] = "URL not available";
$strings['Meetings'] = "Meetings";
$strings['ConferenceType'] = "Conference type";
$strings['ForEveryone'] = "Everyone";
$strings['SomeUsers'] = "Some users (Select later)";
$strings['Activity'] = "Activity";
$strings['ConferenceNotAvailable'] = "Conference not available";

@ -0,0 +1,138 @@
<?php
/* License: see /license.txt */
// Needed in order to show the plugin title
$strings['plugin_title'] = "Conférence vidéo Zoom";
$strings['plugin_comment'] = "Intégration de conférences vidéo Zoom dans les cours et les sessions";
$strings['tool_enable'] = 'Outil de conférence vidéos Zoom activé';
$strings['apiKey'] = "Clé d'API (<em>API Key</em>)";
$strings['apiSecret'] = "Code secret d'API (<em>API Secret</em>)";
$strings['enableParticipantRegistration'] = "Activer l'inscription des participants";
$strings['enableCloudRecording'] = "Type d'enregistrement automatique";
$strings['enableGlobalConference'] = "Activer les conférences globales";
$strings['enableGlobalConferencePerUser'] = "Activer les conférences globales par utilisateur";
$strings['globalConferenceAllowRoles'] = "Visibilité du lien de vidéo conférence global pour les profils suivant";
$strings['globalConferencePerUserAllowRoles'] = "Visibilité du lien de vidéo conférence global par utilisateur pour les profils suivant";
$strings['tool_enable_help'] = "Choisissez si vous voulez activer l'outil de conférence vidéo Zoom.
Une fois activé, il apparaitra dans les pages d'accueil de tous les cours :
les enseignants pourront <strong>démarrer</strong> une conférence et les étudiants la <strong>rejoindre</strong>.
<br/>
Ce plugin requiert un compte Zoom pour gérer les conférences.
L'API de Zoom utilise les <em>JSON Web Tokens (JWT)</em> pour autoriser l'accès à un compte.
<strong>Une <em>clé</em> et un <em>code secret</em> d'API sont requis</strong> pour s'authentifier avec JWT.
Pour les obtenir, créez une <em>JWT app</em> :
<br/>1. logguez vous sur <a href=\"https://zoom.us/profile\">Votre profil Zoom</a>
<br/>2. cliquez sur <em>Avancé / Marketplace d'application</em>
<br/>3. cliquez sur <em><a href=\"https://marketplace.zoom.us/develop/create\">Develop / build App</a></em>
<br/>4. choisissez <em>JWT / Create</em>
<br/>5. Information: remplissez quelques champs à propos de votre \"App\"
(noms de l'application, de l'entreprise, nom et adresse de courriel de contact)
<br/>6. cliquez sur <em>Continue</em>
<br/>7. App Credentials :
<strong>copiez la clé (API Key) et le code secret (API Secret) dans les champs ci-dessous.</strong>
<br/>8. cliquez sur <em>Continue</em>
<br/>9. Feature :
activez <em>Event Subscriptions</em> pour en ajouter une avec comme endpoint URL
<code>https://your.chamilo.url/plugin/zoom/endpoint.php</code>
et ajoutez ces types d'événements :
<br/>- Start Meeting
<br/>- End Meeting
<br/>- Participant/Host joined meeting
<br/>- Participant/Host left meeting
<br/>- All Recordings have completed
<br/>- Recording transcript files have completed
<br/>puis cliquez sur <em>Done</em> puis sur <em>Save</em>
et <strong>copiez votre Verification Token dans le champ ci-dessous</strong>.
<br/>10. cliquez sur <em>Continue</em>
<br/>
<strong>Attention</strong> :
<br/>Zoom n'est <em>PAS</em> un logiciel libre
et des règles spécifiques de protection des données personnelles s'y appliquent.
Merci de vérifier auprès de Zoom qu'elles sont satisfaisantes pour vous et les apprenants qui l'utiliseront.";
$strings['enableParticipantRegistration_help'] = "Nécessite un profil Zoom payant.
Ne fonctionnera pas pour un profil <em>de base</em>.";
$strings['enableCloudRecording_help'] = "Nécessite un profil Zoom payant.
Ne fonctionnera pas pour un profil <em>de base</em>.";
// please keep these lines alphabetically sorted
$strings['AllCourseUsersWereRegistered'] = "Tous les étudiants du cours sont inscrits";
$strings['Agenda'] = "Ordre du jour";
$strings['CannotRegisterWithoutEmailAddress'] = "Impossible d'inscrire un utilisateur sans adresse de courriel";
$strings['CopyingJoinURL'] = "Copie de l'URL pour rejoindre en cours";
$strings['CopyJoinAsURL'] = "Copier l'URL pour 'rejoindre en tant que'";
$strings['CopyToCourse'] = "Copier dans le cours";
$strings['CouldNotCopyJoinURL'] = "Échec de la copie de l'URL pour rejoindre";
$strings['Course'] = "Cours";
$strings['CreatedAt'] = "Créé à";
$strings['CreateLinkInCourse'] = "Créer dans le cours un ou des lien(s) vers le(s) fichier(s)";
$strings['CreateUserVideoConference'] = "Créer des conferences par utilisateur";
$strings['DateMeetingTitle'] = "%s : %s";
$strings['DeleteMeeting'] = "Effacer la conférence";
$strings['DeleteFile'] = "Supprimer ce(s) fichier(s)";
$strings['Details'] = "Détail";
$strings['DoIt'] = "Fais-le";
$strings['Duration'] = "Durée";
$strings['DurationFormat'] = "%hh%I";
$strings['DurationInMinutes'] = "Durée (en minutes)";
$strings['EndDate'] = "Date de fin";
$strings['EnterMeeting'] = "Entrer dans la conférence";
$strings['ViewMeeting'] = "Voir la conférence";
$strings['Files'] = "Fichiers";
$strings['Finished'] = "terminée";
$strings['FileWasCopiedToCourse'] = "Le fichier a été copié dans le cours";
$strings['FileWasDeleted'] = "Le fichier a été effacé";
$strings['GlobalMeeting'] = "Conférence globale";
$strings['GroupUsersWereRegistered'] = "Les membres des groupes ont été inscrits";
$strings['InstantMeeting'] = "Conférence instantanée";
$strings['Join'] = "Rejoindre";
$strings['JoinGlobalVideoConference'] = "Rejoindre la conférence globale";
$strings['JoinURLCopied'] = "URL pour rejoindre copiée";
$strings['JoinURLToSendToParticipants'] = "URL pour assister à la conférence (à envoyer aux participants)";
$strings['LiveMeetings'] = "Conférences en cours";
$strings['LinkToFileWasCreatedInCourse'] = "Un lien vers le fichier a été ajouter au cours";
$strings['MeetingDeleted'] = "Conférence effacée";
$strings['MeetingsFound'] = "Conférences trouvées";
$strings['MeetingUpdated'] = "Conférence mise à jour";
$strings['NewMeetingCreated'] = "Nouvelle conférence créée";
$strings['Password'] = "Mot de passe";
$strings['RecurringWithFixedTime'] = "Recurrent, à heure fixe";
$strings['RecurringWithNoFixedTime'] = "Recurrent, sans heure fixe";
$strings['RegisterAllCourseUsers'] = "Inscrire tous les utilisateurs du cours";
$strings['RegisteredUserListWasUpdated'] = "Liste des utilisateurs inscrits mise à jour";
$strings['RegisteredUsers'] = "Utilisateurs inscrits";
$strings['RegisterNoUser'] = "N'inscrire aucun utilisateur";
$strings['RegisterTheseGroupMembers'] = "Inscrire les membres de ces groupes";
$strings['ScheduleAMeeting'] = "Programmer une conférence";
$strings['ScheduledMeeting'] = "Conférence programmée";
$strings['ScheduledMeetings'] = "Conférences programmées";
$strings['ScheduleAMeeting'] = "Programmer une conférence";
$strings['SearchMeeting'] = "Rechercher une conférence";
$strings['Session'] = "Session";
$strings['StartDate'] = "Date de début";
$strings['Started'] = "démarrée";
$strings['StartInstantMeeting'] = "Démarrer une conférence instantanée";
$strings['StartMeeting'] = "Démarrer la conférence";
$strings['StartTime'] = "Heure de début";
$strings['Topic'] = "Objet";
$strings['TopicAndAgenda'] = "Objet et ordre du jour";
$strings['Type'] = "Type";
$strings['UpcomingMeeting'] = "Conférences à venir";
$strings['UpdateMeeting'] = "Mettre à jour la conférence";
$strings['UpdateRegisteredUserList'] = "Mettre à jour la liste des utilisateurs inscrits";
$strings['UserRegistration'] = "Inscription des utilisateurs";
$strings['Y-m-d H:i'] = "d/m/Y à H\hi";
$strings['verificationToken'] = 'Verification Token';
$strings['Waiting'] = "en attente";
$strings['XRecordingOfMeetingXFromXDurationXDotX'] = "Enregistrement (%s) de la conférence %s de %s (%s).%s";
$strings['YouAreNotRegisteredToThisMeeting'] = "Vous n'êtes pas inscrit à cette conférence";
$strings['ZoomVideoConferences'] = "Conférences vidéo Zoom";
$strings['Recordings'] = "Enregistrements";
$strings['CreateGlobalVideoConference'] = "Créer une conférence global";
$strings['JoinURLNotAvailable'] = "URL pas disponible";
$strings['Meetings'] = "Conférences";
$strings['Activity'] = "Activité";
$strings['ConferenceNotAvailable'] = "Conférence non disponible";

@ -0,0 +1,138 @@
<?php
/* License: see /license.txt */
// Needed in order to show the plugin title
$strings['plugin_title'] = "Videoconferencia Zoom";
$strings['plugin_comment'] = "Integración de videoconferencias Zoom en los cursos y las sesiones";
$strings['tool_enable'] = 'Herramienta activada';
$strings['apiKey'] = "Clave API (<em>API Key</em>)";
$strings['apiSecret'] = "Código secreto de API (<em>API Secret</em>)";
$strings['enableParticipantRegistration'] = "Activar la inscripción de participantes";
$strings['enableCloudRecording'] = "Tipo de grabación automática";
$strings['enableGlobalConference'] = "Activar las conferencias globales";
$strings['enableGlobalConferencePerUser'] = "Activar las conferencias globales por usuario";
$strings['globalConferenceAllowRoles'] = "Visibilidad del enlace global de videoconferencia para los perfiles siguientes";
$strings['globalConferencePerUserAllowRoles'] = "Visibilidad del enlace global de videoconferencia por usuario para los perfiles siguientes";
$strings['tool_enable_help'] = "Escoja si desea activar la herramienta Zoom.
Una vez activada, aparecerá en las páginas principales de todos los cursos. Los profesores podrán
<strong>iniciar</strong> una conferencia y los alumnos <strong>juntarse</strong> a ella.
<br/>
Este plugin requiere una cuenta Zoom para gestionar las conferencias.
El API de Zoom utiliza los <em>JSON Web Tokens (JWT)</em> para autorizar el acceso a una cuenta.
<strong>Una <em>clave</em> y un <em>código secreto</em> de API son necesarios</strong> para identificarse con JWT.
Para obtenerlos, crea una <em>app JWT</em> :
<br/>1. logéase en <a href=\"https://zoom.us/profile\">Su perfil Zoom</a>
<br/>2. de clic en <em>Avanzado / Marketplace de aplicaciones</em>
<br/>3. de clic en <em><a href=\"https://marketplace.zoom.us/develop/create\">Develop / build App</a></em>
<br/>4. escoja <em>JWT / Create</em>
<br/>5. Information: ingrese algunas informaciones sobre vuestra \"App\"
(nombres de la aplicación, de la empresa, nombre y dirección de correo de contacto)
<br/>6. de clic en <em>Continue</em>
<br/>7. App Credentials:
muestra la clave (API Key) y el código secreto (API Secret) por ingresar aquí.
<strong>copiez la clé (API Key) et le code secret (API Secret) dans les champs ci-dessous.</strong>
<br/>8. de clic en <em>Continue</em>
<br/>9. Feature :
activez <em>Event Subscriptions</em> para agregar uno con su endpoint URL
<code>https://your.chamilo.url/plugin/zoom/endpoint.php</code>
y agrega este tipo de eventos:
<br/>- Start Meeting
<br/>- End Meeting
<br/>- Participant/Host joined meeting
<br/>- Participant/Host left meeting
<br/>- All Recordings have completed
<br/>- Recording transcript files have completed
<br/>de clic en <em>Done</em> y luego en <em>Save</em>
y <strong>copie su Verification Token en el campo a continuación</strong>.
<br/>10. de clic en <em>Continue</em>
<br/>
<strong>Atención</strong> :
<br/>Zoom <em>NO ES</em> un software libre, y reglas específicas de protección de datos se aplican a este.
Por favor verifique con Zoom que éstas le den satisfacción a Usted y los alumnos que la usarán.";
$strings['enableParticipantRegistration_help'] = "Requiere un perfil Zoom de pago.
No funcionará para un perfil <em>base/gratuito</em>.";
$strings['enableCloudRecording_help'] = "Requiere un perfil Zoom de pago.
No funcionará para un perfil <em>base/gratuito</em>.";
// please keep these lines alphabetically sorted
$strings['AllCourseUsersWereRegistered'] = "Todos los alumnos del curso están inscritos";
$strings['Agenda'] = "Orden del día";
$strings['CannotRegisterWithoutEmailAddress'] = "No se puede registrar usuario sin dirección de correo electrónico";
$strings['CopyingJoinURL'] = "Copia de la URL para ingresar";
$strings['CopyJoinAsURL'] = "Copiar la URL para 'ingresar como'";
$strings['CopyToCourse'] = "Copiar en el curso";
$strings['CouldNotCopyJoinURL'] = "Falló la copia de la URL de ingreso";
$strings['Course'] = "Curso";
$strings['CreatedAt'] = "Creado el";
$strings['CreateLinkInCourse'] = "Crear en el curso uno o más vínculos hacia el/los archivo(s)";
$strings['CreateUserVideoConference'] = "Crear conferencias de usario";
$strings['DateMeetingTitle'] = "%s: %s";
$strings['DeleteMeeting'] = "Borrar la conferencia";
$strings['DeleteFile'] = "Borrar este/estos archivo(s)";
$strings['Details'] = "Detalle";
$strings['DoIt'] = "Hágalo";
$strings['Duration'] = "Duración";
$strings['DurationFormat'] = "%hh%I";
$strings['DurationInMinutes'] = "Duración (en minutos)";
$strings['EndDate'] = "Fecha de fin";
$strings['EnterMeeting'] = "Ingresar la conferencia";
$strings['ViewMeeting'] = "Ver la conferencia";
$strings['Files'] = "Archivos";
$strings['Finished'] = "terminada";
$strings['FileWasCopiedToCourse'] = "El archivo ha sido copiado en el curso";
$strings['FileWasDeleted'] = "El archivo ha sido borrado";
$strings['GlobalMeeting'] = "Conferencia global";
$strings['GroupUsersWereRegistered'] = "Miembros de los grupos han sido registrados";
$strings['InstantMeeting'] = "Conferencia instantánea";
$strings['Join'] = "Ingresar";
$strings['JoinGlobalVideoConference'] = "Ingresar la conrencia global";
$strings['JoinURLCopied'] = "URL para juntarse copiada";
$strings['JoinURLToSendToParticipants'] = "URL para asistir a la conferencia (para enviar a los participantes)";
$strings['LiveMeetings'] = "Conferencias activas";
$strings['LinkToFileWasCreatedInCourse'] = "Un enlace al archivo ha sido añadido al curso";
$strings['MeetingDeleted'] = "Conferencia borrada";
$strings['MeetingsFound'] = "Conferencias encontradas";
$strings['MeetingUpdated'] = "Conferencias actualizadas";
$strings['NewMeetingCreated'] = "Nueva conferencia creada";
$strings['Password'] = "Contraseña";
$strings['RecurringWithFixedTime'] = "Recurrente, a una hora fija";
$strings['RecurringWithNoFixedTime'] = "Recurrente, sin hora fija";
$strings['RegisterAllCourseUsers'] = "Inscribir todos los usuarios del curso";
$strings['RegisteredUserListWasUpdated'] = "Lista de usuarios inscritos actualizada";
$strings['RegisteredUsers'] = "Usuarios inscritos";
$strings['RegisterNoUser'] = "No inscribir ningún usuario";
$strings['RegisterTheseGroupMembers'] = "Inscribir los miembros de estos grupos";
$strings['ScheduleAMeeting'] = "Programar una conferencia";
$strings['ScheduledMeeting'] = "Conferencia programada";
$strings['ScheduledMeetings'] = "Conferencias programadas";
$strings['ScheduleAMeeting'] = "Programar una conferencia";
$strings['SearchMeeting'] = "Buscar una conferencia";
$strings['Session'] = "Sesión";
$strings['StartDate'] = "Fecha de inicio";
$strings['Started'] = "iniciada";
$strings['StartInstantMeeting'] = "Iniciar una conferencia instantánea";
$strings['StartMeeting'] = "Iniciar la conferencia";
$strings['StartTime'] = "Hora de inicio";
$strings['Topic'] = "Objeto";
$strings['TopicAndAgenda'] = "Objeto y orden del día";
$strings['Type'] = "Tipo";
$strings['UpcomingMeeting'] = "Próximas conferencias";
$strings['UpdateMeeting'] = "Actualizar la conferencia";
$strings['UpdateRegisteredUserList'] = "Actualizar la lista de usuarios inscritos";
$strings['UserRegistration'] = "Inscripción de los usuarios";
$strings['Y-m-d H:i'] = "d/m/Y a las H\hi";
$strings['verificationToken'] = 'Verification Token';
$strings['Waiting'] = "en espera";
$strings['XRecordingOfMeetingXFromXDurationXDotX'] = "Grabación (%s) de la conferencia %s de %s (%s).%s";
$strings['YouAreNotRegisteredToThisMeeting'] = "No estás registrado en esta reunión";
$strings['ZoomVideoConferences'] = "Videoconferencias Zoom";
$strings['Recordings'] = "Grabaciones";
$strings['CreateGlobalVideoConference'] = "Crear una videoconferencia global";
$strings['JoinURLNotAvailable'] = "URL no disponible";
$strings['Meetings'] = "Conferencias";
$strings['Activity'] = "Actividad";
$strings['ConferenceNotAvailable'] = "Conferencia no disponible";

@ -0,0 +1,32 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
/**
* Trait BaseMeetingTrait.
* Common meeting properties definitions.
*/
trait BaseMeetingTrait
{
/** @var string */
public $topic;
/** @var int */
public $type;
/** @var string "yyyy-MM-dd'T'HH:mm:ss'Z'" for GMT, same without 'Z' for local time (as set on zoom account) */
public $start_time;
/** @var int in minutes, for scheduled meetings only */
public $duration;
/** @var string the timezone for start_time */
public $timezone;
/** @var string description */
public $agenda;
/** @var string description */
public $host_email;
}

@ -0,0 +1,56 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Interface Client.
* Two implementations are currently possible : OAuth and JWT.
*
* @see https://marketplace.zoom.us/docs/api-reference/zoom-api
*/
abstract class Client
{
/** @var Client */
private static $instance;
/**
* Returns an initialized Client.
*
* @return Client
*/
public static function getInstance()
{
return self::$instance;
}
/**
* Sends a Zoom API-compliant HTTP request and retrieves the response.
*
* On success, returns the body of the response
* On error, throws an exception with an detailed error message
*
* @param string $httpMethod GET, POST, PUT, DELETE ...
* @param string $relativePath to append to https://api.zoom.us/v2/
* @param array $parameters request query parameters
* @param object $requestBody json-encoded body of the request
*
* @throws Exception describing the error (message and code)
*
* @return string response body (not json-decoded)
*/
abstract public function send($httpMethod, $relativePath, $parameters = [], $requestBody = null);
/**
* Registers an initialized Client.
*
* @param Client $instance
*/
protected static function register($instance)
{
self::$instance = $instance;
}
}

@ -0,0 +1,42 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class CreatedRegistration.
* An instance of this class is returned by the Zoom server upon recording a registrant to a meeting.
*/
class CreatedRegistration
{
use JsonDeserializableTrait;
/** @var int meeting ID */
public $id;
/** @var string Unique URL for this registrant to join the meeting.
* This URL should only be shared with the registrant for whom the API request was made.
* If the meeting was created with manual approval type (1), the join URL will not be returned in the response.
*/
public $join_url;
/** @var string Unique identifier of the registrant */
public $registrant_id;
/** @var string The start time for the meeting. */
public $start_time;
/** @var string Topic of the meeting. */
public $topic;
/**
* {@inheritdoc}
*/
protected function itemClass($propertyName)
{
throw new Exception("no such array property $propertyName");
}
}

@ -0,0 +1,18 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
/**
* Class CustomQuestion.
* A list of instances of this class is included in a MeetingRegistrant instance.
*/
class CustomQuestion
{
/** @var string */
public $title;
/** @var string */
public $value;
}

@ -0,0 +1,39 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class GlobalDialInNumber.
* A list of these is included in a meeting settings.
*/
class GlobalDialInNumber
{
use JsonDeserializableTrait;
/** @var string Country code. For example, BR. */
public $country;
/** @var string Full name of country. For example, Brazil. */
public $country_name;
/** @var string City of the number, if any. For example, Chicago. */
public $city;
/** @var string Phone number. For example, +1 2332357613. */
public $number;
/** @var string Type of number. Either "toll" or "tollfree". */
public $type;
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,94 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
use Firebase\JWT\JWT;
/**
* Class JWTClient.
*
* @see https://marketplace.zoom.us/docs/guides/auth/jwt
*/
class JWTClient extends Client
{
public $token;
/**
* JWTClient constructor.
* Requires JWT app credentials.
*
* @param string $apiKey JWT API Key
* @param string $apiSecret JWT API Secret
*/
public function __construct($apiKey, $apiSecret)
{
$this->token = JWT::encode(
[
'iss' => $apiKey,
'exp' => (time() + 60) * 1000, // will expire in one minute
],
$apiSecret
);
self::register($this);
}
/**
* {@inheritdoc}
*/
public function send($httpMethod, $relativePath, $parameters = [], $requestBody = null)
{
$options = [
CURLOPT_CUSTOMREQUEST => $httpMethod,
CURLOPT_ENCODING => '',
CURLOPT_HTTPHEADER => [
'authorization: Bearer '.$this->token,
'content-type: application/json',
],
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_MAXREDIRS => 10,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
];
if (!is_null($requestBody)) {
$jsonRequestBody = json_encode($requestBody);
if (false === $jsonRequestBody) {
throw new Exception('Could not generate JSON request body');
}
$options[CURLOPT_POSTFIELDS] = $jsonRequestBody;
}
$url = "https://api.zoom.us/v2/$relativePath";
if (!empty($parameters)) {
$url .= '?'.http_build_query($parameters);
}
$curl = curl_init($url);
if (false === $curl) {
throw new Exception("curl_init returned false");
}
curl_setopt_array($curl, $options);
$responseBody = curl_exec($curl);
$responseCode = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
$curlError = curl_error($curl);
curl_close($curl);
if ($curlError) {
throw new Exception("cURL Error: $curlError");
}
if (false === $responseBody || !is_string($responseBody)) {
throw new Exception('cURL Error');
}
if (empty($responseCode)
|| $responseCode < 200
|| $responseCode >= 300
) {
throw new Exception($responseBody, $responseCode);
}
return $responseBody;
}
}

@ -0,0 +1,122 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Trait JsonDeserializableTrait.
* Utility functions to help convert server-generated JSON to API class instances.
*/
trait JsonDeserializableTrait
{
/**
* Builds a class instance from the Json description of the object.
*
* @param string $json
*
* @throws Exception on JSON-decode error or unexpected object property
*
* @return static
*/
public static function fromJson($json)
{
if (empty($json)) {
throw new Exception('Cannot JSON-decode empty string');
}
$object = json_decode($json);
if (null === $object) {
throw new Exception('Could not decode JSON: '.$json);
}
return static::fromObject($object);
}
/**
* Builds a class instance from an already json-decoded object.
*
* @param object $object
*
* @throws Exception on unexpected object property
*
* @return static
*/
public static function fromObject($object)
{
$instance = new static();
static::recursivelyCopyObjectProperties($object, $instance);
return $instance;
}
/**
* Returns the class name of the items to be found in the named array property.
*
* To override in classes that have a property of type array
*
* @param string $propertyName array property name
*
* @throws Exception if not implemented for this propertyName
*
* @return string class name of the items to be found in the named array property
*/
abstract public function itemClass($propertyName);
/**
* Initializes properties that can be calculated from json-decoded properties.
*
* Called at the end of method recursivelyCopyObjectProperties()
* and indirectly at the end of static method fromJson().
*
* By default it does nothing.
*/
public function initializeExtraProperties()
{
// default does nothing
}
/**
* Copies values from another object properties to an instance, recursively.
*
* @param object $source source object
* @param object $destination specific class instance, with already initialized properties
*
* @throws Exception when the source object has an unexpected property
*/
protected static function recursivelyCopyObjectProperties($source, &$destination)
{
foreach (get_object_vars($source) as $name => $value) {
if (property_exists($destination, $name)) {
if (is_object($value)) {
if (is_object($destination->$name)) {
static::recursivelyCopyObjectProperties($value, $destination->$name);
} else {
throw new Exception("Source property $name is an object, which is not expected");
}
} elseif (is_array($value)) {
if (is_array($destination->$name)) {
$itemClass = $destination->itemClass($name);
foreach ($value as $sourceItem) {
if ('string' === $itemClass) {
$destination->$name[] = $sourceItem;
} else {
$item = new $itemClass();
static::recursivelyCopyObjectProperties($sourceItem, $item);
$destination->$name[] = $item;
}
}
} else {
throw new Exception("Source property $name is an array, which is not expected");
}
} else {
$destination->$name = $value;
}
} else {
error_log("Source object has property $name, which was not expected: ".json_encode($source));
}
}
$destination->initializeExtraProperties();
}
}

@ -0,0 +1,84 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class Meeting, minimal meeting definition required to create one from scratch or update an existing one
* Also referred to as MeetingUpdate in the API documentation
* Does not represent an actual created meeting.
*/
class Meeting
{
use BaseMeetingTrait;
use JsonDeserializableTrait;
const TYPE_INSTANT = 1;
const TYPE_SCHEDULED = 2;
const TYPE_RECURRING_WITH_NO_FIXED_TIME = 3;
const TYPE_RECURRING_WITH_FIXED_TIME = 8;
/** @var string password to join. [a-z A-Z 0-9 @ - _ *]. Max of 10 characters. */
public $password;
/** @var TrackingField[] Tracking fields */
public $tracking_fields;
/** @var object, only for a recurring meeting with fixed time (type 8) */
public $recurrence;
/** @var MeetingSettings */
public $settings;
/**
* Meeting constructor.
*/
protected function __construct()
{
$this->tracking_fields = [];
$this->settings = new MeetingSettings();
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('tracking_fields' === $propertyName) {
return TrackingField::class;
}
throw new Exception("no such array property $propertyName");
}
/**
* Creates a meeting on the server and returns the resulting MeetingInfoGet.
*
* @throws Exception describing the error (message and code)
*
* @return MeetingInfoGet meeting
*/
public function create()
{
return MeetingInfoGet::fromJson(Client::getInstance()->send('POST', 'users/me/meetings', [], $this));
}
/**
* Creates a Meeting instance from a topic.
*
* @param string $topic
* @param int $type
*
* @return static
*/
public static function fromTopicAndType($topic, $type = self::TYPE_SCHEDULED)
{
$instance = new static();
$instance->topic = $topic;
$instance->type = $type;
return $instance;
}
}

@ -0,0 +1,34 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
/**
* Class MeetingInfo
* Used to define MeetingInfoGet
* Does not seem to be used directly.
*/
class MeetingInfo extends Meeting
{
/** @var string */
public $created_at;
/** @var string, allows host to start the meeting as the host (without password) - not to be shared */
public $start_url;
/** @var string, for participants to join the meeting - to share with users to invite */
public $join_url;
/** @var string undocumented */
public $registration_url;
/** @var string H.323/SIP room system password */
public $h323_password;
/** @var int Personal Meeting Id. Only used for scheduled meetings and recurring meetings with no fixed time */
public $pmi;
/** @var object[] */
public $occurrences;
}

@ -0,0 +1,148 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class MeetingInfoGet
* Full Meeting as returned by the server, with unique identifiers and current status.
*/
class MeetingInfoGet extends MeetingInfo
{
/** @var string unique meeting instance ID */
public $uuid;
/** @var string meeting number */
public $id;
/** @var string host Zoom user id */
public $host_id;
/** @var string meeting status, either "waiting", "started" or "finished" */
public $status;
/** @var string undocumented */
public $pstn_password;
/** @var string Encrypted password for third party endpoints (H323/SIP). */
public $encrypted_password;
/**
* Retrieves a meeting from its numeric identifier.
*
* @param int $id
*
* @throws Exception
*
* @return static the meeting
*/
public static function fromId($id)
{
return static::fromJson(Client::getInstance()->send('GET', "meetings/$id"));
}
/**
* Updates the meeting on server.
*
* @throws Exception
*/
public function update()
{
Client::getInstance()->send('PATCH', 'meetings/'.$this->id, [], $this);
}
/**
* Ends the meeting on server.
*
* @throws Exception
*/
public function endNow()
{
Client::getInstance()->send('PUT', "meetings/$this->id/status", [], (object) ['action' => 'end']);
}
/**
* Deletes the meeting on server.
*
* @throws Exception
*/
public function delete()
{
Client::getInstance()->send('DELETE', "meetings/$this->id");
}
/**
* Adds a registrant to the meeting.
*
* @param MeetingRegistrant $registrant with at least 'email' and 'first_name'.
* 'last_name' will also be recorded by Zoom.
* Other properties remain ignored, or not returned by Zoom
* (at least while using profile "Pro")
* @param string $occurrenceIds separated by comma
*
* @throws Exception
*
* @return CreatedRegistration with unique join_url and registrant_id properties
*/
public function addRegistrant($registrant, $occurrenceIds = '')
{
return CreatedRegistration::fromJson(
Client::getInstance()->send(
'POST',
"meetings/$this->id/registrants",
empty($occurrenceIds) ? [] : ['occurrence_ids' => $occurrenceIds],
$registrant
)
);
}
/**
* Removes registrants from the meeting.
*
* @param MeetingRegistrant[] $registrants registrants to remove (id and email)
* @param string $occurrenceIds separated by comma
*
* @throws Exception
*/
public function removeRegistrants($registrants, $occurrenceIds = '')
{
if (!empty($registrants)) {
Client::getInstance()->send(
'PUT',
"meetings/$this->id/registrants/status",
empty($occurrenceIds) ? [] : ['occurrence_ids' => $occurrenceIds],
(object) [
'action' => 'cancel',
'registrants' => $registrants,
]
);
}
}
/**
* Retrieves meeting registrants.
*
* @throws Exception
*
* @return MeetingRegistrantListItem[] the meeting registrants
*/
public function getRegistrants()
{
return MeetingRegistrantList::loadMeetingRegistrants($this->id);
}
/**
* Retrieves the meeting's instances.
*
* @throws Exception
*
* @return MeetingInstance[]
*/
public function getInstances()
{
return MeetingInstances::fromMeetingId($this->id)->meetings;
}
}

@ -0,0 +1,50 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class MeetingInstance
* A meeting (numerical id) can have one or more instances (string UUID).
* Each instance has its own start time, participants and recording files.
*
* @see MeetingInstances
* @see PastMeeting for the full record
*/
class MeetingInstance
{
/** @var string */
public $uuid;
/** @var string */
public $start_time;
/**
* Retrieves the recording of the instance.
*
* @throws Exception with code 404 when there is no recording for this meeting
*
* @return RecordingMeeting the recording
*/
public function getRecordings()
{
return RecordingMeeting::fromJson(
Client::getInstance()->send('GET', 'meetings/'.htmlentities($this->uuid).'/recordings')
);
}
/**
* Retrieves the instance's participants.
*
* @throws Exception
*
* @return ParticipantListItem[]
*/
public function getParticipants()
{
return ParticipantList::loadInstanceParticipants($this->uuid);
}
}

@ -0,0 +1,53 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class MeetingInstances. The list of one meeting's ended instances.
*
* @see MeetingInstance
*/
class MeetingInstances
{
use JsonDeserializableTrait;
/** @var MeetingInstance[] List of ended meeting instances. */
public $meetings;
/**
* MeetingInstances constructor.
*/
public function __construct()
{
$this->meetings = [];
}
/**
* Retrieves a meeting's instances.
*
* @param int $meetingId
*
* @throws Exception
*
* @return MeetingInstances the meeting's instances
*/
public static function fromMeetingId($meetingId)
{
return static::fromJson(Client::getInstance()->send('GET', "past_meetings/$meetingId/instances"));
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('meetings' === $propertyName) {
return MeetingInstance::class;
}
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,58 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class MeetingList. Lists Meetings.
*
* @see MeetingListItem
*/
class MeetingList
{
use Pagination;
const TYPE_SCHEDULED = 'scheduled'; // all valid past meetings (unexpired),
// live meetings and upcoming scheduled meetings.
const TYPE_LIVE = 'live'; // all the ongoing meetings.
const TYPE_UPCOMING = 'upcoming'; // all upcoming meetings, including live meetings.
/** @var MeetingListItem[] */
public $meetings;
/**
* MeetingList constructor.
*/
public function __construct()
{
$this->meetings = [];
}
/**
* Retrieves all meetings of a type.
*
* @param int $type TYPE_SCHEDULED, TYPE_LIVE or TYPE_UPCOMING
*
* @throws Exception
*
* @return MeetingListItem[] all meetings
*/
public static function loadMeetings($type)
{
return static::loadItems('meetings', 'users/me/meetings', ['type' => $type]);
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('meetings' === $propertyName) {
return MeetingListItem::class;
}
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,44 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class MeetingListItem. Item of a list of meetings.
*
* @see MeetingList
*/
class MeetingListItem
{
use BaseMeetingTrait;
use JsonDeserializableTrait;
/** @var string unique meeting instance ID */
public $uuid;
/** @var string meeting number */
public $id;
/** @var string host Zoom user id */
public $host_id;
/** @var string */
public $created_at;
/** @var string */
public $join_url;
/** @var string truncated to 250 characters */
// public $agenda;
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
throw new Exception("no such array property $propertyName");
}
}

@ -0,0 +1,105 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class MeetingRegistrant.
* Structure of the information to send the server in order to register someone to a meeting.
*/
class MeetingRegistrant
{
use JsonDeserializableTrait;
/** @var string */
public $email;
/** @var string */
public $first_name;
/** @var string */
public $last_name;
/** @var string */
public $address;
/** @var string */
public $city;
/** @var string */
public $country;
/** @var string */
public $zip;
/** @var string */
public $state;
/** @var string */
public $phone;
/** @var string */
public $industry;
/** @var string */
public $org;
/** @var string */
public $job_title;
/** @var string */
public $purchasing_time_frame;
/** @var string */
public $role_in_purchase_process;
/** @var string */
public $no_of_employees;
/** @var string */
public $comments;
/** @var object[] title => value */
public $custom_questions;
/**
* MeetingRegistrant constructor.
*/
public function __construct()
{
$this->custom_questions = [];
}
/**
* @param string $email
* @param string $firstName
* @param string $lastName
*
* @return MeetingRegistrant
*/
public static function fromEmailAndFirstName($email, $firstName, $lastName = null)
{
$instance = new static();
$instance->first_name = $firstName;
$instance->email = $email;
if (null !== $lastName) {
$instance->last_name = $lastName;
}
return $instance;
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('custom_questions' === $propertyName) {
return CustomQuestion::class;
}
throw new Exception("no such array property $propertyName");
}
}

@ -0,0 +1,53 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class MeetingRegistrantList. List of meeting registrants.
*
* @see MeetingRegistrantListItem
*/
class MeetingRegistrantList
{
use Pagination;
/** @var MeetingRegistrantListItem[] */
public $registrants;
/**
* MeetingRegistrantList constructor.
*/
public function __construct()
{
$this->registrants = [];
}
/**
* Retrieves all registrant for a meeting.
*
* @param int $meetingId
*
* @throws Exception
*
* @return MeetingRegistrantListItem[] all registrants of the meeting
*/
public static function loadMeetingRegistrants($meetingId)
{
return static::loadItems('registrants', "meetings/$meetingId/registrants");
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('registrants' === $propertyName) {
return MeetingRegistrantListItem::class;
}
throw new Exception("no such array property $propertyName");
}
}

@ -0,0 +1,29 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
/**
* Class MeetingRegistrantListItem. Item in a list of meeting registrants.
*
* @see MeetingRegistrantList
*/
class MeetingRegistrantListItem extends MeetingRegistrant
{
/** @var string Registrant ID. */
public $id;
/** @var string The status of the registrant's registration.
* `approved`: User has been successfully approved for the webinar.
* `pending`: The registration is still pending.
* `denied`: User has been denied from joining the webinar.
*/
public $status;
/** @var string The time at which the registrant registered. */
public $create_time;
/** @var string The URL using which an approved registrant can join the webinar. */
public $join_url;
}

@ -0,0 +1,143 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class MeetingSettings. An instance of this class is included in each Meeting instance.
*/
class MeetingSettings
{
use JsonDeserializableTrait;
const APPROVAL_TYPE_AUTOMATICALLY_APPROVE = 0;
const APPROVAL_TYPE_MANUALLY_APPROVE = 1;
const APPROVAL_TYPE_NO_REGISTRATION_REQUIRED = 2;
const REGISTRATION_TYPE_REGISTER_ONCE_ATTEND_ANY = 1;
const REGISTRATION_TYPE_REGISTER_EACH = 2;
const REGISTRATION_TYPE_REGISTER_ONCE_CHOOSE = 3;
/** @var bool Start video when the host joins the meeting */
public $host_video;
/** @var bool Start video when participants join the meeting */
public $participant_video;
/** @var bool Host meeting in China */
public $cn_meeting;
/** @var bool Host meeting in India */
public $in_meeting;
/** @var bool Allow participants to join the meeting before the host starts the meeting.
* Only used for scheduled or recurring meetings.
*/
public $join_before_host;
/** @var bool Mute participants upon entry */
public $mute_upon_entry;
/** @var bool Add watermark when viewing a shared screen */
public $watermark;
/** @var bool Use a personal meeting ID.
* Only used for scheduled meetings and recurring meetings with no fixed time.
*/
public $use_pmi;
/** @var int Enable registration and set approval for the registration.
* Note that this feature requires the host to be of **Licensed** user type.
* **Registration cannot be enabled for a basic user.**
*/
public $approval_type;
/** @var int Used for recurring meeting with fixed time only. */
public $registration_type;
/** @var string either both, telephony or voip */
public $audio;
/** @var string either local, cloud or none */
public $auto_recording;
/** @var bool @deprecated only signed in users can join this meeting */
public $enforce_login;
/** @var string @deprecated only signed in users with specified domains can join meetings */
public $enforce_login_domains;
/** @var string Alternative host's emails or IDs: multiple values separated by a comma. */
public $alternative_hosts;
/** @var bool Close registration after event date */
public $close_registration;
/** @var bool Enable waiting room */
public $waiting_room;
/** @var bool undocumented */
public $request_permission_to_unmute_participants;
/** @var string[] List of global dial-in countries */
public $global_dial_in_countries;
/** @var GlobalDialInNumber[] Global Dial-in Countries/Regions */
public $global_dial_in_numbers;
/** @var string Contact name for registration */
public $contact_name;
/** @var string Contact email for registration */
public $contact_email;
/** @var bool Send confirmation email to registrants upon successful registration */
public $registrants_confirmation_email;
/** @var bool Send email notifications to registrants about approval, cancellation, denial of the registration.
* The value of this field must be set to true in order to use the `registrants_confirmation_email` field.
*/
public $registrants_email_notification;
/** @var bool Only authenticated users can join meetings. */
public $meeting_authentication;
/** @var string Meeting authentication option id. */
public $authentication_option;
/** @var string
* @see https://support.zoom.us/hc/en-us/articles/360037117472-Authentication-Profiles-for-Meetings-and-Webinars#h_5c0df2e1-cfd2-469f-bb4a-c77d7c0cca6f
*/
public $authentication_domains;
/** @var string
* @see https://support.zoom.us/hc/en-us/articles/360037117472-Authentication-Profiles-for-Meetings-and-Webinars#h_5c0df2e1-cfd2-469f-bb4a-c77d7c0cca6f
*/
public $authentication_name;
/**
* MeetingSettings constructor.
*/
public function __construct()
{
$this->global_dial_in_countries = [];
$this->global_dial_in_numbers = [];
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('global_dial_in_countries' === $propertyName) {
return 'string';
}
if ('global_dial_in_numbers' === $propertyName) {
return GlobalDialInNumber::class;
}
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,68 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Trait Pagination
* properties for Pagination objects, which are paginated lists of items,
* retrieved in chunks from the server over one or several API calls, one per page.
*/
trait Pagination
{
use JsonDeserializableTrait;
/** @var int The number of pages returned for the request made. */
public $page_count;
/** @var int The page number of the current results, counting from 1 */
public $page_number;
/** @var int The number of records returned with a single API call. Default 30, max 300. */
public $page_size;
/** @var int The total number of all the records available across pages. */
public $total_records;
/**
* Retrieves all items from the server, possibly generating several API calls.
*
* @param string $arrayPropertyName item array property name
* @param string $relativePath relative path to pass to Client::send
* @param array $parameters parameter array to pass to Client::send
*
* @throws Exception
*
* @return array united list of items
*/
protected static function loadItems($arrayPropertyName, $relativePath, $parameters = [])
{
$items = [];
$pageCount = 1;
$pageSize = 300;
$totalRecords = 0;
for ($pageNumber = 1; $pageNumber <= $pageCount; $pageNumber++) {
$response = static::fromJson(
Client::getInstance()->send(
'GET',
$relativePath,
array_merge(['page_size' => $pageSize, 'page_number' => $pageNumber], $parameters)
)
);
$items = array_merge($items, $response->$arrayPropertyName);
if (0 === $totalRecords) {
$pageCount = $response->page_count;
$pageSize = $response->page_size;
$totalRecords = $response->total_records;
}
}
if (count($items) !== $totalRecords) {
error_log('Zoom announced '.$totalRecords.' records but returned '.count($items));
}
return $items;
}
}

@ -0,0 +1,71 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Trait PaginationToken
* properties for PaginationToken objects, which are paginated lists of items,
* retrieved in chunks from the server over one or several API calls, one per page.
*/
trait PaginationToken
{
use JsonDeserializableTrait;
/** @var int The number of pages returned for the request made. */
public $page_count;
/** @var int The number of records returned within a single API call. Default 30, max 300. */
public $page_size;
/** @var int The number of all records available across pages. */
public $total_records;
/** @var string The next page token is used to paginate through large result sets.
* A next page token will be returned whenever the set of available results exceeds the current page size.
* The expiration period for this token is 15 minutes.
*/
public $next_page_token;
/**
* Retrieves all items from the server, possibly generating several API calls.
*
* @param string $arrayPropertyName item array property name
* @param string $relativePath relative path to pass to Client::send
* @param array $parameters parameter array to pass to Client::send
*
* @throws Exception
*
* @return array united list of items
*/
protected static function loadItems($arrayPropertyName, $relativePath, $parameters = [])
{
$items = [];
$pageSize = 300;
$totalRecords = 0;
$nextPageToken = '';
do {
$response = static::fromJson(
Client::getInstance()->send(
'GET',
$relativePath,
array_merge(['page_size' => $pageSize, 'next_page_token' => $nextPageToken], $parameters)
)
);
$items = array_merge($items, $response->$arrayPropertyName);
$nextPageToken = $response->next_page_token;
if (0 === $totalRecords) {
$pageSize = $response->page_size;
$totalRecords = $response->total_records;
}
} while (!empty($nextPagetoken));
if (count($items) !== $totalRecords) {
error_log('Zoom announced '.$totalRecords.' records but returned '.count($items));
}
return $items;
}
}

@ -0,0 +1,57 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class ParticipantList
* List of past meeting instance participants.
*
* @see ParticipantListItem;
*/
class ParticipantList
{
use PaginationToken;
/** @var ParticipantListItem[] */
public $participants;
/**
* ParticipantList constructor.
*/
public function __construct()
{
$this->participants = [];
}
/**
* Retrieves a meeting instance's participants.
*
* @param string $instanceUUID
*
* @throws Exception
*
* @return ParticipantListItem[] participants
*/
public static function loadInstanceParticipants($instanceUUID)
{
return static::loadItems(
'participants',
'past_meetings/'.htmlentities($instanceUUID).'/participants'
);
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('participants' === $propertyName) {
return ParticipantListItem::class;
}
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,22 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
/**
* Class ParticipantListItem. Item in a list of past meeting instance participants.
*
* @see ParticipantList
*/
class ParticipantListItem
{
/** @var string participant UUID */
public $id;
/** @var string display name */
public $name;
/** @var string Email address of the user; will be returned if the user logged into Zoom to join the meeting. */
public $user_email;
}

@ -0,0 +1,74 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class PastMeeting.
* A past meeting, really a past meeting instance, as returned from the server.
*
* Each past meeting instance is identified by its own UUID.
* Many past meeting instances can be part of the same meeting, identified by property 'id'.
* Each instance has its own start time, participants and recording files.
*/
class PastMeeting extends Meeting
{
/** @var string unique meeting instance ID */
public $uuid;
/** @var string meeting number */
public $id;
/** @var string host Zoom user id */
public $host_id;
/** @var string user display name */
public $user_name;
/** @var string */
public $user_email;
/** @var string "yyyy-MM-dd'T'HH:mm:ss'Z'" (GMT) */
public $start_time;
/** @var string "yyyy-MM-dd'T'HH:mm:ss'Z'" (GMT) */
public $end_time;
/** @var int sum of meeting minutes from all participants in the meeting. */
public $total_minutes;
/** @var int number of meeting participants */
public $participants_count;
/** @var string undocumented */
public $dept;
/**
* Retrieves a past meeting instance from its identifier.
*
* @param string $uuid
*
* @throws Exception
*
* @return PastMeeting the past meeting
*/
public static function fromUUID($uuid)
{
return static::fromJson(Client::getInstance()->send('GET', 'past_meetings/'.htmlentities($uuid)));
}
/**
* Retrieves information on participants from a past meeting instance.
*
* @throws Exception
*
* @return ParticipantListItem[] participants
*/
public function getParticipants()
{
return ParticipantList::loadInstanceParticipants($this->uuid);
}
}

@ -0,0 +1,115 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class RecordingFile. A video, audio or text file, part of a past meeting instance recording.
*
* @see RecordingMeeting
*/
class RecordingFile
{
use JsonDeserializableTrait;
/** @var string The recording file ID. Included in the response of general query. */
public $id;
/** @var string The meeting ID. */
public $meeting_id;
/** @var string The recording start time. */
public $recording_start;
/** @var string The recording end time. Response in general query. */
public $recording_end;
/** @var string The recording file type. The value of this field could be one of the following:<br>
* `MP4`: Video file of the recording.<br>
* `M4A` Audio-only file of the recording.<br>
* `TIMELINE`: Timestamp file of the recording.
* To get a timeline file, the "Add a timestamp to the recording" setting must be enabled in the recording settings
* (https://support.zoom.us/hc/en-us/articles/203741855-Cloud-recording#h_3f14c3a4-d16b-4a3c-bbe5-ef7d24500048).
* The time will display in the host's timezone, set on their Zoom profile.
* `TRANSCRIPT`: Transcription file of the recording.
* `CHAT`: A TXT file containing in-meeting chat messages that were sent during the meeting.
* `CC`: File containing closed captions of the recording.
* A recording file object with file type of either `CC` or `TIMELINE` **does not have** the following properties:
* `id`, `status`, `file_size`, `recording_type`, and `play_url`.
*/
public $file_type;
/** @var int The recording file size. */
public $file_size;
/** @var string The URL using which a recording file can be played. */
public $play_url;
/** @var string The URL using which the recording file can be downloaded.
* To access a private or password protected cloud recording, you must use a [Zoom JWT App Type]
* (https://marketplace.zoom.us/docs/guides/getting-started/app-types/create-jwt-app).
* Use the generated JWT token as the value of the `access_token` query parameter
* and include this query parameter at the end of the URL as shown in the example.
* Example: `https://api.zoom.us/recording/download/{{ Download Path }}?access_token={{ JWT Token }}`
*/
public $download_url;
/** @var string The recording status. "completed". */
public $status;
/** @var string The time at which recording was deleted. Returned in the response only for trash query. */
public $deleted_time;
/** @var string The recording type. The value of this field can be one of the following:
* `shared_screen_with_speaker_view(CC)`
* `shared_screen_with_speaker_view`
* `shared_screen_with_gallery_view`
* `speaker_view`
* `gallery_view`
* `shared_screen`
* `audio_only`
* `audio_transcript`
* `chat_file`
* `TIMELINE`
*/
public $recording_type;
/**
* Builds the recording file download URL with the access_token query parameter.
*
* @see RecordingFile::$download_url
*
* @param string $token
*
* @return string full URL
*/
public function getFullDownloadURL($token)
{
return $this->download_url.'?access_token='.$token;
}
/**
* Deletes the file.
*
* @throws Exception
*/
public function delete()
{
Client::getInstance()->send(
'DELETE',
"/meetings/$this->meeting_id/recordings/$this->id",
['action' => 'delete']
);
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,65 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use DateTime;
use Exception;
/**
* Class RecordingList. A list of past meeting instance recordings generated between two dates.
*
* @see RecordingMeeting
*/
class RecordingList
{
use PaginationToken;
/** @var string Start Date */
public $from;
/** @var string End Date */
public $to;
/** @var RecordingMeeting[] List of recordings */
public $meetings;
public function __construct()
{
$this->meetings = [];
}
/**
* Retrieves all recordings from a period of time.
*
* @param DateTime $startDate first day of the period
* @param DateTime $endDate last day of the period
*
* @throws Exception
*
* @return RecordingMeeting[] all recordings from that period
*/
public static function loadPeriodRecordings($startDate, $endDate)
{
return static::loadItems(
'meetings',
'users/me/recordings',
[
'from' => $startDate->format('Y-m-d'),
'to' => $endDate->format('Y-m-d'),
]
);
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('meetings' === $propertyName) {
return RecordingMeeting::class;
}
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,95 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class RecordingMeeting.
* A meeting instance can be recorded, hence creating an instance of this class.
* Contains a list of recording files.
*
* @see PastMeeting
* @see RecordingFile
*/
class RecordingMeeting
{
use JsonDeserializableTrait;
/** @var string Unique Meeting Identifier. Each instance of the meeting will have its own UUID. */
public $uuid;
/** @var string Meeting ID - also known as the meeting number. */
public $id;
/** @var string Unique Identifier of the user account. */
public $account_id;
/** @var string ID of the user set as host of meeting. */
public $host_id;
/** @var string Meeting topic. */
public $topic;
/** @var int undocumented */
public $type;
/** @var string The time at which the meeting started. */
public $start_time;
/** @var string undocumented */
public $timezone;
/** @var int Meeting duration. */
public $duration;
/** @var string Total size of the recording. */
public $total_size;
/** @var string Number of recording files returned in the response of this API call. */
public $recording_count;
/** @var string undocumented */
public $share_url;
/** @var string */
public $password;
/** @var RecordingFile[] List of recording file. */
public $recording_files;
/**
* RecordingMeeting constructor.
*/
public function __construct()
{
$this->recording_files = [];
}
/**
* Deletes the recording on the server.
*
* @throws Exception
*/
public function delete()
{
Client::getInstance()->send(
'DELETE',
'meetings/'.htmlentities($this->uuid).'/recordings',
['action' => 'delete']
);
}
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
if ('recording_files' === $propertyName) {
return RecordingFile::class;
}
throw new Exception("No such array property $propertyName");
}
}

@ -0,0 +1,29 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom\API;
use Exception;
/**
* Class TrackingField. Instances of this class can be listed in a meeting object.
*/
class TrackingField
{
use JsonDeserializableTrait;
/** @var string Tracking fields type */
public $field;
/** @var string Tracking fields value */
public $value;
/**
* {@inheritdoc}
*/
public function itemClass($propertyName)
{
throw new Exception("no such array property $propertyName");
}
}

@ -0,0 +1,204 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CourseBundle\Entity\CGroupInfo;
use Chamilo\PageBundle\Entity\User;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
/**
* Class MeetingRepository.
*/
class MeetingRepository extends EntityRepository
{
/**
* Retrieves information about meetings having a start_time between two dates.
*
* @param DateTime $startDate
* @param DateTime $endDate
*
* @return Meeting[]
*/
public function periodMeetings($startDate, $endDate)
{
$matching = [];
$all = $this->findAll();
foreach ($all as $candidate) {
if ($candidate->startDateTime >= $startDate && $candidate->startDateTime <= $endDate) {
$matching[] = $candidate;
}
}
return $matching;
}
/**
* @return ArrayCollection|Collection|Meeting[]
*/
public function globalMeetings()
{
return $this->matching(
Criteria::create()->where(
Criteria::expr()->andX(
Criteria::expr()->eq('course', null),
Criteria::expr()->eq('user', null)
)
)
);
}
/**
* @return ArrayCollection|Collection|Meeting[]
*/
public function unfinishedGlobalMeetings()
{
return $this->globalMeetings()->filter(
function ($meeting) {
return 'finished' !== $meeting->getMeetingInfoGet()->status;
}
);
}
/**
* Returns either a user's meetings or all user meetings.
*
* @param User|null $user
*
* @return QueryBuilder
*/
public function userMeetings($user = null)
{
$qb = $this->createQueryBuilder('m');
$qb
->select('m')
->leftJoin('m.registrants', 'r');
//$qb->select('m');
/*$criteria = Criteria::create()->where(
Criteria::expr()->andX(
Criteria::expr()->isNull('course'),
Criteria::expr()->orX(
Criteria::expr()->isNull('user'),
Criteria::expr()->eq('user', $user)
)
));*/
/*$qb->where(Criteria::expr()->andX(
Criteria::expr()->isNull('course'),
Criteria::expr()->orX(
Criteria::expr()->isNull('user'),
Criteria::expr()->eq('user', $user)
)
));*/
$qb
->andWhere('m.course IS NULL')
->andWhere('m.user IS NULL OR m.user = :user OR r.user = :user');
$qb->setParameters(['user' => $user]);
return $qb;
/*return $this->matching(
,
Criteria::expr()->andX(
Criteria::expr()->eq('registrants', null),
Criteria::expr()->orX(
Criteria::expr()->eq('user', null),
Criteria::expr()->eq('user', $user)
)
)
)
);*/
/*return $this->matching(
Criteria::create()->where(
Criteria::expr()->andX(
Criteria::expr()->eq('course', null),
Criteria::expr()->orX(
Criteria::expr()->eq('user', null),
Criteria::expr()->eq('user', $user)
)
)
)
);*/
}
/**
* @param User|null $user
*
* @return Meeting[]
*/
public function unfinishedUserMeetings($user = null)
{
/*return $this->userMeetings($user)->filter(
function ($meeting) {
return 'finished' !== $meeting->getMeetingInfoGet()->status;
}
);*/
$results = @$this->userMeetings($user)->getQuery()->getResult();
$list = [];
foreach ($results as $meeting) {
if ('finished' === $meeting->getMeetingInfoGet()->status) {
$list[] = $meeting;
}
}
return $list;
}
/**
* @param DateTime $start
* @param DateTime $end
* @param User $user
*
* @return ArrayCollection|Collection|Meeting[]
*/
public function periodUserMeetings($start, $end, $user = null)
{
/*return $this->userMeetings($user)->filter(
function ($meeting) use ($start, $end) {
return $meeting->startDateTime >= $start && $meeting->startDateTime <= $end;
}
);*/
$results = @$this->userMeetings($user)->getQuery()->getResult();
$list = [];
if ($results) {
foreach ($results as $meeting) {
if ($meeting->startDateTime >= $start && $meeting->startDateTime <= $end) {
$list[] = $meeting;
}
}
}
return $list;
}
/**
* Returns either a course's meetings or all course meetings.
*
* @return ArrayCollection|Collection|Meeting[]
*/
public function courseMeetings(Course $course, CGroupInfo $group = null, Session $session = null)
{
return $this->matching(
Criteria::create()->where(
Criteria::expr()->andX(
Criteria::expr()->eq('group', $group),
Criteria::expr()->eq('course', $course),
Criteria::expr()->eq('session', $session)
)
)
);
}
}

@ -0,0 +1,65 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\ClassificationBundle\Entity\Collection;
use Chamilo\UserBundle\Entity\User;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityRepository;
/**
* Class RecordingRepository.
*/
class RecordingRepository extends EntityRepository
{
public function getPeriodRecordings($startDate, $endDate)
{
$matching = [];
$all = $this->findAll();
foreach ($all as $candidate) {
if ($candidate->startDateTime >= $startDate && $candidate->startDateTime <= $endDate) {
$matching[] = $candidate;
}
}
return $matching;
}
/**
* Returns a user's meeting recordings.
*
* @param User $user
*
* @return ArrayCollection|Collection|Recording[]
*/
/*public function userRecordings($user)
{
return $this->matching(
Criteria::create()->where(
Criteria::expr()->in(
'meeting',
$this->getEntityManager()->getRepository(Meeting::class)->userMeetings($user)->toArray()
)
)
);
}*/
/**
* @param DateTime $start
* @param DateTime $end
* @param User $user
*
* @return ArrayCollection|Recording[]
*/
/*public function getPeriodUserRecordings($start, $end, $user = null)
{
return $this->userRecordings($user)->filter(
function ($meeting) use ($start, $end) {
return $meeting->startDateTime >= $start && $meeting->startDateTime <= $end;
}
);
}*/
}

@ -0,0 +1,33 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Zoom;
use Chamilo\UserBundle\Entity\User;
use DateInterval;
use DateTime;
use Doctrine\ORM\EntityRepository;
/**
* Class RegistrantEntityRepository.
*/
class RegistrantRepository extends EntityRepository
{
/**
* Returns the upcoming meeting registrations for the given user.
*
* @param User $user
*
* @return array|Registrant[]
*/
public function meetingsComingSoonRegistrationsForUser($user)
{
$start = new DateTime();
$end = new DateTime();
$end->add(new DateInterval('P7D'));
$meetings = $this->getEntityManager()->getRepository(Meeting::class)->periodMeetings($start, $end);
return $this->findBy(['meeting' => $meetings, 'user' => $user]);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,94 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Zoom\Meeting;
require_once __DIR__.'/config.php';
$meetingId = isset($_REQUEST['meetingId']) ? (int) $_REQUEST['meetingId'] : 0;
if (empty($meetingId)) {
api_not_allowed(true);
}
$plugin = ZoomPlugin::create();
/** @var Meeting $meeting */
$meeting = $plugin->getMeetingRepository()->findOneBy(['meetingId' => $meetingId]);
if (null === $meeting) {
api_not_allowed(true, $plugin->get_lang('MeetingNotFound'));
}
$course_plugin = 'zoom'; // needed in order to load the plugin lang variables
$returnURL = 'meetings.php';
$urlExtra = '';
if ($meeting->isCourseMeeting()) {
api_protect_course_script(true);
$this_section = SECTION_COURSES;
$urlExtra = api_get_cidreq();
$returnURL = 'start.php?'.$urlExtra;
if (api_is_in_group()) {
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.$urlExtra,
'name' => get_lang('Groups'),
];
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group_space.php?'.$urlExtra,
'name' => get_lang('GroupSpace').' '.$meeting->getGroup()->getName(),
];
}
}
$logInfo = [
'tool' => 'Videoconference Zoom',
];
Event::registerLog($logInfo);
$interbreadcrumb[] = [
'url' => $returnURL,
'name' => $plugin->get_lang('ZoomVideoConferences'),
];
$tpl = new Template($meeting->getMeetingId());
if ($plugin->userIsConferenceManager($meeting)) {
// user can edit, start and delete meeting
$tpl->assign('isConferenceManager', true);
$tpl->assign('editMeetingForm', $plugin->getEditMeetingForm($meeting)->returnForm());
$tpl->assign('deleteMeetingForm', $plugin->getDeleteMeetingForm($meeting, $returnURL)->returnForm());
if (false === $meeting->isGlobalMeeting() && false == $meeting->isCourseMeeting()) {
if ('true' === $plugin->get('enableParticipantRegistration') && $meeting->requiresRegistration()) {
$tpl->assign('registerParticipantForm', $plugin->getRegisterParticipantForm($meeting)->returnForm());
$tpl->assign('registrants', $meeting->getRegistrants());
}
}
if (ZoomPlugin::RECORDING_TYPE_NONE !== $plugin->getRecordingSetting() &&
$meeting->hasCloudAutoRecordingEnabled()
) {
$tpl->assign('fileForm', $plugin->getFileForm($meeting, $returnURL)->returnForm());
$tpl->assign('recordings', $meeting->getRecordings());
}
} elseif ($meeting->requiresRegistration()) {
$userId = api_get_user_id();
try {
foreach ($meeting->getRegistrants() as $registrant) {
if ($registrant->getUser()->getId() == $userId) {
$tpl->assign('currentUserJoinURL', $registrant->getJoinUrl());
break;
}
}
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
}
}
$tpl->assign('actions', $plugin->getToolbar());
$tpl->assign('meeting', $meeting);
$tpl->assign('url_extra', $urlExtra);
$tpl->assign('content', $tpl->fetch('zoom/view/meeting.tpl'));
$tpl->display_one_col_template();

@ -0,0 +1,28 @@
<?php
/* For license terms, see /license.txt */
$course_plugin = 'zoom'; // needed in order to load the plugin lang variables
$cidReset = true;
require_once __DIR__.'/config.php';
if (!ZoomPlugin::currentUserCanJoinGlobalMeeting()) {
api_not_allowed(true);
}
$plugin = ZoomPlugin::create();
$user = api_get_user_entity(api_get_user_id());
$form = $plugin->getAdminSearchForm();
$startDate = new DateTime($form->getElement('start')->getValue());
$endDate = new DateTime($form->getElement('end')->getValue());
$scheduleForm = $plugin->getScheduleMeetingForm($user);
$tpl = new Template();
$tpl->assign('meetings', $plugin->getMeetingRepository()->periodUserMeetings($startDate, $endDate, $user));
$tpl->assign('allow_recording', $plugin->hasRecordingAvailable());
$tpl->assign('actions', $plugin->getToolbar());
$tpl->assign('search_form', $form->returnForm());
$tpl->assign('schedule_form', $scheduleForm->returnForm());
$tpl->assign('content', $tpl->fetch('zoom/view/meetings.tpl'));
$tpl->display_one_col_template();

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

@ -0,0 +1,118 @@
<?php
/* For license terms, see /license.txt */
$course_plugin = 'zoom'; // needed in order to load the plugin lang variables
require_once __DIR__.'/config.php';
api_protect_course_script(true);
$this_section = SECTION_COURSES;
$logInfo = [
'tool' => 'Videoconference Zoom',
];
Event::registerLog($logInfo);
$course = api_get_course_entity();
if (null === $course) {
api_not_allowed(true);
}
$group = api_get_group_entity();
$session = api_get_session_entity();
$plugin = ZoomPlugin::create();
if (null !== $group) {
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.api_get_cidreq(),
'name' => get_lang('Groups'),
];
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'group/group_space.php?'.api_get_cidreq(),
'name' => get_lang('GroupSpace').' '.$group->getName(),
];
}
$url = api_get_self().'?'.api_get_cidreq(true, false).'&gidReq=';
$htmlHeadXtra[] = '<script>
$(function() {
$("#group_select").on("change", function() {
var groupId = $(this).find("option:selected").val();
var url = "'.$url.'";
window.location.replace(url+groupId);
});
});
</script>';
$tool_name = $plugin->get_lang('ZoomVideoConferences');
$tpl = new Template($tool_name);
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';
$isManager = $plugin->userIsCourseConferenceManager();
if ($isManager) {
$groupId = api_get_group_id();
$groups = GroupManager::get_groups();
if (!empty($groups)) {
$form = new FormValidator('group_filter');
$groupList[0] = get_lang('Select');
foreach ($groups as $groupData) {
$itemGroupId = $groupData['iid'];
/*if (isset($meetingsGroup[$itemGroupId]) && $meetingsGroup[$itemGroupId] == 1) {
$groupData['name'] .= ' ('.get_lang('Active').')';
}*/
$groupList[$itemGroupId] = $groupData['name'];
}
$form->addSelect('group_id', get_lang('Groups'), $groupList, ['id' => 'group_select']);
$form->setDefaults(['group_id' => $groupId]);
$formToString = $form->returnForm();
$tpl->assign('group_form', $formToString);
}
switch ($action) {
case 'delete':
$meeting = $plugin->getMeetingRepository()->findOneBy(['meetingId' => $_REQUEST['meetingId']]);
if ($meeting && $meeting->isCourseMeeting()) {
$plugin->deleteMeeting($meeting, api_get_self().'?'.api_get_cidreq());
}
break;
}
$user = api_get_user_entity(api_get_user_id());
$tpl->assign(
'instant_meeting_form',
$plugin->getCreateInstantMeetingForm(
$user,
$course,
$group,
$session
)->returnForm()
);
$tpl->assign(
'schedule_meeting_form',
$plugin->getScheduleMeetingForm(
$user,
$course,
$group,
$session
)->returnForm()
);
}
try {
$tpl->assign(
'meetings',
$plugin->getMeetingRepository()->courseMeetings($course, $group, $session)
);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message('Could not retrieve scheduled meeting list: '.$exception->getMessage(), 'error')
);
}
$tpl->assign('is_manager', $isManager);
$tpl->assign('content', $tpl->fetch('zoom/view/start.tpl'));
$tpl->display_one_col_template();

@ -0,0 +1,8 @@
<?php
/* For license terms, see /license.txt */
if (!api_is_platform_admin()) {
die('You must have admin permissions to uninstall plugins');
}
ZoomPlugin::create()->uninstall();

@ -0,0 +1,57 @@
<h4>
{{ meeting.typeName }} {{ meeting.meetingId }}
</h4>
<a class="btn btn-primary" href="meeting.php?meetingId={{ meeting.meetingId }}&{{ url_extra }}">
{{ 'Edit'|get_lang }}
</a>
<table class="table">
<thead>
<tr>
<th>{{ 'Type'|get_lang }}</th>
<th>{{ 'Action'|get_plugin_lang('ZoomPlugin') }}</th>
{# <th>{{ 'User'|get_lang }}</th>#}
<th>{{ 'Date'|get_lang }}</th>
<th>{{ 'Details'|get_lang }} </th>
</tr>
</thead>
<tbody>
{% for activity in meeting.activities %}
<tr>
<td>
{{ activity.type }}
</td>
<td>
{{ activity.name }}
</td>
<td>
{{ activity.createdAt | api_convert_and_format_date(3)}}
</td>
{# <td>#}
{# {% if _u.is_admin %}#}
{# <a href="{{ _p.web_main }}admin/user_information.php?user_id={{ activity.user.id }}" >#}
{# {{ activity.user.firstname }} {{ activity.user.lastname }} ({{ activity.user.username }})#}
{# </a>#}
{# {% else %}#}
{# {{ activity.user.firstname }} {{ activity.user.lastname }} ({{ activity.user.username }})#}
{# {% endif %}#}
{# </td>#}
<td>
{% if activity.eventDecoded.registrant %}
{{ 'User' | get_lang }} :
{{ activity.eventDecoded.registrant.first_name }} -
{{ activity.eventDecoded.registrant.last_name }} -
{{ activity.eventDecoded.registrant.email }} -
{{ activity.eventDecoded.registrant.status }}
{% endif %}
{% if activity.eventDecoded.participant %}
{{ 'User' | get_lang }} :
{{ activity.eventDecoded.participant.user_name }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>

@ -0,0 +1,116 @@
<h4>
{{ meeting.typeName }} {{ meeting.meetingId }} ({{ meeting.meetingInfoGet.status }})
</h4>
<div class="btn-group" role="group">
{% if meeting.meetingInfoGet.status != 'finished' %}
<a class="btn btn-primary" href="join_meeting.php?meetingId={{ meeting.meetingId }}&{{ url_extra }}">
{{ 'ViewMeeting'|get_plugin_lang('ZoomPlugin') }}
</a>
{% endif %}
{% if isConferenceManager %}
{% if meeting.status == 'waiting' %}
<a class="btn btn-primary" href="{{ meeting.meetingInfoGet.start_url }}" target="_blank">
{{ 'StartMeeting'|get_plugin_lang('ZoomPlugin') }}
</a>
{% endif %}
<a class="btn btn-default" href="activity.php?meetingId={{ meeting.meetingId }}&{{ url_extra }}">
{{ 'Activity'|get_plugin_lang('ZoomPlugin') }}
</a>
{% endif %}
</div>
{% if isConferenceManager %}
<br />
<br />
<div class="panel panel-default conference">
<div class="panel-body">
<div class="share">
{{ 'JoinURLToSendToParticipants'| get_plugin_lang('ZoomPlugin') }}
</div>
<div class="form-inline">
<div class="form-group">
<input id="share_button_flash" type="text"
style="width:460px"
class="form-control" readonly
value="{{ _p.web }}plugin/zoom/join_meeting.php?meetingId={{ meeting.meetingId }}&{{ url_extra }}"
>
<button onclick="copyTextToClipBoard('share_button_flash');" class="btn btn-default">
<span class="fa fa-copy"></span> {{ 'CopyTextToClipboard' | get_lang }}
</button>
</div>
</div>
</div>
</div>
{% endif %}
{% if currentUserJoinURL %}
{#<p>#}
{# <a href="{{ currentUserJoinURL }}" target="_blank">#}
{# {{ 'JoinMeeting'|get_plugin_lang('ZoomPlugin') }}#}
{# </a>#}
{#</p>#}
{% endif %}
{% if isConferenceManager %}
{{ editMeetingForm }}
{{ deleteMeetingForm }}
{{ registerParticipantForm }}
{{ fileForm }}
{# {% if registrants and meeting.meetingInfoGet.settings.approval_type != 2 %}#}
{% if registrants.count > 0 %}
<script>
function copyJoinURL(event, url) {
event.target.textContent = '{{ 'CopyingJoinURL'|get_plugin_lang('ZoomPlugin')|escape }}';
navigator.clipboard.writeText(url).then(
function() {
event.target.textContent = '{{ 'JoinURLCopied'|get_plugin_lang('ZoomPlugin')|escape }}';
}, function() {
event.target.textContent = '{{ 'CouldNotCopyJoinURL'|get_plugin_lang('ZoomPlugin')|escape }}' + ' ' + url;
}
);
}
</script>
<h3>{{ 'Users' | get_lang }}</h3>
<br />
<table class="table">
{% for registrant in registrants %}
<tr>
<td>
{{ registrant.fullName }}
</td>
<td>
{# {% if registrant.joinUrl %}#}
{# <a class="btn btn-primary" onclick="copyJoinURL(event, '{{ registrant.joinUrl }}')">#}
{# {{ 'CopyJoinAsURL'|get_plugin_lang('ZoomPlugin') }}#}
{# </a>#}
{# {% else %}#}
{# <a class="btn btn-primary disabled" >#}
{# {{ 'JoinURLNotAvailable'|get_plugin_lang('ZoomPlugin') }}#}
{# </a>#}
{# {% endif %}#}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% else %}
<h2>{{ meeting.meetingInfoGet.topic }}</h2>
{% if meeting.meetingInfoGet.agenda %}
<blockquote>{{ meeting.meetingInfoGet.agenda| nl2br }}</blockquote>
{% endif %}
{% if meeting.meetingInfoGet.type == 2 or meeting.meetingInfoGet.type == 8 %}
<dl class="meeting_properties">
<dt>{{ 'StartTime'|get_lang }}</dt>
<dd>{{ meeting.formattedStartTime }}</dd>
<dt>{{ 'Duration'|get_lang }}</dt>
<dd>{{ meeting.formattedDuration }}</dd>
</dl>
{% endif %}
{% endif %}

@ -0,0 +1,62 @@
{% import "default/document/recycle.tpl" as macro %}
{{ schedule_form }}
{{ search_form }}
{% if meetings %}
<h4>{{ 'MeetingsFound'|get_plugin_lang('ZoomPlugin') }}: </h4>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>{{ 'Topic'|get_plugin_lang('ZoomPlugin') }}</th>
<th>{{ 'StartTime'|get_lang }}</th>
<th>{{ 'ForEveryone'|get_plugin_lang('ZoomPlugin') }}</th>
{# <th>{{ 'Course'|get_lang }}</th>#}
{# <th>{{ 'Session'|get_lang }}</th>#}
{% if allow_recording %}
<th>{{ 'Recordings'|get_plugin_lang('ZoomPlugin') }}</th>
{% endif %}
<th></th>
</tr>
</thead>
<tbody>
{% for meeting in meetings %}
<tr>
<td>{{ meeting.meetingInfoGet.topic }}</td>
<td>{{ meeting.formattedStartTime }}</td>
<td>{{ meeting.user ? 'No' : 'Yes' }}</td>
{# <td>{{ meeting.course ? meeting.course : '-' }}</td>#}
{# <td>{{ meeting.session ? meeting.session : '-' }}</td>#}
<td>
{% if allow_recording and meeting.recordings.count > 0 %}
{% for recording in meeting.recordings %}
<dl>
<dt>
{{ recording.formattedStartTime }} ({{ recording.formattedDuration }}) {{ 'Password' | get_lang }}: {{ recording.recordingMeeting.password }}
</dt>
<dd>
<ul>
{% for file in recording.recordingMeeting.recording_files %}
<li>
<a href="{{ file.play_url }}" target="_blank">
{{ file.recording_type }}.{{ file.file_type }}
({{ macro.bytesToSize(file.file_size) }})
</a>
</li>
{% endfor %}
</ul>
</dd>
</dl>
{% endfor %}
{% endif %}
</td>
<td>
<a class="btn btn-primary" href="meeting.php?meetingId={{ meeting.meetingId }}">
{{ 'Details'|get_lang }}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}

@ -0,0 +1,56 @@
{% if instant_meeting_form %}
{{ instant_meeting_form }}
{% endif %}
{% if group_form %}
{{ group_form }}
{% endif %}
{% if meetings.count %}
<div class="page-header">
<h2>{{ 'ScheduledMeetings'|get_lang }}</h2>
</div>
<table class="table">
<tr>
<th>{{ 'Topic'|get_plugin_lang('ZoomPlugin') }}</th>
<th>{{ 'Agenda'|get_plugin_lang('ZoomPlugin') }}</th>
<th>{{ 'StartTime'|get_lang }}</th>
<th>{{ 'Duration'|get_lang }}</th>
<th>{{ 'Actions'|get_lang }}</th>
</tr>
{% for meeting in meetings %}
<tr>
<td>
{{ meeting.meetingInfoGet.topic }}
</td>
<td>
{{ meeting.meetingInfoGet.agenda|nl2br }}
</td>
<td>{{ meeting.formattedStartTime }}</td>
<td>{{ meeting.formattedDuration }}</td>
<td>
<a class="btn btn-primary" href="join_meeting.php?meetingId={{ meeting.meetingId }}&{{ _p.web_cid_query }}">
{{ 'Join'|get_plugin_lang('ZoomPlugin') }}
</a>
{% if is_manager %}
<a class="btn btn-default" href="meeting.php?meetingId={{ meeting.meetingId }}&{{ _p.web_cid_query }}">
{{ 'Details'|get_plugin_lang('ZoomPlugin') }}
</a>
<a class="btn btn-danger"
href="start.php?action=delete&meetingId={{ meeting.meetingId }}&{{ _p.web_cid_query }}"
onclick="javascript:if(!confirm('{{ 'AreYouSureToDelete' | get_lang }}')) return false;"
>
{{ 'Delete'|get_lang }}
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if schedule_meeting_form %}
{{ schedule_meeting_form }}
{% endif %}
Loading…
Cancel
Save