Extension
Adds features to the core application.
Table of Contents
An extension doesn’t necessarily have to modify the core functionality of your application. It can also serve as an API enhancer that does nothing until it’s implemented in a specific layout and utilized by other extensions that depend on it.
Structure
Each extension is stored as a folder within the .\lot\x
directory, with a distinct name:
.\
└── lot\
└── x\
├── extension-1\
├── extension-2\
├── extension-3\
└── …
The following files are typically included in a standard extension:
.\lot\x\extension-1\
├── engine\
│ ├── kernel\
│ │ └── extension-1.php
│ └── plug\
│ └── extension-1.php
├── .gitattributes
├── .gitignore
├── .htaccess
├── LICENSE
├── README.md
├── about.page
├── composer.json
├── index.css
├── index.js
├── index.php
├── index.png
├── package.json
├── state.php
└── task.php
Alternatively, if you prefer a minimalist approach, you can create an extension that consists solely of the index.php
file, as demonstrated in this example:
.\lot\x\extension-1\
└── index.php
Files
.gitattributes
You can add this file to specify which files or folders should be excluded from the archive content when downloading a repository for production. Typically, this file contains a list of commands, such as the following example, which excludes the .gitignore
, README.md
, and the .gitattributes
file itself:
.gitattributes export-ignore
.gitignore export-ignore
README.md export-ignore
.gitignore
This Git-specific file excludes certain files/folders from being committed to the repository, preventing accidental inclusion. However, it should be removed in the production version. Certain package files are also excluded, as they should be freshly installed using CLI commands rather than included in the repository for others to clone:
composer.lock
node_modules
package-lock.json
vendor
.htaccess
You can use this file to enable direct access to files with specific extensions within the folder. Keep in mind that for security reasons, all files in the .\lot
folder are not directly accessible by default. Here’s an example Apache server command that allows direct access to files with .css
, .js
, and .png
extensions:
<Files ~ '[.](css|js|png)$'>
Allow from all
</Files>
To improve readability, consider using the <FilesMatch>
directive. Use the <Files>
directive only to specify exact file names. Keep in mind that this approach can result in lengthy and cumbersome directives:
<Files 'index.css'>
Allow from all
</Files>
<Files 'index.html'>
Allow from all
</Files>
<Files 'index.js'>
Allow from all
</Files>
<FilesMatch '^index\.(a?png|gif|jpe?g)$'>
Allow from all
</FilesMatch>
LICENSE
Mecha is GPL-3.0 licensed, so include a paragraph in this file that specifies the same license.
README.md
This file isn’t essential to the extension but may be necessary for the repository to appear complete on platforms like GitHub and GitLab. Since it’s ignored in the .gitattributes
file, it won’t be included in the archive.
about.page
This file provides information that appears in the control panel’s information page and functions similarly to the README.md
file. It’s designed to be read by the official control panel feature:
---
title: Extension Title
description: Extension description.
type: Markdown
author: Your Name
link: 'http://example.com'
version: 1.0.0
...
Extension usage instruction, written with Markdown syntax.
Below are commonly used about.page
properties and their priority:
Name | Priority | Usage |
---|---|---|
author | 2 | Determines the extension’s author. |
color | 3 | Determines the extension’s icon background color. This data is used exclusively by the control panel extension. Other extensions can use this data for other purposes or can simply ignore it. |
description | 2 | Determines the extension’s description. |
icon | 2 | Determines the extension’s icon path. This data is used exclusively by the control panel extension. Other extensions can use this data for other purposes or can simply ignore it. |
image | 2 | Determines the extension’s image preview. |
images | 2 | Determines the extension’s image previews, usually to be presented in a carousel. |
link | 2 | Determines the extension’s related link. |
price | 2 | Determines the extension’s default price. |
prices | 2 | Determines the extension’s price or prices details. |
stroke | 4 | Determines the extension’s icon color. This data is used exclusively by the control panel extension. Other extensions can use this data for other purposes or can simply ignore it. |
title | 1 | Determines the extension’s title. |
type | 2 | Determines the page content’s type. |
version | 1 | Determines the extension’s version. Without this property, the control panel’s automatic extension version check feature might not apply to the extension. |
External extensions can access this file like a regular page file:
$page = new Page(__DIR__ . D . 'x' . D . 'extension-name' . D . 'about.page');
echo '<h2>' . $page->title . '</h2>';
echo '<p>' . $page->description . '</p>';
echo '<div>' . $page->content . '</div>';
composer.json
Add this file to your extension repository to publish it on Packagist and enable installation via Composer commands. Ensure that the package name follows the correct pattern for our Composer plugin to install it correctly in the extension folder:
{vendor}/x.{folder}
Below is an example of a composer.json
file for your extension repository, assuming it’s already published on GitHub at https://github.com/your-name/x.your-extension-name
:
{
"description": "Your extension description.",
"name": "your-name/x.your-extension-name"
}
To make your extension available for installation via Composer, you’ll need to create a stable version release in your repository and publish it on Packagist. You can do this by visiting https://packagist.org/packages/submit and following the instructions. Once your package is successfully published, others can install your extension using the following Composer command:
composer require your-name/x.your-extension-name
Your new extension should be installed in the .\lot\x\your-extension-name
location smoothly.
index.css
This is a regular CSS file that is loaded in the extension. While it doesn’t have to be named index.css
, I recommend that you use this name as it pairs neatly with index.php
. Once you create the file, ensure that it is publicly accessible by including it in the .htaccess
file, and load it using the Asset extension in the index.php
file.
index.js
If you have an index.css
file, chances are you will also need to include this file. The rule for using and loading it is the same as the index.css
file.
index.php
This file serves as the main entry point for an extension, and is automatically loaded each time the user accesses the application. You can also use this file to load any other files that you want to include in the extension:
<?php
// Load `index.css` and `index.js` files to the page
if (class_exists('Asset')) {
Asset::set(__DIR__ . D . 'index.css');
Asset::set(__DIR__ . D . 'index.js');
}
index.png
You can use this image to give a quick demonstration of how your extension would look like. The actual name of the image is not specific. In order to display this image in the extension information page, you need to manually register it in the image
property of the about.page
file. I suggest naming the image index.png
as it pairs well with index.php
:
---
title: Extension Title
description: Extension description.
image: /lot/x/extension-name/index.png
type: Markdown
...
Review the `.htaccess` file specification if the image is not displaying on the extension information page.
Typically, standard image dimensions for extension demonstration purposes have a 4:3 aspect ratio, and can be of any size, though commonly made with a width and height of 1024×768 or 1200×900 pixels. Additionally, you may also use other image extensions such as .gif
and .jpg
if necessary.
package.json
This file is used exclusively by NPM and does not need to be stored in the extension folder. It is necessary to include this file in order to add build tools for generating the index.css
and index.js
files from external NPM modules. Since Mecha’s extension is not designed to be published on NPM, you don’t need to provide a package name. This file is used to develop CSS and JavaScript code locally. It will be removed in the production version:
{
"devDependencies": {
"@taufik-nurrohman/factory": "*"
},
"private": true,
"scripts": {
"pack": "pack --clean=false --from=.factory --js-format=iife --to=."
},
"type": "module"
}
state.php
This file should return an array that can be used by the extension itself and other extensions that depend on it. The data in the array will be automatically loaded as $state->x->{'extension-name'}
object. If this file doesn’t exist, the $state->x->{'extension-name'}
object will return an empty array:
<?php
return [
'active' => true,
'type' => 2
];
The extension object is useful not only for configuring extension options but also for verifying if a particular extension is installed before activating certain features. Mecha does not have any specific method to check for extension availability, except by using this object or by verifying the existence of specific classes, files, and/or functions:
<?php
if (!extension_loaded('gd')) {
abort(i('Missing %s extension.', ['PHP <a href="https://www.php.net/book.image" rel="nofollow" target="_blank">gd</a>']));
}
if (!isset($state->x->asset)) {
abort(i('Missing %s extension.', ['<a href="https://github.com/mecha-cms/x.asset">Asset</a>']));
}
if (empty($state->x->{'extension-name'}->active)) {
// Extension is disabled by the author via `state.php` file
return;
}
$type = $state->x->{'extension-name'}->type ?? 1; // Set default extension type to `1`
if (1 === $type) {
// Do something if `type` value is `1`
} else if (2 === $type) {
// Do something if `type` value is `2`
} else {
abort(i('Invalid %s value.', ['<code>type</code>']));
}
task.php
This optional file is loaded before the index.php
file and allows you to perform specific tasks such as automatic file repair before the extension is loaded. Once all the tasks are completed, you can add a command to delete the file:
<?php
$done = 0;
$content = <<<PAGE
---
title: Example
description: Edit this page.
type: Markdown
...
You can delete or edit this page from the control panel.
PAGE;
if (!is_dir($folder = LOT . D . 'page' . D . 'store')) {
mkdir($folder, 775, true);
} else {
if (!is_file($file = $folder . D . 'example.page')) {
file_put_contents($file, $content);
} else {
++$done;
}
++$done;
}
if (2 === $done) {
unlink(__FILE__); // Self destruct!
}
Folders
engine\
It usually contains two other folders named kernel
and plug
. Store your application classes in the kernel
folder so you don’t have to load them manually. Classes stored in this folder will be loaded automatically as long as you follow the class naming conventions. You can add custom tasks that need to be executed once a class is loaded inside the plug
folder. If additional files specific to the classes are available then files with the same name as the class name will be loaded automatically:
.\lot\x\extension-1\
└── engine\
├── kernel\
│ └── extension-1.php
└── plug\
├── anemone.php
└── extension-1.php
In the above extension file structure, the .\lot\x\extension-1\engine\kernel\extension-1.php
file must contain the definition of the Extension1
class. Once you declare the Extension1
class somewhere, this file will be loaded automatically. Additionally, the .\lot\x\extension-1\engine\plug\extension-1.php
file will also be loaded since the class has never been loaded before.
The file .\lot\x\extension-1\engine\plug\anemone.php
will also be automatically loaded since it shares the same name as the Anemone
core class. You can use this file to add custom methods and extend the features of the Anemone
class.
index\
This folder can be used to store files related to other extensions, such as the panel.php
file, which Panel extension can find and load for use. The name of this folder comes after index.php
to make it look neat when paired up, but it is non-standard and not detected by any special feature in the core application. Currently, only Panel extension considers the existence of this folder.
state\
The recursive state data for an application can be stored in this folder to improve manageability. By keeping it separate, it becomes easier to load the state data based on specific conditions or requirements. This approach can help simplify the code and make it more organized.
Asset
Manages asset files without touching the layout files.
Base
Database feature.
Comment
Comment feature.
Layout
Common automation features to speed up the site creation process.
Link
Resolves relative links in content.
Markdown
Converts Markdown syntax to HTML.
Page
Converts file and folder structure into web pages.
Panel
Control panel feature.
Test
Test feature.
User
User feature.
YAML
Simple YAML parser.