Files
CHIEFSOFT\ameye df3a033196 first commit
2024-06-08 17:09:23 -04:00

795 lines
30 KiB
PHP

<?php
/**
* @file classes/migration/upgrade/v3_4_0/I7014_DoiMigration.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class I7014_DoiMigration
*
* @brief Describe upgrade/downgrade operations for DB table dois.
*/
namespace APP\migration\upgrade\v3_4_0;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use PKP\doi\Doi;
use PKP\install\DowngradeNotSupportedException;
use PKP\migration\upgrade\v3_4_0\PKPI7014_DoiMigration;
class I7014_DoiMigration extends PKPI7014_DoiMigration
{
/**
* Run the migrations.
*/
public function up(): void
{
parent::up();
// Add doiId to issue
Schema::table('issues', function (Blueprint $table) {
$table->bigInteger('doi_id')->nullable();
$table->foreign('doi_id')->references('doi_id')->on('dois')->nullOnDelete();
$table->index(['doi_id'], 'issues_doi_id');
});
// Add doiId to galley
Schema::table('publication_galleys', function (Blueprint $table) {
$table->bigInteger('doi_id')->nullable();
$table->foreign('doi_id')->references('doi_id')->on('dois')->nullOnDelete();
$table->index(['doi_id'], 'publication_galleys_doi_id');
});
$this->migrateExistingDataUp();
}
/**
* Reverse the downgrades
*
* @throws DowngradeNotSupportedException
*/
public function down(): void
{
throw new DowngradeNotSupportedException();
}
protected function migrateExistingDataUp(): void
{
parent::migrateExistingDataUp();
// Find all existing DOIs, move to new DOI objects and add foreign key for pub object
$this->_migrateGalleyDoisUp();
$this->_migrateIssueDoisUp();
$this->_migrateCrossrefSettingsToContext();
$this->_migrateDataciteSettingsToContext();
}
/**
* Transfer over batch_id, failedMsg and settings, filter info for importExport to generic plugin transfer
*/
private function _migrateCrossrefSettingsToContext()
{
// ===== Filters ===== //
DB::table('filters')
->where('class_name', '=', 'plugins.importexport.crossref.filter.IssueCrossrefXmlFilter')
->update(['class_name' => 'plugins.generic.crossref.filter.IssueCrossrefXmlFilter']);
DB::table('filters')
->where('class_name', '=', 'plugins.importexport.crossref.filter.ArticleCrossrefXmlFilter')
->update(['class_name' => 'plugins.generic.crossref.filter.ArticleCrossrefXmlFilter']);
// ===== Submissions Statuses & Settings ===== //
// 1. Get submissions with Crossref info
$publicationDois = DB::table('publications', 'p')
->select(['p.submission_id', 'p.doi_id'])
->whereNotNull('p.doi_id')
->get()
->map(function ($item) {
return [
'submission_id' => $item->submission_id,
'doi_id' => $item->doi_id
];
});
$galleyDois = DB::table('publication_galleys', 'pg')
->leftJoin('publications as p', 'pg.publication_id', '=', 'p.publication_id')
->select(['p.submission_id', 'pg.doi_id'])
->whereNotNull('pg.doi_id')
->get()
->map(function ($item) {
return [
'submission_id' => $item->submission_id,
'doi_id' => $item->doi_id
];
});
// 2. Get all DOIs possibly associated with submission (publications and galleys)
$doisBySubmission = $publicationDois->concat($galleyDois)
->mapToGroups(function ($item) {
return [$item['submission_id'] => $item['doi_id']];
})
->map(function ($item) {
return ['doiIds' => $item->all()];
})->toArray();
// 3. Apply batchId and failedMsg to all DOIs
DB::table('submissions', 's')
->leftJoin('submission_settings as ss', 's.submission_id', '=', 'ss.submission_id')
->whereIn('ss.setting_name', ['crossref::registeredDoi', 'crossref::status', 'crossref::batchId', 'crossref::failedMsg'])
->select(['ss.submission_id', 'ss.setting_name', 'ss.setting_value'])
->get()
->each(function ($item) use (&$doisBySubmission) {
if (!isset($doisBySubmission[$item->submission_id])) {
return;
}
switch ($item->setting_name) {
case 'crossref::registeredDoi':
$doisBySubmission[$item->submission_id]['crossref::registeredDoi'] = $item->setting_value;
return;
case 'crossref::status':
$status = Doi::STATUS_ERROR;
$registrationAgency = null;
if (in_array($item->setting_value, ['found', 'registered', 'markedRegistered'])) {
$status = Doi::STATUS_REGISTERED;
if ($item->setting_value == 'registered') {
$registrationAgency = 'CrossrefExportPlugin';
}
}
$doisBySubmission[$item->submission_id]['status'] = $status;
if ($registrationAgency !== null) {
$doisBySubmission[$item->submission_id]['registrationAgency'] = $registrationAgency;
}
return;
case 'crossref::batchId':
$doisBySubmission[$item->submission_id]['crossrefplugin_batchId'] = $item->setting_value;
return;
case 'crossref::failedMsg':
$doisBySubmission[$item->submission_id]['crossrefplugin_failedMsg'] = $item->setting_value;
return;
}
});
// 4. Apply status to all DOIs
$doiSettingInserts = [];
$doiStatusUpdates = [];
foreach ($doisBySubmission as $item) {
$doiIds = $item['doiIds'];
foreach ($doiIds as $doiId) {
// Settings
if (isset($item['crossrefplugin_batchId'])) {
$doiSettingInserts[] = [
'doi_id' => $doiId,
'setting_name' => 'crossrefplugin_batchId',
'setting_value' => $item['crossrefplugin_batchId'],
];
}
if (isset($item['crossrefplugin_failedMsg'])) {
$doiSettingInserts[] = [
'doi_id' => $doiId,
'setting_name' => 'crossrefplugin_failedMsg',
'setting_value' => $item['crossrefplugin_failedMsg'],
];
}
// Status
if (isset($item['status'])) {
$doiStatusUpdates[$doiId] = ['status' => $item['status']];
} elseif (isset($item['crossref::registeredDoi'])) {
$doiStatusUpdates[$doiId] = ['status' => 3];
}
if (isset($item['registrationAgency'])) {
$doiSettingInserts[] = [
'doi_id' => $doiId,
'setting_name' => 'registrationAgency',
'setting_value' => $item['registrationAgency']
];
}
}
}
foreach (array_chunk($doiSettingInserts, 100) as $chunkedInserts) {
DB::table('doi_settings')
->insert($chunkedInserts);
}
foreach ($doiStatusUpdates as $doiId => $doiStatusUpdate) {
DB::table('dois')
->where('doi_id', '=', $doiId)
->update($doiStatusUpdate);
}
// 5. Clean up old settings
DB::table('submission_settings')
->whereIn('setting_name', ['crossref::registeredDoi', 'crossref::status', 'crossref::batchId'])
->delete();
// ===== Issue Statuses & Settings ===== //
// 1. Get issues with Crossref-related info
$issueData = DB::table('issues', 'i')
->leftJoin('issue_settings as iss', 'i.issue_id', '=', 'iss.issue_id')
->whereIn('iss.setting_name', ['crossref::registeredDoi', 'crossref::status', 'crossref::batchId', 'crossref::failedMsg'])
->select(['i.issue_id', 'i.doi_id', 'iss.setting_name', 'iss.setting_value'])
->get()
->reduce(function ($carry, $item) {
if (!isset($carry[$item->issue_id])) {
$carry[$item->issue_id] = [
'doi_id' => $item->doi_id
];
}
$carry[$item->issue_id][$item->setting_name] = $item->setting_value;
return $carry;
}, []);
// 2. Map settings/status insert statements
$inserts = [];
$statuses = [];
foreach ($issueData as $item) {
// Settings
if (isset($item['crossref::batchId'])) {
$inserts[] = [
'doi_id' => $item['doi_id'],
'setting_name' => 'crossrefplugin_batchId',
'setting_value' => $item['crossref::batchId'],
];
}
if (isset($item['crossref::failedMsg'])) {
$inserts[] = [
'doi_id' => $item['doi_id'],
'setting_name' => 'crossrefplugin_failedMsg',
'setting_value' => $item['crossref::failedMsg'],
];
}
// Status
if (isset($item['crossref::status'])) {
$status = Doi::STATUS_ERROR;
$registrationAgency = null;
if (in_array($item['crossref::status'], ['found', 'registered', 'markedRegistered'])) {
$status = Doi::STATUS_REGISTERED;
if ($item['crossref::status'] === 'registered') {
$registrationAgency = 'CrossrefExportPlugin';
}
} elseif (isset($item['crossref::registeredDoi'])) {
$status = Doi::STATUS_REGISTERED;
}
$statuses[$item['doi_id']] = ['status' => $status];
if ($registrationAgency !== null) {
$inserts[] = [
'doi_id' => $item['doi_id'],
'setting_name' => 'registrationAgency',
'setting_value' => $registrationAgency
];
}
}
}
// 3. Insert updated settings/statuses
foreach (array_chunk($inserts, 100) as $chunkedInserts) {
DB::table('doi_settings')
->insert($chunkedInserts);
}
foreach ($statuses as $doiId => $insert) {
DB::table('dois')
->where('doi_id', '=', $doiId)
->update($insert);
}
// 4. Clean up old settings
DB::table('issue_settings')
->whereIn('setting_name', ['crossref::registeredDoi', 'crossref::status', 'crossref::batchId', 'crossref::failedMsg'])
->delete();
// ===== General cleanup ===== //
// If any Crossref settings are configured, assume plugin is in use and enable
$contextsWithPluginEnabled = DB::table('journals')
->whereIn('journal_id', function (Builder $q) {
$q->select('context_id')
->from('plugin_settings')
->where('plugin_name', '=', 'crossrefexportplugin');
})
->select(['journal_id'])
->get();
$contextsWithPluginEnabled->each(function ($item) {
DB::table('plugin_settings')
->insert(
[
'plugin_name' => 'crossrefplugin',
'context_id' => $item->journal_id,
'setting_name' => 'enabled',
'setting_value' => 1,
'setting_type' => 'bool'
]
);
});
// Enable automatic DOI deposit if configured
$contextsWithAutomaticDeposit = DB::table('journals')
->whereIn('journal_id', function (Builder $q) {
$q->select(['context_id'])
->from('plugin_settings')
->where('plugin_name', '=', 'crossrefexportplugin')
->where('setting_name', '=', 'automaticRegistration')
->where('setting_value', '=', 1) ;
})
->select(['journal_id'])
->get();
$contextsWithAutomaticDeposit->each(function ($item) {
DB::table('journal_settings')
->upsert(
[
'journal_id' => $item->journal_id,
'setting_name' => 'automaticDoiDeposit',
'setting_value' => 1
],
['journal_id', 'locale', 'setting_name'],
['setting_value']
);
});
DB::table('plugin_settings')
->where('plugin_name', '=', 'crossrefexportplugin')
->where('setting_name', '=', 'automaticRegistration')
->delete();
// Update no-longer-in-use version for importExport plugin
DB::table('versions')
->where('product_type', '=', 'plugins.importexport')
->where('product', '=', 'crossref')
->delete();
// Remove scheduled task
DB::table('scheduled_tasks')
->where('class_name', '=', 'plugins.importexport.crossref.CrossrefInfoSender')
->delete();
}
/**
* Transfer over settings, filter info for importExport to generic plugin transfer
*/
private function _migrateDataciteSettingsToContext()
{
// ===== Filters ===== //
DB::table('filters')
->where('class_name', '=', 'plugins.importexport.datacite.filter.DataciteXmlFilter')
->update(['class_name' => 'plugins.generic.datacite.filter.DataciteXmlFilter']);
// ===== Issues Statuses & Settings ===== //
// 1. Get issues with Datacite-related info
$issueData = DB::table('issues', 'i')
->leftJoin('issue_settings as iss', 'i.issue_id', '=', 'iss.issue_id')
->whereIn('iss.setting_name', ['datacite::registeredDoi', 'datacite::status'])
->select(['i.issue_id', 'i.doi_id', 'iss.setting_name', 'iss.setting_value'])
->get()
->reduce(function ($carry, $item) {
if (!isset($carry[$item->issue_id])) {
$carry[$item->issue_id] = [
'doi_id' => $item->doi_id
];
}
$carry[$item->issue_id][$item->setting_name] = $item->setting_value;
return $carry;
}, []);
// 2. Map statuses insert statements
$statuses = [];
$registrationAgencies = [];
foreach ($issueData as $item) {
// Status
if (isset($item['datacite::status'])) {
$status = Doi::STATUS_ERROR;
$registrationAgency = null;
if (in_array($item['datacite::status'], ['found', 'registered', 'markedRegistered'])) {
if ($item['datacite::status'] === 'registered') {
$registrationAgency = 'DataciteExportPlugin';
}
$status = Doi::STATUS_REGISTERED;
} elseif (isset($item['datacite::registeredDoi'])) {
$status = Doi::STATUS_REGISTERED;
}
$statuses[$item['doi_id']] = ['status' => $status];
$registrationAgencies[$item['doi_id']] = $registrationAgency;
}
}
// 3. Insert updated statuses
foreach ($statuses as $doiId => $insert) {
DB::table('dois')
->where('doi_id', '=', $doiId)
->update($insert);
}
foreach ($registrationAgencies as $doiId => $agency) {
if ($agency === null) {
continue;
}
DB::table('doi_settings')
->insert([
'doi_id' => $doiId,
'setting_name' => 'registrationAgency',
'setting_value' => $agency
]);
}
// 4. Clean up old settings
DB::table('issue_settings')
->whereIn('setting_name', ['datacite::registeredDoi', 'datacite::status'])
->delete();
// ===== Publications Statuses & Settings ===== //
// 1. Get publications with Datacite-related info
$publicationData = DB::table('submissions', 's')
->leftJoin('submission_settings as ss', 's.submission_id', '=', 'ss.submission_id')
->leftJoin('publications as p', 's.current_publication_id', '=', 'p.publication_id')
->whereIn('ss.setting_name', ['datacite::registeredDoi', 'datacite::status'])
->select(['p.publication_id', 'p.doi_id', 'ss.setting_name', 'ss.setting_value'])
->get()
->reduce(function ($carry, $item) {
if (!isset($carry[$item->publication_id])) {
$carry[$item->publication_id] = [
'doi_id' => $item->doi_id
];
}
$carry[$item->publication_id][$item->setting_name] = $item->setting_value;
return $carry;
}, []);
// 2. Map statuses insert statements
$statuses = [];
$registrationAgencies = [];
foreach ($publicationData as $item) {
// Status
if (isset($item['datacite::status'])) {
$status = Doi::STATUS_ERROR;
$registrationAgency = null;
if (in_array($item['datacite::status'], ['found', 'registered', 'markedRegistered'])) {
if ($item['datacite::status'] === 'registered') {
$registrationAgency = 'DataciteExportPlugin';
}
$status = Doi::STATUS_REGISTERED;
} elseif (isset($item['datacite::registeredDoi'])) {
$status = Doi::STATUS_REGISTERED;
}
$statuses[$item['doi_id']] = ['status' => $status];
$registrationAgencies[$item['doi_id']] = $registrationAgency;
}
}
// 3. Insert updated statuses
foreach ($statuses as $doiId => $insert) {
DB::table('dois')
->where('doi_id', '=', $doiId)
->update($insert);
}
foreach ($registrationAgencies as $doiId => $agency) {
if ($agency === null) {
continue;
}
DB::table('doi_settings')
->insert([
'doi_id' => $doiId,
'setting_name' => 'registrationAgency',
'setting_value' => $agency
]);
}
// 4. Clean up old settings
DB::table('publication_settings')
->whereIn('setting_name', ['datacite::registeredDoi', 'datacite::status'])
->delete();
// ===== Galleys Statuses & Settings ===== //
// 1. Get galleys with Datacite-related info
$galleyData = DB::table('publication_galleys', 'pg')
->leftJoin('publication_galley_settings as pgs', 'pg.galley_id', '=', 'pgs.galley_id')
->whereIn('pgs.setting_name', ['datacite::registeredDoi', 'datacite::status'])
->select(['pg.galley_id', 'pg.doi_id', 'pgs.setting_name', 'pgs.setting_value'])
->get()
->reduce(function ($carry, $item) {
if (!isset($carry[$item->galley_id])) {
$carry[$item->galley_id] = [
'doi_id' => $item->doi_id
];
}
$carry[$item->galley_id][$item->setting_name] = $item->setting_value;
return $carry;
}, []);
// 2. Map statuses insert statements
$statuses = [];
$registrationAgencies = [];
foreach ($galleyData as $item) {
// Status
if (isset($item['datacite::status'])) {
$status = Doi::STATUS_ERROR;
$registrationAgency = null;
if (in_array($item['datacite::status'], ['found', 'registered', 'markedRegistered'])) {
if ($item['datacite::status'] === 'registered') {
$registrationAgency = 'DataciteExportPlugin';
}
$status = Doi::STATUS_REGISTERED;
} elseif (isset($item['datacite::registeredDoi'])) {
$status = Doi::STATUS_REGISTERED;
}
$statuses[$item['doi_id']] = ['status' => $status];
$registrationAgencies[$item['doi_id']] = $registrationAgency;
}
}
// 3. Insert updated statuses
foreach ($statuses as $doiId => $insert) {
DB::table('dois')
->where('doi_id', '=', $doiId)
->update($insert);
}
foreach ($registrationAgencies as $doiId => $agency) {
if ($agency === null) {
continue;
}
DB::table('doi_settings')
->insert([
'doi_id' => $doiId,
'setting_name' => 'registrationAgency',
'setting_value' => $agency
]);
}
// 4. Clean up old settings
DB::table('publication_galley_settings')
->whereIn('setting_name', ['datacite::registeredDoi', 'datacite::status'])
->delete();
// ===== General cleanup ===== //
// If any Datacite settings are configured, assume plugin is in use and enable
$contextsWithPluginEnabled = DB::table('journals')
->whereIn('journal_id', function (Builder $q) {
$q->select('context_id')
->from('plugin_settings')
->where('plugin_name', '=', 'dataciteexportplugin');
})
->select(['journal_id'])
->get();
$contextsWithPluginEnabled->each(function ($item) {
DB::table('plugin_settings')
->insert(
[
'plugin_name' => 'dataciteplugin',
'context_id' => $item->journal_id,
'setting_name' => 'enabled',
'setting_value' => 1,
'setting_type' => 'bool'
]
);
});
// Enable automatic DOI deposit if configured
$contextsWithAutomaticDeposit = DB::table('journals')
->whereIn('journal_id', function (Builder $q) {
$q->select(['context_id'])
->from('plugin_settings')
->where('plugin_name', '=', 'dataciteexportplugin')
->where('setting_name', '=', 'automaticRegistration')
->where('setting_value', '=', 1) ;
})
->select(['journal_id'])
->get();
$contextsWithAutomaticDeposit->each(function ($item) {
DB::table('journal_settings')
->upsert(
[
'journal_id' => $item->journal_id,
'setting_name' => 'automaticDoiDeposit',
'setting_value' => 1
],
['journal_id', 'locale', 'setting_name'],
['setting_value']
);
});
DB::table('plugin_settings')
->where('plugin_name', '=', 'dataciteexportplugin')
->where('setting_name', '=', 'automaticRegistration')
->delete();
// Update no-longer-in-use version for importExport plugin
DB::table('versions')
->where('product_type', '=', 'plugins.importexport')
->where('product', '=', 'datacite')
->delete();
// Remove scheduled task
DB::table('scheduled_tasks')
->where('class_name', '=', 'plugins.importexport.datacite.DataciteInfoSender')
->delete();
}
/**
* Move galley DOIs from publication_galley_settings table to DOI objects
*/
private function _migrateGalleyDoisUp(): void
{
$q = DB::table('submissions', 's')
->select(['s.context_id', 'pg.galley_id', 'pg.doi_id', 'pgss.setting_name', 'pgss.setting_value'])
->leftJoin('publications as p', 'p.submission_id', '=', 's.submission_id')
->leftJoin('publication_galleys as pg', 'pg.publication_id', '=', 'p.publication_id')
->leftJoin('publication_galley_settings as pgss', 'pgss.galley_id', '=', 'pg.galley_id')
->where('pgss.setting_name', '=', 'pub-id::doi');
$q->chunkById(1000, function ($items) {
foreach ($items as $item) {
// Double-check to ensure a DOI object does not already exist for galley
if ($item->doi_id === null) {
$doiId = $this->_addDoi($item->context_id, $item->setting_value);
// Add association to newly created DOI to galley
DB::table('publication_galleys')
->where('galley_id', '=', $item->galley_id)
->update(['doi_id' => $doiId]);
} else {
// Otherwise update existing DOI object
$this->_updateDoi($item->doi_id, $item->context_id, $item->setting_value);
}
}
}, 'pg.galley_id', 'galley_id');
}
/**
* Move issue DOIs from issue_settings table to DOI objects
*/
private function _migrateIssueDoisUp(): void
{
$q = DB::table('issues', 'i')
->select(['i.issue_id', 'i.journal_id', 'i.doi_id', 'iss.setting_name', 'iss.setting_value'])
->leftJoin('issue_settings as iss', 'iss.issue_id', '=', 'i.issue_id')
->where('iss.setting_name', '=', 'pub-id::doi');
$q->chunkById(1000, function ($items) {
foreach ($items as $item) {
// Double-check to ensure a DOI object does not already exist for issue
if ($item->doi_id === null) {
$doiId = $this->_addDoi($item->journal_id, $item->setting_value);
// Add association to newly created DOI to issue
DB::table('issues')
->where('issue_id', '=', $item->issue_id)
->update(['doi_id' => $doiId]);
} else {
// Otherwise update existing DOI object
$this->_updateDoi($item->doi_id, $item->journal_id, $item->setting_value);
}
}
}, 'i.issue_id', 'issue_id');
}
/**
* Gets app-specific context table name, e.g. journals
*
*/
protected function getContextTable(): string
{
return 'journals';
}
/**
* Gets app-specific context_id column, e.g. journal_id
*/
protected function getContextIdColumn(): string
{
return 'journal_id';
}
/**
* Gets app-specific context settings table, e.g. journal_settings
*/
protected function getContextSettingsTable(): string
{
return 'journal_settings';
}
/**
* Adds app-specific suffix patterns to data collector stdClass
*/
protected function addSuffixPatternsData(\stdClass $data): \stdClass
{
$data->doiIssueSuffixPattern = [];
return $data;
}
/**
* Add suffix pattern settings from DB into reducer's data
*/
protected function insertSuffixPatternsData(\stdClass $carry, \stdClass $item): \stdClass
{
switch ($item->setting_name) {
case 'doiIssueSuffixPattern':
$carry->doiIssueSuffixPattern[] = [
$this->getContextIdColumn() => $item->context_id,
'setting_name' => $item->setting_name,
'setting_value' => $item->setting_value,
];
return $carry;
default:
return $carry;
}
}
/**
* Add insert-ready statements for all applicable suffix pattern items
*/
protected function prepareSuffixPatternsForInsert(\stdClass $processedData, array $insertData): array
{
foreach ($processedData->doiIssueSuffixPattern as $item) {
$insertData[] = $item;
}
return $insertData;
}
/**
* Add app-specific enabled DOI types for insert into DB
*/
protected function insertEnabledDoiTypes(\stdClass $carry, \stdClass $item): \stdClass
{
if ($item->setting_name === 'enableIssueDoi') {
if (!isset($carry->enabledDoiTypes[$item->context_id])) {
$carry->enabledDoiTypes[$item->context_id] = [
$this->getContextIdColumn() => $item->context_id,
'setting_name' => 'enabledDoiTypes',
'setting_value' => [],
];
}
if ($item->setting_value === '1') {
$carry->enabledDoiTypes[$item->context_id]['setting_value'][] = 'issue';
}
}
return $carry;
}
/**
* Get an array with the keys for each suffix pattern type
*/
protected function getSuffixPatternNames(): array
{
return ['doiPublicationSuffixPattern', 'doiRepresentationSuffixPattern', 'doiIssueSuffixPattern'];
}
/**
* Returns the default pattern for the given suffix pattern type
*/
protected function getSuffixPatternValue(string $suffixPatternName): string
{
$pattern = '';
switch ($suffixPatternName) {
case 'doiPublicationSuffixPattern':
$pattern = '%j.v%vi%i.%a';
break;
case 'doiRepresentationSuffixPattern':
$pattern = '%j.v%vi%i.%a.g%g';
break;
case 'doiIssueSuffixPattern':
$pattern = '%j.v%vi%i';
break;
}
return $pattern;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\APP\migration\upgrade\v3_4_0\I7014_DoiMigration', '\I7014_DoiMigration');
}