Heratio Help Center article. Category: Technical.

Heratio AHG Framework - Technical Architecture

For Technical Discussion with Artifactual

Prepared by: The Archive and Heritage Digital Commons Group
Date: January 2026


1. Executive Summary

The Heratio AHG Framework is a non-invasive modernization layer that:

  • Integrates Laravel Query Builder into Heratio 2.10 without modifying core files
  • Provides database-driven plugin management replacing hardcoded configurations
  • Maintains 100% backward compatibility with existing Heratio functionality
  • Targets GLAM institutions with South African regulatory compliance (POPIA, NARSSA, PAIA, GRAP 103)

Key Principle: We enhance Heratio, we don't fork it.


2. Architectural Overview

USERINTERFACE(Bootstrap5viaahgThemeB5Plugin)Heratio2.10PRESENTATIONLAYER(Symfony1.xControllers,Actions,Templates)UNCHANGEDINTEGRATIONPOINTProjectConfiguration.class.phploadPluginsFromDatabase()NEWFUNCTIONSYMFONY1.xORMatom-framework(Propel)(LaravelQueryCoreHeratioDataBuilder)ExtensionDataMySQL8DATABASECoreHeratioAHGExtensionsTablesTables(atom_*)(unchanged)

3. How the Framework Connects to Heratio

3.1 Bootstrap Integration Point

The framework bootstraps via a single function added to Heratio's config:

// /config/ProjectConfiguration.class.php

class ProjectConfiguration extends sfProjectConfiguration
{
    public function setup()
    {
        // 1. Load core Symfony plugins (unchanged)
        $corePlugins = array(
            'sfWebBrowserPlugin',
            'sfThumbnailPlugin',
            // ... standard Heratio plugins
        );
        
        // 2. NEW: Load database-driven plugins
        $this->loadPluginsFromDatabase($corePlugins);
    }
    
    // NEW FUNCTION - Only addition to Heratio config
    protected function loadPluginsFromDatabase($corePlugins)
    {
        // Initialize Laravel Query Builder
        require_once dirname(__FILE__) . '/../atom-framework/bootstrap.php';
        
        // Query atom_plugin table
        $dbPlugins = DB::table('atom_plugin')
            ->where('is_enabled', 1)
            ->orderBy('load_order')
            ->pluck('name')
            ->toArray();
        
        // Merge and enable
        $allPlugins = array_unique(array_merge($corePlugins, $dbPlugins));
        $this->enablePlugins($allPlugins);
    }
}

3.2 Framework Bootstrap Sequence

Requestindex.phpProjectConfiguration::setup()1.StandardSymfonyinit2.loadPluginsFromDatabase()requireatom-framework/bootstrap.phpComposerautoloadInitializeCapsule(LaravelDB)ConnecttoMySQLQueryatom_plugintableReturnenabledplugins3.enablePlugins($merged)4.ContinueSymfonylifecycle

4. Database Architecture

4.1 Plugin Management Table

