Skip to content

ahgRequestToPublishPlugin Technical Reference

Overview

Laravel Query Builder implementation for managing publication requests. Replaces QubitRequestToPublish Propel model with modern, maintainable code following the AHG Framework patterns.

Architecture Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                        ahgRequestToPublishPlugin                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐         │
│  │     Actions     │    │    Services     │    │  Repositories   │         │
│  ├─────────────────┤    ├─────────────────┤    ├─────────────────┤         │
│  │ browseAction    │───▶│ RequestTo       │───▶│ RequestTo       │         │
│  │ editAction      │    │ PublishService  │    │ PublishRepo     │         │
│  │ submitAction    │    │                 │    │                 │         │
│  │ deleteAction    │    │ • submitRequest │    │ • findById      │         │
│  └─────────────────┘    │ • approveReq    │    │ • findBySlug    │         │
│           │             │ • rejectReq     │    │ • paginate      │         │
│           ▼             │ • getStats      │    │ • create        │         │
│  ┌─────────────────┐    └─────────────────┘    │ • update        │         │
│  │   Templates     │             │             │ • delete        │         │
│  ├─────────────────┤             │             └─────────────────┘         │
│  │ browseSuccess   │             │                      │                  │
│  │ editSuccess     │             ▼                      ▼                  │
│  │ submitSuccess   │    ┌─────────────────────────────────────────┐        │
│  │ deleteSuccess   │    │         Laravel Query Builder           │        │
│  └─────────────────┘    │    Illuminate\Database\Capsule\Manager  │        │
│                         └─────────────────────────────────────────┘        │
│                                          │                                 │
└──────────────────────────────────────────┼─────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│                              MySQL Database                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌──────────────┐  ┌───────────────────────┐  ┌──────────────────┐         │
│  │    object    │  │  request_to_publish   │  │ request_to_      │         │
│  ├──────────────┤  ├───────────────────────┤  │ publish_i18n     │         │
│  │ id           │◀─│ id (FK)               │◀─├──────────────────┤         │
│  │ class_name   │  │ parent_id             │  │ id (FK)          │         │
│  │ created_at   │  │ rtp_type_id           │  │ culture          │         │
│  │ updated_at   │  │ lft                   │  │ rtp_name         │         │
│  └──────────────┘  │ rgt                   │  │ rtp_surname      │         │
│         ▲          │ source_culture        │  │ rtp_email        │         │
│         │          └───────────────────────┘  │ rtp_phone        │         │
│  ┌──────────────┐                             │ rtp_institution  │         │
│  │     slug     │                             │ rtp_planned_use  │         │
│  ├──────────────┤                             │ rtp_motivation   │         │
│  │ object_id(FK)│                             │ rtp_need_image_by│         │
│  │ slug         │                             │ status_id        │         │
│  └──────────────┘                             │ object_id        │         │
│                                               │ created_at       │         │
│                                               │ completed_at     │         │
│                                               │ rtp_admin_notes  │         │
│                                               └──────────────────┘         │
└─────────────────────────────────────────────────────────────────────────────┘

Request Flow Diagrams

User Submission Flow

┌──────────┐     ┌─────────────────┐     ┌──────────────────┐     ┌─────────────┐
│   User   │     │ Information     │     │ Submit Form      │     │  Database   │
│          │     │ Object View     │     │ (submitAction)   │     │             │
└────┬─────┘     └────────┬────────┘     └────────┬─────────┘     └──────┬──────┘
     │                    │                       │                      │
     │  View Record       │                       │                      │
     │───────────────────▶│                       │                      │
     │                    │                       │                      │
     │  Click "Request    │                       │                      │
     │  to Publish"       │                       │                      │
     │───────────────────▶│                       │                      │
     │                    │                       │                      │
     │                    │  Route to submit      │                      │
     │                    │  with slug            │                      │
     │                    │──────────────────────▶│                      │
     │                    │                       │                      │
     │                    │                       │  GET: Load form      │
     │◀──────────────────────────────────────────│  with object info    │
     │                    │                       │                      │
     │  Fill form &       │                       │                      │
     │  Submit            │                       │                      │
     │───────────────────────────────────────────▶│                      │
     │                    │                       │                      │
     │                    │                       │  POST: Validate      │
     │                    │                       │────────────────────▶ │
     │                    │                       │                      │
     │                    │                       │  INSERT object       │
     │                    │                       │────────────────────▶ │
     │                    │                       │                      │
     │                    │                       │  INSERT request_to_  │
     │                    │                       │  publish             │
     │                    │                       │────────────────────▶ │
     │                    │                       │                      │
     │                    │                       │  INSERT request_to_  │
     │                    │                       │  publish_i18n        │
     │                    │                       │────────────────────▶ │
     │                    │                       │                      │
     │                    │                       │  INSERT slug         │
     │                    │                       │────────────────────▶ │
     │                    │                       │                      │
     │  Redirect with     │                       │◀────────────────────│
     │  success message   │                       │                      │
     │◀──────────────────────────────────────────│                      │
     │                    │                       │                      │

