parent
246612dfaf
commit
0aae15ffd3
@ -0,0 +1,84 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
namespace Chamilo\PluginBundle\Zoom; |
||||
|
||||
use DateTime; |
||||
use Doctrine\ORM\Mapping as ORM; |
||||
|
||||
/** |
||||
* @ORM\Entity() |
||||
* @ORM\Table(name="plugin_zoom_signature") |
||||
*/ |
||||
class Signature |
||||
{ |
||||
/** |
||||
* @var int |
||||
* |
||||
* @ORM\Column(type="integer") |
||||
* @ORM\Id |
||||
* @ORM\GeneratedValue(strategy="AUTO") |
||||
*/ |
||||
private $id; |
||||
/** |
||||
* @var Registrant |
||||
* |
||||
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Zoom\Registrant", inversedBy="signature") |
||||
* @ORM\JoinColumn(name="registrant_id", referencedColumnName="id") |
||||
*/ |
||||
private $registrant; |
||||
/** |
||||
* @var string |
||||
* |
||||
* @ORM\Column(name="signature", type="text") |
||||
*/ |
||||
private $file; |
||||
/** |
||||
* @var DateTime |
||||
* |
||||
* @ORM\Column(name="registered_at", type="datetime") |
||||
*/ |
||||
private $registeredAt; |
||||
|
||||
public function getId(): int |
||||
{ |
||||
return $this->id; |
||||
} |
||||
|
||||
public function getRegistrant(): Registrant |
||||
{ |
||||
return $this->registrant; |
||||
} |
||||
|
||||
public function setRegistrant(Registrant $registrant): Signature |
||||
{ |
||||
$this->registrant = $registrant; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
public function getFile(): string |
||||
{ |
||||
return $this->file; |
||||
} |
||||
|
||||
public function setFile(string $file): Signature |
||||
{ |
||||
$this->file = $file; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
public function getRegisteredAt(): DateTime |
||||
{ |
||||
return $this->registeredAt; |
||||
} |
||||
|
||||
public function setRegisteredAt(DateTime $registeredAt): Signature |
||||
{ |
||||
$this->registeredAt = $registeredAt; |
||||
|
||||
return $this; |
||||
} |
||||
} |
@ -0,0 +1,181 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\PluginBundle\Zoom\Meeting; |
||||
use Chamilo\PluginBundle\Zoom\Registrant; |
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest; |
||||
|
||||
require_once __DIR__.'/config.php'; |
||||
|
||||
api_block_anonymous_users(); |
||||
|
||||
$httpRequest = HttpRequest::createFromGlobals(); |
||||
|
||||
$meetingId = $httpRequest->get('meetingId', 0); |
||||
|
||||
if (empty($meetingId)) { |
||||
api_not_allowed(true); |
||||
} |
||||
|
||||
$plugin = ZoomPlugin::create(); |
||||
$em = Database::getManager(); |
||||
/** @var Meeting $meeting */ |
||||
$meeting = $plugin->getMeetingRepository()->findOneBy(['meetingId' => $meetingId]); |
||||
$registrantsRepo = $em->getRepository(Registrant::class); |
||||
|
||||
if (null === $meeting) { |
||||
api_not_allowed( |
||||
true, |
||||
Display::return_message($plugin->get_lang('MeetingNotFound'), 'error') |
||||
); |
||||
} |
||||
|
||||
if (!$plugin->userIsConferenceManager($meeting) |
||||
|| !$meeting->isSignAttendance() |
||||
) { |
||||
api_not_allowed( |
||||
true, |
||||
Display::return_message(get_lang('NotAvailable'), 'warning') |
||||
); |
||||
} |
||||
|
||||
$getNumberOfSignatures = function () use ($meeting) { |
||||
return $meeting->getRegistrants()->count(); |
||||
}; |
||||
|
||||
$getSignaturesData = function ( |
||||
$from, |
||||
$limit, |
||||
$column, |
||||
$direction |
||||
) use ($registrantsRepo, $meeting) { |
||||
if (0 === $column) { |
||||
$columnField = 'u.lastname'; |
||||
} elseif (1 === $column) { |
||||
$columnField = 'u.firstname'; |
||||
} else { |
||||
$columnField = 's.registeredAt'; |
||||
} |
||||
|
||||
$result = $registrantsRepo->findByMeetingPaginated($meeting, $from, $limit, $columnField, $direction); |
||||
|
||||
return array_map( |
||||
function (Registrant $registrant) { |
||||
$signature = $registrant->getSignature(); |
||||
|
||||
return [ |
||||
$registrant->getUser()->getLastname(), |
||||
$registrant->getUser()->getFirstname(), |
||||
$signature ? $signature->getRegisteredAt() : null, |
||||
$signature ? $signature->getFile() : null, |
||||
]; |
||||
}, |
||||
$result |
||||
); |
||||
}; |
||||
|
||||
if ($httpRequest->query->has('export')) { |
||||
$plugin->exportSignatures( |
||||
$meeting, |
||||
$httpRequest->query->getAlnum('export') |
||||
); |
||||
} |
||||
|
||||
$table = new SortableTable('zoom_signatures', $getNumberOfSignatures, $getSignaturesData, 2); |
||||
$table->set_header(0, get_lang('LastName')); |
||||
$table->set_header(1, get_lang('FirstName')); |
||||
$table->set_header(2, get_lang('DateTime'), true, ['class' => 'text-center'], ['class' => 'text-center']); |
||||
$table->set_header(3, $plugin->get_lang('Signature'), false, ['style' => 'width: 200px', 'class' => 'text-center']); |
||||
$table->set_additional_parameters( |
||||
array_filter( |
||||
$httpRequest->query->all(), |
||||
function ($key): bool { |
||||
return strpos($key, 'zoom_signatures_') === false; |
||||
}, |
||||
ARRAY_FILTER_USE_KEY |
||||
) |
||||
); |
||||
$table->set_column_filter( |
||||
2, |
||||
function ($dateTime) { |
||||
return $dateTime ? api_convert_and_format_date($dateTime, DATE_TIME_FORMAT_LONG) : null; |
||||
} |
||||
); |
||||
$table->set_column_filter( |
||||
3, |
||||
function ($imgData) use ($plugin) { |
||||
if (empty($imgData)) { |
||||
return null; |
||||
} |
||||
|
||||
return Display::img( |
||||
$imgData, |
||||
$plugin->get_lang('SignatureDone'), |
||||
['class' => 'img-thumbnail'], |
||||
false |
||||
); |
||||
} |
||||
); |
||||
|
||||
$cidReq = api_get_cidreq(); |
||||
$queryParams = 'meetingId='.$meeting->getMeetingId().'&'.$cidReq; |
||||
$returnURL = 'meetings.php'; |
||||
|
||||
if ($meeting->isCourseMeeting()) { |
||||
api_protect_course_script(true); |
||||
|
||||
$this_section = SECTION_COURSES; |
||||
|
||||
$returnURL = 'start.php?'.$cidReq; |
||||
|
||||
if (api_is_in_group()) { |
||||
$interbreadcrumb[] = [ |
||||
'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.$cidReq, |
||||
'name' => get_lang('Groups'), |
||||
]; |
||||
$interbreadcrumb[] = [ |
||||
'url' => api_get_path(WEB_CODE_PATH).'group/group_space.php?'.$cidReq, |
||||
'name' => get_lang('GroupSpace').' '.$meeting->getGroup()->getName(), |
||||
]; |
||||
} |
||||
} |
||||
|
||||
$interbreadcrumb[] = [ |
||||
'url' => $returnURL, |
||||
'name' => $plugin->get_lang('ZoomVideoConferences'), |
||||
]; |
||||
$interbreadcrumb[] = [ |
||||
'url' => 'meeting.php?'.$queryParams, |
||||
'name' => $meeting->getMeetingInfoGet()->topic, |
||||
]; |
||||
|
||||
$exportPdfLink = Display::url( |
||||
Display::return_icon('pdf.png', get_lang('ExportToPDF'), [], ICON_SIZE_MEDIUM), |
||||
api_get_self().'?'.$queryParams.'&export=pdf' |
||||
); |
||||
$exportXlsLink = Display::url( |
||||
Display::return_icon('excel.png', get_lang('ExportAsXLS'), [], ICON_SIZE_MEDIUM), |
||||
api_get_self().'?'.$queryParams.'&export=xls' |
||||
); |
||||
|
||||
$pageTitle = $plugin->get_lang('Attendance'); |
||||
|
||||
$content = ' |
||||
<dl> |
||||
<dt>'.$plugin->get_lang('ReasonToSign').'</dt> |
||||
<dd>'.$meeting->getReasonToSignAttendance().'</dd> |
||||
</dl> |
||||
'.$table->return_table(); |
||||
|
||||
$tpl = new Template($pageTitle); |
||||
$tpl->assign( |
||||
'actions', |
||||
Display::toolbarAction( |
||||
'attendance-actions', |
||||
[$exportPdfLink.PHP_EOL.$exportXlsLink] |
||||
) |
||||
); |
||||
$tpl->assign('header', $pageTitle); |
||||
$tpl->assign('content', $content); |
||||
$tpl->display_one_col_template(); |
@ -0,0 +1,50 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\PluginBundle\Zoom\Meeting; |
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest; |
||||
|
||||
require_once __DIR__.'/config.php'; |
||||
|
||||
api_block_anonymous_users(false); |
||||
|
||||
$httpRequest = HttpRequest::createFromGlobals(); |
||||
|
||||
$meetingId = $httpRequest->get('meetingId', 0); |
||||
|
||||
if (empty($meetingId)) { |
||||
api_not_allowed(); |
||||
} |
||||
|
||||
$plugin = ZoomPlugin::create(); |
||||
/** @var Meeting $meeting */ |
||||
$meeting = $plugin->getMeetingRepository()->findOneBy(['meetingId' => $meetingId]); |
||||
$currentUserId = api_get_user_id(); |
||||
$currentUser = api_get_user_entity($currentUserId); |
||||
|
||||
if (null === $meeting) { |
||||
api_not_allowed(false, $plugin->get_lang('MeetingNotFound')); |
||||
} |
||||
|
||||
switch ($httpRequest->get('a')) { |
||||
case 'sign_attempt': |
||||
if (!$meeting->isSignAttendance() || |
||||
!$meeting->hasRegisteredUser($currentUser) |
||||
) { |
||||
api_not_allowed(); |
||||
} |
||||
|
||||
$registrant = $meeting->getRegistrant($currentUser); |
||||
|
||||
$file = $httpRequest->request->get('file', ''); |
||||
|
||||
$secToken = Security::get_token('zoom_signature'); |
||||
|
||||
if (!Security::check_token($secToken, null, 'zoom_signature')) { |
||||
api_not_allowed(); |
||||
} |
||||
|
||||
echo (int) $plugin->saveSignature($registrant, $file); |
||||
exit; |
||||
} |
@ -0,0 +1,192 @@ |
||||
{{ meeting.introduction }} |
||||
|
||||
{% if is_conference_manager and meeting.isSignAttendance %} |
||||
<p class="text-info"> |
||||
<span class="fa fa-list-alt"></span> |
||||
{{ 'ConferenceWithAttendance'|get_plugin_lang('ZoomPlugin') }} |
||||
</p> |
||||
{% endif %} |
||||
|
||||
<hr> |
||||
|
||||
{% set btn_start = '' %} |
||||
|
||||
{% if start_url %} |
||||
{% set btn_start %} |
||||
<a href="{{ start_url }}" class="btn btn-primary"> |
||||
{{ 'EnterMeeting'|get_plugin_lang('ZoomPlugin') }} |
||||
</a> |
||||
{% endset %} |
||||
{% endif %} |
||||
|
||||
{% if not is_conference_manager %} |
||||
{% if meeting.isSignAttendance %} |
||||
<div class="row"> |
||||
<div class="col-md-offset-3 col-md-6"> |
||||
<div class="panel panel-info"> |
||||
<div class="panel-heading"> |
||||
<h3 class="panel-title"> |
||||
<span class="fa fa-pencil-square-o fa-fw" aria-hidden="true"></span> |
||||
{{ 'Attendance'|get_lang }} |
||||
</h3> |
||||
</div> |
||||
<div class="panel-body"> |
||||
<p>{{ meeting.reasonToSignAttendance }}</p> |
||||
|
||||
{% if signature %} |
||||
<div class="thumbnail"> |
||||
<img src="{{ signature.file }}" |
||||
alt="{{ 'SignatureDone'|get_plugin_lang('ZoomPlugin') }}"> |
||||
<div class="caption text-center"> |
||||
{{ signature.registeredAt|api_convert_and_format_date(constant('DATE_TIME_FORMAT_LONG')) }} |
||||
</div> |
||||
</div> |
||||
{% else %} |
||||
{% set btn_start = '' %} |
||||
|
||||
{% if 'started' == meeting.meetingInfoGet.status %} |
||||
<button class="btn btn-info" id="btn-sign" data-toggle="modal" |
||||
data-target="#signature-modal"> |
||||
<i class="fa fa-pencil fa-fw" aria-hidden="true"></i> |
||||
{{ 'Sign'|get_plugin_lang('ZoomPlugin') }} |
||||
</button> |
||||
|
||||
<div class="modal fade" tabindex="-1" role="dialog" id="signature-modal" |
||||
data-backdrop="static"> |
||||
<div class="modal-dialog" role="document"> |
||||
<div class="modal-content"> |
||||
<div class="modal-header"> |
||||
<button type="button" class="close" data-dismiss="modal" |
||||
aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
<h4 class="modal-title">{{ 'SignAttendance'|get_plugin_lang('ZoomPlugin') }}</h4> |
||||
</div> |
||||
<div class="modal-body"> |
||||
<div id="signature-modal--signature-area" class="well"> |
||||
<canvas></canvas> |
||||
</div> |
||||
</div> |
||||
<div class="modal-footer"> |
||||
<span id="signature-modal--loader" aria-hidden="true" |
||||
class="fa fa-refresh fa-spin" |
||||
aria-label="{{ 'Loading'|get_lang }}" style="display: none;"> |
||||
</span> |
||||
<span id="signature-modal--save-controls"> |
||||
<button id="signature-modal--btn-save" class="btn btn-primary"> |
||||
<em class="fa fa-save" aria-hidden="true"></em> |
||||
{{ 'Save'|get_lang }} |
||||
</button> |
||||
<button id="signature-modal--btn-clean" class="btn btn-default"> |
||||
<em class="fa fa-eraser" aria-hidden="true"></em> |
||||
{{ 'Clean'|get_lang }} |
||||
</button> |
||||
</span> |
||||
<div id="signature-modal--close-controls" style="display: none;"> |
||||
<span id="signature-modal--results"></span> |
||||
<button class="btn btn-default" |
||||
data-dismiss="modal">{{ 'Close'|get_lang }}</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<script> |
||||
$(function () { |
||||
var $signatureArea = $('#signature-modal--signature-area') |
||||
var $loader = $('#signature-modal--loader') |
||||
var $saveControls = $('#signature-modal--save-controls') |
||||
var $btnSave = $('#signature-modal--btn-save') |
||||
var $btnClean = $('#signature-modal--btn-clean') |
||||
var $closeControls = $('#signature-modal--close-controls') |
||||
var $txtResults = $('#signature-modal--results') |
||||
|
||||
var imageFormat = 'image/png' |
||||
var canvas = document.querySelector('#signature-modal--signature-area canvas') |
||||
var signaturePad = new SignaturePad(canvas) |
||||
|
||||
$('#signature-modal') |
||||
.on('shown.bs.modal', function (e) { |
||||
var parentWidth = $signatureArea.width() |
||||
var parentHeight = $signatureArea.height() |
||||
|
||||
canvas.setAttribute('width', parentWidth + 'px') |
||||
canvas.setAttribute('height', parentHeight + 'px') |
||||
|
||||
signaturePad = new SignaturePad(canvas) |
||||
}) |
||||
.on('hide.bs.modal', function (e) { |
||||
$loader.hide() |
||||
$saveControls.show() |
||||
$closeControls.hide() |
||||
$signatureArea.show() |
||||
$btnSave.prop('disabled', false) |
||||
$btnClean.prop('disabled', false) |
||||
}) |
||||
|
||||
$btnClean.on('click', function () { |
||||
signaturePad.clear() |
||||
}) |
||||
|
||||
$btnSave.on('click', function () { |
||||
if (signaturePad.isEmpty()) { |
||||
alert('{{ 'ProvideASignatureFirst'|get_plugin_lang('ZoomPlugin')|e('js') }}') |
||||
|
||||
return false |
||||
} |
||||
|
||||
var dataURL = signaturePad.toDataURL(imageFormat) |
||||
|
||||
$.ajax({ |
||||
beforeSend: function () { |
||||
$loader.show() |
||||
$btnSave.prop('disabled', true) |
||||
$btnClean.prop('disabled', true) |
||||
}, |
||||
type: 'POST', |
||||
url: 'meeting.ajax.php?{{ _p.web_cid_query }}', |
||||
data: { |
||||
a: 'sign_attempt', |
||||
meetingId: {{ meeting.meetingId }}, |
||||
file: dataURL |
||||
}, |
||||
success: function (data) { |
||||
$btnSave.prop('disabled', false) |
||||
$btnClean.prop('disabled', false) |
||||
$loader.hide() |
||||
$saveControls.hide() |
||||
$signatureArea.hide() |
||||
|
||||
signaturePad.clear() |
||||
|
||||
if ('1' === data) { |
||||
$txtResults.html('{{ 'Saved'|get_lang }}') |
||||
|
||||
window.location.reload() |
||||
} else { |
||||
$txtResults.html('{{ 'Error'|get_lang }}') |
||||
} |
||||
|
||||
$closeControls.show() |
||||
}, |
||||
}) |
||||
}) |
||||
}) |
||||
</script> |
||||
{% endif %} |
||||
{% endif %} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
{% endif %} |
||||
|
||||
{{ btn_start }} |
||||
|
||||
{% if details_url %} |
||||
<a href="{{ details_url }}" class="btn btn-default"> |
||||
{{ 'Details'|get_lang }} |
||||
</a> |
||||
{% endif %} |
Loading…
Reference in new issue