From a97e5796351c59cfd4b44f6a248774add1891bcb Mon Sep 17 00:00:00 2001 From: Joshua Fernandes <14798955+joshua1234511@users.noreply.github.com> Date: Mon, 7 Sep 2026 14:02:37 +0530 Subject: [PATCH] feat(plugins): add sale document view hooks, webhook CSRF exemption, and WhatsApp plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../3.5.0_PluginConfigTableCreate.sql | 2 +- .../3.5.1_PluginMigrationsTableCreate.sql | 2 +- app/Plugins/README.md | 2 +- app/Plugins/WhatsAppPlugin/Config/Routes.php | 25 ++ .../Controllers/IconController.php | 28 ++ .../Controllers/WebhookController.php | 191 ++++++++++ .../Controllers/WhatsAppController.php | 183 +++++++++ app/Plugins/WhatsAppPlugin/LICENSE | 27 ++ .../WhatsAppPlugin/Language/ar-EG/Module.php | 7 + .../Language/ar-EG/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/ar-LB/Module.php | 7 + .../Language/ar-LB/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/az/Module.php | 7 + .../Language/az/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/bg/Module.php | 7 + .../Language/bg/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/bs/Module.php | 7 + .../Language/bs/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/ckb/Module.php | 7 + .../Language/ckb/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/cs/Module.php | 7 + .../Language/cs/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/da/Module.php | 7 + .../Language/da/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/de-CH/Module.php | 7 + .../Language/de-CH/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/de-DE/Module.php | 7 + .../Language/de-DE/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/el/Module.php | 7 + .../Language/el/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/en-GB/Module.php | 7 + .../Language/en-GB/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/en/Module.php | 7 + .../Language/en/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/es-ES/Module.php | 7 + .../Language/es-ES/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/es-MX/Module.php | 7 + .../Language/es-MX/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/fa/Module.php | 7 + .../Language/fa/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/fr/Module.php | 7 + .../Language/fr/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/he/Module.php | 7 + .../Language/he/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/hr-HR/Module.php | 7 + .../Language/hr-HR/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/hu/Module.php | 7 + .../Language/hu/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/hy/Module.php | 7 + .../Language/hy/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/id/Module.php | 7 + .../Language/id/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/it/Module.php | 7 + .../Language/it/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/ka/Module.php | 7 + .../Language/ka/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/km/Module.php | 7 + .../Language/km/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/lo/Module.php | 7 + .../Language/lo/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/ml/Module.php | 7 + .../Language/ml/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/nb/Module.php | 7 + .../Language/nb/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/nl-BE/Module.php | 7 + .../Language/nl-BE/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/nl-NL/Module.php | 7 + .../Language/nl-NL/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/pl/Module.php | 7 + .../Language/pl/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/pt-BR/Module.php | 7 + .../Language/pt-BR/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/ro/Module.php | 7 + .../Language/ro/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/ru/Module.php | 7 + .../Language/ru/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/sv/Module.php | 7 + .../Language/sv/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/sw-KE/Module.php | 7 + .../Language/sw-KE/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/sw-TZ/Module.php | 7 + .../Language/sw-TZ/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/ta/Module.php | 7 + .../Language/ta/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/th/Module.php | 7 + .../Language/th/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/tl/Module.php | 7 + .../Language/tl/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/tr/Module.php | 7 + .../Language/tr/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/uk/Module.php | 7 + .../Language/uk/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/ur/Module.php | 7 + .../Language/ur/WhatsAppPlugin.php | 68 ++++ .../WhatsAppPlugin/Language/vi/Module.php | 7 + .../Language/vi/WhatsAppPlugin.php | 68 ++++ .../Language/zh-Hans/Module.php | 7 + .../Language/zh-Hans/WhatsAppPlugin.php | 68 ++++ .../Language/zh-Hant/Module.php | 7 + .../Language/zh-Hant/WhatsAppPlugin.php | 68 ++++ .../Libraries/OutboundMessage.php | 49 +++ .../WhatsAppPlugin/Libraries/SaleDocument.php | 352 ++++++++++++++++++ .../Libraries/WhatsAppConnector.php | 287 ++++++++++++++ ...0728120000_CreateWhatsAppMessagesTable.php | 56 +++ .../WhatsAppPlugin/Models/WhatsAppMessage.php | 132 +++++++ app/Plugins/WhatsAppPlugin/README.md | 52 +++ app/Plugins/WhatsAppPlugin/Views/config.php | 231 ++++++++++++ .../WhatsAppPlugin/Views/conversation.php | 34 ++ .../WhatsAppPlugin/Views/form_whatsapp.php | 94 +++++ .../WhatsAppPlugin/Views/module_icon.php | 7 + .../Views/sale_document_button.php | 39 ++ app/Plugins/WhatsAppPlugin/Views/whatsapp.php | 121 ++++++ app/Plugins/WhatsAppPlugin/WhatsAppPlugin.php | 334 +++++++++++++++++ app/Plugins/WhatsAppPlugin/whatsapp.svg | 5 + 114 files changed, 5700 insertions(+), 3 deletions(-) create mode 100644 app/Plugins/WhatsAppPlugin/Config/Routes.php create mode 100644 app/Plugins/WhatsAppPlugin/Controllers/IconController.php create mode 100644 app/Plugins/WhatsAppPlugin/Controllers/WebhookController.php create mode 100644 app/Plugins/WhatsAppPlugin/Controllers/WhatsAppController.php create mode 100644 app/Plugins/WhatsAppPlugin/LICENSE create mode 100644 app/Plugins/WhatsAppPlugin/Language/ar-EG/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ar-EG/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ar-LB/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ar-LB/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/az/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/az/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/bg/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/bg/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/bs/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/bs/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ckb/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ckb/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/cs/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/cs/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/da/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/da/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/de-CH/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/de-CH/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/de-DE/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/de-DE/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/el/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/el/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/en-GB/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/en-GB/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/en/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/en/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/es-ES/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/es-ES/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/es-MX/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/es-MX/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/fa/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/fa/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/fr/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/fr/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/he/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/he/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/hr-HR/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/hr-HR/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/hu/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/hu/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/hy/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/hy/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/id/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/id/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/it/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/it/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ka/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ka/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/km/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/km/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/lo/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/lo/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ml/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ml/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/nb/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/nb/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/nl-BE/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/nl-BE/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/nl-NL/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/nl-NL/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/pl/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/pl/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/pt-BR/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/pt-BR/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ro/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ro/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ru/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ru/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/sv/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/sv/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/sw-KE/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/sw-KE/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/sw-TZ/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/sw-TZ/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ta/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ta/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/th/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/th/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/tl/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/tl/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/tr/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/tr/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/uk/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/uk/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ur/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/ur/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/vi/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/vi/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/zh-Hans/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/zh-Hans/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/zh-Hant/Module.php create mode 100644 app/Plugins/WhatsAppPlugin/Language/zh-Hant/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/Libraries/OutboundMessage.php create mode 100644 app/Plugins/WhatsAppPlugin/Libraries/SaleDocument.php create mode 100644 app/Plugins/WhatsAppPlugin/Libraries/WhatsAppConnector.php create mode 100644 app/Plugins/WhatsAppPlugin/Migrations/20260728120000_CreateWhatsAppMessagesTable.php create mode 100644 app/Plugins/WhatsAppPlugin/Models/WhatsAppMessage.php create mode 100644 app/Plugins/WhatsAppPlugin/README.md create mode 100644 app/Plugins/WhatsAppPlugin/Views/config.php create mode 100644 app/Plugins/WhatsAppPlugin/Views/conversation.php create mode 100644 app/Plugins/WhatsAppPlugin/Views/form_whatsapp.php create mode 100644 app/Plugins/WhatsAppPlugin/Views/module_icon.php create mode 100644 app/Plugins/WhatsAppPlugin/Views/sale_document_button.php create mode 100644 app/Plugins/WhatsAppPlugin/Views/whatsapp.php create mode 100644 app/Plugins/WhatsAppPlugin/WhatsAppPlugin.php create mode 100644 app/Plugins/WhatsAppPlugin/whatsapp.svg diff --git a/app/Database/Migrations/sqlscripts/3.5.0_PluginConfigTableCreate.sql b/app/Database/Migrations/sqlscripts/3.5.0_PluginConfigTableCreate.sql index f65cd82f2..97c500a29 100644 --- a/app/Database/Migrations/sqlscripts/3.5.0_PluginConfigTableCreate.sql +++ b/app/Database/Migrations/sqlscripts/3.5.0_PluginConfigTableCreate.sql @@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS `ospos_plugin_config` ( PRIMARY KEY (`id`), UNIQUE KEY `uq_plugin_key` (`plugin_id`, `key`), KEY `idx_plugin_id` (`plugin_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; INSERT IGNORE INTO `ospos_modules` (`name_lang_key`, `desc_lang_key`, `sort`, `module_id`) VALUES ('module_plugins', 'module_plugins_desc', 111, 'plugins'); diff --git a/app/Database/Migrations/sqlscripts/3.5.1_PluginMigrationsTableCreate.sql b/app/Database/Migrations/sqlscripts/3.5.1_PluginMigrationsTableCreate.sql index 20e1d19f4..99e6ea61d 100644 --- a/app/Database/Migrations/sqlscripts/3.5.1_PluginMigrationsTableCreate.sql +++ b/app/Database/Migrations/sqlscripts/3.5.1_PluginMigrationsTableCreate.sql @@ -3,4 +3,4 @@ CREATE TABLE IF NOT EXISTS `ospos_plugin_migrations` ( `version` bigint(20) UNSIGNED NOT NULL DEFAULT 0, `ran_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`plugin_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; diff --git a/app/Plugins/README.md b/app/Plugins/README.md index ea9b4a990..083c17566 100644 --- a/app/Plugins/README.md +++ b/app/Plugins/README.md @@ -731,7 +731,7 @@ CREATE TABLE IF NOT EXISTS `ospos_plugin_config` ( `created_at` timestamp NOT NULL DEFAULT current_timestamp(), `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`key`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; ``` For custom tables, plugins can create them during `install()` and drop them during `uninstall()`. diff --git a/app/Plugins/WhatsAppPlugin/Config/Routes.php b/app/Plugins/WhatsAppPlugin/Config/Routes.php new file mode 100644 index 000000000..b32ce2ce8 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Config/Routes.php @@ -0,0 +1,25 @@ +get('whatsapp', '\App\Plugins\WhatsAppPlugin\Controllers\WhatsAppController::getIndex'); +$routes->get('whatsapp/view/(:num)', '\App\Plugins\WhatsAppPlugin\Controllers\WhatsAppController::getView/$1'); +$routes->get('whatsapp/conversation/(:segment)', '\App\Plugins\WhatsAppPlugin\Controllers\WhatsAppController::getConversation/$1'); +$routes->post('whatsapp/send', '\App\Plugins\WhatsAppPlugin\Controllers\WhatsAppController::postSend'); +// POST, unlike core's sales/sendPdf: this delivers a document to a customer, so +// it stays behind the CSRF filter. +$routes->post('whatsapp/sendDocument/(:num)/(:segment)', '\App\Plugins\WhatsAppPlugin\Controllers\WhatsAppController::postSendDocument/$1/$2'); +$routes->post('whatsapp/sendForm/(:num)', '\App\Plugins\WhatsAppPlugin\Controllers\WhatsAppController::postSendForm/$1'); + +$routes->get('plugins/whatsapp/icon', '\App\Plugins\WhatsAppPlugin\Controllers\IconController::getIcon'); + +// Public Meta callback, CSRF-exempt via the 'plugins/*/webhook' pattern in +// app/Config/Filters.php — which is why this route keeps the 'plugins/' prefix. +$routes->get('plugins/whatsapp/webhook', '\App\Plugins\WhatsAppPlugin\Controllers\WebhookController::index'); +$routes->post('plugins/whatsapp/webhook', '\App\Plugins\WhatsAppPlugin\Controllers\WebhookController::index'); diff --git a/app/Plugins/WhatsAppPlugin/Controllers/IconController.php b/app/Plugins/WhatsAppPlugin/Controllers/IconController.php new file mode 100644 index 000000000..0d568c933 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Controllers/IconController.php @@ -0,0 +1,28 @@ +response->setStatusCode(404)->setBody(''); + } + + return $this->response + ->setHeader('Content-Type', 'image/svg+xml') + ->setBody((string) file_get_contents($path)); + } +} diff --git a/app/Plugins/WhatsAppPlugin/Controllers/WebhookController.php b/app/Plugins/WhatsAppPlugin/Controllers/WebhookController.php new file mode 100644 index 000000000..a2fb05d24 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Controllers/WebhookController.php @@ -0,0 +1,191 @@ +getPlugin('whatsapp'); + + // Routes exist for disabled plugins too — do not accept deliveries then. + if (! $plugin instanceof WhatsAppPlugin || ! $plugin->isEnabled()) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->plugin = $plugin; + } + + public function index(): ResponseInterface + { + if ($this->request->getMethod() === 'GET') { + return $this->verify(); + } + + return $this->receive(); + } + + /** + * Verification handshake: echoes hub.challenge when the verify token matches. + */ + private function verify(): ResponseInterface + { + $mode = $this->request->getGet('hub_mode'); + $token = $this->request->getGet('hub_verify_token'); + $challenge = $this->request->getGet('hub_challenge'); + + $expected = (string) ($this->plugin->getSettings()['verify_token'] ?? ''); + + if ($mode === 'subscribe' && $expected !== '' && hash_equals($expected, (string) $token)) { + // text/plain so the dev debug toolbar does not inject HTML into the + // challenge echo, which Meta compares byte-for-byte. + return $this->response->setStatusCode(200)->setContentType('text/plain')->setBody((string) $challenge); + } + + return $this->response->setStatusCode(403)->setContentType('text/plain')->setBody('Forbidden'); + } + + /** + * Receives inbound messages and status updates and stores them. + * + * Always returns 200 so Meta does not retry on our own processing errors. + */ + private function receive(): ResponseInterface + { + $raw = $this->request->getBody() ?? ''; + $appSecret = $this->plugin->secret('app_secret'); + + // Without an app secret the sender cannot be authenticated, so refuse to + // persist anything rather than fail open to spoofed payloads. + if ($appSecret === '') { + $this->log('warning', 'App secret not configured; rejecting inbound payload.'); + + return $this->acknowledge(); + } + + if (! $this->hasValidSignature($raw, $appSecret)) { + $this->log('warning', 'Invalid signature, ignoring payload.'); + + return $this->acknowledge(); + } + + try { + $payload = json_decode($raw, true); + $this->process(is_array($payload) ? $payload : []); + } catch (Throwable $e) { + $this->log('error', 'Webhook processing error: ' . $e->getMessage()); + } + + return $this->acknowledge(); + } + + /** + * Walks the webhook payload storing inbound messages and status updates. + */ + private function process(array $payload): void + { + $messageModel = model(WhatsAppMessage::class); + $connector = $this->plugin->connector(); + + foreach ($payload['entry'] ?? [] as $entry) { + foreach ($entry['changes'] ?? [] as $change) { + $value = $change['value'] ?? []; + + foreach ($value['messages'] ?? [] as $message) { + $phone = $connector->normalizePhone((string) ($message['from'] ?? '')); + + if ($phone === '') { + continue; + } + + $waMessageId = isset($message['id']) ? (string) $message['id'] : null; + + if ($waMessageId !== null && $messageModel->existsByWaMessageId($waMessageId)) { + continue; + } + + $type = (string) ($message['type'] ?? 'text'); + + $messageModel->storeWhatsAppMessage([ + 'person_id' => null, + 'phone' => $phone, + 'direction' => 'in', + 'type' => $type, + 'body' => $this->extractInboundBody($message, $type), + 'wa_message_id' => $waMessageId, + 'status' => 'received', + 'created_at' => isset($message['timestamp']) + ? date('Y-m-d H:i:s', (int) $message['timestamp']) + : date('Y-m-d H:i:s'), + ]); + } + + foreach ($value['statuses'] ?? [] as $status) { + if (! empty($status['id']) && ! empty($status['status'])) { + $messageModel->updateStatus((string) $status['id'], (string) $status['status']); + } + } + } + } + } + + private function extractInboundBody(array $message, string $type): string + { + return match ($type) { + 'text' => (string) ($message['text']['body'] ?? ''), + 'button' => (string) ($message['button']['text'] ?? ''), + 'image', 'document', 'video', 'audio' => (string) ($message[$type]['caption'] ?? ''), + default => '', + }; + } + + private function hasValidSignature(string $raw, string $appSecret): bool + { + if ($appSecret === '') { + return false; + } + + $header = $this->request->getHeaderLine('X-Hub-Signature-256'); + + if ($header === '' || ! str_starts_with($header, 'sha256=')) { + return false; + } + + $expected = 'sha256=' . hash_hmac('sha256', $raw, $appSecret); + + return hash_equals($expected, $header); + } + + /** + * Empty 200: tells Meta the delivery was received so it is not retried. + */ + private function acknowledge(): ResponseInterface + { + return $this->response->setStatusCode(200)->setContentType('text/plain')->setBody(''); + } + + private function log(string $level, string $message): void + { + log_plugin_message($level, $message, $this->plugin->getPluginId()); + } +} diff --git a/app/Plugins/WhatsAppPlugin/Controllers/WhatsAppController.php b/app/Plugins/WhatsAppPlugin/Controllers/WhatsAppController.php new file mode 100644 index 000000000..41f0925f5 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Controllers/WhatsAppController.php @@ -0,0 +1,183 @@ +getPlugin('whatsapp'); + + // Routes are registered for every plugin directory, enabled or not, so a + // disabled plugin must refuse to serve rather than rely on the route + // simply not existing. + if (! $plugin instanceof WhatsAppPlugin || ! $plugin->isEnabled()) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->plugin = $plugin; + $this->connector = $plugin->connector(); + $this->messageModel = model(WhatsAppMessage::class); + } + + public function getIndex(): string + { + return $this->plugin->renderPluginView('whatsapp', [ + 'conversations' => $this->messageModel->getRecentConversations(), + 'configured' => $this->connector->isConfigured(), + 'saved_message' => $this->plugin->savedMessage(), + ]); + } + + /** + * Per-person modal: prefilled form plus that person's conversation thread. + */ + public function getView(int $personId = NEW_ENTRY): string + { + $info = model(Person::class)->getInfo($personId); + $phone = $this->connector->normalizePhone($info->phone_number ?? ''); + + return $this->plugin->renderPluginView('form_whatsapp', [ + 'person_info' => $info, + 'phone' => $phone, + 'saved_message' => $this->plugin->savedMessage(), + 'messages' => $phone !== '' + ? $this->messageModel->getConversation($phone)->getResult() + : [], + ]); + } + + /** + * Returns the conversation thread partial for a phone number (AJAX refresh). + */ + public function getConversation(string $phone = ''): string + { + $phone = $this->connector->normalizePhone($phone); + + return $this->plugin->renderPluginView('conversation', [ + 'messages' => $phone !== '' + ? $this->messageModel->getConversation($phone)->getResult() + : [], + ]); + } + + public function postSend(): ResponseInterface + { + $phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS); + // Send the message body verbatim: WhatsApp renders plain text, not HTML. + // Entity-encoding here would surface literal &/' to the customer + // and double-encode when the log is later shown via esc(). + $message = trim((string) $this->request->getPost('message')); + + $sent = $this->connector->sendText((string) $phone, $message); + + return $this->response->setJSON([ + 'success' => $sent, + 'message' => lang($sent ? 'WhatsAppPlugin.successfully_sent' : 'WhatsAppPlugin.unsuccessfully_sent') . ' ' . esc($phone), + ]); + } + + /** + * Used in Views/form_whatsapp.php. + * + * @noinspection PhpUnused + */ + public function postSendForm(int $personId = NEW_ENTRY): ResponseInterface + { + $phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS); + // See postSend() for why the body is not escaped here. + $message = trim((string) $this->request->getPost('message')); + + $sent = $this->connector->sendText( + (string) $phone, + $message, + $personId === NEW_ENTRY ? null : $personId, + ); + + return $this->response->setJSON([ + 'success' => $sent, + 'message' => lang($sent ? 'WhatsAppPlugin.successfully_sent' : 'WhatsAppPlugin.unsuccessfully_sent') . ' ' . esc($phone), + 'person_id' => $sent ? $personId : NEW_ENTRY, + ]); + } + + /** + * Sends a sale document (invoice/quote/work order/receipt) as a PDF over + * WhatsApp. Triggered by the button injected into the sale document views. + * + * @noinspection PhpUnused + */ + public function postSendDocument(int $saleId, string $type = 'invoice'): ResponseInterface + { + // $type is interpolated into a view path, so restrict it to known types. + if (! in_array($type, WhatsAppPlugin::DOCUMENT_TYPES, true)) { + $type = 'invoice'; + } + + $saleDocument = new SaleDocument(); + + // Checked before rendering so a missing phone number and a failed PDF are + // never reported as each other. + $phone = $this->connector->normalizePhone($saleDocument->customerPhone($saleId)); + + if ($phone === '') { + return $this->response->setJSON([ + 'success' => false, + 'message' => lang('WhatsAppPlugin.no_phone'), + 'id' => $saleId, + ]); + } + + $document = $saleDocument->renderPdf($saleId, $type); + + if ($document === null) { + return $this->response->setJSON([ + 'success' => false, + 'message' => lang('WhatsAppPlugin.document_failed'), + 'id' => $saleId, + ]); + } + + $sent = $this->connector->sendDocument( + $document['phone'], + $document['path'], + $document['display_name'], + $document['caption'], + $document['person_id'], + ); + + if (is_file($document['path'])) { + unlink($document['path']); + } + + return $this->response->setJSON([ + 'success' => $sent, + 'message' => lang('WhatsAppPlugin.' . $type . ($sent ? '_sent' : '_unsent')) . ' ' . $document['phone'], + 'id' => $saleId, + ]); + } +} diff --git a/app/Plugins/WhatsAppPlugin/LICENSE b/app/Plugins/WhatsAppPlugin/LICENSE new file mode 100644 index 000000000..f932bb012 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/LICENSE @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2026 Joshua Fernandes (aka joshua1234511) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Additionally, you cannot claim copyright or ownership of the Software. + +The credit for the original source code of the project MUST BE RETAINED, MUST +BE VISIBLE IN EVERY PAGE and this credit CANNOT BE MODIFIED. + diff --git a/app/Plugins/WhatsAppPlugin/Language/ar-EG/Module.php b/app/Plugins/WhatsAppPlugin/Language/ar-EG/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ar-EG/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ar-EG/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/ar-EG/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ar-EG/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ar-LB/Module.php b/app/Plugins/WhatsAppPlugin/Language/ar-LB/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ar-LB/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ar-LB/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/ar-LB/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ar-LB/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/az/Module.php b/app/Plugins/WhatsAppPlugin/Language/az/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/az/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/az/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/az/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/az/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/bg/Module.php b/app/Plugins/WhatsAppPlugin/Language/bg/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/bg/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/bg/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/bg/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/bg/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/bs/Module.php b/app/Plugins/WhatsAppPlugin/Language/bs/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/bs/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/bs/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/bs/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/bs/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ckb/Module.php b/app/Plugins/WhatsAppPlugin/Language/ckb/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ckb/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ckb/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/ckb/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ckb/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/cs/Module.php b/app/Plugins/WhatsAppPlugin/Language/cs/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/cs/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/cs/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/cs/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/cs/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/da/Module.php b/app/Plugins/WhatsAppPlugin/Language/da/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/da/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/da/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/da/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/da/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/de-CH/Module.php b/app/Plugins/WhatsAppPlugin/Language/de-CH/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/de-CH/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/de-CH/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/de-CH/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/de-CH/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/de-DE/Module.php b/app/Plugins/WhatsAppPlugin/Language/de-DE/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/de-DE/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/de-DE/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/de-DE/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/de-DE/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/el/Module.php b/app/Plugins/WhatsAppPlugin/Language/el/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/el/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/el/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/el/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/el/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/en-GB/Module.php b/app/Plugins/WhatsAppPlugin/Language/en-GB/Module.php new file mode 100644 index 000000000..d4e69e448 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/en-GB/Module.php @@ -0,0 +1,7 @@ + 'WhatsApp', + 'whatsapp_desc' => 'Send WhatsApp messages to customers and view conversations.', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/en-GB/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/en-GB/WhatsAppPlugin.php new file mode 100644 index 000000000..ad945785d --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/en-GB/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + 'API Base URL', + 'api_version' => 'API Version', + 'app_secret' => 'App Secret', + 'app_secret_help' => 'Required to receive replies: inbound payloads are rejected unless their signature matches this secret.', + 'business_id' => 'WhatsApp Business Account ID', + 'clear_secret' => 'Clear the stored value', + 'configuration' => 'WhatsApp Configuration', + 'conversation' => 'Conversation', + 'default_country_code' => 'Default Country Code', + 'default_country_code_placeholder' => 'e.g. 1', + 'description' => 'Send WhatsApp messages and sale documents to customers through the WhatsApp Business Cloud API, and view the replies.', + 'docs_link' => 'How to get these credentials — read the WhatsApp Business Cloud API documentation', + 'document_failed' => 'The document could not be generated.', + 'first_name' => 'First name', + 'invoice_sent' => 'Invoice sent via WhatsApp to', + 'invoice_unsent' => 'Invoice failed to be sent via WhatsApp to', + 'last_activity' => 'Last activity', + 'last_name' => 'Last name', + 'media_upload_failed' => 'The document could not be uploaded to WhatsApp.', + 'message' => 'Message', + 'message_placeholder' => 'Your message here...', + 'message_required' => 'Message required', + 'messages' => 'Messages', + 'no_conversations' => 'No conversations yet.', + 'no_messages' => 'No messages yet.', + 'no_phone' => 'This customer does not have a valid phone number.', + 'not_configured' => 'WhatsApp is not configured or is disabled.', + 'not_configured_notice' => 'WhatsApp is not configured. Set your credentials in the plugin configuration before sending messages.', + 'phone' => 'Phone number', + 'phone_help' => 'Include the country code (digits only), e.g. 14155552671', + 'phone_id' => 'Phone Number ID', + 'phone_id_required' => 'Phone Number ID is a required field', + 'phone_number_required' => 'Phone number required', + 'phone_placeholder' => 'Phone number with country code...', + 'quote_sent' => 'Quote sent via WhatsApp to', + 'quote_unsent' => 'Quote failed to be sent via WhatsApp to', + 'receipt_sent' => 'Receipt sent via WhatsApp to', + 'receipt_unsent' => 'Receipt failed to be sent via WhatsApp to', + 'recent_conversations' => 'Recent conversations', + 'saved_message' => 'Saved Text Message', + 'saved_message_placeholder' => 'Default message pre-filled on the WhatsApp send page. Leave blank for none.', + 'secret_stored' => 'A value is stored — leave blank to keep it', + 'secret_unchanged_help' => 'The stored value is never displayed. Leave the field blank to keep it, type a new one to replace it, or tick the box to remove it.', + 'send_failed' => 'The WhatsApp API rejected the message.', + 'send_whatsapp' => 'Send via WhatsApp', + 'status_delivered' => 'delivered', + 'status_failed' => 'failed', + 'status_read' => 'read', + 'status_received' => 'received', + 'status_sent' => 'sent', + 'successfully_sent' => 'WhatsApp message successfully sent to:', + 'token' => 'Access Token', + 'token_required' => 'Access Token is a required field', + 'unsuccessfully_sent' => 'WhatsApp message could not be sent to:', + 'verify_token' => 'Verify Token', + 'view' => 'View', + 'webhook_heading' => 'Webhook (inbound replies)', + 'webhook_url' => 'Callback URL', + 'webhook_url_help' => 'Register this URL in the Meta app dashboard. It must be publicly reachable over HTTPS to receive replies.', + 'whatsapp' => 'WhatsApp', + 'whatsapp_send' => 'Send WhatsApp', + 'window_notice' => 'Note: free-form messages are only delivered within 24 hours of the customer\'s last message. Outside that window Meta requires an approved message template.', + 'work_order_sent' => 'Work Order sent via WhatsApp to', + 'work_order_unsent' => 'Work Order failed to be sent via WhatsApp to', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/en/Module.php b/app/Plugins/WhatsAppPlugin/Language/en/Module.php new file mode 100644 index 000000000..d4e69e448 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/en/Module.php @@ -0,0 +1,7 @@ + 'WhatsApp', + 'whatsapp_desc' => 'Send WhatsApp messages to customers and view conversations.', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/en/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/en/WhatsAppPlugin.php new file mode 100644 index 000000000..ad945785d --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/en/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + 'API Base URL', + 'api_version' => 'API Version', + 'app_secret' => 'App Secret', + 'app_secret_help' => 'Required to receive replies: inbound payloads are rejected unless their signature matches this secret.', + 'business_id' => 'WhatsApp Business Account ID', + 'clear_secret' => 'Clear the stored value', + 'configuration' => 'WhatsApp Configuration', + 'conversation' => 'Conversation', + 'default_country_code' => 'Default Country Code', + 'default_country_code_placeholder' => 'e.g. 1', + 'description' => 'Send WhatsApp messages and sale documents to customers through the WhatsApp Business Cloud API, and view the replies.', + 'docs_link' => 'How to get these credentials — read the WhatsApp Business Cloud API documentation', + 'document_failed' => 'The document could not be generated.', + 'first_name' => 'First name', + 'invoice_sent' => 'Invoice sent via WhatsApp to', + 'invoice_unsent' => 'Invoice failed to be sent via WhatsApp to', + 'last_activity' => 'Last activity', + 'last_name' => 'Last name', + 'media_upload_failed' => 'The document could not be uploaded to WhatsApp.', + 'message' => 'Message', + 'message_placeholder' => 'Your message here...', + 'message_required' => 'Message required', + 'messages' => 'Messages', + 'no_conversations' => 'No conversations yet.', + 'no_messages' => 'No messages yet.', + 'no_phone' => 'This customer does not have a valid phone number.', + 'not_configured' => 'WhatsApp is not configured or is disabled.', + 'not_configured_notice' => 'WhatsApp is not configured. Set your credentials in the plugin configuration before sending messages.', + 'phone' => 'Phone number', + 'phone_help' => 'Include the country code (digits only), e.g. 14155552671', + 'phone_id' => 'Phone Number ID', + 'phone_id_required' => 'Phone Number ID is a required field', + 'phone_number_required' => 'Phone number required', + 'phone_placeholder' => 'Phone number with country code...', + 'quote_sent' => 'Quote sent via WhatsApp to', + 'quote_unsent' => 'Quote failed to be sent via WhatsApp to', + 'receipt_sent' => 'Receipt sent via WhatsApp to', + 'receipt_unsent' => 'Receipt failed to be sent via WhatsApp to', + 'recent_conversations' => 'Recent conversations', + 'saved_message' => 'Saved Text Message', + 'saved_message_placeholder' => 'Default message pre-filled on the WhatsApp send page. Leave blank for none.', + 'secret_stored' => 'A value is stored — leave blank to keep it', + 'secret_unchanged_help' => 'The stored value is never displayed. Leave the field blank to keep it, type a new one to replace it, or tick the box to remove it.', + 'send_failed' => 'The WhatsApp API rejected the message.', + 'send_whatsapp' => 'Send via WhatsApp', + 'status_delivered' => 'delivered', + 'status_failed' => 'failed', + 'status_read' => 'read', + 'status_received' => 'received', + 'status_sent' => 'sent', + 'successfully_sent' => 'WhatsApp message successfully sent to:', + 'token' => 'Access Token', + 'token_required' => 'Access Token is a required field', + 'unsuccessfully_sent' => 'WhatsApp message could not be sent to:', + 'verify_token' => 'Verify Token', + 'view' => 'View', + 'webhook_heading' => 'Webhook (inbound replies)', + 'webhook_url' => 'Callback URL', + 'webhook_url_help' => 'Register this URL in the Meta app dashboard. It must be publicly reachable over HTTPS to receive replies.', + 'whatsapp' => 'WhatsApp', + 'whatsapp_send' => 'Send WhatsApp', + 'window_notice' => 'Note: free-form messages are only delivered within 24 hours of the customer\'s last message. Outside that window Meta requires an approved message template.', + 'work_order_sent' => 'Work Order sent via WhatsApp to', + 'work_order_unsent' => 'Work Order failed to be sent via WhatsApp to', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/es-ES/Module.php b/app/Plugins/WhatsAppPlugin/Language/es-ES/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/es-ES/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/es-ES/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/es-ES/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/es-ES/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/es-MX/Module.php b/app/Plugins/WhatsAppPlugin/Language/es-MX/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/es-MX/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/es-MX/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/es-MX/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/es-MX/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/fa/Module.php b/app/Plugins/WhatsAppPlugin/Language/fa/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/fa/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/fa/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/fa/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/fa/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/fr/Module.php b/app/Plugins/WhatsAppPlugin/Language/fr/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/fr/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/fr/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/fr/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/fr/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/he/Module.php b/app/Plugins/WhatsAppPlugin/Language/he/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/he/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/he/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/he/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/he/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/hr-HR/Module.php b/app/Plugins/WhatsAppPlugin/Language/hr-HR/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/hr-HR/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/hr-HR/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/hr-HR/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/hr-HR/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/hu/Module.php b/app/Plugins/WhatsAppPlugin/Language/hu/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/hu/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/hu/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/hu/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/hu/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/hy/Module.php b/app/Plugins/WhatsAppPlugin/Language/hy/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/hy/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/hy/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/hy/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/hy/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/id/Module.php b/app/Plugins/WhatsAppPlugin/Language/id/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/id/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/id/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/id/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/id/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/it/Module.php b/app/Plugins/WhatsAppPlugin/Language/it/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/it/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/it/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/it/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/it/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ka/Module.php b/app/Plugins/WhatsAppPlugin/Language/ka/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ka/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ka/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/ka/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ka/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/km/Module.php b/app/Plugins/WhatsAppPlugin/Language/km/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/km/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/km/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/km/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/km/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/lo/Module.php b/app/Plugins/WhatsAppPlugin/Language/lo/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/lo/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/lo/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/lo/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/lo/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ml/Module.php b/app/Plugins/WhatsAppPlugin/Language/ml/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ml/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ml/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/ml/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ml/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/nb/Module.php b/app/Plugins/WhatsAppPlugin/Language/nb/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/nb/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/nb/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/nb/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/nb/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/nl-BE/Module.php b/app/Plugins/WhatsAppPlugin/Language/nl-BE/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/nl-BE/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/nl-BE/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/nl-BE/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/nl-BE/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/nl-NL/Module.php b/app/Plugins/WhatsAppPlugin/Language/nl-NL/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/nl-NL/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/nl-NL/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/nl-NL/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/nl-NL/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/pl/Module.php b/app/Plugins/WhatsAppPlugin/Language/pl/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/pl/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/pl/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/pl/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/pl/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/pt-BR/Module.php b/app/Plugins/WhatsAppPlugin/Language/pt-BR/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/pt-BR/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/pt-BR/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/pt-BR/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/pt-BR/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ro/Module.php b/app/Plugins/WhatsAppPlugin/Language/ro/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ro/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ro/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/ro/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ro/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ru/Module.php b/app/Plugins/WhatsAppPlugin/Language/ru/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ru/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ru/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/ru/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ru/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/sv/Module.php b/app/Plugins/WhatsAppPlugin/Language/sv/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/sv/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/sv/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/sv/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/sv/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/sw-KE/Module.php b/app/Plugins/WhatsAppPlugin/Language/sw-KE/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/sw-KE/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/sw-KE/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/sw-KE/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/sw-KE/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/sw-TZ/Module.php b/app/Plugins/WhatsAppPlugin/Language/sw-TZ/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/sw-TZ/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/sw-TZ/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/sw-TZ/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/sw-TZ/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ta/Module.php b/app/Plugins/WhatsAppPlugin/Language/ta/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ta/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ta/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/ta/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ta/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/th/Module.php b/app/Plugins/WhatsAppPlugin/Language/th/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/th/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/th/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/th/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/th/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/tl/Module.php b/app/Plugins/WhatsAppPlugin/Language/tl/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/tl/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/tl/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/tl/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/tl/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/tr/Module.php b/app/Plugins/WhatsAppPlugin/Language/tr/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/tr/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/tr/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/tr/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/tr/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/uk/Module.php b/app/Plugins/WhatsAppPlugin/Language/uk/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/uk/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/uk/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/uk/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/uk/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ur/Module.php b/app/Plugins/WhatsAppPlugin/Language/ur/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ur/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/ur/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/ur/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/ur/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/vi/Module.php b/app/Plugins/WhatsAppPlugin/Language/vi/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/vi/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/vi/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/vi/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/vi/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/zh-Hans/Module.php b/app/Plugins/WhatsAppPlugin/Language/zh-Hans/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/zh-Hans/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/zh-Hans/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/zh-Hans/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/zh-Hans/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/zh-Hant/Module.php b/app/Plugins/WhatsAppPlugin/Language/zh-Hant/Module.php new file mode 100644 index 000000000..904c80359 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/zh-Hant/Module.php @@ -0,0 +1,7 @@ + '', + 'whatsapp_desc' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Language/zh-Hant/WhatsAppPlugin.php b/app/Plugins/WhatsAppPlugin/Language/zh-Hant/WhatsAppPlugin.php new file mode 100644 index 000000000..9e666d647 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Language/zh-Hant/WhatsAppPlugin.php @@ -0,0 +1,68 @@ + '', + 'api_version' => '', + 'app_secret' => '', + 'app_secret_help' => '', + 'business_id' => '', + 'clear_secret' => '', + 'configuration' => '', + 'conversation' => '', + 'default_country_code' => '', + 'default_country_code_placeholder' => '', + 'description' => '', + 'docs_link' => '', + 'document_failed' => '', + 'first_name' => '', + 'invoice_sent' => '', + 'invoice_unsent' => '', + 'last_activity' => '', + 'last_name' => '', + 'media_upload_failed' => '', + 'message' => '', + 'message_placeholder' => '', + 'message_required' => '', + 'messages' => '', + 'no_conversations' => '', + 'no_messages' => '', + 'no_phone' => '', + 'not_configured' => '', + 'not_configured_notice' => '', + 'phone' => '', + 'phone_help' => '', + 'phone_id' => '', + 'phone_id_required' => '', + 'phone_number_required' => '', + 'phone_placeholder' => '', + 'quote_sent' => '', + 'quote_unsent' => '', + 'receipt_sent' => '', + 'receipt_unsent' => '', + 'recent_conversations' => '', + 'saved_message' => '', + 'saved_message_placeholder' => '', + 'secret_stored' => '', + 'secret_unchanged_help' => '', + 'send_failed' => '', + 'send_whatsapp' => '', + 'status_delivered' => '', + 'status_failed' => '', + 'status_read' => '', + 'status_received' => '', + 'status_sent' => '', + 'successfully_sent' => '', + 'token' => '', + 'token_required' => '', + 'unsuccessfully_sent' => '', + 'verify_token' => '', + 'view' => '', + 'webhook_heading' => '', + 'webhook_url' => '', + 'webhook_url_help' => '', + 'whatsapp' => '', + 'whatsapp_send' => '', + 'window_notice' => '', + 'work_order_sent' => '', + 'work_order_unsent' => '', +]; diff --git a/app/Plugins/WhatsAppPlugin/Libraries/OutboundMessage.php b/app/Plugins/WhatsAppPlugin/Libraries/OutboundMessage.php new file mode 100644 index 000000000..b1d9615e5 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Libraries/OutboundMessage.php @@ -0,0 +1,49 @@ + $this->personId, + 'phone' => $this->phone, + 'direction' => 'out', + 'type' => $this->type, + 'body' => $this->body, + 'media_id' => $this->mediaId, + 'filename' => $this->filename, + 'wa_message_id' => $this->waMessageId, + 'status' => $this->status, + 'error' => $this->error, + ]; + } +} diff --git a/app/Plugins/WhatsAppPlugin/Libraries/SaleDocument.php b/app/Plugins/WhatsAppPlugin/Libraries/SaleDocument.php new file mode 100644 index 000000000..5aebe2eb5 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Libraries/SaleDocument.php @@ -0,0 +1,352 @@ +config = config(OSPOS::class)->settings; + } + + /** + * Returns the customer's phone number for a sale, or '' when the sale has no + * customer or the customer has no number. + * + * Deliberately cheap and side-effect free: this runs while a sale document view + * is rendering, so it must not touch the register's cart session. + * + * Reads customer_id straight from `sales` rather than via Sale::getInfo(), + * which builds a temp table and inner-joins sales_items/payments to compute + * totals. That would be wasted work for a single column here, and it returns + * no row at all for a sale with no line items. + */ + public function customerPhone(int $saleId): string + { + $sale = Database::connect() + ->table('sales') + ->select('customer_id') + ->where('sale_id', $saleId) + ->get() + ->getRow(); + + $customerId = (int) ($sale->customer_id ?? 0); + + if ($customerId === 0) { + return ''; + } + + return (string) (model(Customer::class)->getInfo($customerId)->phone_number ?? ''); + } + + /** + * Renders a sale document to a temporary PDF file. $type is one of + * invoice|quote|work_order|receipt, already validated by the caller, which + * also owns the returned file and must unlink it. + * + * The register session is restored before returning, so sending a document + * for an old sale never disturbs the sale the operator has open. + * + * @return array{path: string, display_name: string, caption: string, phone: string, person_id: int|null}|null + * Null when the PDF could not be generated. + */ + public function renderPdf(int $saleId, string $type): ?array + { + $register = $this->snapshotRegister(); + + try { + return $this->render($saleId, $type); + } finally { + $this->restoreRegister($register); + } + } + + private function render(int $saleId, string $type): ?array + { + $data = $this->buildData($saleId); + $number = $this->documentNumber($data, $type); + + $emailLib = new Email_lib(); + $data['mimetype'] = $emailLib->getLogoMimeType(); + $data['img_tag'] = $emailLib->buildLogoImgTag(); + + // Same view core uses for the emailed attachment. + $html = Services::renderer()->setData($data)->render("sales/{$type}_email", $data); + + helper(['dompdf', 'file']); + + // A unique temp name avoids a TOCTOU race with core's getSendPdf(), which + // writes to a deterministic path. The recipient still sees a friendly name. + $path = tempnam(sys_get_temp_dir(), 'wa_'); + + if ($path === false || file_put_contents($path, create_pdf($html)) === false) { + if ($path !== false && is_file($path)) { + unlink($path); + } + + return null; + } + + return [ + 'path' => $path, + 'display_name' => lang('Sales.' . $type) . '-' . str_replace('/', '-', $number) . '.pdf', + 'caption' => $this->buildCaption($data, $number), + 'phone' => (string) ($data['customer_phone'] ?? ''), + 'person_id' => isset($data['customer_id']) ? (int) $data['customer_id'] : null, + ]; + } + + /** + * Identifier used in the filename and the caption's invoice-sequence token. + * + * Receipts have no number of their own — the receipt view identifies the sale + * by its POS id — and invoice/quote/work-order numbers are null on a sale that + * was never issued as that document type. Both fall back to the POS id so the + * filename is never left empty. + */ + private function documentNumber(array $data, string $type): string + { + $number = (string) ($data[$type . '_number'] ?? ''); + + return $number !== '' ? $number : (string) $data['sale_id']; + } + + /** + * Renders the configured invoice message with the sale's tokens substituted. + */ + private function buildCaption(array $data, string $number): string + { + $tokens = [ + new Token_invoice_sequence($number), + new Token_invoice_count('POS ' . $data['sale_id_num']), + new Token_customer($data), + ]; + + return (new Token_lib())->render((string) ($this->config['invoice_email_message'] ?? ''), $tokens); + } + + /** + * Builds the view data for a sale document. + * + * Like core, this loads the sale into the register cart to compute totals. + * renderPdf() puts the cart back afterwards. + */ + private function buildData(int $saleId): array + { + $saleModel = model(Sale::class); + $saleLib = new Sale_lib(); + $taxLib = new Tax_lib(); + + $saleLib->clear_all(); + + $data = ['cash_rounding' => $saleLib->reset_cash_rounding()]; + + $saleInfo = $saleModel->getInfo($saleId)->getRowArray(); + $saleLib->copy_entire_sale($saleId); + + $data['cart'] = $saleLib->get_cart(); + $data['payments'] = $saleLib->getPayments(); + $data['selected_payment_type'] = $saleLib->get_payment_type(); + + $taxDetails = $taxLib->get_taxes($data['cart'], $saleId); + $data['taxes'] = $saleModel->get_sales_taxes($saleId); + $data['discount'] = $saleLib->get_discount(); + $data['transaction_time'] = to_datetime(strtotime($saleInfo['sale_time'])); + $data['transaction_date'] = to_date(strtotime($saleInfo['sale_time'])); + $data['show_stock_locations'] = model(Stock_location::class)->show_locations('sales'); + $data['include_hsn'] = (bool) $this->config['include_hsn']; + + $totals = $saleLib->get_totals($taxDetails[0]); + $session = session(); + $session->set('cash_adjustment_amount', $totals['cash_adjustment_amount']); + + $data['subtotal'] = $totals['subtotal']; + $data['payments_total'] = $totals['payment_total']; + $data['payments_cover_total'] = $totals['payments_cover_total']; + $data['cash_mode'] = $session->get('cash_mode'); + $data['prediscount_subtotal'] = $totals['prediscount_subtotal']; + $data['cash_total'] = $totals['cash_total']; + $data['non_cash_total'] = $totals['total']; + $data['cash_amount_due'] = $totals['cash_amount_due']; + $data['non_cash_amount_due'] = $totals['amount_due']; + + if ($data['cash_mode'] && ($data['selected_payment_type'] === lang('Sales.cash') || $data['payments_total'] > 0)) { + $data['total'] = $totals['cash_total']; + $data['amount_due'] = $totals['cash_amount_due']; + } else { + $data['total'] = $totals['total']; + $data['amount_due'] = $totals['amount_due']; + } + + $data['amount_change'] = $data['amount_due'] * -1; + + $employeeInfo = model(Employee::class)->getInfo($saleLib->get_employee()); + $data['employee'] = $employeeInfo->first_name . ' ' . mb_substr($employeeInfo->last_name, 0, 1); + + $this->loadCustomerData($saleLib->get_customer(), $data); + + $data['sale_id_num'] = $saleId; + $data['sale_id'] = 'POS ' . $saleId; + $data['comments'] = $saleInfo['comment']; + $data['invoice_number'] = $saleInfo['invoice_number']; + $data['quote_number'] = $saleInfo['quote_number']; + $data['sale_status'] = $saleInfo['sale_status']; + + // Sale::getInfo() does not select work_order_number, but the + // work_order_email view requires it, so read it from the model directly. + $data['work_order_number'] = $saleModel->get_work_order_number($saleId); + + $data['company_info'] = implode("\n", [$this->config['address'], $this->config['phone']]); + + if ($this->config['account_number']) { + $data['company_info'] .= "\n" . lang('Sales.account_number') . ': ' . $this->config['account_number']; + } + + if ($this->config['tax_id'] !== '') { + $data['company_info'] .= "\n" . lang('Sales.tax_id') . ': ' . $this->config['tax_id']; + } + + $data['barcode'] = (new Barcode_lib())->generate_receipt_barcode($data['sale_id']); + $data['print_after_sale'] = false; + $data['price_work_orders'] = false; + $data['config'] = $this->config; + + return $data; + } + + /** + * Mirrors the customer half of `Sales::_load_customer_data()`, plus the phone + * number the WhatsApp send needs (core's version does not expose it). + */ + private function loadCustomerData(int $customerId, array &$data): void + { + if ($customerId === NEW_ENTRY) { + return; + } + + $customerInfo = model(Customer::class)->getInfo($customerId); + + $data['customer_id'] = $customerId; + $data['customer'] = ! empty($customerInfo->company_name) + ? $customerInfo->company_name + : $customerInfo->first_name . ' ' . $customerInfo->last_name; + $data['first_name'] = $customerInfo->first_name; + $data['last_name'] = $customerInfo->last_name; + $data['customer_email'] = $customerInfo->email; + $data['customer_phone'] = $customerInfo->phone_number; + $data['customer_address'] = $customerInfo->address_1; + $data['customer_location'] = ! empty($customerInfo->zip) || ! empty($customerInfo->city) + ? $customerInfo->zip . ' ' . $customerInfo->city . "\n" . $customerInfo->state + : ''; + $data['customer_account_number'] = $customerInfo->account_number; + $data['customer_discount'] = $customerInfo->discount; + $data['customer_discount_type'] = $customerInfo->discount_type; + + $data['customer_info'] = implode("\n", [ + $data['customer'], + $data['customer_address'], + $data['customer_location'], + ]); + + if ($data['customer_account_number']) { + $data['customer_info'] .= "\n" . lang('Sales.account_number') . ': ' . $data['customer_account_number']; + } + + if ($customerInfo->tax_id !== '') { + $data['customer_info'] .= "\n" . lang('Sales.tax_id') . ': ' . $customerInfo->tax_id; + } + + $data['tax_id'] = $customerInfo->tax_id; + } + + /** + * @return array Only the keys that were set, so restoreRegister() can tell + * "absent" from "present and empty". + */ + private function snapshotRegister(): array + { + $session = session(); + $snapshot = []; + + foreach (self::REGISTER_SESSION_KEYS as $key) { + if ($session->has($key)) { + $snapshot[$key] = $session->get($key); + } + } + + return $snapshot; + } + + private function restoreRegister(array $snapshot): void + { + $session = session(); + + foreach (self::REGISTER_SESSION_KEYS as $key) { + if (array_key_exists($key, $snapshot)) { + $session->set($key, $snapshot[$key]); + } else { + $session->remove($key); + } + } + } +} diff --git a/app/Plugins/WhatsAppPlugin/Libraries/WhatsAppConnector.php b/app/Plugins/WhatsAppPlugin/Libraries/WhatsAppConnector.php new file mode 100644 index 000000000..4435cf300 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Libraries/WhatsAppConnector.php @@ -0,0 +1,287 @@ +settings = $settings; + $this->messageModel = model(WhatsAppMessage::class); + } + + /** + * Sends a free-form text message. $phone may be in any format. + * + * Note: free-form messages are only delivered within the 24 hour customer + * service window (i.e. after the customer has messaged the business). + * Outside that window an approved message template is required. + */ + public function sendText(string $phone, string $message, ?int $personId = null): bool + { + $to = $this->normalizePhone($phone); + $logEntry = new OutboundMessage($to, 'text', $message, $personId); + + if (! $this->isConfigured() || $to === '' || $message === '') { + $logEntry->error = lang('WhatsAppPlugin.not_configured'); + $this->logOutbound($logEntry); + + return false; + } + + $response = $this->post($this->messagesUrl(), [ + 'messaging_product' => 'whatsapp', + 'recipient_type' => 'individual', + 'to' => $to, + 'type' => 'text', + 'text' => ['preview_url' => false, 'body' => $message], + ]); + + $waMessageId = $this->extractMessageId($response); + $success = $waMessageId !== null; + + $logEntry->waMessageId = $waMessageId; + $logEntry->status = $success ? 'sent' : 'failed'; + $logEntry->error = $success ? null : $this->extractError($response); + + $this->logOutbound($logEntry); + + return $success; + } + + /** + * Sends a document (e.g. a PDF invoice) as an attachment. $filepath must be an + * absolute local path; $filename is what the recipient sees. + * + * The file is uploaded to the WhatsApp media endpoint first, then a document + * message referencing the returned media id is sent. + */ + public function sendDocument(string $phone, string $filepath, string $filename, string $caption = '', ?int $personId = null): bool + { + $to = $this->normalizePhone($phone); + $logEntry = new OutboundMessage($to, 'document', $caption, $personId); + $logEntry->filename = $filename; + + if (! $this->isConfigured() || $to === '' || ! is_file($filepath)) { + $logEntry->error = lang('WhatsAppPlugin.not_configured'); + $this->logOutbound($logEntry); + + return false; + } + + $mediaId = $this->uploadMedia($filepath, 'application/pdf'); + + if ($mediaId === null) { + $logEntry->error = lang('WhatsAppPlugin.media_upload_failed'); + $this->logOutbound($logEntry); + + return false; + } + + $logEntry->mediaId = $mediaId; + $document = ['id' => $mediaId, 'filename' => $filename]; + + if ($caption !== '') { + $document['caption'] = $caption; + } + + $response = $this->post($this->messagesUrl(), [ + 'messaging_product' => 'whatsapp', + 'recipient_type' => 'individual', + 'to' => $to, + 'type' => 'document', + 'document' => $document, + ]); + + $waMessageId = $this->extractMessageId($response); + $success = $waMessageId !== null; + + $logEntry->waMessageId = $waMessageId; + $logEntry->status = $success ? 'sent' : 'failed'; + $logEntry->error = $success ? null : $this->extractError($response); + + $this->logOutbound($logEntry); + + return $success; + } + + /** + * Normalizes a phone number to the digits-only E.164 form the API expects + * (no '+', spaces or punctuation). When a default country code is configured + * and the number does not already start with it, it is prepended. + * + * A leading '+' means the caller already gave a full international number, so + * the default is not applied — otherwise '+4420...' would become '14420...' + * on a register configured for country code 1. + * + * @return string Normalized number, or '' when no digits are present. + */ + public function normalizePhone(string $phone): string + { + $isInternational = str_starts_with(ltrim($phone), '+'); + + $digits = preg_replace('/\D+/', '', $phone) ?? ''; + + if ($digits === '') { + return ''; + } + + $country = preg_replace('/\D+/', '', (string) ($this->settings['default_country_code'] ?? '')) ?? ''; + + if (! $isInternational && $country !== '' && ! str_starts_with($digits, $country)) { + // Only the single national trunk '0' is dropped; further leading zeros + // are part of the subscriber number and must survive. + $digits = $country . preg_replace('/^0/', '', $digits, 1); + } + + return $digits; + } + + /** + * @return bool True when the plugin is enabled and the credentials are set. + */ + public function isConfigured(): bool + { + return ! empty($this->settings['enabled']) + && ! empty($this->settings['phone_id']) + && $this->token() !== ''; + } + + /** + * @return string|null The uploaded media id, or null on failure. + */ + private function uploadMedia(string $filepath, string $mime): ?string + { + try { + $response = $this->client()->post($this->mediaUrl(), [ + 'headers' => ['Authorization' => 'Bearer ' . $this->token()], + 'multipart' => [ + 'messaging_product' => 'whatsapp', + 'type' => $mime, + 'file' => new CURLFile($filepath, $mime, basename($filepath)), + ], + ]); + + $body = json_decode((string) $response->getBody(), true); + + if (is_array($body) && ! empty($body['id'])) { + return (string) $body['id']; + } + + $this->logApi('error', 'Media upload failed: ' . (string) $response->getBody()); + } catch (Throwable $e) { + $this->logApi('error', 'Media upload exception: ' . $e->getMessage()); + } + + return null; + } + + /** + * Sends a JSON POST to a Graph API endpoint with the bearer token. + * + * @return array|null Decoded response body, or null on transport failure. + */ + private function post(string $url, array $payload): ?array + { + try { + $response = $this->client()->post($url, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->token(), + 'Content-Type' => 'application/json', + ], + 'body' => json_encode($payload), + ]); + + $body = json_decode((string) $response->getBody(), true); + + return is_array($body) ? $body : null; + } catch (Throwable $e) { + $this->logApi('error', 'API exception: ' . $e->getMessage()); + + return null; + } + } + + private function logOutbound(OutboundMessage $message): void + { + try { + $this->messageModel->storeWhatsAppMessage($message->toArray()); + } catch (Throwable $e) { + log_plugin_message('error', 'Conversation log write failed: ' . $e->getMessage(), self::PLUGIN_ID); + } + } + + /** + * Graph API failures go to a dedicated api log so they stay separate from the + * plugin's general log. + */ + private function logApi(string $level, string $message): void + { + log_plugin_message($level, $message, self::PLUGIN_ID, 'api'); + } + + private function token(): string + { + return (string) ($this->settings['token'] ?? ''); + } + + private function client(): CURLRequest + { + return Services::curlrequest([ + 'timeout' => 15, + 'http_errors' => false, + ]); + } + + private function apiBase(): string + { + $url = rtrim((string) ($this->settings['api_url'] ?? 'https://graph.facebook.com'), '/'); + $version = trim((string) ($this->settings['api_version'] ?? 'v21.0'), '/'); + + return $url . '/' . $version; + } + + private function messagesUrl(): string + { + return $this->apiBase() . '/' . $this->settings['phone_id'] . '/messages'; + } + + private function mediaUrl(): string + { + return $this->apiBase() . '/' . $this->settings['phone_id'] . '/media'; + } + + private function extractMessageId(?array $response): ?string + { + return $response['messages'][0]['id'] ?? null; + } + + private function extractError(?array $response): ?string + { + return $response['error']['message'] ?? lang('WhatsAppPlugin.send_failed'); + } +} diff --git a/app/Plugins/WhatsAppPlugin/Migrations/20260728120000_CreateWhatsAppMessagesTable.php b/app/Plugins/WhatsAppPlugin/Migrations/20260728120000_CreateWhatsAppMessagesTable.php new file mode 100644 index 000000000..7791d0586 --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Migrations/20260728120000_CreateWhatsAppMessagesTable.php @@ -0,0 +1,56 @@ +forge->addField([ + 'message_id' => ['type' => 'INT', 'constraint' => 10, 'unsigned' => true, 'auto_increment' => true], + 'person_id' => ['type' => 'INT', 'constraint' => 10, 'null' => true], + 'phone' => ['type' => 'VARCHAR', 'constraint' => 20], + 'direction' => ['type' => 'VARCHAR', 'constraint' => 3], // 'in' | 'out' + 'type' => ['type' => 'VARCHAR', 'constraint' => 20, 'default' => 'text'], + 'body' => ['type' => 'TEXT', 'null' => true], + 'media_id' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true], + 'filename' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true], + 'wa_message_id' => ['type' => 'VARCHAR', 'constraint' => 128, 'null' => true], + 'status' => ['type' => 'VARCHAR', 'constraint' => 20, 'null' => true], + 'error' => ['type' => 'TEXT', 'null' => true], + 'created_at' => ['type' => 'DATETIME', 'null' => true], + ]); + + $this->forge->addKey('message_id', true); + $this->forge->addKey('phone'); + $this->forge->addKey('person_id'); + $this->forge->addKey('created_at'); + // Unique so a redelivered webhook cannot log the same message twice. + // Sends that never reached Meta store null, which a unique index allows + // any number of. + $this->forge->addUniqueKey('wa_message_id'); + + // utf8mb4 so emoji and full multilingual message content can be stored. + // utf8mb4_unicode_520_ci is the newest collation both MySQL and MariaDB + // support, so the same CREATE TABLE runs on either. + $this->forge->createTable('whatsapp_messages', true, [ + 'CHARSET' => 'utf8mb4', + 'COLLATE' => 'utf8mb4_unicode_520_ci', + ]); + } +} diff --git a/app/Plugins/WhatsAppPlugin/Models/WhatsAppMessage.php b/app/Plugins/WhatsAppPlugin/Models/WhatsAppMessage.php new file mode 100644 index 000000000..c263a5aaa --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Models/WhatsAppMessage.php @@ -0,0 +1,132 @@ +insert($data, true) ? (int) $this->getInsertID() : 0; + } + + /** + * Meta redelivers a webhook whose acknowledgement it did not see, so the same + * inbound message can arrive twice. The unique index on wa_message_id is the + * backstop; this keeps a redelivery from becoming a failed insert part-way + * through a payload. + */ + public function existsByWaMessageId(string $waMessageId): bool + { + return $this->db->table('whatsapp_messages') + ->where('wa_message_id', $waMessageId) + ->countAllResults() > 0; + } + + /** + * @param string $phone Normalized phone number (digits only). + */ + public function getConversation(string $phone, int $limit = 200): ResultInterface + { + $builder = $this->db->table('whatsapp_messages'); + $builder->where('phone', $phone); + $builder->orderBy('created_at', 'asc'); + $builder->orderBy('message_id', 'asc'); + $builder->limit($limit); + + return $builder->get(); + } + + /** + * Distinct phone numbers with a conversation, most recently active first. + */ + public function getRecentConversations(int $limit = 50): array + { + // Grouped by phone only: inbound webhook messages and general outbound + // sends log person_id => null, while sale sends log a real person_id. + // Grouping on both would split one customer's thread into two rows. + $builder = $this->db->table('whatsapp_messages'); + $builder->select('phone, MAX(person_id) AS person_id, MAX(created_at) AS last_activity, COUNT(*) AS message_count'); + $builder->groupBy('phone'); + $builder->orderBy('last_activity', 'desc'); + $builder->limit($limit); + + return $builder->get()->getResultArray(); + } + + /** + * Updates the delivery status of an outbound message, identified by its + * WhatsApp message id (wamid). + * + * Status callbacks can arrive out of order, so backward transitions along the + * sent -> delivered -> read progression are ignored to stop a late "delivered" + * downgrading an already "read" message. Statuses outside that ordering + * (e.g. "failed") are always applied. + * + * The ordering lives in the WHERE clause rather than in a read-then-compare, + * because two callbacks for the same message can be handled concurrently and + * the later write would otherwise win on a stale comparison. + * + * @return bool False when the statement itself fails. A skipped backward + * transition and an unknown wamid both count as success. + */ + public function updateStatus(string $waMessageId, string $status): bool + { + $rank = ['sent' => 1, 'delivered' => 2, 'read' => 3]; + + $builder = $this->db->table('whatsapp_messages')->where('wa_message_id', $waMessageId); + + if (isset($rank[$status])) { + $overtakes = array_keys(array_filter($rank, static fn (int $r): bool => $r < $rank[$status])); + + $builder->groupStart() + ->where('status', null) + ->orWhereNotIn('status', array_keys($rank)); + + if ($overtakes !== []) { + $builder->orWhereIn('status', $overtakes); + } + + $builder->groupEnd(); + } + + return $builder->update(['status' => $status]); + } +} diff --git a/app/Plugins/WhatsAppPlugin/README.md b/app/Plugins/WhatsAppPlugin/README.md new file mode 100644 index 000000000..473ec9d4d --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/README.md @@ -0,0 +1,52 @@ +# WhatsApp Plugin + +Sends WhatsApp messages and sale documents to customers through the [WhatsApp Business Cloud API](https://developers.facebook.com/documentation/business-messaging/whatsapp/get-started), and records the replies. + +The plugin is self-contained: it adds no file to core, and everything it installs (its settings, its module entry and its `whatsapp_messages` table) is removed again on uninstall. + +## What it adds + +- A **WhatsApp** office module — a page listing recent conversations, with a send form and the full thread per customer. +- A **Send via WhatsApp** button on all four sale documents (invoice, quote, work order, receipt), which delivers the document as a PDF with the configured invoice message as its caption. +- A per-person send form at `whatsapp/view/{person_id}`, mirroring the SMS Messages one: prefilled with that person's number, their saved message and their thread. +- A webhook that stores inbound replies and delivery-status updates (`sent` → `delivered` → `read`, or `failed`). + +## Requirements + +A Meta developer app with WhatsApp Business enabled. From it you need: + +| Setting | Where it comes from | Required | +| --- | --- | --- | +| Phone Number ID | WhatsApp → API Setup | Yes | +| Access Token | WhatsApp → API Setup (use a permanent System User token; the test token expires in 24 hours) | Yes | +| WhatsApp Business Account ID | WhatsApp → API Setup | No | +| App Secret | App Settings → Basic | Only to receive replies | +| Verify Token | Any string you choose; you enter the same one in Meta | Only to receive replies | +| Default Country Code | Your locale, digits only (e.g. `1`) | No | + +The site must be reachable over public HTTPS to receive replies. Sending works without that. + +## Installation + +1. **Plugins → WhatsApp → Install.** This creates the settings, registers the `whatsapp` module and permission, and runs the migration that creates `whatsapp_messages`. +2. **Grant the `whatsapp` permission** to the employees who should see the module (Employees → edit → permissions). +3. **Configure** (the gear icon on the plugin row) and enter at least the Phone Number ID and Access Token. +4. **Enable** the plugin. + +The Access Token and App Secret are stored encrypted, and are never rendered back into the configuration form: leave a secret field blank to keep the stored value, type a new one to replace it, or tick its **Clear the stored value** box to remove it. + +## Receiving replies + +1. Copy the **Callback URL** shown in the configuration form (`https://your-site/plugins/whatsapp/webhook`). +2. In the Meta app dashboard, WhatsApp → Configuration → Webhooks, paste it as the callback URL along with the same **Verify Token** you saved here, and subscribe to the `messages` field. +3. Save the **App Secret** here. Inbound payloads whose `X-Hub-Signature-256` does not match it are discarded, and with no app secret stored nothing inbound is accepted at all. + +## Sending + +Free-form messages are only delivered inside WhatsApp's 24-hour customer service window — that is, within 24 hours of the customer's last message to you. Outside it Meta requires an approved message template, which this plugin does not send. A message rejected for that reason is logged with the API's error against the conversation. + +Numbers are normalized to the digits-only international form the API expects. A number entered with a leading `+` is taken as already international; otherwise the Default Country Code is prepended when the number does not already start with it. + +## Uninstalling + +Uninstall removes the module, the permission and the settings, and **drops `whatsapp_messages`** — the conversation history goes with it. Disable the plugin instead if you only want to stop sending. diff --git a/app/Plugins/WhatsAppPlugin/Views/config.php b/app/Plugins/WhatsAppPlugin/Views/config.php new file mode 100644 index 000000000..aea2bb94a --- /dev/null +++ b/app/Plugins/WhatsAppPlugin/Views/config.php @@ -0,0 +1,231 @@ + + += form_open(site_url('plugins/saveConfig/whatsapp'), ['id' => 'config_form', 'enctype' => 'multipart/form-data', 'class' => 'form-horizontal']) ?> +
= lang('WhatsAppPlugin.no_messages') ?>
+ + + direction === 'out'; ?> +| = lang('WhatsAppPlugin.phone') ?> | += lang('WhatsAppPlugin.messages') ?> | += lang('WhatsAppPlugin.last_activity') ?> | ++ |
|---|---|---|---|
| = lang('WhatsAppPlugin.no_conversations') ?> | |||
| = esc($conversation['phone']) ?> | += esc($conversation['message_count']) ?> | += esc($conversation['last_activity']) ?> | ++ + | +