Admin Review Flow

┌──────────┐     ┌─────────────────┐     ┌──────────────────┐     ┌─────────────┐
│  Admin   │     │ Browse Dashboard│     │  Edit/Review     │     │  Database   │
│          │     │ (browseAction)  │     │  (editAction)    │     │             │
└────┬─────┘     └────────┬────────┘     └────────┬─────────┘     └──────┬──────┘
     │                    │                       │                      │
     │  Navigate to       │                       │                      │
     │  /requesttopublish │                       │                      │
     │  /browse           │                       │                      │
     │───────────────────▶│                       │                      │
     │                    │                       │                      │
     │                    │  Query requests       │                      │
     │                    │  with pagination      │                      │
     │                    │──────────────────────────────────────────────▶
     │                    │                       │                      │
     │                    │◀─────────────────────────────────────────────│
     │                    │                       │                      │
     │  Display list      │                       │                      │
     │  with status tabs  │                       │                      │
     │◀──────────────────│                       │                      │
     │                    │                       │                      │
     │  Click review      │                       │                      │
     │  icon on request   │                       │                      │
     │───────────────────▶│                       │                      │
     │                    │                       │                      │
     │                    │  Route to edit        │                      │
     │                    │  with slug            │                      │
     │                    │──────────────────────▶│                      │
     │                    │                       │                      │
     │                    │                       │  Query request       │
     │                    │                       │  + info object       │
     │                    │                       │──────────────────────▶
     │                    │                       │                      │
     │  Display request   │                       │◀─────────────────────│
     │  details           │                       │                      │
     │◀──────────────────────────────────────────│                      │
     │                    │                       │                      │
     │  Click Approve     │                       │                      │
     │  or Reject         │                       │                      │
     │───────────────────────────────────────────▶│                      │
     │                    │                       │                      │
     │                    │                       │  UPDATE status_id    │
     │                    │                       │  SET completed_at    │
     │                    │                       │──────────────────────▶
     │                    │                       │                      │
     │  Redirect to       │                       │◀─────────────────────│
     │  browse with msg   │                       │                      │
     │◀──────────────────────────────────────────│                      │
     │                    │                       │                      │

Status State Machine

                    ┌─────────────────┐
                    │   User Submit   │
                    └────────┬────────┘
                    ┌─────────────────┐
                    │    PENDING      │
                    │   (status=220)  │
                    └────────┬────────┘
              ┌──────────────┼──────────────┐
              │              │              │
              ▼              │              ▼
     ┌─────────────────┐     │     ┌─────────────────┐
     │    APPROVED     │     │     │    REJECTED     │
     │   (status=219)  │     │     │   (status=221)  │
     └─────────────────┘     │     └─────────────────┘
              │              │              │
              │              ▼              │
              │     ┌─────────────────┐     │
              │     │     DELETE      │     │
              │     │   (optional)    │     │
              │     └─────────────────┘     │
              │              ▲              │
              └──────────────┴──────────────┘

Database Schema Details

Table: request_to_publish

CREATE TABLE request_to_publish (
    id             INT NOT NULL PRIMARY KEY,  -- References object.id
    parent_id      VARCHAR(50) NULL,
    rtp_type_id    INT NULL,
    lft            INT NOT NULL DEFAULT 0,    -- Nested set left
    rgt            INT NOT NULL DEFAULT 1,    -- Nested set right
    source_culture VARCHAR(14) NOT NULL DEFAULT 'en'
);

Table: request_to_publish_i18n

CREATE TABLE request_to_publish_i18n (
    id                INT NOT NULL,
    culture           VARCHAR(14) NOT NULL DEFAULT 'en',
    unique_identifier VARCHAR(1024) NULL,
    rtp_name          VARCHAR(50) NULL,
    rtp_surname       VARCHAR(50) NULL,
    rtp_phone         VARCHAR(50) NULL,
    rtp_email         VARCHAR(50) NULL,
    rtp_institution   VARCHAR(200) NULL,
    rtp_motivation    TEXT NULL,
    rtp_planned_use   TEXT NULL,
    rtp_need_image_by DATETIME NULL,
    status_id         INT NOT NULL DEFAULT 220,
    object_id         VARCHAR(50) NULL,
    completed_at      DATETIME NULL,
    created_at        DATETIME NOT NULL,
    rtp_admin_notes   TEXT NULL,
    PRIMARY KEY (id, culture),
    FOREIGN KEY (id) REFERENCES request_to_publish(id) ON DELETE CASCADE
);

