Heratio Help Center article. Category: Plugin Reference.
ahgDoiPlugin - Technical Documentation
Version: 1.0.0
Category: Identifiers / Integration
Dependencies: atom-framework, DataCite API account
Overview
DOI (Digital Object Identifier) minting and management via DataCite for persistent identifiers on archival records. Supports batch minting, metadata synchronization, queue-based processing, and lifecycle management.
Features
| Feature |
Description |
| DOI Minting |
Single and batch minting via DataCite API |
| Metadata Sync |
Keep DOI metadata current with record changes |
| Queue Processing |
Async processing to handle rate limits |
| Auto-Mint |
Automatic minting on record publish |
| Deactivation |
Tombstone workflow for deleted records |
| Export |
CSV/JSON export of all DOIs |
| Reports |
Statistics and analytics dashboard |
Database Schema
ERD Diagram
+-----------------------------------------------+
| ahg_doi |
+-----------------------------------------------+
| PK id BIGINT UNSIGNED |
| FK object_id INT |---+
| object_type VARCHAR(50) | |
| | |
| -- DOI DATA -- | |
| doi VARCHAR(255) UNIQUE | |
| datacite_id VARCHAR(255) | |
| status ENUM | |
| prefix VARCHAR(50) | |
| suffix VARCHAR(100) | |
| | |
| -- METADATA -- | |
| object_title VARCHAR(500) | |
| target_url TEXT | |
| metadata_json JSON | |
| | |
| -- STATUS -- | |
| minted_at TIMESTAMP | |
| synced_at TIMESTAMP | |
| verified_at TIMESTAMP | |
| deactivated_at TIMESTAMP | |
| deactivation_reason TEXT | |
| | |
| -- AUDIT -- | |
| FK created_by INT | |
| FK repository_id INT | |
| created_at TIMESTAMP | |
| updated_at TIMESTAMP | |
+-----------------------------------------------+ |
| |
| 1:N |
v |
+-----------------------------------------------+ |
| ahg_doi_queue | |
+-----------------------------------------------+ |
| PK id BIGINT UNSIGNED | |
| FK object_id INT |---+
| action ENUM |
| status ENUM |
| priority INT |
| attempts INT |
| max_attempts INT |
| error_message TEXT |
| scheduled_at TIMESTAMP |
| started_at TIMESTAMP |
| completed_at TIMESTAMP |
| created_at TIMESTAMP |
| updated_at TIMESTAMP |
+-----------------------------------------------+
+-----------------------------------------------+
| ahg_doi_config |
+-----------------------------------------------+
| PK id BIGINT UNSIGNED |
| FK repository_id INT |
| datacite_repository_id VARCHAR(255) |
| datacite_password VARCHAR(255) ENCRYPTED |
| datacite_prefix VARCHAR(50) |
| datacite_shoulder VARCHAR(50) |
| environment ENUM('test', 'production') |
| auto_mint TINYINT |
| default_state ENUM |
| url_template TEXT |
| is_active TINYINT |
| created_at TIMESTAMP |
| updated_at TIMESTAMP |
+-----------------------------------------------+
+-----------------------------------------------+
| ahg_doi_log |
+-----------------------------------------------+
| PK id BIGINT UNSIGNED |
| FK doi_id BIGINT UNSIGNED |
| action VARCHAR(50) |
| status VARCHAR(50) |
| request_data JSON |
| response_data JSON |
| error_message TEXT |
| FK performed_by INT |
| created_at TIMESTAMP |
+-----------------------------------------------+
DOI States
| State |
DataCite API Value |
Description |
| draft |
draft |
Reserved, not resolvable |
| registered |
registered |
Resolvable but not indexed |
| findable |
findable |
Fully public and indexed |
| deleted |
State change event |
Tombstone, was public |
| failed |
(internal) |
Minting/sync error |
Service Methods
DoiService
namespace ahgDoiPlugin\Services;
class DoiService
{
// Configuration
public function getConfig(?int $repositoryId = null): ?object
public function saveConfig(array $data): bool
public function testConnection(?int $repositoryId = null): array
// Minting
public function mintDoi(int $objectId): array
public function queueForMinting(int $objectId, string $action = 'mint'): int
public function processQueue(int $limit = 50): array
public function buildMetadata(object $object): array
// Sync
public function syncDoi(int $doiId): array
public function bulkSync(array $options = []): array
public function queueForSync(array $options = []): int
// State Management
public function updateDoiState(int $doiId, string $state): array
public function deactivateDoi(int $doiId, string $reason = ''): array
public function reactivateDoi(int $doiId): array
// Queries
public function getDoi(int $id): ?object
public function getDoiByObjectId(int $objectId): ?object
public function hasDoi(int $objectId): bool
public function getStats(): array
public function getRecentDois(int $limit = 10): Collection
// Export
public function exportToCsv(array $options = []): string
public function exportToJson(array $options = []): string
// Verification
public function verifyResolution(int $doiId): array
}
DataCite API Integration
Endpoints Used
| Endpoint |
Method |
Purpose |
/dois |
POST |
Create new DOI |
/dois/{id} |
PUT |
Update DOI metadata |
/dois/{id} |
GET |
Retrieve DOI data |
/dois/{id} |
DELETE |
Delete DOI (draft only) |
Authentication
// Base64 encoded credentials
$auth = base64_encode("{$repositoryId}:{$password}");
$headers = [
'Authorization' => "Basic {$auth}",
'Content-Type' => 'application/vnd.api+json',
];
API Environments
| Environment |
Base URL |
| Test |
https://api.test.datacite.org |
| Production |
https://api.datacite.org |
DataCite Schema 4.4 Mapping
| DataCite Field |
Heratio Source |
titles[0].title |
informationObject.title |
creators[0].name |
informationObject.creators[0].name or repository name |
publisher |
Repository name |
publicationYear |
informationObject.dates[0].date year |
types.resourceTypeGeneral |
Based on level of description |
descriptions[0].description |
informationObject.scopeAndContent |
subjects |
informationObject.subjects |
dates |
informationObject.dates |
language |
informationObject.language |
rightsList |
informationObject.accessConditions |
Resource Type Mapping
| Level of Description |
ResourceTypeGeneral |
| Fonds |
Collection |
| Series |
Collection |
| File |
Text |
| Item |
Text |
| Collection |
Collection |
| (Digital) |
Image, Audiovisual, Dataset |
CLI Tasks
doi:mint
php symfony doi:mint [options]
Options:
--id=ID Mint for specific record
--all Mint for all eligible records
--repository=ID Filter by repository
--level=LEVEL Filter by level of description
--limit=N Maximum to process (default: 100)
--dry-run Preview without minting
doi:sync
php symfony doi:sync [options]
Options:
--all Sync all DOIs
--id=ID Sync specific DOI
--status=STATUS Filter by status
--repository=ID Filter by repository
--limit=N Maximum to sync
--queue Queue for async processing
--dry-run Preview without syncing
doi:deactivate
php symfony doi:deactivate [options]
Options:
--id=ID DOI record ID to deactivate
--object-id=ID Object ID whose DOI to deactivate
--reason=TEXT Reason for deactivation
--reactivate Reactivate instead of deactivate
--list-deleted List all deactivated DOIs
--dry-run Preview without changes
Auto-Mint Hook
// In ahgDoiPluginConfiguration.class.php
$this->dispatcher->connect('QubitInformationObject.postSave', [$this, 'onRecordSave']);
public function onRecordSave(sfEvent $event)
{
$record = $event->getSubject();
if ($service->shouldAutoMint($record)) {
$service->queueForMinting($record->id, 'mint');
}
}
Auto-Mint Conditions
- Record is published (
publication_status_id = published)
- Auto-mint enabled for repository
- Record level matches configured levels
- No existing DOI for record
Queue Processing
Queue Worker
// Process pending queue items
$service->processQueue(50);
// Cron job (every 5 minutes)
*/5 * * * * cd /path/to/atom && php symfony doi:process-queue >> /var/log/atom/doi.log 2>&1
Queue Actions
| Action |
Description |
| mint |
Create new DOI |
| update |
Update DOI metadata |
| sync |
Full metadata sync |
| deactivate |
Set to deleted state |
| reactivate |
Restore to findable |
Routes
| Route |
Action |
Description |
/admin/doi |
index |
Dashboard |
/admin/doi/config |
config |
Configuration |
/admin/doi/browse |
browse |
List all DOIs |
/admin/doi/view/:id |
view |
View DOI details |
/admin/doi/mint/:id |
mint |
Mint DOI for record |
/admin/doi/batch-mint |
batchMint |
Batch minting |
/admin/doi/update/:id |
update |
Update DOI metadata |
/admin/doi/queue |
queue |
Queue management |
/admin/doi/sync |
sync |
Bulk sync |
/admin/doi/deactivate/:id |
deactivate |
Deactivate DOI |
/admin/doi/reactivate/:id |
reactivate |
Reactivate DOI |
/admin/doi/verify/:id |
verify |
Verify resolution |
/admin/doi/export |
export |
Export CSV/JSON |
/admin/doi/report |
report |
Reports |
/api/doi/mint/:id |
apiMint |
API: Mint |
/api/doi/status/:id |
apiStatus |
API: Get status |
/doi/:doi |
resolve |
Public DOI landing |
GlamIdentifierService Integration
// In GlamIdentifierService.php
public function getMintedDoi(int $objectId): ?string
{
$doi = DB::table('ahg_doi')
->where('object_id', $objectId)
->whereIn('status', ['findable', 'registered'])
->first();
return $doi->doi ?? null;
}
public function hasMintedDoi(int $objectId): bool
{
return DB::table('ahg_doi')
->where('object_id', $objectId)
->whereIn('status', ['findable', 'registered', 'draft'])
->exists();
}
public function getAllIdentifiers(int $objectId, ?string $sector = null): array
{
$identifiers = [];
// Add DOI if exists
if ($doi = $this->getMintedDoi($objectId)) {
$identifiers['doi'] = [
'type' => 'doi',
'value' => $doi,
'url' => "https://doi.org/{$doi}",
'display' => "DOI: {$doi}",
];
}
// Add sector-specific identifiers...
return $identifiers;
}
Record Badge Integration
The _recordBadge.php partial is included in information object views:
// In sfIsadPlugin/templates/indexSuccess.php
<?php include_partial('doi/recordBadge', ['resource' => $resource]) ?>
Features:
- Shows DOI link if exists
- "Mint DOI" button for admins if no DOI
- AJAX minting without page reload
- Status badge (findable, registered, draft)
AHG Settings Integration
System Info Page
DOI statistics displayed via getDoiStatistics():
- Total DOIs
- Count by status
- Queue pending
- Configuration status
Cron Jobs Page
DOI commands listed:
doi:mint - Batch minting
doi:sync - Metadata sync
doi:deactivate - Deactivation
doi:process-queue - Queue processing
Error Handling
| Error Code |
Meaning |
Action |
| 401 |
Invalid credentials |
Check config |
| 404 |
DOI not found |
Re-mint |
| 422 |
Invalid metadata |
Check required fields |
| 429 |
Rate limited |
Queue and retry |
| 500 |
DataCite error |
Log and retry |
Retry Logic
$maxAttempts = 3;
$retryDelay = [60, 300, 900]; // 1min, 5min, 15min
if ($attempts < $maxAttempts) {
$queue->update([
'status' => 'pending',
'scheduled_at' => now()->addSeconds($retryDelay[$attempts]),
'attempts' => $attempts + 1,
]);
}
Security
- DataCite passwords stored encrypted
- Admin-only access to minting
- Audit log for all DOI operations
- Rate limiting on API endpoints
Part of the Heratio AHG Framework