2026-05-30 17:51:37 -05:00
|
|
|
<?php
|
|
|
|
|
/**
|
2026-06-07 09:25:45 -05:00
|
|
|
* @package MokoSuite
|
|
|
|
|
* @subpackage plg_system_mokosuite
|
2026-05-30 17:51:37 -05:00
|
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
|
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
|
|
|
*
|
|
|
|
|
* FILE INFORMATION
|
|
|
|
|
* DEFGROUP: Joomla.Plugin
|
2026-06-07 09:25:45 -05:00
|
|
|
* INGROUP: MokoSuite
|
2026-06-12 02:55:44 +00:00
|
|
|
* VERSION: 02.34.83
|
2026-05-30 17:51:37 -05:00
|
|
|
* PATH: /src/Field/CopyableTokenField.php
|
|
|
|
|
* BRIEF: Read-only token field with a copy-to-clipboard button
|
|
|
|
|
*/
|
|
|
|
|
|
2026-06-07 09:25:45 -05:00
|
|
|
namespace Moko\Plugin\System\MokoSuite\Field;
|
2026-05-30 17:51:37 -05:00
|
|
|
|
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
|
|
|
|
|
|
use Joomla\CMS\Form\FormField;
|
2026-06-06 17:40:09 -05:00
|
|
|
use Joomla\CMS\Session\Session;
|
2026-05-30 17:51:37 -05:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Renders a read-only text input with a "Copy" button, similar to
|
|
|
|
|
* Joomla's API token field in the user profile.
|
|
|
|
|
*
|
|
|
|
|
* @since 02.25.00
|
|
|
|
|
*/
|
|
|
|
|
class CopyableTokenField extends FormField
|
|
|
|
|
{
|
|
|
|
|
protected $type = 'CopyableToken';
|
|
|
|
|
|
|
|
|
|
protected function getInput()
|
|
|
|
|
{
|
|
|
|
|
$value = htmlspecialchars($this->value ?? '', ENT_QUOTES, 'UTF-8');
|
|
|
|
|
$id = $this->id;
|
|
|
|
|
|
|
|
|
|
if (empty($this->value))
|
|
|
|
|
{
|
|
|
|
|
return '<div class="alert alert-warning mb-0 py-2">Token will be generated automatically on first save.</div>';
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 17:40:09 -05:00
|
|
|
$pin = strtoupper(substr($this->value, 0, 4) . '-' . substr($this->value, 4, 4));
|
|
|
|
|
$token = Session::getFormToken();
|
2026-06-07 09:25:45 -05:00
|
|
|
$ajaxUrl = 'index.php?option=com_mokosuite&task=display.sendHeartbeat&format=json';
|
2026-06-06 09:55:46 -05:00
|
|
|
|
2026-05-30 17:51:37 -05:00
|
|
|
return <<<HTML
|
2026-06-06 09:55:46 -05:00
|
|
|
<div class="input-group mb-2">
|
2026-05-30 17:51:37 -05:00
|
|
|
<input type="text" id="{$id}" name="{$this->name}" value="{$value}"
|
|
|
|
|
class="form-control" readonly="readonly" style="font-family:monospace;font-size:0.85em" />
|
|
|
|
|
<button type="button" class="btn btn-outline-secondary" onclick="
|
|
|
|
|
var inp = document.getElementById('{$id}');
|
|
|
|
|
if (navigator.clipboard) {
|
|
|
|
|
navigator.clipboard.writeText(inp.value).then(function() {
|
|
|
|
|
var btn = inp.nextElementSibling;
|
|
|
|
|
var orig = btn.innerHTML;
|
|
|
|
|
btn.innerHTML = '<span class="icon-check" aria-hidden="true"></span> Copied';
|
|
|
|
|
btn.classList.replace('btn-outline-secondary','btn-success');
|
|
|
|
|
setTimeout(function(){ btn.innerHTML = orig; btn.classList.replace('btn-success','btn-outline-secondary'); }, 2000);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
inp.select(); document.execCommand('copy');
|
|
|
|
|
}
|
|
|
|
|
"><span class="icon-copy" aria-hidden="true"></span> Copy</button>
|
2026-06-07 09:25:45 -05:00
|
|
|
<button type="button" class="btn btn-outline-primary" id="mokosuite-send-heartbeat" onclick="
|
2026-06-06 17:40:09 -05:00
|
|
|
var btn = this;
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
var orig = btn.innerHTML;
|
|
|
|
|
btn.innerHTML = '<span class="icon-spinner icon-spin" aria-hidden="true"></span> Sending...';
|
|
|
|
|
var fd = new FormData();
|
|
|
|
|
fd.append('{$token}', '1');
|
|
|
|
|
fetch('{$ajaxUrl}', {method:'POST', body:fd, headers:{'X-Requested-With':'XMLHttpRequest'}})
|
|
|
|
|
.then(function(r){return r.json()})
|
|
|
|
|
.then(function(d){
|
|
|
|
|
if(d.success){
|
|
|
|
|
btn.innerHTML='<span class="icon-check" aria-hidden="true"></span> Sent';
|
|
|
|
|
btn.classList.replace('btn-outline-primary','btn-success');
|
|
|
|
|
} else {
|
|
|
|
|
btn.innerHTML='<span class="icon-times" aria-hidden="true"></span> Failed';
|
|
|
|
|
btn.classList.replace('btn-outline-primary','btn-danger');
|
|
|
|
|
}
|
|
|
|
|
setTimeout(function(){btn.innerHTML=orig;btn.className='btn btn-outline-primary';btn.disabled=false;},3000);
|
|
|
|
|
})
|
|
|
|
|
.catch(function(){
|
|
|
|
|
btn.innerHTML='<span class="icon-times" aria-hidden="true"></span> Error';
|
|
|
|
|
btn.classList.replace('btn-outline-primary','btn-danger');
|
|
|
|
|
setTimeout(function(){btn.innerHTML=orig;btn.className='btn btn-outline-primary';btn.disabled=false;},3000);
|
|
|
|
|
});
|
|
|
|
|
"><span class="icon-heart" aria-hidden="true"></span> Send Heartbeat</button>
|
2026-05-30 17:51:37 -05:00
|
|
|
</div>
|
2026-06-06 09:55:46 -05:00
|
|
|
<div class="d-flex align-items-center gap-2">
|
|
|
|
|
<span class="badge bg-dark" style="font-family:monospace;font-size:1rem;letter-spacing:0.1em;">MOKO-{$pin}</span>
|
|
|
|
|
<small class="text-muted">Support verification PIN — ask your provider for this code to verify your identity.</small>
|
|
|
|
|
</div>
|
2026-05-30 17:51:37 -05:00
|
|
|
HTML;
|
|
|
|
|
}
|
|
|
|
|
}
|