Plugin System Introduction¶
This chapter introduces the LabPress plugin system. It describes the architectural principles, the hook mechanism, the plugin structure, the lifecycle, and how plugins integrate with the core without modifying it. The information is intended for developers who want to understand the plugin system before creating their own extensions.
For hands-on instructions on building a plugin, refer to Developing Your First Plugin. For a detailed list of available hooks, see Hooks Reference.
1. Overview¶
LabPress includes a lightweight plugin system inspired by the WordPress hook model. It allows developers to add new functionality or modify existing behavior without changing any core file.
Plugins are self-contained directories inside the plugins/ folder. Each plugin contains a plugin.php file that describes the plugin and registers callbacks on actions and filters. The core loads active plugins during initialization and provides them with access to the LabPress hook system, configuration, database, and translation functions.
The plugin system is designed around four principles:
- Isolation β Plugins live in separate directories and do not modify core files.
- Extensibility β All core output can be modified through hooks.
- Lifecycle awareness β Plugins can run setup and cleanup tasks through lifecycle hooks.
- Dynamic discovery β Plugins can be registered, activated, deactivated, and uninstalled through the admin panel.
2. Core Components¶
The plugin system consists of the following components.
2.1 LabPress_Hooks Class¶
The LabPress_Hooks class is the foundation of the plugin system. It provides static methods for registering and triggering actions and filters.
Actions are executed at specific points during the request lifecycle. Filters modify data and return the modified value.
The class is defined in includes/hooks.php and maintains two static arrays:
$actionsβ stores action callbacks by hook name and priority.$filtersβ stores filter callbacks by hook name and priority.
Callbacks registered with higher priority values are executed later. All callbacks for the same priority are executed in the order they were registered.
2.2 plugins Directory¶
All plugin files are stored under:
Each plugin occupies its own directory. The directory name is the plugin slug and must be unique. For example:
plugins/
βββ FooterInfo/
β βββ plugin.php
βββ dynamic-multilang/
β βββ plugin.php
β βββ admin-page.php
βββ hello-word/
βββ plugin.php
The plugin slug must match the directory name exactly because the database registration and the plugin loading process both rely on this value.
2.3 plugins Database Table¶
The core maintains a plugins table that tracks plugin metadata and activation status.
| Column | Description |
|---|---|
slug | Plugin directory name, unique |
name | Human-readable plugin name from the plugin header |
description | Short description from the plugin header |
version | Plugin version |
author | Plugin author |
status | 1 for active, 0 for inactive |
The admin plugin manager uses this table to display plugin information and to control activation and deactivation.
3. Hook System¶
The hook system is the primary extension mechanism in LabPress. It provides two types of hooks.
3.1 Actions¶
Actions allow plugins to execute code at specific points in the request.
Registering an action:
Triggering an action:
When doAction() is called, all callbacks registered for that hook are executed in priority order. The function supports passing additional arguments to callbacks.
Example:
The core triggers footer_scripts before the closing body tag on the frontend, so this callback will output text in the footer.
3.2 Filters¶
Filters allow plugins to modify data before it is returned or output.
Registering a filter:
Applying a filter:
The first argument to applyFilters() is the value to modify. Each registered callback receives the current value as its first parameter and must return the modified value.
Example:
When the core applies the news_title filter, all news titles will be prefixed with [LabPress].
3.3 Hook Naming Conventions¶
Core hooks use descriptive names that indicate their location or purpose. Examples include:
initβ fired after all plugins are loaded.before_head_endβ fired in the frontend<head>.footer_scriptsβ fired in the frontend footer.admin_before_head_endβ fired in the admin<head>.nav_menu_itemβ filter applied to each menu item.translateβ filter applied to translated strings.slides_dataβ filter applied to slide data.
Plugins can define their own custom hooks, but they are not required to do so. A full list of core hooks is available in Hooks Reference.
4. Plugin Structure¶
A minimal plugin consists of a single plugin.php file inside a directory under plugins/. The file must contain a header comment that provides metadata about the plugin.
Example minimal plugin:
<?php
/**
* Plugin Name: Hello World
* Description: Adds a greeting to the footer.
* Version: 1.0.0
* Author: LabPress
*/
LabPress_Hooks::addAction('footer_scripts', function() {
echo '<p>Hello World!</p>';
});
The header comment is parsed by the plugin scanner to populate the plugins table.
In addition to plugin.php, a plugin may include:
admin-page.phpβ a callback for a custom admin page.languages/β plugin language files.assets/β CSS and JavaScript files.- Other PHP files or classes needed by the plugin.
Plugins do not need to use a special class or base class. They can be written as plain procedural PHP with closures, or they can use classes and namespaces.
5. Plugin Loading¶
Active plugins are loaded during the initialization phase of every request.
The loading process is implemented in includes/functions.php through the loadPlugins() function. The function:
- Retrieves all active plugin slugs from the
pluginstable usinggetActivePlugins(). - Sanitizes each slug to allow only letters, numbers, underscores, and hyphens.
- For each active plugin, builds the path:
- If the file exists, it is included using
require_once.
After all active plugins are loaded, the core fires the init action hook. Plugins should register their own actions and filters on init to ensure that all core functions and the database connection are available.
6. Plugin Lifecycle¶
The plugin lifecycle consists of four main stages: discovery, activation, deactivation, and uninstallation.
6.1 Discovery¶
Plugins that exist in the plugins/ directory are not automatically registered in the database. The Scan for New Plugins button in the admin panel triggers the syncPlugins() function, which scans the directory and inserts any new plugin directories into the plugins table with a status of 0.
6.2 Activation¶
Activation updates the plugin status to 1 and includes the plugin file. The core fires the plugin_activation action hook after updating the status.
If the plugin file defines a function named plugin_install(), that function is called during activation. After that, the plugin_installed action hook is fired.
Plugins use activation to create custom database tables, register default options, or perform other setup tasks.
6.3 Deactivation¶
Deactivation updates the plugin status to 0. The core fires the plugin_deactivation action hook.
The current implementation also attempts to call plugin_uninstall() if the function exists, and fires the plugin_uninstalled action. This behavior may be refined in future versions. Developers should ensure that any cleanup logic is safe to run during deactivation or should only place destructive cleanup in the uninstall hook.
6.4 Uninstallation¶
Uninstallation is available only for inactive plugins. The process:
- Fires the
plugin_uninstalledaction. - Deletes the plugin directory from
plugins/. - Removes the plugin record from the
pluginstable.
Plugins use the plugin_uninstalled hook to drop database tables, delete options, or remove other data before their files are deleted.
7. Admin Pages¶
Plugins can register custom admin pages using the LabPress::registerAdminPage() method.
The method accepts:
$slugβ the page identifier, used in the?view=query parameter.$titleβ the title shown in the admin sidebar.$callbackβ a callable that renders the page content.$permissionβ the permission required to view the page.
Registered pages are merged into the admin menu during the admin panel initialization. The menu item appears in the sidebar, and the page content is rendered when the corresponding view is requested.
8. Plugin Language Support¶
Plugins can provide their own language files under a languages/ directory inside the plugin folder.
The core supports two loading methods:
- Automatic loading β The translation function scans all active plugins and merges their language files into the global language array. This can cause key collisions if plugin keys are not unique.
- Explicit loading β The plugin calls
__('key', 'plugin:plugin-slug')to load translations only from that plugin. This avoids conflicts and is the recommended approach for plugins that define common keys.
Plugin language keys should use a unique prefix even when using explicit loading, to avoid confusion and to make the translation files easier to maintain.
For more details, see Core Language Packs.
9. Accessing Core Services¶
Plugins can access core services through static methods and global functions provided by LabPress.
Commonly used services include:
LabPress::db()β returns the global PDO instance for database access.LabPress::config($key, $default)β returns a site configuration value.LabPress::setConfig($key, $value)β sets a site configuration value.LabPress::enqueueStyle($url)β registers a stylesheet to be loaded on the frontend.LabPress::enqueueScript($url)β registers a script to be loaded in the frontend footer.__($key, $module)β returns a translated string.getLocale()β returns the current locale.
Plugins can also use global functions such as getNavMenus(), getSlides(), and getActivePlugins() when needed. However, using the public API methods is preferred for compatibility.
10. Decoupling from Core¶
A key design goal of the plugin system is zero coupling. The core never references plugin functions directly. Instead, plugins observe and modify core behavior through hooks.
Examples:
- The Dynamic Multi-Language plugin translates dynamic content using the
translatefilter. The core template only calls__(). It has no knowledge of the plugin. - The FooterInfo plugin adds footer text using the
footer_scriptsaction. The footer template only fires the hook. - Menu items are translated through the
nav_menu_itemfilter. The header and footer templates apply this filter without knowing which plugins are active.
This design means that disabling or uninstalling all plugins leaves a fully functional core site that simply loses the extra plugin-provided features.
11. Security Considerations¶
Plugins can execute arbitrary PHP code. Therefore, only trusted plugins should be installed and activated on a production site.
The admin plugin manager is restricted to users with the all permission. The upload installer performs basic checks, such as verifying that the uploaded file is a ZIP archive and contains a plugin.php file, but it does not inspect the code for malicious behavior.
Administrators should:
- Install plugins only from trusted sources.
- Review plugin code before activation.
- Keep the
plugins/directory protected from direct public access. - Back up the site before installing untested plugins.
12. Next Steps¶
After reading this introduction, continue with:
- Developing Your First Plugin β a practical guide to building a plugin.
- Hooks Reference β the complete list of core actions and filters.
- Plugin Examples β working examples of plugins included with LabPress.
- Plugin Management β how to manage plugins from the admin panel.
- Core Language Packs β how plugin language files are loaded and used.