Status Term IDs

Status Term ID AtoM Constant Description
Pending 220 QubitTerm::IN_REVIEW_ID Awaiting admin review
Approved 219 QubitTerm::APPROVED_ID Request granted
Rejected 221 QubitTerm::REJECTED_ID Request denied

File Structure

atom-ahg-plugins/ahgRequestToPublishPlugin/
├── config/
│   └── ahgRequestToPublishPluginConfiguration.class.php
├── lib/
│   ├── Repositories/
│   │   └── RequestToPublishRepository.php
│   └── Services/
│       └── RequestToPublishService.php
├── modules/
│   └── requestToPublish/
│       ├── actions/
│       │   ├── browseAction.class.php
│       │   ├── editAction.class.php
│       │   ├── deleteAction.class.php
│       │   └── submitAction.class.php
│       ├── config/
│       │   └── module.yml
│       └── templates/
│           ├── browseSuccess.php
│           ├── editSuccess.php
│           ├── submitSuccess.php
│           └── deleteSuccess.php
├── data/
└── extension.json

Routing Configuration

Routes are registered in ahgRequestToPublishPluginConfiguration.class.php using Symfony's routing system:

public function loadRoutes(sfEvent $event)
{
    $routing = $event->getSubject();

    // Order matters! Generic routes first, specific last (prepend adds to front)

    // Generic slug route (matched last)
    $routing->prependRoute('requesttopublish_edit',
        new sfRoute('/requesttopublish/:slug',
            ['module' => 'requestToPublish', 'action' => 'edit']));

    // Delete route
    $routing->prependRoute('requesttopublish_delete',
        new sfRoute('/requesttopublish/delete/:slug',
            ['module' => 'requestToPublish', 'action' => 'delete']));

    // Submit route (public form)
    $routing->prependRoute('requesttopublish_submit',
        new sfRoute('/requestToPublish/submit/:slug',
            ['module' => 'requestToPublish', 'action' => 'submit']));

    // Browse route (matched first - most specific)
    $routing->prependRoute('requesttopublish_browse',
        new sfRoute('/requesttopublish/browse',
            ['module' => 'requestToPublish', 'action' => 'browse']));
}

Route Table

Route Method Module Action Description
/requesttopublish/browse GET requestToPublish browse Admin dashboard
/requesttopublish/:slug GET/POST requestToPublish edit Review/update request
/requesttopublish/delete/:slug GET/POST requestToPublish delete Delete confirmation
/requestToPublish/submit/:slug GET/POST requestToPublish submit Public submission form

Repository Class

RequestToPublishRepository

Located at: lib/Repositories/RequestToPublishRepository.php

Methods

Method Parameters Return Description
findById int $id ?object Get request by ID
findBySlug string $slug ?object Get request by URL slug
paginate int $page, int $perPage, ?string $status, ?string $sort, string $order array Paginated list with filters
countByStatus ?string $status int Count requests by status
create array $data int Create new request, returns ID
update int $id, array $data bool Update request
delete int $id bool Delete request and related records
getStatusCounts none array Get counts for all statuses
getStatusLabel int $statusId string Human-readable status
getStatusBadgeClass int $statusId string Bootstrap badge class

Create Method Flow

create(array $data)
    ├──▶ INSERT INTO object (class_name='QubitRequestToPublish')
    │    └── Returns: $objectId
    ├──▶ INSERT INTO request_to_publish (id=$objectId, lft=0, rgt=1)
    ├──▶ INSERT INTO request_to_publish_i18n (id=$objectId, culture='en', ...)
    └──▶ INSERT INTO slug (object_id=$objectId, slug='request-to-publish-{$objectId}')
         └── Returns: $objectId

Delete Method Flow

delete(int $id)
    ├──▶ DELETE FROM slug WHERE object_id = $id
    ├──▶ DELETE FROM request_to_publish_i18n WHERE id = $id
    ├──▶ DELETE FROM request_to_publish WHERE id = $id
    └──▶ DELETE FROM object WHERE id = $id
         └── Returns: true

Service Class

RequestToPublishService

Located at: lib/Services/RequestToPublishService.php

Methods

