Files
opensourcepos/app/Views/plugins/manage.php
Ollama a9669ddf19 feat(plugins): Implement modular plugin system with self-registering events
This implements a clean plugin architecture based on PR #4255 discussion:

Core Components:
- PluginInterface: Standard contract all plugins must implement
- BasePlugin: Abstract class with common functionality
- PluginManager: Discovers and loads plugins from app/Plugins/
- Plugin_config: Model for plugin settings storage

Architecture:
- Each plugin registers its own event listeners via registerEvents()
- No hardcoded plugin dependencies in core Events.php
- Generic event triggers (item_sale, item_change, etc.) remain in core code
- Plugins can be enabled/disabled via database settings
- Clean separation: plugin orchestrators vs MVC components

Example Implementations:
- ExamplePlugin: Simple plugin demonstrating event logging
- MailchimpPlugin: Integration with Mailchimp for customer sync

Admin UI:
- Plugin management controller at Controllers/Plugins/Manage.php
- Plugin management view at Views/plugins/manage.php

Database:
- ospos_plugin_config table for plugin settings (key-value store)
- Migration creates table with timestamps

Documentation:
- Comprehensive README with architecture patterns
- Simple vs complex plugin examples
- MVC directory structure guidance
2026-03-09 21:58:53 +01:00

119 lines
6.1 KiB
PHP

<?= view('partial/header') ?>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<span class="glyphicon glyphicon-puzzle"></span> <?= lang('Plugins.plugin_management') ?>
</h3>
</div>
<div class="panel-body">
<?php if (empty($plugins)): ?>
<div class="alert alert-info">
<?= lang('Plugins.no_plugins_found') ?>
</div>
<?php else: ?>
<table class="table table-striped table-hover">
<thead>
<tr>
<th><?= lang('Plugins.plugin_name') ?></th>
<th><?= lang('Plugins.plugin_description') ?></th>
<th><?= lang('Plugins.plugin_version') ?></th>
<th><?= lang('Plugins.plugin_status') ?></th>
<th><?= lang('Common.actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($plugins as $pluginId => $plugin): ?>
<tr id="plugin-row-<?= esc($pluginId) ?>">
<td>
<strong><?= esc($plugin['name']) ?></strong>
<br><small class="text-muted"><?= esc($plugin['id']) ?></small>
</td>
<td><?= esc($plugin['description']) ?></td>
<td><span class="label label-default"><?= esc($plugin['version']) ?></span></td>
<td>
<?php if ($plugin['enabled']): ?>
<span class="label label-success"><?= lang('Plugins.active') ?></span>
<?php else: ?>
<span class="label label-default"><?= lang('Plugins.inactive') ?></span>
<?php endif; ?>
</td>
<td>
<?php if ($plugin['enabled']): ?>
<button class="btn btn-warning btn-xs plugin-action"
data-action="disable"
data-plugin-id="<?= esc($pluginId) ?>">
<span class="glyphicon glyphicon-pause"></span> <?= lang('Plugins.disable') ?>
</button>
<?php else: ?>
<button class="btn btn-success btn-xs plugin-action"
data-action="enable"
data-plugin-id="<?= esc($pluginId) ?>">
<span class="glyphicon glyphicon-play"></span> <?= lang('Plugins.enable') ?>
</button>
<?php endif; ?>
<?php if ($plugin['has_config'] && $plugin['enabled']): ?>
<button class="btn btn-primary btn-xs plugin-config"
data-plugin-id="<?= esc($pluginId) ?>">
<span class="glyphicon glyphicon-cog"></span> <?= lang('Plugins.configure') ?>
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<!-- Plugin Config Modal -->
<div class="modal fade" id="plugin-config-modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title"><?= lang('Plugins.plugins') ?></h4>
</div>
<div class="modal-body" id="plugin-config-content">
<!-- Config form loaded dynamically -->
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
$('.plugin-action').on('click', function() {
var btn = $(this);
var action = btn.data('action');
var pluginId = btn.data('plugin-id');
$.post('plugins/manage/' + action + '/' + pluginId, {
<?= esc(csrf_token()) ?>: '<?= esc(csrf_hash()) ?>'
}, function(response) {
if (response.success) {
$.notify({ message: response.message }, { type: 'success' });
setTimeout(function() { location.reload(); }, 1000);
} else {
$.notify({ message: response.message }, { type: 'danger' });
}
}, 'json');
});
$('.plugin-config').on('click', function() {
var pluginId = $(this).data('plugin-id');
$('#plugin-config-content').load('plugins/manage/config/' + pluginId);
$('#plugin-config-modal').modal('show');
});
});
</script>
<?= view('partial/footer') ?>