Version 3.0.0

I can’t seem to get out of Parkinson’s law, sorry.

Table of Contents
  1. Class-Less Markup
  2. Secure Extension and Layout Loader
  3. Reduction of Hook Sortation Task
  4. Layout Switcher Feature
  5. Layout Folder Name
  6. Layout Behavior
  7. Layout Error
  8. Fewer Classes, More Functions
    1. Cache
    2. Client
    3. Cookie
    4. Files
    5. Folders
    6. Get
    7. Guard
    8. Path
    9. Post
    10. Request
    11. Route
    12. Server
    13. Session
    14. SGML
  9. Miscellaneous

It took me four years to get convinced to release this version, and it was out of a sense of urgency. As someone who develops my own work (I don’t call it a job), there is no barrier that prevents me from stopping at some point so that a work is considered stable and viable. I am stuck with my belief that the last commit I have pushed into the repository is always the best version of my work. Until one day someone warned me about a security vulnerability in my application that was too severe to ignore.

It was all caused by this one line, which turned out not to be very reliable in securing the file system:

$path = strtr($path, ['../' => ""]);

I will provide the link that explains how a hacker could damage a user’s system by exploiting this vulnerability in the future, but if you are currently using the latest version before I publish this article, I suggest you to change that line to this one, which should cover most of the vulnerability issues. I would also suggest you to update the User and Panel extension to the latest version as well:

while (false !== strpos($path, '../')) {
    $path = strtr($path, ['../' => ""]);
}

I suggest you also read the version 3.0.1 release article to get a more secure update. Apart from that, here are the changes that I have made over the past 4 years.

Class-Less Markup

Extensions should not specify classes in their HTML markup (except for certain extensions that produce complex HTML markup, such as the Comment extension). Alternatively, extension developers can consider using the attributes as standardized by WAI-ARIA, or else they can use custom elements for client-side rendering purposes.

Secure Extension and Layout Loader

Loading extensions/layouts is now wrapped in a try/catch block, so any extensions/layouts that are not compatible or broken can be disabled immediately. If you ever install an extension/layout and have trouble getting it to work, check to see if the index.php file in the extension/layout you just installed has been renamed to an .index.php file. If so, the core application may have rejected it. If you have the test feature enabled, you should find log files in the .\engine\log folder named error-x (for extension rejections) and error-y (for layout rejections), or error (for general rejections).

Reduction of Hook Sortation Task

A hook will be sorted once for each addition. This can improve the performance of hooks, especially when they are executed inside a loop. Previously, every time a Hook::fire() method is called, a task to sort the added hooks by the stack property is performed. This task can actually be reduced by performing it as rarely as possible.

Sorting is quite expensive. It is like doing a preparation loop before doing the actual loop: the first loop is to re-order the hooks, and the second loop is to execute the hooks in order.

There are some indicators that will be used to trigger the re-sorting task of the hooks:

  1. Calling Hook::set() method will trigger the sorting task.
  2. Calling Hook::get() method will not trigger the sorting task.
  3. Calling Hook::let() method will not trigger the sorting task as it’s just an action to remove or halt a hook.
  4. Calling Hook::fire() method will not trigger the sorting task, as it’s already done by the Hook::set() method.
  5. If there is only one hook in the current hook container, the sorting task will not be performed.

Layout Switcher Feature

The Layout switcher feature is now available (again). Just after 2 years, 2 months and 28 days have passed since the release of version 2.2.0, I started to realize that I have intolerable difficulties in synchronizing the local layout file structure to the remote repository associated with that layout. It’s very difficult for me to develop automatic update features for the layout, because when a layout is uploaded, its folder name is gone and all the files in it are combined into one in the .\lot\layout folder. In contrast, extensions are uploaded with the folder, so the mapping between the extension name and the remote repository associated with that extension can be retained.

Typical extension file structure in version 2.6.4:

.\
└── lot\
    └── x\
        └── foo-bar\ ← this is `foo-bar` extension, obviously!
            ├── engine\
            ├── lot\
            ├── about.page
            ├── index.php
            └── state.php

Typical extension repository structure in version 2.6.4:

https://github.com/
└── mecha-cms/
    └── x.foo-bar/
        └── foo-bar/
            ├── engine/
            ├── lot/
            ├── about.page
            ├── index.php
            └── state.php

Typical layout file structure in version 2.6.4:

.\
└── lot\
    └── layout\ ← what layout is this?
        ├── asset\
        ├── about.page
        ├── index.php
        └── state.php

Typical layout repository structure in version 2.6.4:

https://github.com/
└── mecha-cms/
    └── layout.foo-bar/
        ├── asset/
        ├── about.page
        ├── index.php
        └── state.php

Initially, I tried to make a git property proposal in about.page file 1 to solve this problem. Hopefully this can also be applied to third-party extensions uploaded outside the https://github.com/mecha-cms organization, so that they can tell the Panel about their repository name in the git property of an about.page file to be fetched.

---
title: Layout Title
description: Layout description.
git: 'taufik-nurrohman/layout.foo-bar'
type: Markdown
...

Lorem ipsum dolor sit amet.

This should work fine when the git property exists, but gets a bit hacky when I have to guess the repository name by creating a folder name from the layout title. Layout title is meant to be freely definable and has no relation to the internal structure of the file system. Repository name errors will occur when an unaware developer decides to change their layout title without changing their remote repository name.

Layout Folder Name

In order to match the naming style of the extension folder, I also decided to change the path of the layout folder from .\lot\layout to .\lot\y. We now have unique folders called .\lot\x and .\lot\y, just like in algebraic mathematics. Here, x becomes an abbreviation for extension (noun) and extend (verb), and y becomes an abbreviation for layout (noun) and yield (verb).