CREATE TABLE atom_plugin (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL UNIQUE,        -- Plugin class name
    class_name VARCHAR(255) NOT NULL,          -- Configuration class
    version VARCHAR(50),
    description TEXT,
    category VARCHAR(100) DEFAULT 'general',   -- theme|ahg|sector|utility
    is_enabled TINYINT(1) DEFAULT 0,
    is_core TINYINT(1) DEFAULT 0,              -- Cannot be disabled
    is_locked TINYINT(1) DEFAULT 0,            -- Cannot be modified
    load_order INT DEFAULT 100,                -- Plugin loading sequence
    plugin_path VARCHAR(500),
    settings JSON,                             -- Per-plugin configuration
    enabled_at TIMESTAMP,
    disabled_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

4.2 Dual-ORM Strategy

DATAACCESSPATTERNSPROPEL(Symfony1.xORM)LARAVELQUERYBUILDER•CoreHeratioentities:•Extensiontables:-QubitInformationObject-atom_plugin-QubitActor-atom_landing_page-QubitTerm-atom_security_clearance-QubitRepository-atom_researcher-QubitDigitalObject-atom_audit_log•WhyPropelforcore:•WhyLaravelforextensions:-MaintainsHeratiocompatibility-Modernfluentsyntax-Existingrelationshipswork-Easiertesting-Nomigrationrisk-Repositorypatternsupport-CollectionhelpersEXCEPTION:PluginManagerusesPDOdirectlyduetoautoloaderconflictsduringSymfonyboot

5. Plugin Architecture

5.1 Directory Structure

/usr/share/nginx/archive/atom-framework/#LAYER1:CoreFrameworkbootstrap.php#Laravelinitializationcomposer.json#Dependenciesbin/install#Installationscriptatom#CLIentrypointrelease#Versionmanagementconfig/ProjectConfiguration.class.php.templatedatabase/install.sql#Schemadefinitionssrc/Extensions/ExtensionManager.php#PluginmanagementserviceExtensionProtection.phpRepositories/#BaserepositoryclassesServices/#BusinesslogicservicesHelpers/#Utilityclassesatom-ahg-plugins/#LAYER2:PluginSourceahgThemeB5Plugin/#Bootstrap5theme(LOCKED)ahgSecurityClearancePlugin/#Securitysystem(LOCKED)ahgLibraryPlugin/#LibrarysectorfeaturesahgLandingPagePlugin/#Landingpagebuilder[26additionalplugins]plugins/#SymlinksforSymfonyahgThemeB5Plugin->../atom-ahg-plugins/ahgThemeB5PluginahgLibraryPlugin->../atom-ahg-plugins/ahgLibraryPlugin[symlinksforallenabledplugins]config/ProjectConfiguration.class.php#ModifiedwithloadPluginsFromDatabase()

5.2 Plugin Loading Flow

PLUGINLOADINGSEQUENCE1.SymfonyInitializationProjectConfiguration::setup()2.CorePlugins(Hardcoded)$corePlugins=['sfWebBrowserPlugin','sfThumbnailPlugin',...]3.DatabaseQuery(NEW)SELECTnameFROMatom_pluginWHEREis_enabled=1ORDERBYload_order4.MergeArrays$allPlugins=array_unique(merge($core,$db))5.EnablePlugins$this->enablePlugins($allPlugins)Symfonyloadseachplugin'sConfigurationclassRoutesregisteredTemplatesavailableAssetsaccessible

6. CLI Interface

6.1 Command Structure

# Extension Management
php bin/atom extension:discover      # Scan and list available extensions
php bin/atom extension:enable <name> # Enable a plugin
php bin/atom extension:disable <name># Disable a plugin (if not locked)
php bin/atom extension:list          # Show enabled/disabled status

# Framework Management  
php bin/atom framework:install       # Initial setup
php bin/atom framework:update        # Pull latest changes
php bin/atom framework:version       # Display version info

# Version Release (Development)
./bin/release patch "message"        # Bump patch version
./bin/release minor "message"        # Bump minor version

6.2 Extension Enable Process

phpbin/atomextension:enableahgLibraryPlugin1.Validatepluginexistsinatom-ahg-plugins/2.Checkdependencies(extension.json)3.Createsymlink:plugins/->atom-ahg-plugins/4.UPDATEatom_pluginSETis_enabled=15.Runplugin'sinstalltask(ifexists)6.Loaddatafiles(taxonomyterms,etc.)7.ClearSymfonycache8.Outputsuccessmessage

7. What We Don't Modify

7.1 Core Heratio Integrity

UNTOUCHEDCOMPONENTSDATABASESCHEMA(NeverModified)•object•slug•information_object•information_object_i18n•actor•actor_i18n•term•term_i18n•taxonomy•repository•digital_object•physical_object•relation•event•user•acl_*PHPFILES(NeverPatched)•lib/QubitInformationObject.php•lib/QubitActor.php•lib/model/map/*.php•lib/model/om/*.php•apps/qubit/modules/*(coremodules)CONFIGURATION(TemplateReplacementOnly)•ProjectConfiguration.class.phpTemplateREPLACESentirefile(notpatched)ContainsloadPluginsFromDatabase()additionROUTING(Preserved)•AllcoreHeratioroutesworkunchanged•ExtensionsaddNEWroutesonly

8. Repository Pattern

8.1 Extension Repository Example

<?php
// atom-framework/src/Repositories/BaseRepository.php

namespace AtomExtensions\Repositories;

use Illuminate\Database\Capsule\Manager as DB;
use Illuminate\Support\Collection;

abstract class BaseRepository
{
    protected string $table;
    protected string $primaryKey = 'id';
    
    public function find(int $id): ?array
    {
        return DB::table($this->table)
            ->where($this->primaryKey, $id)
            ->first();
    }
    
    public function findWhere(array $conditions): Collection
    {
        $query = DB::table($this->table);
        foreach ($conditions as $column => $value) {
            $query->where($column, $value);
        }
        return $query->get();
    }
    
    public function create(array $data): int
    {
        return DB::table($this->table)->insertGetId($data);
    }
    
    public function update(int $id, array $data): bool
    {
        return DB::table($this->table)
            ->where($this->primaryKey, $id)
            ->update($data) > 0;
    }
}

8.2 Plugin-Specific Repository

<?php
// atom-ahg-plugins/ahgLibraryPlugin/lib/Repositories/LibraryItemRepository.php

namespace ahgLibraryPlugin\Repositories;

use AtomExtensions\Repositories\BaseRepository;
use Illuminate\Support\Collection;

class LibraryItemRepository extends BaseRepository
{
    protected string $table = 'atom_library_item';
    
    public function findByCallNumber(string $callNumber): ?array
    {
        return DB::table($this->table)
            ->where('call_number', $callNumber)
            ->first();
    }
    
    public function getCheckedOutItems(): Collection
    {
        return DB::table($this->table)
            ->where('status', 'checked_out')
            ->orderBy('due_date')
            ->get();
    }
}

9. Theme Integration

9.1 Bootstrap 5 Implementation

THEMEARCHITECTUREahgThemeB5Plugin/css/main.css#Bootstrap5core_variables.css#CSScustompropertiescomponents/#Component-specificstylesjs/bootstrap.bundle.min.jsahg-theme.js#ThemeJavaScripttemplates/layout.php#Mainlayout(overridescore)_partials/#Reusablecomponentsconfig/ahgThemeB5PluginConfiguration.class.phpCSSVariableConvention(BEMNamespace)ahg-primary:#0d6efd;ahg-card__headerbg:var(ahg-primary);ahg-landing__heroheight:400px;

10. Deployment Model

10.1 Installation Process

#Step1:Clonerepositoriesgitclonehttps://github.com/ArchiveHeritageGroup/atom-framework.gitgitclonehttps://github.com/ArchiveHeritageGroup/atom-ahg-plugins.git#Step2:Installdependenciescdatom-framework&&composerinstall#Step3:Runinstallerbashbin/install#Whatbin/installdoes:#Createssymlinksforplugins#Runsdatabasemigrations(install.sql)#CopiesProjectConfiguration.class.php.template#Loadsplugindatafiles#Enablesrequiredplugins(ahgThemeB5Plugin,ahgSecurityClearancePlugin)#Copiesdistassets#Clearscache

10.2 Update Process

# Framework updates
cd atom-framework
git pull origin main
bash bin/install --update

# Plugin updates  
cd atom-ahg-plugins
git pull origin main

# Individual plugin enable
php bin/atom extension:enable ahgNewPlugin

11. Technical Specifications

Component Technology
Base Platform Heratio 2.10
PHP Version 8.3
Framework ORM Laravel Query Builder (Illuminate\Database)
Core ORM Propel (Symfony 1.x) - unchanged
Database MySQL 8
Search Elasticsearch 7.10
Web Server nginx
Theme Bootstrap 5
CLI Symfony Tasks + Custom Commands

12. ProjectConfiguration.class.php.template

<?php
class ProjectConfiguration extends sfProjectConfiguration
{
    protected $pluginsLoaded = false;
    
    public function setup()
    {
        $corePlugins = array(
            'sfWebBrowserPlugin',
            'sfThumbnailPlugin',
            'sfJSONPlugin',
            'sfInstallPlugin',
            'qtAccessionPlugin',
            // ... other core plugins
        );
        
        $this->loadPluginsFromDatabase($corePlugins);
    }
    
    protected function loadPluginsFromDatabase($corePlugins)
    {
        if ($this->pluginsLoaded) {
            return;
        }
        
        $frameworkPath = dirname(__FILE__) . '/../atom-framework';
        
        if (!file_exists($frameworkPath . '/bootstrap.php')) {
            $this->enablePlugins($corePlugins);
            return;
        }
        
        require_once $frameworkPath . '/bootstrap.php';
        
        try {
            $dbPlugins = \Illuminate\Database\Capsule\Manager::table('atom_plugin')
                ->where('is_enabled', 1)
                ->orderBy('load_order')
                ->pluck('name')
                ->toArray();
                
            $allPlugins = array_unique(array_merge($corePlugins, $dbPlugins));
            $this->enablePlugins($allPlugins);
        } catch (\Exception $e) {
            $this->enablePlugins($corePlugins);
        }
        
        $this->pluginsLoaded = true;
    }
}

Document Version: 1.0.0
Last Updated: January 2026
The Archive and Heritage Digital Commons Group