mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-10-26 10:08:42 -04:00
feat(plugins): add view hooks for injecting plugin content into core views
Add event-based view hook system allowing plugins to inject UI elements into core views without modifying core files. Includes helper functions and example CasposPlugin demonstrating the pattern.
This commit is contained in:
1 parent
bd8b4fa6c1
commit
66f7d70749
7 files changed
+387
-2
No files matched your search
@@ -205,6 +205,7 @@ class Autoload extends AutoloadConfig
|
||||
'cookie',
|
||||
'tabular',
|
||||
'locale',
|
||||
'security'
|
||||
'security',
|
||||
'plugin'
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use CodeIgniter\Events\Events;
|
||||
|
||||
if (!function_exists('plugin_content'))
|
||||
{
|
||||
function plugin_content(string $section, array $data = []): string
|
||||
{
|
||||
$results = Events::trigger("view:{$section}", $data);
|
||||
|
||||
if (is_array($results))
|
||||
{
|
||||
return implode('', array_filter($results, fn($r) => is_string($r)));
|
||||
}
|
||||
|
||||
return is_string($results) ? $results : '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('plugin_content_exists'))
|
||||
{
|
||||
function plugin_content_exists(string $section): bool
|
||||
{
|
||||
$observers = Events::listRegistered("view:{$section}");
|
||||
return !empty($observers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace App\Plugins\CasposPlugin;
|
||||
|
||||
use App\Libraries\Plugins\BasePlugin;
|
||||
use CodeIgniter\Events\Events;
|
||||
|
||||
class CasposPlugin extends BasePlugin
|
||||
{
|
||||
public function getPluginId(): string
|
||||
{
|
||||
return 'caspos';
|
||||
}
|
||||
|
||||
public function getPluginName(): string
|
||||
{
|
||||
return 'CASPOS Integration';
|
||||
}
|
||||
|
||||
public function getPluginDescription(): string
|
||||
{
|
||||
return 'Azerbaijan government cash register integration for fiscal receipts';
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return '1.0.0';
|
||||
}
|
||||
|
||||
public function registerEvents(): void
|
||||
{
|
||||
Events::on('item_sale', [$this, 'onItemSale']);
|
||||
|
||||
Events::on('view:receipt_actions', [$this, 'injectReceiptButton']);
|
||||
Events::on('view:customer_tabs', [$this, 'injectCustomerTab']);
|
||||
|
||||
$this->log('debug', 'CASPOS plugin events registered');
|
||||
}
|
||||
|
||||
public function install(): bool
|
||||
{
|
||||
$this->log('info', 'Installing CASPOS plugin');
|
||||
|
||||
$this->setSetting('api_url', '');
|
||||
$this->setSetting('api_key', '');
|
||||
$this->setSetting('merchant_id', '');
|
||||
$this->setSetting('show_receipt_button', '1');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function uninstall(): bool
|
||||
{
|
||||
$this->log('info', 'Uninstalling CASPOS plugin');
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getConfigView(): ?string
|
||||
{
|
||||
return 'Plugins/CasposPlugin/Views/config';
|
||||
}
|
||||
|
||||
public function getSettings(): array
|
||||
{
|
||||
return [
|
||||
'api_url' => $this->getSetting('api_url', ''),
|
||||
'api_key' => $this->getSetting('api_key', ''),
|
||||
'merchant_id' => $this->getSetting('merchant_id', ''),
|
||||
'show_receipt_button' => $this->getSetting('show_receipt_button', '1'),
|
||||
];
|
||||
}
|
||||
|
||||
public function saveSettings(array $settings): bool
|
||||
{
|
||||
if (isset($settings['api_url']))
|
||||
{
|
||||
$this->setSetting('api_url', $settings['api_url']);
|
||||
}
|
||||
|
||||
if (isset($settings['api_key']))
|
||||
{
|
||||
$this->setSetting('api_key', $settings['api_key']);
|
||||
}
|
||||
|
||||
if (isset($settings['merchant_id']))
|
||||
{
|
||||
$this->setSetting('merchant_id', $settings['merchant_id']);
|
||||
}
|
||||
|
||||
if (isset($settings['show_receipt_button']))
|
||||
{
|
||||
$this->setSetting('show_receipt_button', $settings['show_receipt_button'] ? '1' : '0');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onItemSale(array $saleData): void
|
||||
{
|
||||
if (!$this->isEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log('info', "CASPOS: Processing sale {$saleData['sale_id_num']}");
|
||||
}
|
||||
|
||||
public function injectReceiptButton(array $data): string
|
||||
{
|
||||
if (!$this->isEnabled() || $this->getSetting('show_receipt_button', '1') !== '1')
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
return view('Plugins/CasposPlugin/Views/receipt_button', $data);
|
||||
}
|
||||
|
||||
public function injectCustomerTab(array $data): string
|
||||
{
|
||||
if (!$this->isEnabled())
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
return view('Plugins/CasposPlugin/Views/customer_tab', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<div class="form-group">
|
||||
<label class="control-label">
|
||||
<span class="glyphicon glyphicon-print"> </span>
|
||||
<?= esc(lang('Plugins.caspos_config') ?? 'CASPOS Configuration') ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="api_url"><?= esc(lang('Plugins.caspos_api_url') ?? 'API URL') ?></label>
|
||||
<input type="text" class="form-control" name="api_url"
|
||||
value="<?= esc($settings['api_url'] ?? '') ?>"
|
||||
placeholder="https://api.caspos.gov.az/v1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="api_key"><?= esc(lang('Plugins.caspos_api_key') ?? 'API Key') ?></label>
|
||||
<input type="text" class="form-control" name="api_key"
|
||||
value="<?= esc($settings['api_key'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="merchant_id"><?= esc(lang('Plugins.caspos_merchant_id') ?? 'Merchant ID') ?></label>
|
||||
<input type="text" class="form-control" name="merchant_id"
|
||||
value="<?= esc($settings['merchant_id'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="show_receipt_button" value="1"
|
||||
<?= ($settings['show_receipt_button'] ?? '1') === '1' ? 'checked' : '' ?>>
|
||||
<?= esc(lang('Plugins.caspos_show_button') ?? 'Show fiscal receipt button on receipts') ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<li>
|
||||
<a href="#caspos_panel" data-toggle="tab">
|
||||
<span class="glyphicon glyphicon-print"> </span>
|
||||
<?= esc(lang('Plugins.caspos_tab') ?? 'Fiscal Records') ?>
|
||||
</a>
|
||||
</li>
|
||||
@@ -0,0 +1,4 @@
|
||||
<a href="javascript:void(0);" class="btn btn-info btn-sm" id="show_print_caspos_button" onclick="printCasposReceipt(<?= $sale['sale_id'] ?? '' ?>)">
|
||||
<span class="glyphicon glyphicon-print"> </span>
|
||||
<?= esc(lang('Plugins.caspos_print_receipt') ?? 'Print Fiscal Receipt') ?>
|
||||
</a>
|
||||
+186
-1
@@ -100,6 +100,123 @@ OSPOS fires these events that plugins can listen to:
|
||||
| `items_csv_import` | `array $importData` | Fired after items CSV import |
|
||||
| `customers_csv_import` | `array $importData` | Fired after customers CSV import |
|
||||
|
||||
## View Hooks (Injecting Plugin Content into Views)
|
||||
|
||||
Plugins can inject UI elements into core views using the event-based view hook system. This allows plugins to add buttons, tabs, or other content without modifying core view files.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Core views define hook points** using the `plugin_content()` helper
|
||||
2. **Plugins register listeners** for these view hooks in `registerEvents()`
|
||||
3. **Content is conditionally rendered** only if the plugin is enabled
|
||||
|
||||
### Step 1: Adding Hook Points in Core Views
|
||||
|
||||
In your core view files, use the `plugin_content()` helper to define injection points:
|
||||
|
||||
```php
|
||||
// In app/Views/sales/receipt.php
|
||||
<div class="receipt-actions">
|
||||
<!-- Existing buttons -->
|
||||
<?= plugin_content('receipt_actions', ['sale' => $sale]) ?>
|
||||
</div>
|
||||
|
||||
// In app/Views/customers/form.php
|
||||
<ul class="nav nav-tabs">
|
||||
<!-- Existing tabs -->
|
||||
<?= plugin_content('customer_tabs', ['customer' => $customer]) ?>
|
||||
</ul>
|
||||
```
|
||||
|
||||
### Step 2: Plugin Registers View Hook
|
||||
|
||||
In your plugin class, register a listener that returns HTML content:
|
||||
|
||||
```php
|
||||
class CasposPlugin extends BasePlugin
|
||||
{
|
||||
public function registerEvents(): void
|
||||
{
|
||||
Events::on('item_sale', [$this, 'onItemSale']);
|
||||
|
||||
// View hooks - inject content into core views
|
||||
Events::on('view:receipt_actions', [$this, 'injectReceiptButton']);
|
||||
Events::on('view:customer_tabs', [$this, 'injectCustomerTab']);
|
||||
}
|
||||
|
||||
public function injectReceiptButton(array $data): string
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Return HTML from plugin's own view file
|
||||
return view('Plugins/CasposPlugin/Views/receipt_button', $data);
|
||||
}
|
||||
|
||||
public function injectCustomerTab(array $data): string
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return view('Plugins/CasposPlugin/Views/customer_tab', $data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Plugin View Files
|
||||
|
||||
The plugin's view files are self-contained within the plugin directory:
|
||||
|
||||
```php
|
||||
// app/Plugins/CasposPlugin/Views/receipt_button.php
|
||||
<a href="javascript:void(0);" class="btn btn-info btn-sm" onclick="printCasposReceipt(<?= $sale['sale_id'] ?>)">
|
||||
<span class="glyphicon glyphicon-print"> </span>
|
||||
Print Fiscal Receipt
|
||||
</a>
|
||||
|
||||
// app/Plugins/CasposPlugin/Views/customer_tab.php
|
||||
<li>
|
||||
<a href="#caspos_panel" data-toggle="tab">
|
||||
<span class="glyphicon glyphicon-print"> </span>
|
||||
Fiscal Records
|
||||
</a>
|
||||
</li>
|
||||
```
|
||||
|
||||
### Helper Functions
|
||||
|
||||
The `plugin_helper.php` provides two functions:
|
||||
|
||||
```php
|
||||
// Render plugin content for a hook point
|
||||
plugin_content(string $section, array $data = []): string
|
||||
|
||||
// Check if any plugin has registered for a hook (for conditional rendering)
|
||||
plugin_content_exists(string $section): bool
|
||||
```
|
||||
|
||||
### Standard Hook Points
|
||||
|
||||
Core views should define these standard hook points:
|
||||
|
||||
| Hook Name | Location | Usage |
|
||||
|-----------|----------|-------|
|
||||
| `view:receipt_actions` | Receipt view action buttons | Add receipt-related buttons |
|
||||
| `view:customer_tabs` | Customer form tabs | Add customer-related tabs |
|
||||
| `view:item_form_buttons` | Item form action buttons | Add item-related buttons |
|
||||
| `view:sales_complete` | Sale complete screen | Post-sale integration UI |
|
||||
| `view:reports_menu` | Reports menu | Add custom report links |
|
||||
|
||||
### Benefits
|
||||
|
||||
- **Self-Contained**: Plugin UI stays in plugin directory
|
||||
- **Conditional**: Only renders when plugin is enabled
|
||||
- **Data Access**: Pass context (sale, customer, etc.) to plugin views
|
||||
- **Multiple Plugins**: Multiple plugins can hook the same location
|
||||
- **Clean Separation**: Core views remain unmodified
|
||||
|
||||
## Creating a Plugin
|
||||
|
||||
### Simple Plugin (Single File)
|
||||
@@ -485,4 +602,72 @@ CasposPlugin-1.0.0.zip
|
||||
└── README.md # Plugin documentation
|
||||
```
|
||||
|
||||
Users extract the zip to `app/Plugins/` and the plugin is ready to use.
|
||||
Users extract the zip to `app/Plugins/` and the plugin is ready to use.
|
||||
|
||||
## Example: Plugin with View Hooks
|
||||
|
||||
Here's a complete example of a plugin that injects UI elements into core views:
|
||||
|
||||
```
|
||||
app/Plugins/CasposPlugin/
|
||||
├── CasposPlugin.php # Main class with view hook registration
|
||||
└── Views/
|
||||
├── receipt_button.php # Button for receipt view
|
||||
├── customer_tab.php # Tab for customer form
|
||||
└── config.php # Plugin configuration
|
||||
```
|
||||
|
||||
**Main Plugin Class:**
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace App\Plugins\CasposPlugin;
|
||||
|
||||
use App\Libraries\Plugins\BasePlugin;
|
||||
use CodeIgniter\Events\Events;
|
||||
|
||||
class CasposPlugin extends BasePlugin
|
||||
{
|
||||
public function getPluginId(): string { return 'caspos'; }
|
||||
|
||||
public function getPluginName(): string { return 'CASPOS Integration'; }
|
||||
|
||||
public function registerEvents(): void
|
||||
{
|
||||
// Data events
|
||||
Events::on('item_sale', [$this, 'onItemSale']);
|
||||
|
||||
// View hooks - inject content into core views
|
||||
Events::on('view:receipt_actions', [$this, 'injectReceiptButton']);
|
||||
Events::on('view:customer_tabs', [$this, 'injectCustomerTab']);
|
||||
}
|
||||
|
||||
public function injectReceiptButton(array $data): string
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return '';
|
||||
}
|
||||
return view('Plugins/CasposPlugin/Views/receipt_button', $data);
|
||||
}
|
||||
|
||||
public function injectCustomerTab(array $data): string
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return '';
|
||||
}
|
||||
return view('Plugins/CasposPlugin/Views/customer_tab', $data);
|
||||
}
|
||||
|
||||
// ... rest of implementation
|
||||
}
|
||||
```
|
||||
|
||||
Core views that want to support plugin hooks:
|
||||
|
||||
```php
|
||||
// app/Views/sales/receipt.php
|
||||
<div class="receipt-actions">
|
||||
<?= anchor('sales/reprint/' . $sale_id, 'Reprint', 'class="btn btn-primary"') ?>
|
||||
<?= plugin_content('view:receipt_actions', ['sale' => $sale]) ?>
|
||||
</div>
|
||||
```
|
||||
Reference in new issue
Block a user