.\
└── lot\
    ├── asset\
    ├── page\
    ├── x\
    │   ├── extension-1\
    │   ├── extension-2\
    │   ├── extension-3\
    │   └── …
    └── y\
        ├── layout-1\
        ├── layout-2\
        ├── layout-3\
        └── …

Layout Behavior

Layout now behaves like extensions.

I have made the .\lot\y\*\index.php file mandatory, so that if it does not exist, the front-end view will display a blank web page. This allows Mecha to continue to be a “tiny web framework-ish” CMS, rather than a “has the potential to be full feature-ish” CMS that starts out as a blogging CMS, just like WordPress. One could use Mecha as an application without a front-end 2 by having a layout that does not display any content.

Layout Error

File 404.php is no longer a requirement for a layout. To make an error page, you can mix it in the page.php and pages.php files by verifying the existence of a page:

<?php if ($page->exist): ?>
  <h2>
    <?= $page->title; ?>
  </h2>
  <div>
    <?= $page->content; ?>
  </div>
<?php else: ?>
  <p role="status">
    <?= i('%s does not exist.', 'Page'); ?>
  </p>
<?php endif; ?>

Fewer Classes, More Functions

I finally threw away some classes that were more of a cosmetic than a necessity, and substituted them with more practical generic functions. Some of the classes that have been thrown away include:

Cache

I simply provide a .\lot\cache folder. It is up to you how you want to generate the cache file, I don’t care. An example of how a cache file might be created would be something like this:

$comments = [];
$comments_cache = LOT . D . 'cache' . D . 'comments.php';

if (!exist($comments_cache, 1)) {
    save($comments_cache, '<?' . 'php return' . z($comments = recent_comments()) . ';', 0600);
    $comments = new Comments($comments);
} else {
    $comments = new Comments(require $comments_cache);
}

foreach ($comments as $comment) { … }

Client

Method Client::IP() is now replaced by function ip(), and method Client::UA() is now replaced by function ua().

Cookie

This class is now replaced by a simple cookie() function.

Files

It does make sense as a base class for Pages, but I didn’t find any use for the Folders class, so I decided to throw away both of them. This class name is also in conflict with the choices I have made for the names of the classes Cookie, Get, Post, Request, Server, and Session based on the global variable names $_COOKIE, $_GET, $_POST, $_REQUEST, $_SERVER, and $_SESSION.

I have no room for $_FILES variable name 🙁

Folders

There has been no practical use for it so far, so I threw it away!

Get

I decided to throw away this class, just as I decided to throw away the Files class. This was to resolve the inconsistency in class naming semantics. Methods like Get::get(), Get::let(), and Get::set() should be replaceable in this fashion:

$status = get($_GET, 'user.status'); // `Get::get('user.status')`
let($_GET, 'user.status'); // `Get::let('user.status')`
set($_GET, 'user.status', 1); // `Get::set('user.status', 1)`

Guard

All of the methods in this class are now available as generic functions: abort(), check(), token().

Path

Not cool. It was meant to be paired with the URL class, but this class is more of a converter where the methods are callable statically.

Post

I decided to throw away this class, just as I decided to throw away the Get class.

Request

I decided to throw away this class, just as I decided to throw away the Get class.

Route

Route complexity has been taken care of by a simple hook named route. From the very beginning, the Route class is really just a syntactic sugar for constructing “if” conditions on the URL::path() value. Both of the following instructions can be considered identical:

Route::set("", function () { … });
Route::set('*', function ($path) { … });
Route::set('product/:name', function ($name) { … });

Route::start();
$lot = [];
$route = trim($url->path ?? "", '/');
$then = function () {};

if ("" === $route) {
    $then = function () { … };
}

if ("" !== $route) {
    $lot = [$route];
    $then = function ($path) { … };
}

if (preg_match('/^product\/([^\/]+)$/', $route, $m)) {
    $lot = [$m[1]];
    $then = function ($name) { … };
}

call_user_func($then, $lot);

Just because routes are executed at the very end and have a function stack feature, it becomes a safer option for extensions to add custom pages because extensions can control the execution priority of the route functions by adding a stack value to the route definition so that the core system can execute them in order. But in fact, this can be implemented with a simple hook system as well:

echo Hook::fire('route', [null, $url->path]);

Now a developer can add new route rules and override the existing route rules in the same way that a developer would add a new hook:

Hook::set('route', function ($content, $path) { … });

Of course, it doesn’t have pattern functionality and such, but other developers can make extensions for that and run the pattern parser against the $path value in the hook.

Server

I decided to throw away this class, just as I decided to throw away the Get class.

Session

I decided to throw away this class, just as I decided to throw away the Get class.

SGML

The SGML syntax is too generic, and to develop a parser for its alternative syntax (the short tag syntax) is a bit of a pain, so I decided to focus on the more consistent and strict XML syntax instead.

Miscellaneous

  • #159 The DEBUG constant has been renamed to TEST to make it consistent with the test() function.
  • The DS constant has been renamed to D.
  • The ROOT constant has been renamed to PATH.
  • #165 The $parent variable is no longer available through the Page extension, but method Page::parent() is available on variable $page as the replacement.
  • #166: The get(), has(), let(), and set() methods are mostly no longer present in instantiatable classes. They all have been replaced by the built-in __get(), __isset(), __set(), and __unset() methods.
  • #167 The e() function will no longer evaluate string in the form of a JSON to keep it reversible with the s() function.
  • #233 You now have the option 3 to recursively parse elements with HTML and XML classes.
  • Alert, Art, and Form extensions are no longer part of the core extensions.

  1. An attempt to use a git property in the about.page file. 

  2. Head-less content management system. 

  3. Opinionated Layout Hook System 

0 Comments

No comments yet.