A personal fork of bitweaver — a PHP CMS and application framework
originally derived from TikiWiki. This organisation holds one repository per package. All packages
are deployed together under a single web root (bitweaver5/).
Having used bitweaver for many years across several live sites, this fork has been carried forward into a modern stack: PHP 8.5, Firebird 5, Smarty 5 with namespaces, and adodb on a custom branch. It is published here in the hope that parts of it are useful to others.
| Layer | Technology |
|---|---|
| Language | PHP 8.6 |
| Database | Firebird 5 via adodb (v5.22.11-lsc branch) |
| Templates | Smarty 5 |
| CSS/JS | Bootstrap 3, jQuery — no npm, no Node.js |
| Web server | nginx + php-fpm |
Bitweaver is built around a package system. Every feature area (articles, blogs, contact, stock, etc.) is a self-contained directory under the web root. Packages are loosely coupled through a small set of global objects and a shared database.
Each package directory follows this layout:
<package>/
admin/ package administration pages
includes/
bit_setup_inc.php package self-registration (always loaded)
classes/ PHP classes for this package
config_defaults_inc.php package-level config defaults (optional)
templates/ Smarty templates (.tpl)
modules/ sidebar/layout module templates
index.php package landing page
*.php entry-point pages (list, edit, view, add…)
bit_setup_inc.php is the only file loaded unconditionally for every request. It calls
$gBitSystem->registerPackage() to declare the package and $gBitSystem->registerAppMenu()
to add a navbar dropdown.
index.php(web root) →kernel/includes/setup_inc.phpsetup_inc.phpinitialises global objects ($gBitDb,$gBitSystem,$gBitSmarty,$gBitUser), detoxifies$_GET, and loadsbit_setup_inc.phpfor every active package.- The requested page file runs: loads a content object, does DB work, assigns Smarty
variables, calls
$gBitSmarty->display( 'bitpackage:pkg/template.tpl' ). - Smarty renders through
kernel/templates/html.tpl→ layout columns → modules → page body.
BitBase (kernel — abstract, DB-aware base)
└── LibertyBase (liberty — adds mContentId, mContentTypeGuid)
└── LibertyContent (liberty — full content lifecycle: store, load, expunge)
└── <Package class> e.g. Contact, StockAssembly, StockComponent, StockMovement
Additional kernel classes (not in the content hierarchy):
BitSystem($gBitSystem) — package registry, config store, menu registration, layout, error handling.BitDbAdodb($gBitDb) — thin adodb wrapper; all DB access goes through this.BitSingleton— base for objects that must exist once (BitSystem inherits from this).KernelTools— static utility methods:tra()(translation),detoxify(), etc.RolePermUser($gBitUser) — the logged-in user; holds roles, permissions, preferences.
| Variable | Class | Purpose |
|---|---|---|
$gBitDb |
BitDbAdodb |
Database connection |
$gBitSystem |
BitSystem |
Package registry, config, menus |
$gBitSmarty |
Smarty subclass | Template engine |
$gBitUser |
RolePermUser |
Current user (anonymous or authenticated) |
$gContent |
LibertyContent subclass |
The content object for the current page |
All queries use adodb — no raw PDO or mysqli. The wrapper is BitDbAdodb in
kernel/includes/classes/BitDbAdodb.php.
Key adodb methods used throughout the codebase:
| Method | Purpose |
|---|---|
getArray($sql, $vals) |
Returns all rows as a numerically-indexed array |
getAssoc($sql, $vals) |
Returns rows keyed by the first column |
getOne($sql, $vals) |
Returns a single scalar value |
getRow($sql, $vals) |
Returns a single row as an associative array |
query($sql, $vals) |
Execute; returns a recordset (iterate with fetchRow()) |
executeQuery($sql, $vals, $numrows, $offset) |
Paginated query |
Placeholders are always positional ?. Named binds are not used.
The database is Firebird 5. Firebird is stricter than MySQL in several ways that matter:
- Every non-aggregate
SELECTcolumn must appear inGROUP BY. IN ()with an empty list is a syntax error — always guard with a fallback.FIRST n/ROWS nfor limiting result sets (notLIMIT).LOCALTIMESTAMP/CURRENT_TIMESTAMPas default expressions behave differently from MySQL.
Templates live in <package>/templates/. The bitpackage: Smarty resource prefix resolves
templates through a search path that allows per-site overrides:
config/themes/<site>/(symlink to/etc/webstack/domains/<site>/themes/<site>/)<package>/templates/
This means dropping a template file into the site-specific path silently overrides the package default — useful for site-specific customisation without forking the package.
Important Smarty conventions in this codebase:
{tr}...{/tr}for user-visible strings (maps toKernelTools::tra())."string"|trais not a valid Smarty modifier and will throw a compiler error.{form}...{/form}block plugin auto-injects the CSRF ticket hidden field.{strip}removes inter-tag whitespace; keep content like•inside valid elements.- Per-site footer scripts go in
kernel/footer_inc.tpl, notkernel/footer.tpl.footer_inc.tplis loaded via themAuxFilesloop inhtml.tpland is reliable.footer.tplas a theme override only loads when the active style name matches exactly.
Roles are stored in the users_roles table. Default role IDs:
| role_id | Name | perm_level |
|---|---|---|
| 1 | Administrators | admin |
| 2 | Editors | editors |
| 3 | Registered | registered |
| -1 | Anonymous | basic |
Permissions are declared in <package>/admin/schema_inc.php and stored in
users_role_permissions. $gBitUser->hasPermission('p_pkg_action') is the standard check.
When writing role-filter queries, guard the IN() list with:
array_keys($gBitUser->mRoles ?? []) ?: [-1]Firebird rejects IN () with an empty list.
The liberty package is the content engine. All content types (pages, articles, contacts,
stock items, movements, …) share a single liberty_content table row identified by a
content_id and a content_type_guid string.
A package registers its content type in schema_inc.php and provides a class that extends
LibertyContent. The class overrides store(), load(), and expunge() to handle its
own additional tables/xrefs on top of the base liberty row.
liberty_xref is a flexible key-value extension table attached to any content item.
Rows are grouped by x_group and typed by item. The OOP layer wraps this:
LibertyXrefType— describes one xref slot (group, item key, cardinality, etc.)LibertyXref— one populated xref rowLibertyXrefGroup— a collection of xref rows sharing anx_groupLibertyXrefInfo— the full set of xref groups for a content item
Loading pattern (display and edit pages):
$gContent->loadXrefInfo();
$gBitSmarty->assign('gXrefInfo', $gContent->mXrefInfo);Template pattern:
{foreach $gXrefInfo->mGroups as $xrefGroup}
{include file=$gContent->getXrefListTemplate($xrefGroup->mTemplate)
xrefGroup=$xrefGroup allow_edit=false}
{/foreach}Group templates receive $xrefGroup (a LibertyXrefGroup object). The first two lines
of every group template set local flags:
{assign var=xrefAllowEdit value=$allow_edit|default:false}
{assign var=isHistory value=($xrefGroup->mXGroup eq 'history')}Cookie name: bit-user-{site_title_stripped} (lowercase alphanumeric).
Login stores the PHP session_id() in users_cnxn.cookie mapped to user_id. Subsequent
requests look up the cookie value in users_cnxn to identify the user. This is independent
of PHP's own session mechanism even though they share the same cookie name.
Code is developed and tested live in /srv/website/bitweaver5/ (with xdebug available).
Proven changes are copied to the matching package repo under ~/Development/bitweaver-lsces/<pkg>/,
reviewed with BeyondCompare, then committed and pushed.
Servers (srv9, srv10) pull from the desktop's local git repos — not from GitHub.
The deploy script is /etc/webstack/scripts/server-pull-all.sh <package>.
Test on srv9 first. srv10 is production and only receives changes proven on srv9.
After any deploy that touches Smarty templates: clear the template cache and restart php-fpm.
| Package | Content type | Notes |
|---|---|---|
| kernel | — | Bootstrap, globals, BitSystem, BitBase |
| liberty | liberty_content |
Content engine, xref system |
| users | — | Auth, roles, permissions |
| themes | — | Layout, CSS loading, module system |
| languages | — | Translations, tra() string lookup |
| config | — | Site-level overrides (CSS, templates) |
| contact | contact |
Person and business contacts, CSV import |
| stock | stockassembly, stockcomponent, stockmovement |
BOM, stock levels, movement CSV import |
| articles | article |
— |
| blogs | blog, blogpost |
— |
| wiki | wiki page |
— |
| fisheye | fisheye |
Image gallery |
| messages | — | Internal messaging |
| util | — | Shared JavaScript, icons, cross-package utilities |