mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-09-16 07:18:04 -04:00
Add three missing plugin hook points for sale documents so plugins can
inject buttons into invoice, quote, and work order views alongside the
existing receipt hook. All four pass ['saleId' => $sale_id_num] and
follow the same naming pattern (view:sales_{type}_buttons).
Exempt plugins/*/webhook from CSRF filtering to support server-to-server
provider callbacks. Also convert the CSRF except list from a string to an
array — the previous 'login|migrate' string produced a single unanchored
pattern, making login/anything CSRF-exempt. Separate array entries anchor
each one individually. Plugin webhook handlers are responsible for their
own authentication.
Add WhatsApp Business Cloud API plugin (app/Plugins/WhatsAppPlugin/):
- Free-form messaging page registered as the 'whatsapp' office module
with its own permission, plus per-customer modal and thread view
- "Send via WhatsApp" button on all four sale document types via the new
hooks; renders only when the customer has a phone number
- PDF delivery using the same sales/{type}_email view core uses for email
- Inbound webhook at plugins/whatsapp/webhook authenticated by
X-Hub-Signature-256 HMAC; fails closed on missing/bad signature;
always returns 200 to suppress Meta retries
- Out-of-order status callbacks cannot downgrade sent → delivered → read
- Conversation log table via plugin migration; dropped on uninstall with
version reset so re-install recreates it cleanly
- Access token and app secret encrypted at rest in plugin_config; no
writes to app_config or initial_schema.sql
- utf8mb4_unicode_520_ci collation throughout (MySQL and MariaDB compat)
- Language file stubs for all existing locales; English strings complete
- README covering credentials, install, webhook setup, and uninstall
122 lines
4.9 KiB
PHP
122 lines
4.9 KiB
PHP
<?= view('partial/header') ?>
|
|
<?php
|
|
/**
|
|
* WhatsApp landing page: send a free-form message and browse recent conversations.
|
|
*
|
|
* @var array $conversations
|
|
* @var bool $configured
|
|
* @var string $saved_message
|
|
*/
|
|
?>
|
|
|
|
<script type="text/javascript">
|
|
dialog_support.init("a.modal-dlg");
|
|
</script>
|
|
|
|
<div class="container-fluid">
|
|
|
|
<?php if (! $configured): ?>
|
|
<div class="alert alert-warning"><?= lang('WhatsAppPlugin.not_configured_notice') ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="row">
|
|
<div class="col-sm-6">
|
|
<?= form_open(site_url('whatsapp/send'), ['id' => 'send_whatsapp_form', 'class' => 'form-horizontal']) ?>
|
|
<fieldset>
|
|
<legend><?= lang('WhatsAppPlugin.whatsapp_send') ?></legend>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<label for="phone" class="col-xs-3 control-label"><?= lang('WhatsAppPlugin.phone') ?></label>
|
|
<div class="col-xs-9">
|
|
<input class="form-control input-sm required" type="text" name="phone" id="phone" placeholder="<?= esc(lang('WhatsAppPlugin.phone_placeholder')) ?>">
|
|
<span class="help-block"><?= lang('WhatsAppPlugin.phone_help') ?></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<label for="message" class="col-xs-3 control-label"><?= lang('WhatsAppPlugin.message') ?></label>
|
|
<div class="col-xs-9">
|
|
<textarea class="form-control input-sm required" rows="3" id="message" name="message" placeholder="<?= esc(lang('WhatsAppPlugin.message_placeholder')) ?>"><?= esc($saved_message) ?></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<?= form_submit([
|
|
'name' => 'submit_form',
|
|
'id' => 'submit_form',
|
|
'value' => lang('WhatsAppPlugin.whatsapp_send'),
|
|
'class' => 'btn btn-primary btn-sm pull-right',
|
|
]) ?>
|
|
</fieldset>
|
|
<?= form_close() ?>
|
|
</div>
|
|
|
|
<div class="col-sm-6">
|
|
<legend><?= lang('WhatsAppPlugin.recent_conversations') ?></legend>
|
|
<table class="table table-hover table-condensed">
|
|
<thead>
|
|
<tr>
|
|
<th><?= lang('WhatsAppPlugin.phone') ?></th>
|
|
<th><?= lang('WhatsAppPlugin.messages') ?></th>
|
|
<th><?= lang('WhatsAppPlugin.last_activity') ?></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($conversations)): ?>
|
|
<tr><td colspan="4" class="text-muted"><?= lang('WhatsAppPlugin.no_conversations') ?></td></tr>
|
|
<?php else: ?>
|
|
<?php foreach ($conversations as $conversation): ?>
|
|
<tr>
|
|
<td><?= esc($conversation['phone']) ?></td>
|
|
<td><?= esc($conversation['message_count']) ?></td>
|
|
<td><?= esc($conversation['last_activity']) ?></td>
|
|
<td>
|
|
<button type="button" class="btn btn-xs btn-info view-conversation" data-phone="<?= esc($conversation['phone']) ?>">
|
|
<?= lang('WhatsAppPlugin.view') ?>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
<div id="conversation_container"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?= view('partial/footer') ?>
|
|
|
|
<script type="text/javascript">
|
|
$(document).ready(function() {
|
|
var load_conversation = function(phone) {
|
|
$.get('<?= site_url('whatsapp/conversation') ?>/' + encodeURIComponent(phone), function(html) {
|
|
$('#conversation_container').html(html);
|
|
});
|
|
};
|
|
|
|
$('#send_whatsapp_form').validate({
|
|
submitHandler: function(form) {
|
|
$(form).ajaxSubmit({
|
|
success: function(response) {
|
|
$.notify({
|
|
message: response.message
|
|
}, {
|
|
type: response.success ? 'success' : 'danger'
|
|
});
|
|
|
|
if (response.success) {
|
|
load_conversation($('#phone').val());
|
|
}
|
|
},
|
|
dataType: 'json'
|
|
});
|
|
}
|
|
});
|
|
|
|
$('.view-conversation').on('click', function() {
|
|
load_conversation($(this).data('phone'));
|
|
});
|
|
});
|
|
</script>
|