Heratio Help Center article. Category: Plugin Reference.
ahgFeedbackPlugin Technical Documentation
Overview
The ahgFeedbackPlugin provides user feedback management functionality for Heratio using Laravel Query Builder. It allows users to submit feedback on archival records or general feedback, with full CRUD operations for administrators.
Architecture
Plugin Structure
ahgFeedbackPlugin/
├── config/
│ └── ahgFeedbackPluginConfiguration.class.php
├── data/
│ └── install.sql
├── extension.json
├── lib/
│ └── task/
├── modules/
│ └── ahgFeedback/
│ ├── actions/
│ │ ├── browseAction.class.php
│ │ ├── deleteAction.class.php
│ │ ├── editAction.class.php
│ │ ├── generalAction.class.php
│ │ ├── submitAction.class.php
│ │ └── viewAction.class.php
│ └── templates/
│ ├── browseSuccess.php
│ ├── deleteSuccess.php
│ ├── editSuccess.php
│ ├── generalSuccess.php
│ ├── submitSuccess.php
│ └── viewSuccess.php
Database Schema
The plugin uses existing Heratio feedback tables with the object inheritance pattern:
-- Base object table (Heratio standard)
object (
id INT PRIMARY KEY AUTO_INCREMENT,
class_name VARCHAR(255), -- 'QubitFeedback'
created_at DATETIME,
updated_at DATETIME,
serial_number INT
)
-- Main feedback table
feedback (
id INT PRIMARY KEY, -- FK to object.id
feed_name VARCHAR(50),
feed_surname VARCHAR(50),
feed_phone VARCHAR(50),
feed_email VARCHAR(50),
feed_relationship TEXT,
parent_id VARCHAR(50),
feed_type_id INT,
lft INT, -- Nested set
rgt INT, -- Nested set
source_culture VARCHAR(14)
)
-- Internationalized fields
feedback_i18n (
id INT, -- FK to feedback.id
culture VARCHAR(14),
name VARCHAR(1024), -- Subject/record title
unique_identifier VARCHAR(1024),
remarks TEXT,
object_id TEXT, -- FK to information_object.id
completed_at DATETIME,
created_at DATETIME,
status_id INT -- QubitTerm::PENDING_ID or COMPLETED_ID
)
Key Design Decisions
- No QubitFeedback Model: Uses Laravel Query Builder exclusively to avoid Propel model dependencies
- Object Table Inheritance: Inserts into
objecttable first, thenfeedback, thenfeedback_i18n - Nested Set Pattern: Maintains
lft/rgtcolumns for Heratio compatibility - i18n Support: Culture-aware queries joining
feedbackandfeedback_i18n
Actions
browseAction
Lists all feedback with filtering and pagination.
Route: /feedback or /feedback?filter=pending|completed
Query Builder Pattern:
$query = DB::table('feedback')
->join('feedback_i18n', 'feedback.id', '=', 'feedback_i18n.id')
->where('feedback_i18n.culture', $culture)
->select('feedback.*', 'feedback_i18n.*');
Template Variables:
$feedbackItems- Collection of feedback records$totalCount,$pendingCount,$completedCount- Count statistics$filter,$sort,$page- Current filter state$totalPages,$currentPage- Pagination info
generalAction
Handles general feedback submission (not linked to a record).
Route: /feedback/general
Insert Pattern:
// 1. Insert into object table
$objectId = DB::table('object')->insertGetId([
'class_name' => 'QubitFeedback',
'created_at' => $now,
'updated_at' => $now,
'serial_number' => 0,
]);
// 2. Get nested set values
$maxRgt = DB::table('feedback')->max('rgt') ?? 0;
// 3. Insert into feedback table
DB::table('feedback')->insert([
'id' => $objectId,
'feed_name' => $value,
// ... other fields
'lft' => $maxRgt + 1,
'rgt' => $maxRgt + 2,
'source_culture' => $culture,
]);
// 4. Insert into feedback_i18n
DB::table('feedback_i18n')->insert([
'id' => $objectId,
'culture' => $culture,
'status_id' => QubitTerm::PENDING_ID,
// ... other fields
]);
submitAction
Handles feedback submission linked to an information object.
Route: /{slug}/ahgFeedback/submit
Key Difference from generalAction:
- Receives
slugparameter to identify linked record - Stores
object_idinfeedback_i18nreferencing the information object - Pre-populates
namefield with record title
editAction
Administrator edit interface for feedback.
Route: /feedback/{id}/edit
Update Pattern:
DB::table('feedback')
->where('id', $id)
->update([...]);
DB::table('feedback_i18n')
->where('id', $id)
->where('culture', $culture)
->update([...]);
DB::table('object')
->where('id', $id)
->update(['updated_at' => $now]);
deleteAction
Deletes feedback (cascades via foreign keys).
Route: /feedback/{id}/delete
Delete Pattern:
// Object table has ON DELETE CASCADE
DB::table('object')->where('id', $id)->delete();
Integration Points
Template Integration
The Item Feedback button is conditionally displayed based on plugin availability:
<?php if (class_exists('ahgFeedbackPluginConfiguration')): ?>
<?php echo link_to(
'<i class="fas fa-comment me-1"></i>' . __('Item Feedback'),
['module' => 'ahgFeedback', 'action' => 'submit', 'slug' => $resource->slug],
['class' => 'btn btn-sm btn-outline-secondary']
); ?>
<?php endif; ?>
Integrated Templates:
| Location | Template | Button Type |
|---|---|---|
| ISAD | sfIsadPlugin/templates/indexSuccess.php |
Standalone button |
| Museum (CCO) | ahgMuseumPlugin/modules/cco/templates/indexSuccess.php |
Standalone button |
| Museum | ahgMuseumPlugin/modules/ahgMuseumPlugin/templates/indexSuccess.php |
Standalone button |
| Library | ahgLibraryPlugin/modules/ahgLibraryPlugin/templates/indexSuccess.php |
Dropdown menu |
| Gallery | ahgGalleryPlugin/modules/ahgGalleryPlugin/templates/indexSuccess.php |
Dropdown menu |
| DAM/Others | ahgThemeB5Plugin/modules/informationobject/templates/_actions.php |
Dropdown menu |
Routing
Defined in ahgThemeB5Plugin/config/routing.yml:
ahg_feedback_browse:
url: /feedback
param: { module: ahgFeedback, action: browse }
ahg_feedback_general:
url: /feedback/general
param: { module: ahgFeedback, action: general }
ahg_feedback_submit:
url: /:slug/ahgFeedback/submit
param: { module: ahgFeedback, action: submit }
ahg_feedback_edit:
url: /feedback/:id/edit
param: { module: ahgFeedback, action: edit }
ahg_feedback_delete:
url: /feedback/:id/delete
param: { module: ahgFeedback, action: delete }
Plugin Registration
Registered in atom_plugin table:
INSERT INTO atom_plugin (name, class_name, is_enabled, category, version, description)
VALUES (
'ahgFeedbackPlugin',
'ahgFeedbackPluginConfiguration',
1,
'ahg',
'1.0.0',
'User feedback and suggestions management'
);
Feedback Types
Stored in feed_type_id:
| ID | Type |
|---|---|
| 0 | General Feedback |
| 1 | Error Report |
| 2 | Suggestion |
| 3 | Correction Request |
| 4 | Need Assistance |
Status Values
Uses Heratio's QubitTerm constants:
| Constant | Value | Description |
|---|---|---|
QubitTerm::PENDING_ID |
(varies) | Awaiting review |
QubitTerm::COMPLETED_ID |
(varies) | Addressed/closed |
Security
- Authentication: Required for browse/edit/delete actions
- Authorization: Administrator access required for management
- Public Access: Submit and general actions can be public (configurable)
- XSS Prevention: All output escaped with
esc_entities()
Dependencies
- atom-framework: Laravel Query Builder (
Illuminate\Database) - Heratio 2.10: Base system with Symfony 1.x
- Bootstrap 5: UI framework (via ahgThemeB5Plugin)
Configuration
Plugin can be enabled/disabled via:
- Admin → AHG Settings → Plugin Management
- CLI:
php bin/atom extension:enable ahgFeedbackPlugin
Testing
Test URLs:
- Browse:
https://[domain]/feedback - General:
https://[domain]/feedback/general - Edit:
https://[domain]/feedback/[id]/edit - Item Feedback: Click button on any record
Troubleshooting
Common Issues
"Class QubitFeedback not found"
- Cause: Old action using Propel model
- Solution: All actions must use Laravel Query Builder
"Cannot use object of type stdClass as array"
- Cause: url_for() expecting QubitObject
- Solution: Use explicit URL:
url_for(['module' => 'ahgFeedback', 'action' => 'edit', 'id' => $id])
"Foreign key constraint fails on feedback"
- Cause: Not inserting into
objecttable first - Solution: Insert sequence: object → feedback → feedback_i18n
Debug Queries
// Enable query logging
DB::enableQueryLog();
// ... run queries
dd(DB::getQueryLog());
Version History
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2026-01-13 | Initial release with Laravel Query Builder |