Scriptlog plugin system uses a hook-based architecture. Plugins register hooks using the clip() function, and those hooks are executed at specific points in the application.
When a user uploads a plugin ZIP:
PluginController::installPlugin()validates the ZIPupload_plugin()extracts and validates structure- Plugin info from
plugin.iniis saved totbl_plugin - Plugin appears in admin panel (status: Disabled)
When a user clicks "Enable":
PluginController::enablePlugin($id)is calledPluginService::activateInstalledPlugin():- Executes
schema.sqlif exists (for database tables) - Calls
enable_plugin($plugin_path)to load PHP files - Updates
plugin_status= 'Y' in database
- Executes
- The plugin's PHP class is loaded, hooks are registered
When a user clicks "Disable":
PluginController::disablePlugin($id)is calledPluginService::deactivateInstalledPlugin()updatesplugin_status= 'N'- Plugin files remain but hooks are not loaded
When a user clicks "Delete":
PluginController::remove($id)is calledPluginService::removePlugin()deletes:- Plugin directory from
admin/plugins/ - Database record from
tbl_plugin
- Plugin directory from
admin/plugins/[plugin-name]/
├── plugin.ini # REQUIRED - Plugin configuration
├── YourClassFile.php # REQUIRED - Main plugin class
├── functions.php # OPTIONAL - Helper functions
└── schema.sql # OPTIONAL - Database schema
[INFO]
plugin_name = "My Plugin Name"
plugin_description = "Description of what the plugin does"
plugin_level = "administrator"
plugin_version = "1.0.0"
plugin_author = "Your Name"
plugin_loader = "MyPlugin"
plugin_action = "my-plugin"Fields:
| Field | Required | Description |
|---|---|---|
| plugin_name | Yes | Display name |
| plugin_description | Yes | What the plugin does |
| plugin_level | Yes | "administrator" or "manager" |
| plugin_version | Yes | Semantic version (e.g., 1.0.0) |
| plugin_author | Yes | Author name |
| plugin_loader | Yes | PHP class filename (without .php) |
| plugin_action | Yes | Action identifier for routing |
<?php defined('SCRIPTLOG') || die("Direct access not permitted");
class MyPlugin
{
private $pluginDir;
public function __construct()
{
$this->pluginDir = dirname(__FILE__);
$this->registerHooks();
}
private function registerHooks()
{
// Register frontend content hook
clip('clip_my_plugin', null, function($content = '') {
return $this->frontendContent($content);
});
// Register admin page hook
clip('clip_my_plugin_admin', null, function() {
return $this->adminPage();
});
}
public function activate()
{
// Runs on activation - create tables, set options
return true;
}
public function deactivate()
{
// Runs on deactivation - cleanup temporary data
return true;
}
public function uninstall()
{
// Runs on deletion - remove all plugin data
return true;
}
public function adminPage()
{
return '<div class="box">
<div class="box-header"><h3>My Plugin</h3></div>
<div class="box-body">
<p>Plugin is active!</p>
</div>
</div>';
}
public function frontendContent($content = '')
{
return $content . '<div class="my-plugin">Hello from my plugin!</div>';
}
}<?php defined('SCRIPTLOG') || die("Direct access not permitted");
function my_plugin_instance()
{
static $instance = null;
if (null === $instance) {
$instance = new MyPlugin();
}
return $instance;
}
function my_plugin_display($content = '')
{
return my_plugin_instance()->frontendContent($content);
}-- Create tables for your plugin
CREATE TABLE IF NOT EXISTS tbl_my_plugin (
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Add DROP statement for uninstall (commented by default)
-- DROP TABLE IF EXISTS tbl_my_plugin;clip('hook_name', $value, $callback);Parameters:
hook_name: Unique identifier for the hook$value: Value to pass through the hook chain$callback: Function to process the value (or null to execute hooks)
Usage:
// Register a hook (attach callback)
clip('my_hook', null, function($value) {
return $value . ' modified';
});
// Execute all callbacks attached to hook
$result = clip('my_hook', 'original');
// $result = 'original modified'| Hook Name | Description | Value Passed |
|---|---|---|
clip_[plugin_name] |
Main plugin hook | Content string |
clip_content |
Content filter | Post content |
clip_footer |
Footer section | - |
clip_header |
Header section | - |
| Hook Name | Description |
|---|---|
clip_[plugin_name]_admin |
Admin page content |
clip_plugin_menu |
Plugin navigation |
// Check if plugin is enabled
if (is_plugin_enabled('my-plugin')) {
// Invoke plugin hook
$content = invoke_plugin('my-plugin', $content);
}// Check if plugin exists in database
is_plugin_exist('plugin-name');
// Check if plugin is enabled
is_plugin_enabled('plugin-name');
// Execute plugin hook
invoke_plugin('plugin-name', $args);
// Set plugin navigation in admin sidebar
set_plugin_navigation('plugin-name');admin/plugins/social-share/
├── plugin.ini
├── SocialSharePlugin.php
└── functions.php
[INFO]
plugin_name = "Social Share"
plugin_description = "Add social sharing buttons to posts"
plugin_level = "administrator"
plugin_version = "1.0.0"
plugin_author = "Developer Name"
plugin_loader = "SocialSharePlugin"
plugin_action = "social-share"<?php defined('SCRIPTLOG') || die("Direct access not permitted");
class SocialSharePlugin
{
private $pluginDir;
public function __construct()
{
$this->pluginDir = dirname(__FILE__);
$this->registerHooks();
}
private function registerHooks()
{
// Add social share buttons after post content
clip('clip_social_share', null, function($content = '') {
return $this->addSocialButtons($content);
});
// Add admin settings page
clip('clip_social_share_admin', null, function() {
return $this->adminPage();
});
}
public function activate()
{
return true;
}
public function deactivate()
{
return true;
}
public function addSocialButtons($content = '')
{
$buttons = '<div class="social-share" style="margin-top:20px; padding:15px; border-top:1px solid #eee;">
<h4>Share this post</h4>
<a href="#" class="btn btn-primary">Facebook</a>
<a href="#" class="btn btn-info">Twitter</a>
<a href="#" class="btn btn-danger">Google+</a>
</div>';
return $content . $buttons;
}
public function adminPage()
{
return '<div class="box box-primary">
<div class="box-header"><h3>Social Share Settings</h3></div>
<div class="box-body">
<p>Configure your social share buttons here.</p>
</div>
</div>';
}
}<?php defined('SCRIPTLOG') || die("Direct access not permitted");
function social_share_plugin()
{
static $instance = null;
if (null === $instance) {
$instance = new SocialSharePlugin();
}
return $instance;
}
function social_share_buttons($content = '')
{
return social_share_plugin()->addSocialButtons($content);
}- Install: Upload ZIP → Should appear in plugin list (disabled)
- Activate: Click Enable → Status changes to "Enabled"
- Test functionality:
- Frontend: Visit site, check for plugin output
- Backend: Visit plugin admin page if available
- Deactivate: Click Disable → Status changes to "Disabled"
- Reactivate: Click Enable again → Should work
- Delete: Click Delete → Plugin removed from list and directory
// Add debugging to your plugin
public function __construct()
{
$this->pluginDir = dirname(__FILE__);
// Debug: Log plugin initialization
error_log('MyPlugin: Initialized at ' . date('Y-m-d H:i:s'));
$this->registerHooks();
}| Issue | Solution |
|---|---|
| Plugin not appearing in list | Check plugin.ini syntax |
| Plugin loads but no output | Ensure hooks are registered in constructor |
| Activation fails | Check schema.sql syntax |
| Hook not firing | Verify clip() call exists and plugin is enabled |
- Always check
defined('SCRIPTLOG')at the top of PHP files - Always use unique hook names with plugin prefix
- Always sanitize user input in your plugin
- Avoid modifying core files - use hooks instead
- Document your plugin's hooks and functions
- Test on a development environment first
- Version your plugin using semantic versioning