Method Parameters Return Description
submitRequest array $data int Validate and create request
approveRequest int $id, ?string $adminNotes bool Set status to approved
rejectRequest int $id, ?string $adminNotes bool Set status to rejected
getRequestWithObject int $id ?object Get request with related info object
getDigitalObjects int $objectId array Get digital objects for info object
hasPendingRequest int $objectId, string $email bool Check for duplicate pending
getStatistics none array Dashboard statistics

Action Classes

browseAction

Authentication: Required (Administrator)

Parameters: - filter (string): Status filter (all, pending, approved, rejected) - page (int): Page number - sort (string): Sort field - order (string): Sort order (asc, desc)

Template Variables: - $requests: Collection of request objects - $total: Total count - $pages: Total pages - $statusCounts: Array of counts per status - $repository: Repository instance for helpers

editAction

Authentication: Required (Administrator)

Parameters: - slug (string): Request slug from URL

POST Parameters: - action_type (string): approve, reject, or save - rtp_admin_notes (string): Admin notes

Template Variables: - $resource: Request object with related data - $repository: Repository instance for helpers

submitAction

Authentication: None (Public)

Parameters: - slug (string): Information object slug

POST Parameters: - rtp_name, rtp_surname, rtp_email (required) - rtp_phone, rtp_institution (optional) - rtp_planned_use (required), rtp_motivation (optional) - rtp_need_image_by (optional date)

Template Variables: - $informationObject: The requested record - $userName, $userSurname, $userEmail: Pre-filled from logged-in user

deleteAction

Authentication: Required (Administrator)

Parameters: - slug (string): Request slug

POST Parameters: - confirm (string): Must be 'yes' to delete

Template Integration

Adding Button to Sector Templates

The Request to Publish button is conditionally displayed using:

<?php if (class_exists('ahgRequestToPublishPluginConfiguration')): ?>
  <?php echo link_to(
    '<i class="fas fa-paper-plane me-1"></i>' . __('Request to Publish'),
    ['module' => 'requestToPublish', 'action' => 'submit', 'slug' => $resource->slug],
    ['class' => 'btn btn-sm btn-outline-primary']
  ); ?>
<?php endif; ?>

Templates Updated

Template Location
ISAD ahgThemeB5Plugin/modules/sfIsadPlugin/templates/indexSuccess.php
Museum ahgMuseumPlugin/modules/ahgMuseumPlugin/templates/indexSuccess.php
Museum CCO ahgMuseumPlugin/modules/cco/templates/indexSuccess.php

Plugin Registration

atom_plugin Table Entry

INSERT INTO atom_plugin (
    name, class_name, version, description, author, category,
    is_enabled, is_core, is_locked, load_order
) VALUES (
    'ahgRequestToPublishPlugin',
    'ahgRequestToPublishPluginConfiguration',
    '1.0.0',
    'Manage publication requests for archival images and digital objects',
    'The Archive and Heritage Group',
    'ahg',
    1, 0, 0, 80
);

Enabling/Disabling

# Enable
php bin/atom extension:enable ahgRequestToPublishPlugin

# Disable
php bin/atom extension:disable ahgRequestToPublishPlugin

Error Handling

Flash Message Pattern

All templates use the non-empty check pattern to avoid showing empty error alerts:

<?php if ($sf_user->hasFlash('error') && $sf_user->getFlash('error')): ?>
  <div class="alert alert-danger">...</div>
<?php endif; ?>

Common Errors

Error Cause Solution
Class not found Service not requiring Repository Add require_once for Repository in Service
now() undefined Laravel helper not available Use date('Y-m-d H:i:s') instead
Column not found DB schema mismatch Check i18n table has all required columns
Template not found Missing template file Create submitSuccess.php, etc.

Testing

Manual Test URLs

# Admin browse
https://[domain]/requesttopublish/browse

# Submit form (replace [slug] with actual record slug)
https://[domain]/requestToPublish/submit/[slug]

# Review request (replace [slug] with request slug)
https://[domain]/requesttopublish/[slug]

Database Verification

-- Check request count
SELECT COUNT(*) FROM request_to_publish_i18n WHERE culture = 'en';

-- Check status distribution
SELECT status_id, COUNT(*) as count 
FROM request_to_publish_i18n 
WHERE culture = 'en' 
GROUP BY status_id;

-- View recent requests
SELECT i.*, s.slug 
FROM request_to_publish_i18n i
JOIN slug s ON i.id = s.object_id
WHERE i.culture = 'en'
ORDER BY i.created_at DESC
LIMIT 10;

Version History

Version Date Changes
1.0.0 2026-01-13 Initial release with Laravel Query Builder