first commit
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/Migration.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 Migration
|
||||
*
|
||||
* @brief Base class for PKP migrations.
|
||||
*/
|
||||
|
||||
namespace PKP\migration;
|
||||
|
||||
use PKP\install\Installer;
|
||||
|
||||
abstract class Migration extends \Illuminate\Database\Migrations\Migration
|
||||
{
|
||||
protected array $_attributes;
|
||||
protected Installer $_installer;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Installer $installer, array $attributes)
|
||||
{
|
||||
$this->_attributes = $attributes;
|
||||
$this->_installer = $installer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
abstract public function up(): void;
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
abstract public function down(): void;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/AnnouncementsMigration.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 AnnouncementsMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema as Schema;
|
||||
|
||||
class AnnouncementsMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Announcement types.
|
||||
Schema::create('announcement_types', function (Blueprint $table) {
|
||||
$table->comment('Announcement types allow for announcements to optionally be categorized.');
|
||||
$table->bigInteger('type_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = \APP\core\Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'announcement_types_context_id');
|
||||
});
|
||||
|
||||
// Locale-specific announcement type data
|
||||
Schema::create('announcement_type_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about announcement types, including localized properties like their names.');
|
||||
$table->bigIncrements('announcement_type_setting_id');
|
||||
$table->bigInteger('type_id');
|
||||
$table->foreign('type_id')->references('type_id')->on('announcement_types')->onDelete('cascade');
|
||||
$table->index(['type_id'], 'announcement_type_settings_type_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6);
|
||||
|
||||
$table->unique(['type_id', 'locale', 'setting_name'], 'announcement_type_settings_unique');
|
||||
});
|
||||
|
||||
// Announcements.
|
||||
Schema::create('announcements', function (Blueprint $table) {
|
||||
$table->comment('Announcements are messages that can be presented to users e.g. on the homepage.');
|
||||
$table->bigInteger('announcement_id')->autoIncrement();
|
||||
// NOT NULL not included for upgrade purposes
|
||||
$table->smallInteger('assoc_type')->nullable();
|
||||
$table->bigInteger('assoc_id');
|
||||
|
||||
$table->bigInteger('type_id')->nullable();
|
||||
$table->foreign('type_id')->references('type_id')->on('announcement_types')->onDelete('set null');
|
||||
$table->index(['type_id'], 'announcements_type_id');
|
||||
|
||||
$table->date('date_expire')->nullable();
|
||||
$table->datetime('date_posted');
|
||||
$table->index(['assoc_type', 'assoc_id'], 'announcements_assoc');
|
||||
});
|
||||
|
||||
// Locale-specific announcement data
|
||||
Schema::create('announcement_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about announcements, including localized properties like names and contents.');
|
||||
$table->bigIncrements('announcement_setting_id');
|
||||
$table->bigInteger('announcement_id');
|
||||
$table->foreign('announcement_id')->references('announcement_id')->on('announcements')->onDelete('cascade');
|
||||
$table->index(['announcement_id'], 'announcement_settings_announcement_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['announcement_id', 'locale', 'setting_name'], 'announcement_settings_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('announcement_settings');
|
||||
Schema::drop('announcements');
|
||||
Schema::drop('announcement_type_settings');
|
||||
Schema::drop('announcement_types');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/CategoriesMigration.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 CategoriesMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CategoriesMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('categories', function (Blueprint $table) {
|
||||
$table->comment('Categories permit the organization of submissions into a heirarchical structure.');
|
||||
$table->bigInteger('category_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = \APP\core\Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'category_context_id');
|
||||
|
||||
$table->bigInteger('parent_id')->nullable(); // Self-referential foreign key set below
|
||||
|
||||
$table->bigInteger('seq')->nullable();
|
||||
$table->string('path', 255);
|
||||
$table->text('image')->nullable();
|
||||
|
||||
$table->index(['context_id', 'parent_id'], 'category_context_parent_id');
|
||||
$table->unique(['context_id', 'path'], 'category_path');
|
||||
});
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->foreign('parent_id')->references('category_id')->on('categories')->onDelete('set null');
|
||||
$table->index(['parent_id'], 'category_parent_id');
|
||||
});
|
||||
|
||||
// Category-specific settings
|
||||
Schema::create('category_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about categories, including localized properties such as names.');
|
||||
$table->bigIncrements('category_setting_id');
|
||||
$table->bigInteger('category_id');
|
||||
$table->foreign('category_id')->references('category_id')->on('categories')->onDelete('cascade');
|
||||
$table->index(['category_id'], 'category_settings_category_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['category_id', 'locale', 'setting_name'], 'category_settings_unique');
|
||||
});
|
||||
|
||||
// Associations for categories and publications.
|
||||
Schema::create('publication_categories', function (Blueprint $table) {
|
||||
$table->comment('Associates publications (and thus submissions) with categories.');
|
||||
$table->bigIncrements('publication_category_id');
|
||||
$table->bigInteger('publication_id');
|
||||
$table->foreign('publication_id')->references('publication_id')->on('publications')->onDelete('cascade');
|
||||
$table->index(['publication_id'], 'publication_categories_publication_id');
|
||||
|
||||
$table->bigInteger('category_id');
|
||||
$table->foreign('category_id')->references('category_id')->on('categories')->onDelete('cascade');
|
||||
$table->index(['category_id'], 'publication_categories_category_id');
|
||||
|
||||
$table->unique(['publication_id', 'category_id'], 'publication_categories_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('categories');
|
||||
Schema::drop('category_settings');
|
||||
Schema::drop('publication_categories');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/CommonMigration.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 CommonMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use APP\core\Application;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CommonMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('versions', function (Blueprint $table) {
|
||||
$table->comment('Describes the installation and upgrade version history for the application and all installed plugins.');
|
||||
$table->bigIncrements('version_id');
|
||||
$table->integer('major')->default(0)->comment('Major component of version number, e.g. the 2 in OJS 2.3.8-0');
|
||||
$table->integer('minor')->default(0)->comment('Minor component of version number, e.g. the 3 in OJS 2.3.8-0');
|
||||
$table->integer('revision')->default(0)->comment('Revision component of version number, e.g. the 8 in OJS 2.3.8-0');
|
||||
$table->integer('build')->default(0)->comment('Build component of version number, e.g. the 0 in OJS 2.3.8-0');
|
||||
$table->datetime('date_installed');
|
||||
$table->smallInteger('current')->default(0)->comment('1 iff the version entry being described is currently active. This permits the table to store past installation history for forensic purposes.');
|
||||
$table->string('product_type', 30)->comment('Describes the type of product this row describes, e.g. "plugins.generic" (for a generic plugin) or "core" for the application itself')->nullable();
|
||||
$table->string('product', 30)->comment('Uniquely identifies the product this version row describes, e.g. "ojs2" for OJS 2.x, "languageToggle" for the language toggle block plugin, etc.')->nullable();
|
||||
$table->string('product_class_name', 80)->comment('Specifies the class name associated with this product, for plugins, or the empty string where not applicable.')->nullable();
|
||||
$table->smallInteger('lazy_load')->default(0)->comment('1 iff the row describes a lazy-load plugin; 0 otherwise');
|
||||
$table->smallInteger('sitewide')->default(0)->comment('1 iff the row describes a site-wide plugin; 0 otherwise');
|
||||
$table->unique(['product_type', 'product', 'major', 'minor', 'revision', 'build'], 'versions_unique');
|
||||
});
|
||||
|
||||
// Common site settings.
|
||||
Schema::create('site', function (Blueprint $table) {
|
||||
$table->comment('A singleton table describing basic information about the site.');
|
||||
$table->bigIncrements('site_id');
|
||||
$table->bigInteger('redirect')->default(0)->comment('If not 0, redirect to the specified journal/conference/... site.');
|
||||
$table->string('primary_locale', 14)->comment('Primary locale for the site.');
|
||||
$table->smallInteger('min_password_length')->default(6);
|
||||
$table->string('installed_locales', 1024)->default('en')->comment('Locales for which support has been installed.');
|
||||
$table->string('supported_locales', 1024)->comment('Locales supported by the site (for hosted journals/conferences/...).')->nullable();
|
||||
$table->string('original_style_file_name', 255)->nullable();
|
||||
});
|
||||
|
||||
// Site settings.
|
||||
Schema::create('site_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about the site, including localized properties such as its name.');
|
||||
$table->bigIncrements('site_setting_id');
|
||||
$table->string('setting_name', 255);
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->unique(['setting_name', 'locale'], 'site_settings_unique');
|
||||
});
|
||||
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->comment('All registered users, including authentication data and profile data.');
|
||||
$table->bigInteger('user_id')->autoIncrement();
|
||||
$table->string('username', 32);
|
||||
$table->string('password', 255);
|
||||
$table->string('email', 255);
|
||||
$table->string('url', 2047)->nullable();
|
||||
$table->string('phone', 32)->nullable();
|
||||
$table->string('mailing_address', 255)->nullable();
|
||||
$table->string('billing_address', 255)->nullable();
|
||||
$table->string('country', 90)->nullable();
|
||||
$table->string('locales', 255)->default('[]');
|
||||
$table->text('gossip')->nullable();
|
||||
$table->datetime('date_last_email')->nullable();
|
||||
$table->datetime('date_registered');
|
||||
$table->datetime('date_validated')->nullable();
|
||||
$table->datetime('date_last_login')->nullable();
|
||||
$table->smallInteger('must_change_password')->nullable();
|
||||
$table->bigInteger('auth_id')->nullable();
|
||||
$table->string('auth_str', 255)->nullable();
|
||||
$table->smallInteger('disabled')->default(0);
|
||||
$table->text('disabled_reason')->nullable();
|
||||
$table->smallInteger('inline_help')->nullable();
|
||||
});
|
||||
|
||||
switch (DB::getDriverName()) {
|
||||
case 'mysql':
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->unique(['username'], 'users_username');
|
||||
$table->unique(['email'], 'users_email');
|
||||
});
|
||||
break;
|
||||
case 'pgsql':
|
||||
DB::unprepared('CREATE UNIQUE INDEX users_username on users (LOWER(username));');
|
||||
DB::unprepared('CREATE UNIQUE INDEX users_email on users (LOWER(email));');
|
||||
break;
|
||||
}
|
||||
|
||||
Schema::create('user_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about users, including localized properties like their name and affiliation.');
|
||||
$table->bigIncrements('user_setting_id');
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'user_settings_user_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['user_id', 'locale', 'setting_name'], 'user_settings_unique');
|
||||
$table->index(['setting_name', 'locale'], 'user_settings_locale_setting_name_index');
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->comment('Session data for logged-in users.');
|
||||
$table->string('session_id', 128);
|
||||
|
||||
$table->bigInteger('user_id')->nullable();
|
||||
$table->foreign('user_id', 'sessions_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'sessions_user_id');
|
||||
|
||||
$table->string('ip_address', 39);
|
||||
$table->string('user_agent', 255)->nullable();
|
||||
$table->bigInteger('created')->default(0);
|
||||
$table->bigInteger('last_used')->default(0);
|
||||
$table->smallInteger('remember')->default(0);
|
||||
$table->text('data');
|
||||
$table->string('domain', 255)->nullable();
|
||||
|
||||
$table->unique(['session_id'], 'sessions_pkey');
|
||||
});
|
||||
|
||||
Schema::create('access_keys', function (Blueprint $table) {
|
||||
$table->comment('Access keys are used to provide pseudo-login functionality for security-minimal tasks. Passkeys can be emailed directly to users, who can use them for a limited time in lieu of standard username and password.');
|
||||
$table->bigInteger('access_key_id')->autoIncrement();
|
||||
$table->string('context', 40);
|
||||
$table->string('key_hash', 40);
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'access_keys_user_id');
|
||||
|
||||
$table->bigInteger('assoc_id')->nullable();
|
||||
$table->datetime('expiry_date');
|
||||
$table->index(['key_hash', 'user_id', 'context'], 'access_keys_hash');
|
||||
});
|
||||
|
||||
Schema::create('notifications', function (Blueprint $table) {
|
||||
$table->comment('User notifications created during certain operations.');
|
||||
$table->bigInteger('notification_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id')->nullable();
|
||||
$contextDao = Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'notifications_context_id');
|
||||
|
||||
$table->bigInteger('user_id')->nullable();
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'notifications_user_id');
|
||||
|
||||
$table->bigInteger('level');
|
||||
$table->bigInteger('type');
|
||||
$table->datetime('date_created');
|
||||
$table->datetime('date_read')->nullable();
|
||||
$table->bigInteger('assoc_type')->nullable();
|
||||
$table->bigInteger('assoc_id')->nullable();
|
||||
$table->index(['context_id', 'user_id', 'level'], 'notifications_context_id_user_id');
|
||||
$table->index(['context_id', 'level'], 'notifications_context_id_level');
|
||||
$table->index(['assoc_type', 'assoc_id'], 'notifications_assoc');
|
||||
$table->index(['user_id', 'level'], 'notifications_user_id_level');
|
||||
});
|
||||
|
||||
// Stores metadata for specific notifications
|
||||
Schema::create('notification_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about notifications, including localized properties.');
|
||||
$table->bigIncrements('notification_setting_id');
|
||||
$table->bigInteger('notification_id');
|
||||
$table->foreign('notification_id')->references('notification_id')->on('notifications')->onDelete('cascade');
|
||||
$table->index(['notification_id'], 'notification_settings_notification_id');
|
||||
|
||||
$table->string('locale', 14)->nullable();
|
||||
$table->string('setting_name', 64);
|
||||
$table->mediumText('setting_value');
|
||||
$table->string('setting_type', 6)->comment('(bool|int|float|string|object)');
|
||||
$table->unique(['notification_id', 'locale', 'setting_name'], 'notification_settings_unique');
|
||||
});
|
||||
|
||||
Schema::create('notification_subscription_settings', function (Blueprint $table) {
|
||||
$table->comment('Which email notifications a user has chosen to unsubscribe from.');
|
||||
$table->bigInteger('setting_id')->autoIncrement();
|
||||
$table->string('setting_name', 64);
|
||||
$table->mediumText('setting_value');
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'notification_subscription_settings_user_id');
|
||||
|
||||
$table->bigInteger('context');
|
||||
$contextDao = Application::getContextDAO();
|
||||
$table->foreign('context')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context'], 'notification_subscription_settings_context');
|
||||
|
||||
$table->string('setting_type', 6)->comment('(bool|int|float|string|object)');
|
||||
});
|
||||
|
||||
Schema::create('email_templates_default_data', function (Blueprint $table) {
|
||||
$table->comment('Default email templates created for every installed locale.');
|
||||
$table->bigIncrements('email_templates_default_data_id');
|
||||
$table->string('email_key', 255)->comment('Unique identifier for this email.');
|
||||
$table->string('locale', 14)->default('en');
|
||||
$table->string('name', 255);
|
||||
$table->string('subject', 255);
|
||||
$table->text('body')->nullable();
|
||||
$table->unique(['email_key', 'locale'], 'email_templates_default_data_unique');
|
||||
});
|
||||
|
||||
Schema::create('email_templates', function (Blueprint $table) {
|
||||
$table->comment('Custom email templates created by each context, and overrides of the default templates.');
|
||||
$table->bigInteger('email_id')->autoIncrement();
|
||||
$table->string('email_key', 255)->comment('Unique identifier for this email.');
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'email_templates_context_id');
|
||||
|
||||
$table->string('alternate_to', 255)->nullable();
|
||||
$table->index(['alternate_to'], 'email_templates_alternate_to');
|
||||
|
||||
$table->unique(['email_key', 'context_id'], 'email_templates_email_key');
|
||||
});
|
||||
|
||||
Schema::create('email_templates_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about custom email templates, including localized properties such as the subject and body.');
|
||||
$table->bigIncrements('email_template_setting_id');
|
||||
$table->bigInteger('email_id');
|
||||
$table->foreign('email_id', 'email_templates_settings_email_id')->references('email_id')->on('email_templates')->onDelete('cascade');
|
||||
$table->index(['email_id'], 'email_templates_settings_email_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['email_id', 'locale', 'setting_name'], 'email_templates_settings_unique');
|
||||
});
|
||||
|
||||
// Resumption tokens for the OAI protocol interface.
|
||||
Schema::create('oai_resumption_tokens', function (Blueprint $table) {
|
||||
$table->comment('OAI resumption tokens are used to allow for pagination of large result sets into manageable pieces.');
|
||||
$table->bigIncrements('oai_resumption_token_id');
|
||||
$table->string('token', 32);
|
||||
$table->bigInteger('expire');
|
||||
$table->integer('record_offset');
|
||||
$table->text('params')->nullable();
|
||||
$table->unique(['token'], 'oai_resumption_tokens_unique');
|
||||
});
|
||||
|
||||
// Plugin settings.
|
||||
Schema::create('plugin_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about plugins, including localized properties. This table is frequently used to store plugin-specific configuration.');
|
||||
$table->bigIncrements('plugin_setting_id');
|
||||
$table->string('plugin_name', 80);
|
||||
$table->bigInteger('context_id');
|
||||
$table->string('setting_name', 80);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6)->comment('(bool|int|float|string|object)');
|
||||
$table->index(['plugin_name'], 'plugin_settings_plugin_name');
|
||||
$table->unique(['plugin_name', 'context_id', 'setting_name'], 'plugin_settings_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('plugin_settings');
|
||||
Schema::drop('oai_resumption_tokens');
|
||||
Schema::drop('email_templates_settings');
|
||||
Schema::drop('email_templates');
|
||||
Schema::drop('email_templates_default_data');
|
||||
Schema::drop('mailable_templates');
|
||||
Schema::drop('notification_subscription_settings');
|
||||
Schema::drop('notification_settings');
|
||||
Schema::drop('notifications');
|
||||
Schema::drop('access_keys');
|
||||
Schema::drop('sessions');
|
||||
Schema::drop('user_settings');
|
||||
Schema::drop('users');
|
||||
Schema::drop('site_settings');
|
||||
Schema::drop('site');
|
||||
Schema::drop('versions');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/ControlledVocabMigration.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 ControlledVocabMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ControlledVocabMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Controlled vocabularies
|
||||
Schema::create('controlled_vocabs', function (Blueprint $table) {
|
||||
$table->comment('Every word or phrase used in a controlled vocabulary. Controlled vocabularies are used for submission metadata like keywords and subjects, reviewer interests, and wherever a similar dictionary of words or phrases is required. Each entry corresponds to a word or phrase like "cellular reproduction" and a type like "submissionKeyword".');
|
||||
$table->bigInteger('controlled_vocab_id')->autoIncrement();
|
||||
$table->string('symbolic', 64);
|
||||
$table->bigInteger('assoc_type')->default(0);
|
||||
$table->bigInteger('assoc_id')->default(0);
|
||||
$table->unique(['symbolic', 'assoc_type', 'assoc_id'], 'controlled_vocab_symbolic');
|
||||
});
|
||||
|
||||
// Controlled vocabulary entries
|
||||
Schema::create('controlled_vocab_entries', function (Blueprint $table) {
|
||||
$table->comment('The order that a word or phrase used in a controlled vocabulary should appear. For example, the order of keywords in a publication.');
|
||||
$table->bigInteger('controlled_vocab_entry_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('controlled_vocab_id');
|
||||
$table->foreign('controlled_vocab_id')->references('controlled_vocab_id')->on('controlled_vocabs')->onDelete('cascade');
|
||||
$table->index(['controlled_vocab_id'], 'controlled_vocab_entries_controlled_vocab_id');
|
||||
|
||||
$table->float('seq', 8, 2)->nullable();
|
||||
$table->index(['controlled_vocab_id', 'seq'], 'controlled_vocab_entries_cv_id');
|
||||
});
|
||||
|
||||
// Controlled vocabulary entry settings
|
||||
Schema::create('controlled_vocab_entry_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about a controlled vocabulary entry, including localized properties such as the actual word or phrase.');
|
||||
$table->bigIncrements('controlled_vocab_entry_setting_id');
|
||||
$table->bigInteger('controlled_vocab_entry_id');
|
||||
$table->foreign('controlled_vocab_entry_id', 'c_v_e_s_entry_id')->references('controlled_vocab_entry_id')->on('controlled_vocab_entries')->onDelete('cascade');
|
||||
$table->index(['controlled_vocab_entry_id'], 'c_v_e_s_entry_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6);
|
||||
$table->unique(['controlled_vocab_entry_id', 'locale', 'setting_name'], 'c_v_e_s_pkey');
|
||||
});
|
||||
|
||||
// Reviewer Interests Associative Table
|
||||
Schema::create('user_interests', function (Blueprint $table) {
|
||||
$table->comment('Associates users with user interests (which are stored in the controlled vocabulary tables).');
|
||||
$table->bigIncrements('user_interest_id');
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'user_interests_user_id');
|
||||
|
||||
$table->bigInteger('controlled_vocab_entry_id');
|
||||
$table->foreign('controlled_vocab_entry_id')->references('controlled_vocab_entry_id')->on('controlled_vocab_entries')->onDelete('cascade');
|
||||
$table->index(['controlled_vocab_entry_id'], 'user_interests_controlled_vocab_entry_id');
|
||||
|
||||
$table->unique(['user_id', 'controlled_vocab_entry_id'], 'u_e_pkey');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('user_interests');
|
||||
Schema::drop('controlled_vocab_entry_settings');
|
||||
Schema::drop('controlled_vocab_entries');
|
||||
Schema::drop('controlled_vocabs');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/DoiMigration.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DoiMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class DoiMigration extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// NB: A foreign key constraint is placed on context_id, but this is application dependent and
|
||||
// needs to be added once the context has been created.
|
||||
// It will reference the app specific column (e.g. journal_id, press_id, etc.).
|
||||
Schema::create('dois', function (Blueprint $table) {
|
||||
$table->comment('Stores all DOIs used in the system.');
|
||||
$table->bigInteger('doi_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = \APP\core\Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'dois_context_id');
|
||||
|
||||
$table->string('doi');
|
||||
$table->smallInteger('status')->default(1);
|
||||
});
|
||||
|
||||
// Settings
|
||||
Schema::create('doi_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about DOIs, including the registration agency.');
|
||||
$table->bigIncrements('doi_setting_id');
|
||||
$table->bigInteger('doi_id');
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['doi_id', 'locale', 'setting_name'], 'doi_settings_unique');
|
||||
$table->index(['doi_id'], 'doi_settings_doi_id');
|
||||
$table->foreign('doi_id')->references('doi_id')->on('dois')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('dois');
|
||||
Schema::drop('doi_settings');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/FailedJobsMigration.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 FailedJobsMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class FailedJobsMigration extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// Schema matches https://github.com/illuminate/queue/blob/7.x/Console/stubs/failed_jobs.stub
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->comment('A log of all failed jobs.');
|
||||
$table->bigIncrements('id');
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration install/FilesMigration.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 FilesMigration
|
||||
*
|
||||
* @brief Create the files database table
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class FilesMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('files', function (Blueprint $table) {
|
||||
$table->comment('Records information in the database about files tracked by the system, linking them to the local filesystem.');
|
||||
$table->bigIncrements('file_id');
|
||||
$table->string('path', 255);
|
||||
$table->string('mimetype', 255);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('files');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/GenresMigration.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 GenresMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\submission\Genre;
|
||||
|
||||
class GenresMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// A context's submission file genres.
|
||||
Schema::create('genres', function (Blueprint $table) {
|
||||
$table->comment('The types of submission files configured for each context, such as Article Text, Data Set, Transcript, etc.');
|
||||
$table->bigInteger('genre_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = \APP\core\Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'genres_context_id');
|
||||
|
||||
$table->bigInteger('seq');
|
||||
$table->smallInteger('enabled')->default(1);
|
||||
|
||||
$table->bigInteger('category')->default(Genre::GENRE_CATEGORY_DOCUMENT);
|
||||
|
||||
$table->smallInteger('dependent')->default(0);
|
||||
$table->smallInteger('supplementary')->default(0);
|
||||
$table->smallInteger('required')->default(0)->comment('Whether or not at least one file of this genre is required for a new submission.');
|
||||
$table->string('entry_key', 30)->nullable();
|
||||
});
|
||||
|
||||
// Genre settings
|
||||
Schema::create('genre_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about file genres, including localized properties such as the genre name.');
|
||||
$table->bigIncrements('genre_setting_id');
|
||||
$table->bigInteger('genre_id');
|
||||
$table->foreign('genre_id')->references('genre_id')->on('genres')->onDelete('cascade');
|
||||
$table->index(['genre_id'], 'genre_settings_genre_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6)->comment('(bool|int|float|string|object)');
|
||||
|
||||
$table->unique(['genre_id', 'locale', 'setting_name'], 'genre_settings_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('genre_settings');
|
||||
Schema::drop('genres');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/HighlightsMigration.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class HighlightsMigration
|
||||
*
|
||||
* @brief Describe database table structures for highlights
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema as Schema;
|
||||
|
||||
class HighlightsMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('highlights', function (Blueprint $table) {
|
||||
$table->comment('Highlights are featured items that can be presented to users, for example on the homepage.');
|
||||
$table->bigInteger('highlight_id')->autoIncrement();
|
||||
$table->bigInteger('context_id')->nullable();
|
||||
$contextDao = \APP\core\Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'highlights_context_id');
|
||||
|
||||
$table->bigInteger('sequence');
|
||||
$table->string('url', 2047);
|
||||
});
|
||||
|
||||
Schema::create('highlight_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about highlights, including localized properties like title and description.');
|
||||
$table->bigIncrements('highlight_setting_id');
|
||||
$table->bigInteger('highlight_id');
|
||||
$table->foreign('highlight_id')->references('highlight_id')->on('highlights')->onDelete('cascade');
|
||||
$table->index(['highlight_id'], 'highlight_settings_highlight_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['highlight_id', 'locale', 'setting_name'], 'highlight_settings_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('highlight_settings');
|
||||
Schema::drop('highlights');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/InstitutionsMigration.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class InstitutionsMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use APP\core\Application;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema as Schema;
|
||||
|
||||
class InstitutionsMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('institutions', function (Blueprint $table) {
|
||||
$table->comment('Institutions for statistics and subscriptions.');
|
||||
$table->bigInteger('institution_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'institutions_context_id');
|
||||
|
||||
$table->string('ror', 255)->nullable()->comment('ROR (Research Organization Registry) ID');
|
||||
$table->softDeletes('deleted_at', 0);
|
||||
});
|
||||
|
||||
Schema::create('institution_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about institutions, including localized properties like names.');
|
||||
$table->bigIncrements('institution_setting_id');
|
||||
$table->bigInteger('institution_id');
|
||||
$table->foreign('institution_id')->references('institution_id')->on('institutions')->onDelete('cascade');
|
||||
$table->index(['institution_id'], 'institution_settings_institution_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['institution_id', 'locale', 'setting_name'], 'institution_settings_unique');
|
||||
});
|
||||
|
||||
// Institution IPs and IP ranges.
|
||||
Schema::create('institution_ip', function (Blueprint $table) {
|
||||
$table->comment('Records IP address ranges and associates them with institutions.');
|
||||
$table->bigInteger('institution_ip_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('institution_id');
|
||||
$table->foreign('institution_id')->references('institution_id')->on('institutions')->onDelete('cascade');
|
||||
$table->index(['institution_id'], 'institution_ip_institution_id');
|
||||
|
||||
$table->string('ip_string', 40);
|
||||
$table->bigInteger('ip_start');
|
||||
$table->bigInteger('ip_end')->nullable();
|
||||
|
||||
$table->index(['ip_start'], 'institution_ip_start');
|
||||
$table->index(['ip_end'], 'institution_ip_end');
|
||||
});
|
||||
|
||||
if (Schema::hasTable('institutional_subscriptions') && Schema::hasColumn('institutional_subscriptions', 'institution_id')) {
|
||||
Schema::table('institutional_subscriptions', function (Blueprint $table) {
|
||||
$table->comment('Links institutional subscriptions with institutions.');
|
||||
$table->foreign('institution_id')->references('institution_id')->on('institutions');
|
||||
$table->index(['institution_id'], 'institutional_subscriptions_institution_ip');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('institutional_subscriptions', function (Blueprint $table) {
|
||||
$table->dropForeign('institution_id');
|
||||
});
|
||||
Schema::drop('institution_settings');
|
||||
Schema::drop('institution_ip');
|
||||
Schema::drop('institutions');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/JobsMigration.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 JobsMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class JobsMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->comment('All pending or in-progress jobs.');
|
||||
$table->bigIncrements('id');
|
||||
$table->string('queue');
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
|
||||
$table->index(['queue', 'reserved_at']);
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->comment('Job batches allow jobs to be collected into groups for managed processing.');
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->text('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('jobs');
|
||||
Schema::drop('job_batches');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/LibraryFilesMigration.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 LibraryFilesMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class LibraryFilesMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Library files for a context
|
||||
Schema::create('library_files', function (Blueprint $table) {
|
||||
$table->comment('Library files can be associated with the context (press/server/journal) or with individual submissions, and are typically forms, agreements, and other administrative documents that are not part of the scholarly content.');
|
||||
$table->bigInteger('file_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = \APP\core\Application::getContextDAO();
|
||||
$table->foreign('context_id', 'library_files_context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'library_files_context_id');
|
||||
|
||||
$table->string('file_name', 255);
|
||||
$table->string('original_file_name', 255);
|
||||
$table->string('file_type', 255);
|
||||
$table->bigInteger('file_size');
|
||||
$table->smallInteger('type');
|
||||
$table->datetime('date_uploaded');
|
||||
$table->datetime('date_modified');
|
||||
|
||||
$table->bigInteger('submission_id')->nullable();
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
$table->index(['submission_id'], 'library_files_submission_id');
|
||||
|
||||
$table->smallInteger('public_access')->default(0)->nullable();
|
||||
});
|
||||
|
||||
// Library file metadata.
|
||||
Schema::create('library_file_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about library files, including localized content such as names.');
|
||||
$table->bigIncrements('library_file_setting_id');
|
||||
$table->bigInteger('file_id');
|
||||
$table->foreign('file_id')->references('file_id')->on('library_files')->onDelete('cascade');
|
||||
$table->index(['file_id'], 'library_file_settings_file_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6)->comment('(bool|int|float|string|object|date)');
|
||||
|
||||
$table->unique(['file_id', 'locale', 'setting_name'], 'library_file_settings_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('library_file_settings');
|
||||
Schema::drop('library_files');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/LogMigration.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 LogMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class LogMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('event_log', function (Blueprint $table) {
|
||||
$table->comment('A log of all events related to an object like a submission.');
|
||||
$table->bigInteger('log_id')->autoIncrement();
|
||||
$table->bigInteger('assoc_type');
|
||||
$table->bigInteger('assoc_id');
|
||||
|
||||
$table->bigInteger('user_id')->nullable()->comment('NULL if it\'s system or automated event');
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'event_log_user_id');
|
||||
|
||||
$table->datetime('date_logged');
|
||||
$table->bigInteger('event_type')->nullable();
|
||||
$table->text('message')->nullable();
|
||||
$table->boolean('is_translated')->nullable();
|
||||
$table->index(['assoc_type', 'assoc_id'], 'event_log_assoc');
|
||||
});
|
||||
|
||||
Schema::create('event_log_settings', function (Blueprint $table) {
|
||||
$table->comment('Data about an event log entry. This data is commonly used to display information about an event to a user.');
|
||||
$table->bigIncrements('event_log_setting_id');
|
||||
$table->bigInteger('log_id');
|
||||
$table->foreign('log_id', 'event_log_settings_log_id')->references('log_id')->on('event_log')->onDelete('cascade');
|
||||
$table->index(['log_id'], 'event_log_settings_log_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->unique(['log_id', 'setting_name', 'locale'], 'event_log_settings_unique');
|
||||
});
|
||||
|
||||
// Add partial index (DBMS-specific)
|
||||
switch (DB::getDriverName()) {
|
||||
case 'mysql': DB::unprepared('CREATE INDEX event_log_settings_name_value ON event_log_settings (setting_name(50), setting_value(150))');
|
||||
break;
|
||||
case 'pgsql': DB::unprepared("CREATE INDEX event_log_settings_name_value ON event_log_settings (setting_name, setting_value) WHERE setting_name IN ('fileId', 'submissionId')");
|
||||
break;
|
||||
}
|
||||
|
||||
Schema::create('email_log', function (Blueprint $table) {
|
||||
$table->comment('A record of email messages that are sent in relation to an associated entity, such as a submission.');
|
||||
$table->bigInteger('log_id')->autoIncrement();
|
||||
$table->bigInteger('assoc_type');
|
||||
$table->bigInteger('assoc_id');
|
||||
$table->bigInteger('sender_id');
|
||||
$table->datetime('date_sent');
|
||||
$table->bigInteger('event_type')->nullable();
|
||||
$table->string('from_address', 255)->nullable();
|
||||
$table->text('recipients')->nullable();
|
||||
$table->text('cc_recipients')->nullable();
|
||||
$table->text('bcc_recipients')->nullable();
|
||||
$table->string('subject', 255)->nullable();
|
||||
$table->text('body')->nullable();
|
||||
$table->index(['assoc_type', 'assoc_id'], 'email_log_assoc');
|
||||
});
|
||||
|
||||
// Associations for email logs within a user.
|
||||
Schema::create('email_log_users', function (Blueprint $table) {
|
||||
$table->comment('A record of users associated with an email log entry.');
|
||||
$table->bigIncrements('email_log_user_id');
|
||||
|
||||
$table->bigInteger('email_log_id');
|
||||
$table->foreign('email_log_id')->references('log_id')->on('email_log')->onDelete('cascade');
|
||||
$table->index(['email_log_id'], 'email_log_users_email_log_id');
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'email_log_users_user_id');
|
||||
|
||||
$table->unique(['email_log_id', 'user_id'], 'email_log_user_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('email_log_users');
|
||||
Schema::drop('email_log');
|
||||
Schema::drop('event_log_settings');
|
||||
Schema::drop('event_log');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/MetadataMigration.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 MetadataMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class MetadataMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Citations
|
||||
Schema::create('citations', function (Blueprint $table) {
|
||||
$table->comment('A citation made by an associated publication.');
|
||||
$table->bigInteger('citation_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('publication_id');
|
||||
$table->foreign('publication_id', 'citations_publication')->references('publication_id')->on('publications')->onDelete('cascade');
|
||||
$table->index(['publication_id'], 'citations_publication');
|
||||
|
||||
$table->text('raw_citation');
|
||||
$table->bigInteger('seq')->default(0);
|
||||
|
||||
$table->unique(['publication_id', 'seq'], 'citations_publication_seq');
|
||||
});
|
||||
|
||||
// Citation settings
|
||||
Schema::create('citation_settings', function (Blueprint $table) {
|
||||
$table->comment('Additional data about citations, including localized content.');
|
||||
$table->bigIncrements('citation_setting_id');
|
||||
$table->bigInteger('citation_id');
|
||||
$table->foreign('citation_id', 'citation_settings_citation_id')->references('citation_id')->on('citations')->onDelete('cascade');
|
||||
$table->index(['citation_id'], 'citation_settings_citation_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6);
|
||||
|
||||
$table->unique(['citation_id', 'locale', 'setting_name'], 'citation_settings_unique');
|
||||
});
|
||||
|
||||
// Filter groups
|
||||
Schema::create('filter_groups', function (Blueprint $table) {
|
||||
$table->comment('Filter groups are used to organized filters into named sets, which can be retrieved by the application for invocation.');
|
||||
$table->bigInteger('filter_group_id')->autoIncrement();
|
||||
$table->string('symbolic', 255)->nullable();
|
||||
$table->string('display_name', 255)->nullable();
|
||||
$table->string('description', 255)->nullable();
|
||||
$table->string('input_type', 255)->nullable();
|
||||
$table->string('output_type', 255)->nullable();
|
||||
$table->unique(['symbolic'], 'filter_groups_symbolic');
|
||||
});
|
||||
|
||||
// Configured filter instances (transformations)
|
||||
Schema::create('filters', function (Blueprint $table) {
|
||||
$table->comment('Filters represent a transformation of a supported piece of data from one form to another, such as a PHP object into an XML document.');
|
||||
$table->bigInteger('filter_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('filter_group_id')->default(0);
|
||||
$table->foreign('filter_group_id')->references('filter_group_id')->on('filter_groups')->onDelete('cascade');
|
||||
$table->index(['filter_group_id'], 'filters_filter_group_id');
|
||||
|
||||
$table->bigInteger('context_id')->default(0);
|
||||
$table->string('display_name', 255)->nullable();
|
||||
$table->string('class_name', 255)->nullable();
|
||||
$table->smallInteger('is_template')->default(0);
|
||||
$table->bigInteger('parent_filter_id')->default(0);
|
||||
$table->bigInteger('seq')->default(0);
|
||||
});
|
||||
|
||||
// Filter Settings
|
||||
Schema::create('filter_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about filters, including localized content.');
|
||||
$table->bigIncrements('filter_setting_id');
|
||||
$table->bigInteger('filter_id');
|
||||
$table->foreign('filter_id')->references('filter_id')->on('filters')->onDelete('cascade');
|
||||
$table->index(['filter_id'], 'filter_settings_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6);
|
||||
|
||||
$table->unique(['filter_id', 'locale', 'setting_name'], 'filter_settings_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('filter_settings');
|
||||
Schema::drop('filters');
|
||||
Schema::drop('filter_groups');
|
||||
Schema::drop('citation_settings');
|
||||
Schema::drop('citations');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/NavigationMenusMigration.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 NavigationMenusMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class NavigationMenusMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// NavigationMenus
|
||||
Schema::create('navigation_menus', function (Blueprint $table) {
|
||||
$table->comment('Navigation menus on the website are installed with the software as a default set, and can be customized.');
|
||||
$table->bigInteger('navigation_menu_id')->autoIncrement();
|
||||
$table->bigInteger('context_id');
|
||||
$table->string('area_name', 255)->default('')->nullable();
|
||||
$table->string('title', 255);
|
||||
});
|
||||
|
||||
// NavigationMenuItems
|
||||
Schema::create('navigation_menu_items', function (Blueprint $table) {
|
||||
$table->comment('Navigation menu items are single elements within a navigation menu.');
|
||||
$table->bigInteger('navigation_menu_item_id')->autoIncrement();
|
||||
$table->bigInteger('context_id');
|
||||
$table->string('path', 255)->default('')->nullable();
|
||||
$table->string('type', 255)->default('')->nullable();
|
||||
});
|
||||
|
||||
// Locale-specific navigation menu item data
|
||||
Schema::create('navigation_menu_item_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about navigation menu items, including localized content such as names.');
|
||||
$table->bigIncrements('navigation_menu_item_setting_id');
|
||||
$table->bigInteger('navigation_menu_item_id');
|
||||
$table->foreign('navigation_menu_item_id', 'navigation_menu_item_settings_navigation_menu_id')->references('navigation_menu_item_id')->on('navigation_menu_items')->onDelete('cascade');
|
||||
$table->index(['navigation_menu_item_id'], 'navigation_menu_item_settings_navigation_menu_item_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->longText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6);
|
||||
|
||||
$table->unique(['navigation_menu_item_id', 'locale', 'setting_name'], 'navigation_menu_item_settings_unique');
|
||||
});
|
||||
|
||||
// NavigationMenuItemAssignments which assign menu items to a menu and describe nested menu structure.
|
||||
Schema::create('navigation_menu_item_assignments', function (Blueprint $table) {
|
||||
$table->comment('Links navigation menu items to navigation menus.');
|
||||
$table->bigInteger('navigation_menu_item_assignment_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('navigation_menu_id');
|
||||
$table->foreign('navigation_menu_id')->references('navigation_menu_id')->on('navigation_menus')->onDelete('cascade');
|
||||
$table->index(['navigation_menu_id'], 'navigation_menu_item_assignments_navigation_menu_id');
|
||||
|
||||
$table->bigInteger('navigation_menu_item_id');
|
||||
$table->foreign('navigation_menu_item_id')->references('navigation_menu_item_id')->on('navigation_menu_items')->onDelete('cascade');
|
||||
$table->index(['navigation_menu_item_id'], 'navigation_menu_item_assignments_navigation_menu_item_id');
|
||||
|
||||
$table->bigInteger('parent_id')->nullable();
|
||||
$table->bigInteger('seq')->default(0)->nullable();
|
||||
});
|
||||
|
||||
// Locale-specific navigation menu item assignments data
|
||||
Schema::create('navigation_menu_item_assignment_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about navigation menu item assignments to navigation menus, including localized content.');
|
||||
$table->bigIncrements('navigation_menu_item_assignment_setting_id');
|
||||
$table->bigInteger('navigation_menu_item_assignment_id');
|
||||
$table->foreign('navigation_menu_item_assignment_id', 'assignment_settings_navigation_menu_item_assignment_id')->references('navigation_menu_item_assignment_id')->on('navigation_menu_item_assignments')->onDelete('cascade');
|
||||
$table->index(['navigation_menu_item_assignment_id'], 'navigation_menu_item_assignment_settings_n_m_i_a_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6);
|
||||
|
||||
$table->unique(['navigation_menu_item_assignment_id', 'locale', 'setting_name'], 'navigation_menu_item_assignment_settings_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('navigation_menu_item_assignment_settings');
|
||||
Schema::drop('navigation_menu_item_assignments');
|
||||
Schema::drop('navigation_menu_item_settings');
|
||||
Schema::drop('navigation_menu_items');
|
||||
Schema::drop('navigation_menus');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/NotesMigration.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 NotesMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class NotesMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('notes', function (Blueprint $table) {
|
||||
$table->comment('Notes allow users to annotate associated entities, such as submissions.');
|
||||
$table->bigInteger('note_id')->autoIncrement();
|
||||
$table->bigInteger('assoc_type');
|
||||
$table->bigInteger('assoc_id');
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'notes_user_id');
|
||||
|
||||
$table->datetime('date_created');
|
||||
$table->datetime('date_modified')->nullable();
|
||||
$table->string('title', 255)->nullable();
|
||||
$table->text('contents')->nullable();
|
||||
|
||||
$table->index(['assoc_type', 'assoc_id'], 'notes_assoc');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('notes');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/ReviewFormsMigration.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 ReviewFormsMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ReviewFormsMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Review forms.
|
||||
if (!Schema::hasTable('review_forms')) {
|
||||
Schema::create('review_forms', function (Blueprint $table) {
|
||||
$table->comment('Review forms provide custom templates for peer reviews with several types of questions.');
|
||||
$table->bigInteger('review_form_id')->autoIncrement();
|
||||
$table->bigInteger('assoc_type');
|
||||
$table->bigInteger('assoc_id');
|
||||
$table->float('seq', 8, 2)->nullable();
|
||||
$table->smallInteger('is_active')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
// Review form settings
|
||||
if (!Schema::hasTable('review_form_settings')) {
|
||||
Schema::create('review_form_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about review forms, including localized content such as names.');
|
||||
$table->bigIncrements('review_form_setting_id');
|
||||
$table->bigInteger('review_form_id');
|
||||
$table->foreign('review_form_id', 'review_form_settings_review_form_id')->references('review_form_id')->on('review_forms')->onDelete('cascade');
|
||||
$table->index(['review_form_id'], 'review_form_settings_review_form_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6);
|
||||
|
||||
$table->unique(['review_form_id', 'locale', 'setting_name'], 'review_form_settings_unique');
|
||||
});
|
||||
}
|
||||
|
||||
// Review form elements.
|
||||
if (!Schema::hasTable('review_form_elements')) {
|
||||
Schema::create('review_form_elements', function (Blueprint $table) {
|
||||
$table->comment('Each review form element represents a single question on a review form.');
|
||||
$table->bigInteger('review_form_element_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('review_form_id');
|
||||
$table->foreign('review_form_id', 'review_form_elements_review_form_id')->references('review_form_id')->on('review_forms')->onDelete('cascade');
|
||||
$table->index(['review_form_id'], 'review_form_elements_review_form_id');
|
||||
|
||||
$table->float('seq', 8, 2)->nullable();
|
||||
$table->bigInteger('element_type')->nullable();
|
||||
$table->smallInteger('required')->nullable();
|
||||
$table->smallInteger('included')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
// Review form element settings
|
||||
if (!Schema::hasTable('review_form_element_settings')) {
|
||||
Schema::create('review_form_element_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about review form elements, including localized content such as question text.');
|
||||
$table->bigIncrements('review_form_element_setting_id');
|
||||
$table->bigInteger('review_form_element_id');
|
||||
$table->foreign('review_form_element_id', 'review_form_element_settings_review_form_element_id')->references('review_form_element_id')->on('review_form_elements')->onDelete('cascade');
|
||||
$table->index(['review_form_element_id'], 'review_form_element_settings_review_form_element_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6);
|
||||
|
||||
$table->unique(['review_form_element_id', 'locale', 'setting_name'], 'review_form_element_settings_unique');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('review_form_element_settings');
|
||||
Schema::drop('review_form_elements');
|
||||
Schema::drop('review_form_settings');
|
||||
Schema::drop('review_forms');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/ReviewsMigration.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 ReviewsMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ReviewsMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Review rounds.
|
||||
Schema::create('review_rounds', function (Blueprint $table) {
|
||||
$table->comment('Peer review assignments are organized into multiple rounds on a submission.');
|
||||
$table->bigInteger('review_round_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('submission_id');
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions');
|
||||
$table->index(['submission_id'], 'review_rounds_submission_id');
|
||||
|
||||
$table->bigInteger('stage_id')->nullable();
|
||||
$table->smallInteger('round');
|
||||
$table->bigInteger('review_revision')->nullable();
|
||||
$table->bigInteger('status')->nullable();
|
||||
$table->unique(['submission_id', 'stage_id', 'round'], 'review_rounds_submission_id_stage_id_round_pkey');
|
||||
});
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->foreign('review_round_id')->references('review_round_id')->on('review_rounds')->onDelete('cascade');
|
||||
$table->index(['review_round_id'], 'edit_decisions_review_round_id');
|
||||
});
|
||||
|
||||
// Reviewing assignments.
|
||||
Schema::create('review_assignments', function (Blueprint $table) {
|
||||
$table->comment('Data about peer review assignments for all submissions.');
|
||||
$table->bigInteger('review_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('submission_id');
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions');
|
||||
$table->index(['submission_id'], 'review_assignments_submission_id');
|
||||
|
||||
$table->bigInteger('reviewer_id');
|
||||
$table->foreign('reviewer_id')->references('user_id')->on('users');
|
||||
$table->index(['reviewer_id'], 'review_assignments_reviewer_id');
|
||||
|
||||
$table->text('competing_interests')->nullable();
|
||||
$table->smallInteger('recommendation')->nullable();
|
||||
$table->datetime('date_assigned')->nullable();
|
||||
$table->datetime('date_notified')->nullable();
|
||||
$table->datetime('date_confirmed')->nullable();
|
||||
$table->datetime('date_completed')->nullable();
|
||||
$table->datetime('date_acknowledged')->nullable();
|
||||
$table->datetime('date_due')->nullable();
|
||||
$table->datetime('date_response_due')->nullable();
|
||||
$table->datetime('last_modified')->nullable();
|
||||
$table->smallInteger('reminder_was_automatic')->default(0);
|
||||
$table->smallInteger('declined')->default(0);
|
||||
$table->smallInteger('cancelled')->default(0);
|
||||
$table->datetime('date_rated')->nullable();
|
||||
$table->datetime('date_reminded')->nullable();
|
||||
$table->smallInteger('quality')->nullable();
|
||||
|
||||
$table->bigInteger('review_round_id');
|
||||
$table->foreign('review_round_id')->references('review_round_id')->on('review_rounds');
|
||||
$table->index(['review_round_id', 'reviewer_id'], 'review_assignment_reviewer_round');
|
||||
|
||||
$table->smallInteger('stage_id');
|
||||
|
||||
$table->smallInteger('review_method')->default(\PKP\submission\reviewAssignment\ReviewAssignment::SUBMISSION_REVIEW_METHOD_ANONYMOUS);
|
||||
|
||||
$table->smallInteger('round')->default(1);
|
||||
$table->smallInteger('step')->default(1);
|
||||
|
||||
$table->bigInteger('review_form_id')->nullable();
|
||||
$table->foreign('review_form_id')->references('review_form_id')->on('review_forms');
|
||||
$table->index(['review_form_id'], 'review_assignments_form_id');
|
||||
|
||||
$table->smallInteger('considered')->nullable();
|
||||
$table->smallInteger('request_resent')->default(0);
|
||||
|
||||
// Normally reviewer can't be assigned twice on the same review round.
|
||||
// HOWEVER, if two reviewer user accounts are subsequently merged, both will keep
|
||||
// separate review assignments but the reviewer_id will become the same!
|
||||
// (https://github.com/pkp/pkp-lib/issues/7678)
|
||||
$table->index(['reviewer_id', 'review_id'], 'review_assignments_reviewer_review');
|
||||
});
|
||||
|
||||
// Review form responses.
|
||||
if (!Schema::hasTable('review_form_responses')) {
|
||||
Schema::create('review_form_responses', function (Blueprint $table) {
|
||||
$table->comment('Each review form response records a reviewer\'s answer to a review form element associated with a peer review.');
|
||||
$table->bigIncrements('review_form_response_id');
|
||||
$table->bigInteger('review_form_element_id');
|
||||
$table->foreign('review_form_element_id')->references('review_form_element_id')->on('review_form_elements')->onDelete('cascade');
|
||||
$table->index(['review_form_element_id'], 'review_form_responses_review_form_element_id');
|
||||
|
||||
$table->bigInteger('review_id');
|
||||
$table->foreign('review_id')->references('review_id')->on('review_assignments')->onDelete('cascade');
|
||||
$table->index(['review_id'], 'review_form_responses_review_id');
|
||||
|
||||
$table->string('response_type', 6)->nullable();
|
||||
$table->text('response_value')->nullable();
|
||||
|
||||
$table->index(['review_form_element_id', 'review_id'], 'review_form_responses_unique');
|
||||
});
|
||||
}
|
||||
|
||||
// Submission Files for each review round
|
||||
Schema::create('review_round_files', function (Blueprint $table) {
|
||||
$table->comment('Records the files made available to reviewers for a round of reviews. These can be further customized on a per review basis with review_files.');
|
||||
$table->bigIncrements('review_round_file_id');
|
||||
|
||||
$table->bigInteger('submission_id');
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
$table->index(['submission_id'], 'review_round_files_submission_id');
|
||||
|
||||
$table->bigInteger('review_round_id');
|
||||
$table->foreign('review_round_id')->references('review_round_id')->on('review_rounds')->onDelete('cascade');
|
||||
$table->index(['review_round_id'], 'review_round_files_review_round_id');
|
||||
|
||||
$table->smallInteger('stage_id');
|
||||
|
||||
$table->bigInteger('submission_file_id')->nullable(false)->unsigned();
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
$table->index(['submission_file_id'], 'review_round_files_submission_file_id');
|
||||
|
||||
$table->unique(['submission_id', 'review_round_id', 'submission_file_id'], 'review_round_files_unique');
|
||||
});
|
||||
|
||||
// Associates reviewable submission files with reviews
|
||||
Schema::create('review_files', function (Blueprint $table) {
|
||||
$table->comment('A list of the submission files made available to each assigned reviewer.');
|
||||
$table->bigIncrements('review_file_id');
|
||||
|
||||
$table->bigInteger('review_id');
|
||||
$table->foreign('review_id')->references('review_id')->on('review_assignments')->onDelete('cascade');
|
||||
$table->index(['review_id'], 'review_files_review_id');
|
||||
|
||||
$table->bigInteger('submission_file_id')->nullable(false)->unsigned();
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
$table->index(['submission_file_id'], 'review_files_submission_file_id');
|
||||
|
||||
$table->unique(['review_id', 'submission_file_id'], 'review_files_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('review_form_responses');
|
||||
Schema::drop('review_assignments');
|
||||
Schema::drop('review_files');
|
||||
Schema::drop('review_round_files');
|
||||
Schema::drop('review_rounds');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/RolesAndUserGroupsMigration.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 RolesAndUserGroupsMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use APP\core\Application;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RolesAndUserGroupsMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('user_groups', function (Blueprint $table) {
|
||||
$table->comment('All defined user roles in a context, such as Author, Reviewer, Section Editor and Journal Manager.');
|
||||
$table->bigInteger('user_group_id')->autoIncrement();
|
||||
$table->bigInteger('context_id');
|
||||
$table->bigInteger('role_id');
|
||||
$table->smallInteger('is_default')->default(0);
|
||||
$table->smallInteger('show_title')->default(1);
|
||||
$table->smallInteger('permit_self_registration')->default(0);
|
||||
$table->smallInteger('permit_metadata_edit')->default(0);
|
||||
$table->index(['user_group_id'], 'user_groups_user_group_id');
|
||||
$table->index(['context_id'], 'user_groups_context_id');
|
||||
$table->index(['role_id'], 'user_groups_role_id');
|
||||
});
|
||||
|
||||
Schema::create('user_group_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about user groups, including localized properties such as the name.');
|
||||
$table->bigIncrements('user_group_setting_id');
|
||||
$table->bigInteger('user_group_id');
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['user_group_id', 'locale', 'setting_name'], 'user_group_settings_unique');
|
||||
$table->foreign('user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
$table->index(['user_group_id'], 'user_group_settings_user_group_id');
|
||||
});
|
||||
|
||||
Schema::create('user_user_groups', function (Blueprint $table) {
|
||||
$table->comment('Maps users to their assigned user_groups.');
|
||||
$table->bigIncrements('user_user_group_id');
|
||||
$table->bigInteger('user_group_id');
|
||||
$table->foreign('user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
$table->index(['user_group_id'], 'user_user_groups_user_group_id');
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id', 'user_user_groups_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'user_user_groups_user_id');
|
||||
|
||||
$table->unique(['user_group_id', 'user_id'], 'user_user_groups_unique');
|
||||
});
|
||||
|
||||
Schema::create('user_group_stage', function (Blueprint $table) {
|
||||
$table->comment('Which stages of the editorial workflow the user_groups can access.');
|
||||
$table->bigIncrements('user_group_stage_id');
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = Application::getContextDAO();
|
||||
$table->foreign('context_id', 'user_group_stage_context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'user_group_stage_context_id');
|
||||
|
||||
$table->bigInteger('user_group_id');
|
||||
$table->foreign('user_group_id', 'user_group_stage_user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
$table->index(['user_group_id'], 'user_group_stage_user_group_id');
|
||||
|
||||
$table->bigInteger('stage_id');
|
||||
$table->index(['stage_id'], 'user_group_stage_stage_id');
|
||||
|
||||
$table->unique(['context_id', 'user_group_id', 'stage_id'], 'user_group_stage_unique');
|
||||
});
|
||||
|
||||
Schema::create('stage_assignments', function (Blueprint $table) {
|
||||
$table->comment('Who can access a submission while it is in the editorial workflow. Includes all editorial and author assignments. For reviewers, see review_assignments.');
|
||||
$table->bigInteger('stage_assignment_id')->autoIncrement();
|
||||
|
||||
// The foreign key for this column is declared with the submissions table.
|
||||
$table->bigInteger('submission_id');
|
||||
|
||||
$table->bigInteger('user_group_id');
|
||||
$table->foreign('user_group_id', 'stage_assignments_user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
$table->index(['user_group_id'], 'stage_assignments_user_group_id');
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id', 'stage_assignments_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'stage_assignments_user_id');
|
||||
|
||||
$table->datetime('date_assigned');
|
||||
$table->smallInteger('recommend_only')->default(0);
|
||||
$table->smallInteger('can_change_metadata')->default(0);
|
||||
|
||||
$table->unique(['submission_id', 'user_group_id', 'user_id'], 'stage_assignment');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('stage_assignments');
|
||||
Schema::drop('user_group_stage');
|
||||
Schema::drop('user_user_groups');
|
||||
Schema::drop('user_group_settings');
|
||||
Schema::drop('user_groups');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/ScheduledTasksMigration.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 ScheduledTasksMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ScheduledTasksMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// The last run times of all scheduled tasks.
|
||||
Schema::create('scheduled_tasks', function (Blueprint $table) {
|
||||
$table->comment('The last time each scheduled task was run.');
|
||||
$table->bigIncrements('scheduled_task_id');
|
||||
$table->string('class_name', 255);
|
||||
$table->datetime('last_run')->nullable();
|
||||
$table->unique(['class_name'], 'scheduled_tasks_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('scheduled_tasks');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/SubmissionFilesMigration.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 SubmissionFilesMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class SubmissionFilesMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('submission_files', function (Blueprint $table) {
|
||||
$table->comment('All files associated with a submission, such as those uploaded during submission, as revisions, or by copyeditors or layout editors for production.');
|
||||
$table->bigIncrements('submission_file_id');
|
||||
|
||||
$table->bigInteger('submission_id');
|
||||
$table->foreign('submission_id', 'submission_files_submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
$table->index(['submission_id'], 'submission_files_submission_id');
|
||||
|
||||
$table->bigInteger('file_id')->nullable(false)->unsigned();
|
||||
$table->foreign('file_id')->references('file_id')->on('files')->onDelete('cascade');
|
||||
$table->index(['file_id'], 'submission_files_file_id');
|
||||
|
||||
// FK declared below table (circular reference)
|
||||
$table->bigInteger('source_submission_file_id')->unsigned()->nullable();
|
||||
|
||||
$table->bigInteger('genre_id')->nullable();
|
||||
$table->foreign('genre_id')->references('genre_id')->on('genres')->onDelete('set null');
|
||||
$table->index(['genre_id'], 'submission_files_genre_id');
|
||||
|
||||
$table->bigInteger('file_stage');
|
||||
$table->string('direct_sales_price', 255)->nullable();
|
||||
$table->string('sales_type', 255)->nullable();
|
||||
$table->smallInteger('viewable')->nullable();
|
||||
$table->datetime('created_at');
|
||||
$table->datetime('updated_at');
|
||||
|
||||
$table->bigInteger('uploader_user_id')->nullable();
|
||||
$table->foreign('uploader_user_id')->references('user_id')->on('users')->onDelete('set null');
|
||||
$table->index(['uploader_user_id'], 'submission_files_uploader_user_id');
|
||||
|
||||
$table->bigInteger('assoc_type')->nullable();
|
||||
$table->bigInteger('assoc_id')->nullable();
|
||||
|
||||
// pkp/pkp-lib#5804
|
||||
$table->index(['file_stage', 'assoc_type', 'assoc_id'], 'submission_files_stage_assoc');
|
||||
});
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->foreign('source_submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
$table->index(['source_submission_file_id'], 'submission_files_source_submission_file_id');
|
||||
});
|
||||
|
||||
Schema::create('submission_file_settings', function (Blueprint $table) {
|
||||
$table->comment('Localized data about submission files like published metadata.');
|
||||
$table->bigIncrements('submission_file_setting_id');
|
||||
$table->foreignId('submission_file_id');
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
$table->index(['submission_file_id'], 'submission_file_settings_submission_file_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['submission_file_id', 'locale', 'setting_name'], 'submission_file_settings_unique');
|
||||
});
|
||||
|
||||
// Submission file revisions
|
||||
Schema::create('submission_file_revisions', function (Blueprint $table) {
|
||||
$table->comment('Revisions map submission_file entries to files on the data store.');
|
||||
$table->bigIncrements('revision_id');
|
||||
|
||||
$table->bigInteger('submission_file_id')->unsigned();
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
$table->index(['submission_file_id'], 'submission_file_revisions_submission_file_id');
|
||||
|
||||
$table->bigInteger('file_id')->unsigned();
|
||||
$table->foreign('file_id')->references('file_id')->on('files')->onDelete('cascade');
|
||||
$table->index(['file_id'], 'submission_file_revisions_file_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('submission_file_revisions');
|
||||
Schema::drop('submission_file_settings');
|
||||
Schema::drop('submission_files');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/SubmissionsMigration.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 SubmissionsMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\submission\PKPSubmission;
|
||||
|
||||
class SubmissionsMigration extends \PKP\migration\Migration
|
||||
{
|
||||
protected int $defaultStageId = WORKFLOW_STAGE_ID_SUBMISSION;
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Submissions
|
||||
Schema::create('submissions', function (Blueprint $table) {
|
||||
$table->comment('All submissions submitted to the context, including incomplete, declined and unpublished submissions.');
|
||||
$table->bigInteger('submission_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = \APP\core\Application::getContextDAO();
|
||||
$table->foreign('context_id', 'submissions_context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'submissions_context_id');
|
||||
|
||||
// NOTE: The foreign key relationship on publications is declared where that table is created.
|
||||
$table->bigInteger('current_publication_id')->nullable();
|
||||
|
||||
$table->datetime('date_last_activity')->nullable();
|
||||
$table->datetime('date_submitted')->nullable();
|
||||
$table->datetime('last_modified')->nullable();
|
||||
$table->bigInteger('stage_id')->default($this->defaultStageId);
|
||||
$table->string('locale', 14)->nullable();
|
||||
|
||||
$table->smallInteger('status')->default(PKPSubmission::STATUS_QUEUED);
|
||||
|
||||
$table->string('submission_progress', 50)->default('start');
|
||||
// Used in OMP only; should not be null there
|
||||
$table->smallInteger('work_type')->default(0)->nullable();
|
||||
});
|
||||
Schema::table('stage_assignments', function (Blueprint $table) {
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
$table->index(['submission_id'], 'stage_assignments_submission_id');
|
||||
});
|
||||
|
||||
// Submission metadata
|
||||
Schema::create('submission_settings', function (Blueprint $table) {
|
||||
$table->comment('Localized data about submissions');
|
||||
$table->bigIncrements('submission_setting_id');
|
||||
$table->bigInteger('submission_id');
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
$table->index(['submission_id'], 'submission_settings_submission_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['submission_id', 'locale', 'setting_name'], 'submission_settings_unique');
|
||||
});
|
||||
|
||||
// publication metadata
|
||||
Schema::create('publication_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about publications, including localized properties such as the title and abstract.');
|
||||
$table->bigIncrements('publication_setting_id');
|
||||
|
||||
// The foreign key relationship on this table is defined with the publications table.
|
||||
$table->bigInteger('publication_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['publication_id', 'locale', 'setting_name'], 'publication_settings_unique');
|
||||
});
|
||||
// Add partial index (DBMS-specific)
|
||||
switch (DB::getDriverName()) {
|
||||
case 'mysql': DB::unprepared('CREATE INDEX publication_settings_name_value ON publication_settings (setting_name(50), setting_value(150))');
|
||||
break;
|
||||
case 'pgsql': DB::unprepared("CREATE INDEX publication_settings_name_value ON publication_settings (setting_name, setting_value) WHERE setting_name IN ('indexingState', 'medra::registeredDoi', 'datacite::registeredDoi', 'pub-id::publisher-id')");
|
||||
break;
|
||||
}
|
||||
|
||||
// Authors for submissions.
|
||||
Schema::create('authors', function (Blueprint $table) {
|
||||
$table->comment('The authors of a publication.');
|
||||
$table->bigInteger('author_id')->autoIncrement();
|
||||
$table->string('email', 90);
|
||||
$table->smallInteger('include_in_browse')->default(1);
|
||||
|
||||
// The foreign key relationship on this table is defined with the publications table.
|
||||
$table->bigInteger('publication_id');
|
||||
|
||||
$table->float('seq', 8, 2)->default(0);
|
||||
|
||||
$table->bigInteger('user_group_id')->nullable();
|
||||
$table->foreign('user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
$table->index(['user_group_id'], 'authors_user_group_id');
|
||||
});
|
||||
|
||||
// Language dependent author metadata.
|
||||
Schema::create('author_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about authors, including localized properties such as their name and affiliation.');
|
||||
$table->bigIncrements('author_setting_id');
|
||||
$table->bigInteger('author_id');
|
||||
$table->foreign('author_id', 'author_settings_author_id')->references('author_id')->on('authors')->onDelete('cascade');
|
||||
$table->index(['author_id'], 'author_settings_author_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['author_id', 'locale', 'setting_name'], 'author_settings_unique');
|
||||
});
|
||||
|
||||
// Editor decisions.
|
||||
Schema::create('edit_decisions', function (Blueprint $table) {
|
||||
$table->comment('Editorial decisions recorded on a submission, such as decisions to accept or decline the submission, as well as decisions to send for review, send to copyediting, request revisions, and more.');
|
||||
$table->bigInteger('edit_decision_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('submission_id');
|
||||
$table->foreign('submission_id', 'edit_decisions_submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
$table->index(['submission_id'], 'edit_decisions_submission_id');
|
||||
|
||||
// Foreign key constraint is declared with review_rounds
|
||||
$table->bigInteger('review_round_id')->nullable();
|
||||
|
||||
$table->bigInteger('stage_id')->nullable();
|
||||
$table->smallInteger('round')->nullable();
|
||||
|
||||
$table->bigInteger('editor_id');
|
||||
$table->foreign('editor_id', 'edit_decisions_editor_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['editor_id'], 'edit_decisions_editor_id');
|
||||
|
||||
$table->smallInteger('decision')->comment('A numeric constant indicating the decision that was taken. Possible values are listed in the Decision class.');
|
||||
$table->datetime('date_decided');
|
||||
});
|
||||
|
||||
// Comments posted on submissions
|
||||
Schema::create('submission_comments', function (Blueprint $table) {
|
||||
$table->comment('Comments on a submission, e.g. peer review comments');
|
||||
$table->bigInteger('comment_id')->autoIncrement();
|
||||
$table->bigInteger('comment_type')->nullable();
|
||||
$table->bigInteger('role_id');
|
||||
|
||||
$table->bigInteger('submission_id');
|
||||
$table->foreign('submission_id', 'submission_comments_submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
$table->index(['submission_id'], 'submission_comments_submission_id');
|
||||
|
||||
$table->bigInteger('assoc_id');
|
||||
|
||||
$table->bigInteger('author_id');
|
||||
$table->foreign('author_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['author_id'], 'submission_comments_author_id');
|
||||
|
||||
$table->text('comment_title');
|
||||
$table->text('comments')->nullable();
|
||||
$table->datetime('date_posted')->nullable();
|
||||
$table->datetime('date_modified')->nullable();
|
||||
$table->smallInteger('viewable')->nullable();
|
||||
});
|
||||
|
||||
// Assignments of sub editors to submission groups.
|
||||
Schema::create('subeditor_submission_group', function (Blueprint $table) {
|
||||
$table->comment('Subeditor assignments to e.g. sections and categories');
|
||||
$table->bigIncrements('subeditor_submission_group_id');
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = \APP\core\Application::getContextDAO();
|
||||
$table->foreign('context_id', 'section_editors_context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'subeditor_submission_group_context_id');
|
||||
|
||||
$table->bigInteger('assoc_id');
|
||||
$table->bigInteger('assoc_type');
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id', 'subeditor_submission_group_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'subeditor_submission_group_user_id');
|
||||
|
||||
$table->bigInteger('user_group_id');
|
||||
$table->foreign('user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
$table->index(['user_group_id'], 'subeditor_submission_group_user_group_id');
|
||||
|
||||
$table->index(['assoc_id', 'assoc_type'], 'subeditor_submission_group_assoc_id');
|
||||
$table->unique(['context_id', 'assoc_id', 'assoc_type', 'user_id', 'user_group_id'], 'section_editors_unique');
|
||||
});
|
||||
|
||||
// queries posted on submission workflow
|
||||
Schema::create('queries', function (Blueprint $table) {
|
||||
$table->comment('Discussions, usually related to a submission, created by editors, authors and other editorial staff.');
|
||||
$table->bigInteger('query_id')->autoIncrement();
|
||||
$table->bigInteger('assoc_type');
|
||||
$table->bigInteger('assoc_id');
|
||||
$table->smallInteger('stage_id');
|
||||
$table->float('seq', 8, 2)->default(0);
|
||||
$table->datetime('date_posted')->nullable();
|
||||
$table->datetime('date_modified')->nullable();
|
||||
$table->smallInteger('closed')->default(0);
|
||||
$table->index(['assoc_type', 'assoc_id'], 'queries_assoc_id');
|
||||
});
|
||||
|
||||
// queries posted on submission workflow
|
||||
Schema::create('query_participants', function (Blueprint $table) {
|
||||
$table->comment('The users assigned to a discussion.');
|
||||
$table->bigIncrements('query_participant_id');
|
||||
$table->bigInteger('query_id');
|
||||
$table->foreign('query_id')->references('query_id')->on('queries')->onDelete('cascade');
|
||||
$table->index(['query_id'], 'query_participants_query_id');
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'query_participants_user_id');
|
||||
|
||||
$table->unique(['query_id', 'user_id'], 'query_participants_unique');
|
||||
});
|
||||
|
||||
// List of all keywords.
|
||||
Schema::create('submission_search_keyword_list', function (Blueprint $table) {
|
||||
$table->comment('A list of all keywords used in the search index');
|
||||
$table->bigInteger('keyword_id')->autoIncrement();
|
||||
$table->string('keyword_text', 60);
|
||||
$table->unique(['keyword_text'], 'submission_search_keyword_text');
|
||||
});
|
||||
|
||||
// Indexed objects.
|
||||
Schema::create('submission_search_objects', function (Blueprint $table) {
|
||||
$table->comment('A list of all search objects indexed in the search index');
|
||||
$table->bigInteger('object_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('submission_id');
|
||||
$table->foreign('submission_id', 'submission_search_object_submission')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
$table->index(['submission_id'], 'submission_search_objects_submission_id');
|
||||
|
||||
$table->integer('type')->comment('Type of item. E.g., abstract, fulltext, etc.');
|
||||
$table->bigInteger('assoc_id')->comment('Optional ID of an associated record (e.g., a file_id)')->nullable();
|
||||
});
|
||||
|
||||
// Keyword occurrences for each indexed object.
|
||||
Schema::create('submission_search_object_keywords', function (Blueprint $table) {
|
||||
$table->comment('Relationships between search objects and keywords in the search index');
|
||||
$table->bigIncrements('submission_search_object_keyword_id');
|
||||
$table->bigInteger('object_id');
|
||||
$table->foreign('object_id')->references('object_id')->on('submission_search_objects')->onDelete('cascade');
|
||||
$table->index(['object_id'], 'submission_search_object_keywords_object_id');
|
||||
|
||||
$table->bigInteger('keyword_id');
|
||||
$table->foreign('keyword_id', 'submission_search_object_keywords_keyword_id')->references('keyword_id')->on('submission_search_keyword_list')->onDelete('cascade');
|
||||
$table->index(['keyword_id'], 'submission_search_object_keywords_keyword_id');
|
||||
|
||||
$table->integer('pos')->comment('Word position of the keyword in the object.');
|
||||
|
||||
$table->unique(['object_id', 'pos'], 'submission_search_object_keywords_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('submission_search_object_keywords');
|
||||
Schema::drop('submission_search_objects');
|
||||
Schema::drop('submission_search_keyword_list');
|
||||
Schema::drop('query_participants');
|
||||
Schema::drop('queries');
|
||||
Schema::drop('subeditor_submission_group');
|
||||
Schema::drop('submission_comments');
|
||||
Schema::drop('edit_decisions');
|
||||
Schema::drop('author_settings');
|
||||
Schema::drop('authors');
|
||||
Schema::drop('publication_settings');
|
||||
Schema::drop('submission_settings');
|
||||
Schema::drop('submissions');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/TemporaryFilesMigration.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 TemporaryFilesMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class TemporaryFilesMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Temporary file storage
|
||||
Schema::create('temporary_files', function (Blueprint $table) {
|
||||
$table->comment('Temporary files, e.g. where files are kept during an upload process before they are moved somewhere more appropriate.');
|
||||
$table->bigInteger('file_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('user_id');
|
||||
$table->foreign('user_id', 'temporary_files_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'temporary_files_user_id');
|
||||
|
||||
$table->string('file_name', 90);
|
||||
$table->string('file_type', 255)->nullable();
|
||||
$table->bigInteger('file_size');
|
||||
$table->string('original_file_name', 127)->nullable();
|
||||
$table->datetime('date_uploaded');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('temporary_files');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/install/TombstoneMigration.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 TombstoneMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\install;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class TombstoneMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Unavailable data object tombstones.
|
||||
Schema::create('data_object_tombstones', function (Blueprint $table) {
|
||||
$table->comment('Entries for published data that has been removed. Usually used in the OAI endpoint.');
|
||||
$table->bigInteger('tombstone_id')->autoIncrement();
|
||||
$table->bigInteger('data_object_id');
|
||||
$table->datetime('date_deleted');
|
||||
$table->string('set_spec', 255);
|
||||
$table->string('set_name', 255);
|
||||
$table->string('oai_identifier', 255);
|
||||
$table->index(['data_object_id'], 'data_object_tombstones_data_object_id');
|
||||
});
|
||||
|
||||
// Data object tombstone settings.
|
||||
Schema::create('data_object_tombstone_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about data object tombstones, including localized content.');
|
||||
$table->bigIncrements('tombstone_setting_id');
|
||||
$table->bigInteger('tombstone_id');
|
||||
$table->foreign('tombstone_id', 'data_object_tombstone_settings_tombstone_id')->references('tombstone_id')->on('data_object_tombstones')->onDelete('cascade');
|
||||
$table->index(['tombstone_id'], 'data_object_tombstone_settings_tombstone_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->string('setting_type', 6)->comment('(bool|int|float|string|object)');
|
||||
|
||||
$table->unique(['tombstone_id', 'locale', 'setting_name'], 'data_object_tombstone_settings_unique');
|
||||
});
|
||||
|
||||
// Objects that are part of a data object tombstone OAI set.
|
||||
Schema::create('data_object_tombstone_oai_set_objects', function (Blueprint $table) {
|
||||
$table->comment('Relationships between tombstones and other data that can be collected in OAI sets, e.g. sections.');
|
||||
$table->bigInteger('object_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('tombstone_id');
|
||||
$table->foreign('tombstone_id', 'data_object_tombstone_oai_set_objects_tombstone_id')->references('tombstone_id')->on('data_object_tombstones')->onDelete('cascade');
|
||||
$table->index(['tombstone_id'], 'data_object_tombstone_oai_set_objects_tombstone_id');
|
||||
|
||||
$table->bigInteger('assoc_type');
|
||||
$table->bigInteger('assoc_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('data_object_tombstone_oai_set_objects');
|
||||
Schema::drop('data_object_tombstone_settings');
|
||||
Schema::drop('data_object_tombstones');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/PKPv3_2_1SubeditorCategoriesMigration.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 PKPv3_2_1SubeditorCategoriesMigration
|
||||
*
|
||||
* @brief pkp/pkp-lib#5694 Allow subeditors to be assigned to Categories
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class PKPv3_2_1SubeditorCategoriesMigration extends \PKP\migration\Migration
|
||||
{
|
||||
private const ASSOC_TYPE_SECTION = 0x0000212;
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Schema changes
|
||||
Schema::rename('section_editors', 'subeditor_submission_group');
|
||||
Schema::table('subeditor_submission_group', function (Blueprint $table) {
|
||||
// Change section_id to assoc_type/assoc_id
|
||||
$table->bigInteger('assoc_type')->nullable();
|
||||
$table->renameColumn('section_id', 'assoc_id');
|
||||
|
||||
// Drop indexes
|
||||
$table->dropIndex('section_editors_pkey');
|
||||
$table->dropIndex('section_editors_context_id');
|
||||
$table->dropIndex('section_editors_section_id');
|
||||
$table->dropIndex('section_editors_user_id');
|
||||
|
||||
// Create indexes
|
||||
$table->index(['context_id'], 'section_editors_context_id');
|
||||
$table->index(['assoc_id', 'assoc_type'], 'subeditor_submission_group_assoc_id');
|
||||
$table->index(['user_id'], 'subeditor_submission_group_user_id');
|
||||
$table->unique(['context_id', 'assoc_id', 'assoc_type', 'user_id'], 'section_editors_pkey');
|
||||
});
|
||||
|
||||
// Populate the assoc_type data in the newly created column
|
||||
DB::table('subeditor_submission_group')->update(['assoc_type' => self::ASSOC_TYPE_SECTION]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new \PKP\install\DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,911 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/PKPv3_3_0UpgradeMigration.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 PKPv3_3_0UpgradeMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade;
|
||||
|
||||
use APP\core\Services;
|
||||
use APP\facades\Repo;
|
||||
use Exception;
|
||||
use Illuminate\Database\PostgresConnection;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\db\XMLDAO;
|
||||
use PKP\file\FileManager;
|
||||
|
||||
abstract class PKPv3_3_0UpgradeMigration extends \PKP\migration\Migration
|
||||
{
|
||||
private const ASSOC_TYPE_REVIEW_ROUND = 0x000020B; //PKPApplication::ASSOC_TYPE_REVIEW_ROUND
|
||||
private const ASSOC_TYPE_REVIEW_RESPONSE = 0x0000204; //PKPApplication::ASSOC_TYPE_REVIEW_RESPONSE
|
||||
|
||||
private const SUBMISSION_FILE_FAIR_COPY = 7; //SubmissionFile::SUBMISSION_FILE_FAIR_COPY
|
||||
private const SUBMISSION_FILE_EDITOR = 8; //SubmissionFile::SUBMISSION_FILE_EDITOR
|
||||
private const SUBMISSION_FILE_SUBMISSION = 2; //SubmissionFile::SUBMISSION_FILE_SUBMISSION
|
||||
private const SUBMISSION_FILE_NOTE = 3; //SubmissionFile::SUBMISSION_FILE_NOTE
|
||||
private const SUBMISSION_FILE_REVIEW_FILE = 4; //SubmissionFile::SUBMISSION_FILE_REVIEW_FILE
|
||||
private const SUBMISSION_FILE_REVIEW_ATTACHMENT = 5; //SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT
|
||||
private const SUBMISSION_FILE_REVIEW_REVISION = 15; //SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION
|
||||
private const SUBMISSION_FILE_FINAL = 6; //SubmissionFile::SUBMISSION_FILE_FINAL
|
||||
private const SUBMISSION_FILE_COPYEDIT = 9; //SubmissionFile::SUBMISSION_FILE_COPYEDIT
|
||||
private const SUBMISSION_FILE_DEPENDENT = 17; //SubmissionFile::SUBMISSION_FILE_DEPENDENT
|
||||
private const SUBMISSION_FILE_PROOF = 10; //SubmissionFile::SUBMISSION_FILE_PROOF
|
||||
private const SUBMISSION_FILE_PRODUCTION_READY = 11; //SubmissionFile::SUBMISSION_FILE_PRODUCTION_READY
|
||||
private const SUBMISSION_FILE_ATTACHMENT = 13; //SubmissionFile::SUBMISSION_FILE_ATTACHMENT
|
||||
private const SUBMISSION_FILE_QUERY = 18; //SubmissionFile::SUBMISSION_FILE_QUERY
|
||||
|
||||
abstract protected function getSubmissionPath(): string;
|
||||
abstract protected function getContextPath(): string;
|
||||
abstract protected function getContextTable(): string;
|
||||
abstract protected function getContextKeyField(): string;
|
||||
abstract protected function getContextSettingsTable(): string;
|
||||
abstract protected function getSectionTable(): string;
|
||||
abstract protected function getSerializedSettings(): array;
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (Schema::hasColumn('submissions', 'locale')) {
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
// pkp/pkp-lib#3572 Remove OJS 2.x upgrade tools (OPS doesn't have this)
|
||||
$table->dropColumn('locale');
|
||||
});
|
||||
}
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
// pkp/pkp-lib#6285 submissions.section_id in OMP appears only from 3.2.1
|
||||
if (Schema::hasColumn($table->getTable(), 'section_id')) {
|
||||
// pkp/pkp-lib#2493 Remove obsolete columns
|
||||
$table->dropColumn('section_id');
|
||||
};
|
||||
});
|
||||
Schema::table('publication_settings', function (Blueprint $table) {
|
||||
// pkp/pkp-lib#6096 DB field type TEXT is cutting off long content
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
if (Schema::hasColumn('authors', 'submission_id')) {
|
||||
Schema::table('authors', function (Blueprint $table) {
|
||||
// pkp/pkp-lib#2493 Remove obsolete columns
|
||||
$table->dropColumn('submission_id');
|
||||
});
|
||||
}
|
||||
Schema::table('announcements', function (Blueprint $table) {
|
||||
// pkp/pkp-lib#5865 Change announcement expiry format in database
|
||||
$table->date('date_expire')->change();
|
||||
});
|
||||
Schema::table('announcement_settings', function (Blueprint $table) {
|
||||
// pkp/pkp-lib#6748 Change announcement setting type to permit nulls
|
||||
$table->string('setting_type', 6)->nullable()->change();
|
||||
});
|
||||
|
||||
// Transitional: The stage_id column may have already been added by the ADODB schema toolset
|
||||
if (!Schema::hasColumn('email_templates_default', 'stage_id')) {
|
||||
Schema::table('email_templates_default', function (Blueprint $table) {
|
||||
// pkp/pkp-lib#4796 stage ID as a filter parameter to email templates
|
||||
$table->bigInteger('stage_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
// pkp/pkp-lib#6301: Indexes may be missing that affect search performance.
|
||||
// (These are added for 3.2.1-2 so may or may not be present for this upgrade code.)
|
||||
$schemaManager = DB::getDoctrineSchemaManager();
|
||||
if (!in_array('submissions_publication_id', array_keys($schemaManager->listTableIndexes('submissions')))) {
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
$table->index(['submission_id'], 'submissions_publication_id');
|
||||
});
|
||||
}
|
||||
if (!in_array('submission_search_object_submission', array_keys($schemaManager->listTableIndexes('submission_search_objects')))) {
|
||||
Schema::table('submission_search_objects', function (Blueprint $table) {
|
||||
$table->index(['submission_id'], 'submission_search_object_submission');
|
||||
});
|
||||
}
|
||||
|
||||
// pkp/pkp-lib#6093 Don't allow nulls (previously an upgrade workaround)
|
||||
Schema::table('announcement_types', function (Blueprint $table) {
|
||||
$table->bigInteger('assoc_type')->nullable(false)->change();
|
||||
});
|
||||
Schema::table('email_templates', function (Blueprint $table) {
|
||||
$table->bigInteger('context_id')->default(0)->nullable(false)->change();
|
||||
});
|
||||
Schema::table('genres', function (Blueprint $table) {
|
||||
$table->bigInteger('seq')->nullable(false)->change();
|
||||
$table->smallInteger('supplementary')->default(0)->nullable(false)->change();
|
||||
});
|
||||
Schema::table('event_log', function (Blueprint $table) {
|
||||
$table->bigInteger('assoc_type')->nullable(false)->change();
|
||||
$table->bigInteger('assoc_id')->nullable(false)->change();
|
||||
});
|
||||
Schema::table('email_log', function (Blueprint $table) {
|
||||
$table->bigInteger('assoc_type')->nullable(false)->change();
|
||||
$table->bigInteger('assoc_id')->nullable(false)->change();
|
||||
});
|
||||
Schema::table('notes', function (Blueprint $table) {
|
||||
$table->bigInteger('assoc_type')->nullable(false)->change();
|
||||
$table->bigInteger('assoc_id')->nullable(false)->change();
|
||||
});
|
||||
Schema::table('review_assignments', function (Blueprint $table) {
|
||||
$table->bigInteger('review_round_id')->nullable(false)->change();
|
||||
});
|
||||
Schema::table('authors', function (Blueprint $table) {
|
||||
$table->bigInteger('publication_id')->nullable(false)->change();
|
||||
});
|
||||
DB::unprepared('UPDATE review_assignments SET review_form_id=NULL WHERE review_form_id=0');
|
||||
|
||||
$this->_populateEmailTemplates();
|
||||
$this->_makeRemoteUrlLocalizable();
|
||||
|
||||
// pkp/pkp-lib#6057: Migrate locale property from publications to submissions
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
$table->string('locale', 14)->nullable();
|
||||
});
|
||||
|
||||
$updateQuery = DB::table('submissions as s')
|
||||
->join('publications as p', 'p.publication_id', '=', 's.current_publication_id');
|
||||
if (DB::connection() instanceof PostgresConnection) {
|
||||
$updateQuery->updateFrom(['s.locale' => DB::raw('p.locale')]);
|
||||
} else {
|
||||
$updateQuery->update(['s.locale' => DB::raw('p.locale')]);
|
||||
}
|
||||
|
||||
Schema::table('publications', function (Blueprint $table) {
|
||||
$table->dropColumn('locale');
|
||||
});
|
||||
|
||||
// pkp/pkp-lib#6057 Submission files refactor
|
||||
$this->migrateSubmissionFiles();
|
||||
|
||||
$this->_fixCapitalCustomBlockTitles();
|
||||
$this->_createCustomBlockTitles();
|
||||
|
||||
// Remove item views related to submission files and notes,
|
||||
// and convert the assoc_id to an integer
|
||||
DB::table('item_views')
|
||||
->where('assoc_type', '!=', self::ASSOC_TYPE_REVIEW_RESPONSE)
|
||||
->delete();
|
||||
// PostgreSQL requires an explicit type cast
|
||||
if (DB::connection() instanceof PostgresConnection) {
|
||||
DB::statement('ALTER TABLE item_views ALTER COLUMN assoc_id TYPE BIGINT USING (assoc_id::INTEGER)');
|
||||
} else {
|
||||
Schema::table('item_views', function (Blueprint $table) {
|
||||
$table->bigInteger('assoc_id')->change();
|
||||
});
|
||||
}
|
||||
|
||||
// pkp/pkp-lib#4017 and pkp/pkp-lib#4622
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('queue');
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
$table->index(['queue', 'reserved_at']);
|
||||
});
|
||||
|
||||
// pkp/pkp-lib#6594 Clear context defaults installed for unused languages
|
||||
$settingsWithDefaults = [
|
||||
'authorInformation',
|
||||
'librarianInformation',
|
||||
'openAccessPolicy',
|
||||
'privacyStatement',
|
||||
'readerInformation',
|
||||
'submissionChecklist',
|
||||
'clockssLicense',
|
||||
'lockssLicense',
|
||||
];
|
||||
|
||||
$rows = DB::table($this->getContextSettingsTable() . ' as js')
|
||||
->join($this->getContextSettingsTable() . ' as j', 'j.' . $this->getContextKeyField(), '=', 'js.' . $this->getContextKeyField())
|
||||
->where('js.setting_name', '=', 'supportedFormLocales')
|
||||
->select(['js.' . $this->getContextKeyField() . ' as id', 'js.setting_value as locales'])
|
||||
->get();
|
||||
foreach ($rows as $row) {
|
||||
// account for some locale settings stored as assoc arrays
|
||||
$locales = $row->locales;
|
||||
if (empty($locales)) {
|
||||
$locales = [];
|
||||
} elseif (@unserialize($locales) !== false) {
|
||||
$locales = unserialize($locales);
|
||||
} else {
|
||||
$locales = json_decode($locales, true);
|
||||
}
|
||||
$locales = array_values($locales);
|
||||
DB::table($this->getContextSettingsTable())
|
||||
->where($this->getContextKeyField(), '=', $row->id)
|
||||
->whereIn('setting_name', $settingsWithDefaults)
|
||||
->whereNotIn('locale', $locales)
|
||||
->delete();
|
||||
}
|
||||
|
||||
Schema::table($this->getContextSettingsTable(), function (Blueprint $table) {
|
||||
// pkp/pkp-lib#6096 DB field type TEXT is cutting off long content
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
if (!Schema::hasColumn($this->getSectionTable(), 'is_inactive')) {
|
||||
Schema::table($this->getSectionTable(), function (Blueprint $table) {
|
||||
$table->smallInteger('is_inactive')->default(0);
|
||||
});
|
||||
}
|
||||
if (Schema::hasTable('review_forms')) {
|
||||
Schema::table('review_forms', function (Blueprint $table) {
|
||||
$table->bigInteger('assoc_type')->nullable(false)->change();
|
||||
$table->bigInteger('assoc_id')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
|
||||
// pkp/pkp-lib#6807 Make sure all submission last modification dates are set
|
||||
DB::statement('UPDATE submissions SET last_modified = NOW() WHERE last_modified IS NULL');
|
||||
|
||||
$this->_settingsAsJSON();
|
||||
|
||||
if (Schema::hasColumn('author_settings', 'setting_type')) {
|
||||
Schema::table('author_settings', function (Blueprint $table) {
|
||||
// pkp/pkp-lib#2493 Remove obsolete columns
|
||||
$table->dropColumn('setting_type');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new \PKP\install\DowngradeNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief populate email templates with new records for workflow stage id
|
||||
*/
|
||||
private function _populateEmailTemplates()
|
||||
{
|
||||
$xmlDao = new XMLDAO();
|
||||
$data = $xmlDao->parseStruct(Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(), ['email']);
|
||||
foreach ($data['email'] as $template) {
|
||||
$attr = $template['attributes'];
|
||||
if (array_key_exists('stage_id', $attr)) {
|
||||
DB::table('email_templates_default')->where('email_key', $attr['key'])->update(['stage_id' => $attr['stage_id']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief make remoteUrl navigation item type multilingual and drop the url column
|
||||
*/
|
||||
private function _makeRemoteUrlLocalizable()
|
||||
{
|
||||
$contexts = DB::table($this->getContextTable() . ' AS c')
|
||||
->join($this->getContextSettingsTable() . ' AS s', 's.' . $this->getContextKeyField(), '=', 'c.' . $this->getContextKeyField())
|
||||
->where('s.setting_name', '=', 'supportedLocales')
|
||||
->select(['c.' . $this->getContextKeyField() . ' AS id', 's.setting_value AS supported_locales'])
|
||||
->get();
|
||||
|
||||
foreach ($contexts as $context) {
|
||||
if (($locales = @unserialize($context->supported_locales)) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$navigationItems = DB::table('navigation_menu_items')->where('context_id', $context->id)->pluck('url', 'navigation_menu_item_id')->filter()->all();
|
||||
foreach ($navigationItems as $navigation_menu_item_id => $url) {
|
||||
foreach ($locales as $locale) {
|
||||
DB::table('navigation_menu_item_settings')->insert([
|
||||
'navigation_menu_item_id' => $navigation_menu_item_id,
|
||||
'locale' => $locale,
|
||||
'setting_name' => 'remoteUrl',
|
||||
'setting_value' => $url,
|
||||
'setting_type' => 'string'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$site = DB::table('site')
|
||||
->select('supported_locales')
|
||||
->first();
|
||||
$supportedLocales = @unserialize($site->supported_locales);
|
||||
if ($supportedLocales !== false) {
|
||||
$navigationItems = DB::table('navigation_menu_items')->where('context_id', '0')->pluck('url', 'navigation_menu_item_id')->filter()->all();
|
||||
foreach ($navigationItems as $navigation_menu_item_id => $url) {
|
||||
foreach ($supportedLocales as $locale) {
|
||||
DB::table('navigation_menu_item_settings')->insert([
|
||||
'navigation_menu_item_id' => $navigation_menu_item_id,
|
||||
'locale' => $locale,
|
||||
'setting_name' => 'remoteUrl',
|
||||
'setting_value' => $url,
|
||||
'setting_type' => 'string'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Schema::table('navigation_menu_items', function (Blueprint $table) {
|
||||
$table->dropColumn('url');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate submission files after major refactor
|
||||
*
|
||||
* - Add files table to manage underlying file storage
|
||||
* - Replace the use of file_id/revision as a unique id with a single
|
||||
* auto-incrementing submission_file_id, and update all references.
|
||||
* - Move revisions to a submission_file_revisons table.
|
||||
* - Drop unused columns in submission_files table.
|
||||
*
|
||||
* @see pkp/pkp-lib#6057
|
||||
*/
|
||||
protected function migrateSubmissionFiles()
|
||||
{
|
||||
// pkp/pkp-lib#6616 Delete submission_files entries that correspond to nonexistent submissions
|
||||
$orphanedIds = DB::table('submission_files AS sf')->leftJoin('submissions AS s', 'sf.submission_id', '=', 's.submission_id')->whereNull('s.submission_id')->pluck('sf.submission_id', 'sf.file_id');
|
||||
foreach ($orphanedIds as $fileId => $submissionId) {
|
||||
error_log("Removing orphaned submission_files entry ID {$fileId} with submission_id {$submissionId}");
|
||||
DB::table('submission_files')->where('file_id', '=', $fileId)->delete();
|
||||
}
|
||||
|
||||
// Add partial index (DBMS-specific)
|
||||
if (DB::connection() instanceof PostgresConnection) {
|
||||
DB::unprepared("CREATE INDEX event_log_settings_name_value ON event_log_settings (setting_name, setting_value) WHERE setting_name IN ('fileId', 'submissionId')");
|
||||
} else {
|
||||
DB::unprepared('CREATE INDEX event_log_settings_name_value ON event_log_settings (setting_name(50), setting_value(150))');
|
||||
}
|
||||
|
||||
// Create a new table to track files in file storage
|
||||
Schema::create('files', function (Blueprint $table) {
|
||||
$table->bigIncrements('file_id');
|
||||
$table->string('path', 255);
|
||||
$table->string('mimetype', 255);
|
||||
});
|
||||
|
||||
// Create a new table to track submission file revisions
|
||||
Schema::create('submission_file_revisions', function (Blueprint $table) {
|
||||
$table->bigIncrements('revision_id');
|
||||
$table->unsignedBigInteger('submission_file_id');
|
||||
$table->unsignedBigInteger('file_id');
|
||||
});
|
||||
|
||||
// Add columns to submission_files table
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('new_file_id')->nullable(); // Renamed and made not nullable at the end of the migration
|
||||
});
|
||||
|
||||
// Drop unique keys that will cause trouble while we're migrating
|
||||
Schema::table('review_round_files', function (Blueprint $table) {
|
||||
$table->dropIndex('review_round_files_pkey');
|
||||
});
|
||||
|
||||
// Create entry in files and revisions tables for every submission_file
|
||||
$fileManager = new FileManager();
|
||||
$rows = DB::table('submission_files', 'sf')
|
||||
->join('submissions AS s', 'sf.submission_id', '=', 's.submission_id')
|
||||
->leftJoin('review_round_files AS rrf', function (JoinClause $j) {
|
||||
$j->on('rrf.submission_id', '=', 'sf.submission_id')
|
||||
->on('rrf.file_id', '=', 'sf.file_id')
|
||||
->on('rrf.revision', '=', 'sf.revision');
|
||||
})
|
||||
->orderBy('sf.file_id')
|
||||
->orderBy('rrf.review_round_id')
|
||||
->orderBy('sf.revision')
|
||||
->get([
|
||||
's.context_id',
|
||||
'sf.file_id',
|
||||
'sf.revision',
|
||||
'sf.submission_id',
|
||||
'sf.genre_id',
|
||||
'sf.file_type',
|
||||
'sf.file_stage',
|
||||
'sf.date_uploaded',
|
||||
'sf.original_file_name',
|
||||
'rrf.review_round_id'
|
||||
]);
|
||||
$fileService = Services::get('file');
|
||||
$lastFileId = null;
|
||||
$lastReviewRoundId = null;
|
||||
$lastInsertedFileId = DB::table('submission_files')->max('file_id');
|
||||
$fileIdMap = [];
|
||||
foreach ($rows as $row) {
|
||||
// Due to the new database structure, a file_id that spans across N+1 review rounds needs to be forked to a new file_id, to avoid being consolidated later on the upgrade process, which would cause data loss
|
||||
// Statistics, logs, etc. will keep being referenced by the "main" file_id, which is not ideal, but also not possible to address.
|
||||
// This branch will be accessed once the review_round_id gets changed within a group of the same file_ids
|
||||
if ($lastFileId === $row->file_id && $lastReviewRoundId !== $row->review_round_id) {
|
||||
$fileIdMap = [
|
||||
$row->file_id => ++$lastInsertedFileId,
|
||||
'sourceFileId' => $fileIdMap[$row->file_id] ?? $row->file_id
|
||||
];
|
||||
DB::table('submission_file_settings')->insertUsing(
|
||||
['file_id', 'locale', 'setting_name', 'setting_value', 'setting_type'],
|
||||
function (Builder $q) use ($row, $lastInsertedFileId) {
|
||||
$q->from('submission_file_settings')
|
||||
->where('file_id', '=', $row->file_id)
|
||||
->select(DB::raw($lastInsertedFileId), 'locale', 'setting_name', 'setting_value', 'setting_type');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reproduces the removed method SubmissionFile::_generateFileName()
|
||||
// genre is %s because it can be blank with review attachments
|
||||
$filename = sprintf(
|
||||
'%d-%s-%d-%d-%d-%s.%s',
|
||||
$row->submission_id,
|
||||
$row->genre_id,
|
||||
$row->file_id,
|
||||
$row->revision,
|
||||
$row->file_stage,
|
||||
date('Ymd', strtotime($row->date_uploaded)),
|
||||
strtolower_codesafe($fileManager->parseFileExtension($row->original_file_name))
|
||||
);
|
||||
$path = sprintf(
|
||||
'%s/%s/%s',
|
||||
$this->getContextPath() . '/' . $row->context_id . '/' . $this->getSubmissionPath() . '/' . $row->submission_id,
|
||||
$this->_fileStageToPath($row->file_stage),
|
||||
$filename
|
||||
);
|
||||
if (!$fileService->fs->has($path)) {
|
||||
error_log("A submission file was expected but not found at {$path}.");
|
||||
}
|
||||
$newFileId = DB::table('files')->insertGetId([
|
||||
'path' => $path,
|
||||
'mimetype' => $row->file_type,
|
||||
], 'file_id');
|
||||
$fileId = $fileIdMap[$row->file_id] ?? $row->file_id;
|
||||
$updateData = ['new_file_id' => $newFileId];
|
||||
// If the file_id was forked into a new one, we've got to update the references at the review_round_files and the submission_file itself
|
||||
if ($fileId !== $row->file_id) {
|
||||
$updateData['file_id'] = $fileId;
|
||||
$updateData['source_file_id'] = $fileIdMap['sourceFileId'];
|
||||
DB::table('review_round_files')
|
||||
->where('file_id', '=', $row->file_id)
|
||||
->where('revision', '=', $row->revision)
|
||||
->update(['file_id' => $fileId]);
|
||||
}
|
||||
DB::table('submission_files')
|
||||
->where('file_id', $row->file_id)
|
||||
->where('revision', $row->revision)
|
||||
->update($updateData);
|
||||
DB::table('submission_file_revisions')->insert([
|
||||
'submission_file_id' => $fileId,
|
||||
'file_id' => $newFileId,
|
||||
]);
|
||||
|
||||
// Update revision data in event logs
|
||||
DB::table('event_log_settings as els')
|
||||
->join(
|
||||
'event_log_settings as file_setting',
|
||||
fn (JoinClause $join) =>
|
||||
$join->on('file_setting.log_id', '=', 'els.log_id')
|
||||
->where('file_setting.setting_name', '=', 'fileId')
|
||||
->where('file_setting.setting_value', '=', (string) $row->file_id)
|
||||
)
|
||||
->where('els.setting_name', 'fileRevision')
|
||||
->where('els.setting_value', '=', $row->revision)
|
||||
->update(['els.setting_value' => $newFileId]);
|
||||
|
||||
$lastFileId = $row->file_id;
|
||||
$lastReviewRoundId = $row->review_round_id;
|
||||
}
|
||||
|
||||
// Collect rows that will be deleted because they are old revisions
|
||||
// They are identified by the new_file_id column, which is the only unique
|
||||
// column on the table at this point.
|
||||
$newFileIdsToDelete = [];
|
||||
|
||||
// Get all the unique file_ids. For each one, determine the latest revision
|
||||
// in order to keep it in the table. The others will be flagged for removal
|
||||
foreach (DB::table('submission_files')->select('file_id')->distinct()->get() as $row) {
|
||||
$submissionFileRows = DB::table('submission_files')
|
||||
->where('file_id', '=', $row->file_id)
|
||||
->orderBy('revision', 'desc')
|
||||
->get([
|
||||
'file_id',
|
||||
'new_file_id',
|
||||
]);
|
||||
$latestFileId = $submissionFileRows[0]->new_file_id;
|
||||
foreach ($submissionFileRows as $submissionFileRow) {
|
||||
if ($submissionFileRow->new_file_id !== $latestFileId) {
|
||||
$newFileIdsToDelete[] = $submissionFileRow->new_file_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the rows for old revisions (chunked for performance)
|
||||
foreach (array_chunk($newFileIdsToDelete, 100) as $chunkFileIds) {
|
||||
DB::table('submission_files')
|
||||
->whereIn('new_file_id', $chunkFileIds)
|
||||
->delete();
|
||||
}
|
||||
|
||||
// Remove all review round files that point to file ids
|
||||
// that don't exist, so that the foreign key can be set
|
||||
// up successfully.
|
||||
// See: https://github.com/pkp/pkp-lib/issues/6337
|
||||
DB::table('review_round_files as rrf')
|
||||
->leftJoin('submission_files as sf', 'sf.file_id', '=', 'rrf.file_id')
|
||||
->whereNotNull('rrf.file_id')
|
||||
->whereNull('sf.file_id')
|
||||
->delete();
|
||||
|
||||
// Update review round files
|
||||
$rows = DB::table('review_round_files')->get();
|
||||
foreach ($rows as $row) {
|
||||
// Delete this row if another revision exists for this
|
||||
// submission file. This ensures that when the revision
|
||||
// column is dropped the submission_file_id column will
|
||||
// be unique.
|
||||
$count = DB::table('review_round_files')
|
||||
->where('file_id', '=', $row->file_id)
|
||||
->count();
|
||||
if ($count > 1) {
|
||||
DB::table('review_round_files')
|
||||
->where('file_id', '=', $row->file_id)
|
||||
->where('revision', '=', $row->revision)
|
||||
->delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set assoc_type and assoc_id for all review round files
|
||||
// Run this before migration to internal review file stages
|
||||
DB::table('submission_files')
|
||||
->where('file_id', '=', $row->file_id)
|
||||
->whereIn('file_stage', [self::SUBMISSION_FILE_REVIEW_FILE, self::SUBMISSION_FILE_REVIEW_REVISION])
|
||||
->update([
|
||||
'assoc_type' => self::ASSOC_TYPE_REVIEW_ROUND,
|
||||
'assoc_id' => $row->review_round_id,
|
||||
]);
|
||||
}
|
||||
|
||||
// Update name of event log params to reflect new file structure
|
||||
DB::table('event_log_settings')
|
||||
->where('setting_name', 'fileId')
|
||||
->update(['setting_name' => 'submissionFileId']);
|
||||
DB::table('event_log_settings')
|
||||
->where('setting_name', 'fileRevision')
|
||||
->update(['setting_name' => 'fileId']);
|
||||
|
||||
// Update file name of dependent files, see: pkp/pkp-lib#6801
|
||||
DB::table('submission_files')
|
||||
->select('file_id', 'original_file_name')
|
||||
->where('file_stage', '=', self::SUBMISSION_FILE_DEPENDENT)
|
||||
->chunkById(1000, function ($dependentFiles) {
|
||||
foreach ($dependentFiles as $dependentFile) {
|
||||
DB::table('submission_file_settings')
|
||||
->where('file_id', '=', $dependentFile->file_id)
|
||||
->where('setting_name', '=', 'name')
|
||||
->update(['setting_value' => $dependentFile->original_file_name]);
|
||||
}
|
||||
}, 'file_id');
|
||||
|
||||
// Restructure submission_files and submission_file_settings tables
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->bigInteger('file_id')->nullable(false)->unsigned()->change();
|
||||
});
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->dropPrimary(); // Drop compound primary key constraint
|
||||
});
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->renameColumn('file_id', 'submission_file_id');
|
||||
$table->renameColumn('new_file_id', 'file_id');
|
||||
$table->renameColumn('source_file_id', 'source_submission_file_id');
|
||||
$table->renameColumn('date_uploaded', 'created_at');
|
||||
$table->renameColumn('date_modified', 'updated_at');
|
||||
$table->dropColumn('revision');
|
||||
$table->dropColumn('source_revision');
|
||||
$table->dropColumn('file_size');
|
||||
$table->dropColumn('file_type');
|
||||
$table->dropColumn('original_file_name');
|
||||
$table->primary('submission_file_id');
|
||||
});
|
||||
// pkp/pkp-lib#5804
|
||||
$schemaManager = DB::getDoctrineSchemaManager();
|
||||
if (!in_array('submission_files_stage_assoc', array_keys($schemaManager->listTableIndexes('submission_files')))) {
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->index(['file_stage', 'assoc_type', 'assoc_id'], 'submission_files_stage_assoc');
|
||||
});
|
||||
}
|
||||
// Modify column types and attributes in separate migration
|
||||
// function to prevent error in postgres with unfound columns
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->bigInteger('submission_file_id')->autoIncrement()->unsigned()->change();
|
||||
$table->bigInteger('file_id')->nullable(false)->unsigned()->change();
|
||||
$table->foreign('file_id')->references('file_id')->on('files');
|
||||
});
|
||||
Schema::table('submission_file_settings', function (Blueprint $table) {
|
||||
$table->renameColumn('file_id', 'submission_file_id');
|
||||
$table->string('setting_type', 6)->default('string')->change();
|
||||
});
|
||||
|
||||
// Update columns in related tables
|
||||
Schema::table('review_round_files', function (Blueprint $table) {
|
||||
$table->renameColumn('file_id', 'submission_file_id');
|
||||
$table->dropColumn('revision');
|
||||
});
|
||||
Schema::table('review_round_files', function (Blueprint $table) {
|
||||
$table->bigInteger('submission_file_id')->nullable(false)->unique()->unsigned()->change();
|
||||
$table->unique(['submission_id', 'review_round_id', 'submission_file_id'], 'review_round_files_pkey');
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files');
|
||||
});
|
||||
Schema::table('review_files', function (Blueprint $table) {
|
||||
$table->renameColumn('file_id', 'submission_file_id');
|
||||
});
|
||||
|
||||
// pkp/pkp-lib#6616 Delete review_files entries that correspond to nonexistent submission_files
|
||||
$orphanedIds = DB::table('review_files AS rf')
|
||||
->select('rf.submission_file_id', 'rf.review_id')
|
||||
->leftJoin('submission_files AS sf', 'rf.submission_file_id', '=', 'sf.submission_file_id')
|
||||
->whereNull('sf.submission_file_id')
|
||||
->whereNotNull('rf.submission_file_id')
|
||||
->get();
|
||||
foreach ($orphanedIds as $orphanedId) {
|
||||
$reviewId = $orphanedId->{'review_id'};
|
||||
$submissionFileId = $orphanedId->{'submission_file_id'};
|
||||
error_log("Removing orphaned review_files entry with review_id ID {$reviewId} and submission_file_id {$submissionFileId}");
|
||||
DB::table('review_files')
|
||||
->where('submission_file_id', '=', $submissionFileId)
|
||||
->where('review_id', '=', $reviewId)
|
||||
->delete();
|
||||
}
|
||||
|
||||
Schema::table('review_files', function (Blueprint $table) {
|
||||
$table->bigInteger('submission_file_id')->nullable(false)->unsigned()->change();
|
||||
$table->dropIndex('review_files_pkey');
|
||||
$table->unique(['review_id', 'submission_file_id'], 'review_files_pkey');
|
||||
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files');
|
||||
});
|
||||
Schema::table('submission_file_revisions', function (Blueprint $table) {
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files');
|
||||
$table->foreign('file_id')->references('file_id')->on('files');
|
||||
});
|
||||
|
||||
// Postgres leaves the old file_id autoincrement sequence around, so
|
||||
// we delete it and apply a new sequence.
|
||||
if (DB::connection() instanceof PostgresConnection) {
|
||||
DB::statement('DROP SEQUENCE submission_files_file_id_seq CASCADE');
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->bigIncrements('submission_file_id')->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory of a file based on its file stage
|
||||
*
|
||||
* @param int $fileStage One of SubmissionFile::SUBMISSION_FILE_ constants
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function _fileStageToPath($fileStage)
|
||||
{
|
||||
static $fileStagePathMap = [
|
||||
self::SUBMISSION_FILE_SUBMISSION => 'submission',
|
||||
self::SUBMISSION_FILE_NOTE => 'note',
|
||||
self::SUBMISSION_FILE_REVIEW_FILE => 'submission/review',
|
||||
self::SUBMISSION_FILE_REVIEW_ATTACHMENT => 'submission/review/attachment',
|
||||
self::SUBMISSION_FILE_REVIEW_REVISION => 'submission/review/revision',
|
||||
self::SUBMISSION_FILE_FINAL => 'submission/final',
|
||||
self::SUBMISSION_FILE_FAIR_COPY => 'submission/fairCopy',
|
||||
self::SUBMISSION_FILE_EDITOR => 'submission/editor',
|
||||
self::SUBMISSION_FILE_COPYEDIT => 'submission/copyedit',
|
||||
self::SUBMISSION_FILE_DEPENDENT => 'submission/proof',
|
||||
self::SUBMISSION_FILE_PROOF => 'submission/proof',
|
||||
self::SUBMISSION_FILE_PRODUCTION_READY => 'submission/productionReady',
|
||||
self::SUBMISSION_FILE_ATTACHMENT => 'attachment',
|
||||
self::SUBMISSION_FILE_QUERY => 'submission/query'
|
||||
];
|
||||
|
||||
if (!isset($fileStagePathMap[$fileStage])) {
|
||||
error_log('A file assigned to the file stage ' . $fileStage . ' could not be migrated.');
|
||||
}
|
||||
|
||||
return $fileStagePathMap[$fileStage] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update block names to be all lowercase
|
||||
*
|
||||
* In previous versions, a custom block name would be stored in the
|
||||
* array of blocks with capitals but the block name in the plugin_settings
|
||||
* table is all lowercase. This migration aligns the two places by changing
|
||||
* the block names to always use lowercase.
|
||||
*
|
||||
*/
|
||||
private function _fixCapitalCustomBlockTitles()
|
||||
{
|
||||
$rows = DB::table('plugin_settings')
|
||||
->where('plugin_name', 'customblockmanagerplugin')
|
||||
->where('setting_name', 'blocks')
|
||||
->get();
|
||||
foreach ($rows as $row) {
|
||||
$updateBlocks = false;
|
||||
$blocks = unserialize($row->setting_value);
|
||||
foreach ($blocks as $key => $block) {
|
||||
$newBlock = strtolower_codesafe($block);
|
||||
if ($block !== $newBlock) {
|
||||
$blocks[$key] = $newBlock;
|
||||
$updateBlocks = true;
|
||||
}
|
||||
}
|
||||
if ($updateBlocks) {
|
||||
DB::table('plugin_settings')
|
||||
->where('plugin_name', 'customblockmanagerplugin')
|
||||
->where('setting_name', 'blocks')
|
||||
->where('context_id', $row->context_id)
|
||||
->update(['setting_value' => serialize($blocks)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create titles for custom block plugins
|
||||
*
|
||||
* This method copies the block names, which are a unique id,
|
||||
* into a block setting, `blockTitle`, in the context's
|
||||
* primary locale
|
||||
*
|
||||
* @see https://github.com/pkp/pkp-lib/issues/5619
|
||||
*/
|
||||
private function _createCustomBlockTitles()
|
||||
{
|
||||
$rows = DB::table('plugin_settings')
|
||||
->where('plugin_name', 'customblockmanagerplugin')
|
||||
->where('setting_name', 'blocks')
|
||||
->get();
|
||||
|
||||
$newRows = [];
|
||||
foreach ($rows as $row) {
|
||||
$locale = DB::table($this->getContextTable())
|
||||
->where($this->getContextKeyField(), $row->context_id)
|
||||
->first()
|
||||
->primary_locale;
|
||||
$blocks = unserialize($row->setting_value);
|
||||
foreach ($blocks as $block) {
|
||||
$newRows[] = [
|
||||
'plugin_name' => $block,
|
||||
'context_id' => $row->context_id,
|
||||
'setting_name' => 'blockTitle',
|
||||
'setting_value' => serialize([$locale => $block]),
|
||||
'setting_type' => 'object',
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!DB::table('plugin_settings')->insert($newRows)) {
|
||||
error_log('Failed to create title for custom blocks. This can be fixed manually by editing each custom block and adding a title.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief reset serialized arrays and convert array and objects to JSON for serialization, see pkp/pkp-lib#5772
|
||||
*/
|
||||
private function _settingsAsJSON()
|
||||
{
|
||||
$serializedSettings = $this->getSerializedSettings();
|
||||
$processedTables = [];
|
||||
foreach ($serializedSettings as $tableName => $settings) {
|
||||
$processedTables[] = $tableName;
|
||||
foreach ($settings as $setting) {
|
||||
DB::table($tableName)->where('setting_name', $setting)->get()->each(function ($row) use ($tableName) {
|
||||
$this->_toJSON($row, $tableName, ['setting_name', 'locale'], 'setting_value');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Convert settings where only setting_type column is available
|
||||
$tables = DB::getDoctrineSchemaManager()->listTableNames();
|
||||
foreach ($tables as $tableName) {
|
||||
if (substr($tableName, -9) !== '_settings' || in_array($tableName, $processedTables)) {
|
||||
continue;
|
||||
}
|
||||
if ($tableName === 'plugin_settings') {
|
||||
DB::table($tableName)->where('setting_type', 'object')->get()->each(function ($row) use ($tableName) {
|
||||
$this->_toJSON($row, $tableName, ['plugin_name', 'context_id', 'setting_name'], 'setting_value');
|
||||
});
|
||||
} elseif (Schema::hasColumn($tableName, 'setting_type')) {
|
||||
try {
|
||||
$settings = DB::table($tableName, 's')->where('setting_type', 'object')->get(['setting_name', 'setting_value', 's.*']);
|
||||
} catch (Exception $e) {
|
||||
error_log("Failed to migrate the settings entity \"{$tableName}\"\n" . $e);
|
||||
continue;
|
||||
}
|
||||
$settings->each(fn ($row) => $this->_toJSON($row, $tableName, ['setting_name', 'locale'], 'setting_value'));
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, convert values of other tables dependent from DAO::convertToDB
|
||||
if (Schema::hasTable('review_form_responses')) {
|
||||
DB::table('review_form_responses')->where('response_type', 'object')->get()->each(function ($row) {
|
||||
$this->_toJSON($row, 'review_form_responses', ['review_id'], 'response_value');
|
||||
});
|
||||
}
|
||||
|
||||
DB::table('site')->get()->each(function ($row) {
|
||||
$localeToConvert = function ($localeType) use ($row) {
|
||||
$serializedValue = $row->{$localeType};
|
||||
$oldLocaleValue = @unserialize($serializedValue);
|
||||
if ($oldLocaleValue === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($oldLocaleValue) && $this->_isNumerical($oldLocaleValue)) {
|
||||
$oldLocaleValue = array_values($oldLocaleValue);
|
||||
}
|
||||
|
||||
$newLocaleValue = json_encode($oldLocaleValue, JSON_UNESCAPED_UNICODE);
|
||||
DB::table('site')->take(1)->update([$localeType => $newLocaleValue]);
|
||||
};
|
||||
|
||||
$localeToConvert('installed_locales');
|
||||
$localeToConvert('supported_locales');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $row row representation
|
||||
* @param string $tableName name of a settings table
|
||||
* @param array $searchBy additional parameters to the where clause that should be combined with AND operator
|
||||
* @param string $valueToConvert column name for values to convert to JSON
|
||||
*/
|
||||
private function _toJSON($row, $tableName, $searchBy, $valueToConvert)
|
||||
{
|
||||
// Check if value can be unserialized
|
||||
$serializedOldValue = $row->{$valueToConvert};
|
||||
$oldValue = @unserialize($serializedOldValue);
|
||||
if ($oldValue === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset arrays to avoid keys being mixed up
|
||||
if (is_array($oldValue) && $this->_isNumerical($oldValue)) {
|
||||
$oldValue = array_values($oldValue);
|
||||
}
|
||||
$newValue = json_encode($oldValue, JSON_UNESCAPED_UNICODE); // don't convert utf-8 characters to unicode escaped code
|
||||
|
||||
$id = array_key_first((array)$row); // get first/primary key column
|
||||
|
||||
// Remove empty filters
|
||||
$searchBy = array_filter($searchBy, function ($item) use ($row) {
|
||||
if (empty($row->{$item})) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
$queryBuilder = DB::table($tableName)->where($id, $row->{$id});
|
||||
foreach ($searchBy as $key => $column) {
|
||||
$queryBuilder = $queryBuilder->where($column, $row->{$column});
|
||||
}
|
||||
$queryBuilder->update([$valueToConvert => $newValue]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array to check
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @brief checks unserialized array; returns true if array keys are integers
|
||||
* otherwise if keys are mixed and sequence starts from any positive integer it will be serialized as JSON object instead of an array
|
||||
* See pkp/pkp-lib#5690 for more details
|
||||
*/
|
||||
private function _isNumerical($array)
|
||||
{
|
||||
foreach ($array as $item => $value) {
|
||||
if (!is_integer($item)) {
|
||||
return false;
|
||||
} // is an associative array;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/FailedJobsMigration.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 FailedJobsMigration
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class FailedJobsMigration extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// Schema matches https://github.com/illuminate/queue/blob/7.x/Console/stubs/failed_jobs.stub
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I2890_AddSetNullForOnDeleteToReviewRoundIdForeign.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 I2890_AddSetNullForOnDeleteToReviewRoundIdForeign
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I2890_AddSetNullForOnDeleteToReviewRoundIdForeign extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->dropForeign(['review_round_id']);
|
||||
$table
|
||||
->foreign('review_round_id')
|
||||
->references('review_round_id')
|
||||
->on('review_rounds')
|
||||
->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->dropForeign(['review_round_id']);
|
||||
$table
|
||||
->foreign('review_round_id')
|
||||
->references('review_round_id')
|
||||
->on('review_rounds');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I2890_EmailTemplatesVarcharLengthUpdate.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 I2890_EmailTemplatesVarcharLengthUpdate
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I2890_EmailTemplatesVarcharLengthUpdate extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('email_templates', function (Blueprint $table) {
|
||||
$table->string('email_key', 255)->change();
|
||||
});
|
||||
|
||||
Schema::table('email_templates_default', function (Blueprint $table) {
|
||||
$table->string('email_key', 255)->change();
|
||||
});
|
||||
|
||||
Schema::table('email_templates_default_data', function (Blueprint $table) {
|
||||
$table->string('email_key', 255)->change();
|
||||
$table->string('subject', 255)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('email_templates', function (Blueprint $table) {
|
||||
$table->string('email_key', 64)->change();
|
||||
});
|
||||
|
||||
Schema::table('email_templates_default', function (Blueprint $table) {
|
||||
$table->string('email_key', 64)->change();
|
||||
});
|
||||
|
||||
Schema::table('email_templates_default_data', function (Blueprint $table) {
|
||||
$table->string('email_key', 64)->change();
|
||||
$table->string('subject', 120)->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I3573_AddPrimaryKeys.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I3573_AddPrimaryKeys
|
||||
*
|
||||
* @brief Add primary keys to tables that do not currently have them, to support DB replication.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
abstract class I3573_AddPrimaryKeys extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Rename tablename_pkey unique indexes to tablename_unique to avoid PostgreSQL collision
|
||||
foreach (static::getIndexData() as $tableName => [$oldIndexName, $columns, $newIndexName, $isUnique]) {
|
||||
// If the table does not exist, do not process it.
|
||||
if (!Schema::hasTable($tableName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DB::getDoctrineSchemaManager()->introspectTable($tableName)->hasIndex($oldIndexName)) {
|
||||
// Depending on whether the schema was created with ADODB or Laravel schema management, user_settings_pkey
|
||||
// will either be a constraint or an index. See https://github.com/pkp/pkp-lib/issues/7670.
|
||||
try {
|
||||
Schema::table($tableName, fn (Blueprint $table) => $table->dropUnique($oldIndexName));
|
||||
} catch (Exception $e) {
|
||||
Schema::table($tableName, fn (Blueprint $table) => $table->dropIndex($oldIndexName));
|
||||
}
|
||||
}
|
||||
|
||||
if ($isUnique) {
|
||||
Schema::table($tableName, fn (Blueprint $table) => $table->unique($columns, $newIndexName));
|
||||
} else {
|
||||
Schema::table($tableName, fn (Blueprint $table) => $table->index($columns, $newIndexName));
|
||||
}
|
||||
}
|
||||
|
||||
// Add the autoincrement columns
|
||||
foreach (static::getKeyNames() as $tableName => $keyName) {
|
||||
// If the table does not exist, do not process it.
|
||||
if (!Schema::hasTable($tableName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the table already has the named column, do not process it.
|
||||
if (Schema::hasColumn($tableName, $keyName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Schema::table($tableName, function (Blueprint $table) use ($keyName) {
|
||||
$table->bigIncrements($keyName)->first();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Remove the autoincrement columns
|
||||
foreach (static::getKeyNames() as $tableName => $keyName) {
|
||||
// If the table does not exist, do not process it.
|
||||
if (!Schema::hasTable($tableName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the table does not have the named column, do not process it.
|
||||
if (!Schema::hasColumn($tableName, $keyName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle special case: notification_subscription_settings already had setting_id prior to this migration. Do not drop it.
|
||||
if ($tableName === 'notification_subscription_settings') {
|
||||
continue;
|
||||
}
|
||||
|
||||
Schema::table($tableName, function (Blueprint $table) use ($keyName) {
|
||||
$table->unsignedInteger($keyName)->change(); // Drop auto increment
|
||||
$table->dropPrimary($keyName);
|
||||
$table->dropColumn($keyName);
|
||||
});
|
||||
}
|
||||
|
||||
// Rename tablename_unique indexes back to tablename_pkey
|
||||
foreach (static::getIndexData() as $tableName => [$oldIndexName, $columns, $newIndexName, $isUnique]) {
|
||||
// If the table does not exist, do not process it.
|
||||
if (!Schema::hasTable($tableName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Schema::table($tableName, function (Blueprint $table) use ($columns, $oldIndexName, $newIndexName, $isUnique) {
|
||||
if ($isUnique) {
|
||||
$table->dropUnique($newIndexName);
|
||||
$table->unique($columns, $oldIndexName);
|
||||
} else {
|
||||
$table->dropIndex($newIndexName);
|
||||
$table->index($columns, $oldIndexName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static function getKeyNames(): array
|
||||
{
|
||||
return [
|
||||
'site' => 'site_id',
|
||||
'versions' => 'version_id',
|
||||
'oai_resumption_tokens' => 'oai_resumption_token_id',
|
||||
'review_round_files' => 'review_round_file_id',
|
||||
'user_interests' => 'user_interest_id',
|
||||
'email_templates_default_data' => 'email_templates_default_data_id',
|
||||
'subeditor_submission_group' => 'subeditor_submission_group_id',
|
||||
'scheduled_tasks' => 'scheduled_task_id',
|
||||
'announcement_settings' => 'announcement_setting_id',
|
||||
'publication_categories' => 'publication_category_id',
|
||||
'user_user_groups' => 'user_user_group_id',
|
||||
'review_form_responses' => 'review_form_response_id',
|
||||
'query_participants' => 'query_participant_id',
|
||||
'user_group_stage' => 'user_group_stage_id',
|
||||
'email_log_users' => 'email_log_user_id',
|
||||
'review_files' => 'review_file_id',
|
||||
'submission_search_object_keywords' => 'submission_search_object_keyword_id',
|
||||
'announcement_type_settings' => 'announcement_type_setting_id',
|
||||
'author_settings' => 'author_setting_id',
|
||||
'category_settings' => 'category_setting_id',
|
||||
'citation_settings' => 'citation_setting_id',
|
||||
'controlled_vocab_entry_settings' => 'controlled_vocab_entry_setting_id',
|
||||
'data_object_tombstone_settings' => 'tombstone_setting_id',
|
||||
'doi_settings' => 'doi_setting_id', // Already created with schema during upgrade
|
||||
'email_templates_settings' => 'email_template_setting_id',
|
||||
'event_log_settings' => 'event_log_setting_id',
|
||||
'filter_settings' => 'filter_setting_id',
|
||||
'genre_settings' => 'genre_setting_id',
|
||||
'institution_settings' => 'institution_setting_id', // Already created with schema during upgrade
|
||||
'library_file_settings' => 'library_file_setting_id',
|
||||
'navigation_menu_item_assignment_settings' => 'navigation_menu_item_assignment_setting_id',
|
||||
'navigation_menu_item_settings' => 'navigation_menu_item_setting_id',
|
||||
'notification_settings' => 'notification_setting_id',
|
||||
'notification_subscription_settings' => 'setting_id', // Already had this ID before upgrade
|
||||
'plugin_settings' => 'plugin_setting_id',
|
||||
'publication_settings' => 'publication_setting_id',
|
||||
'review_form_element_settings' => 'review_form_element_setting_id',
|
||||
'review_form_settings' => 'review_form_setting_id',
|
||||
'submission_file_settings' => 'submission_file_setting_id',
|
||||
'submission_settings' => 'submission_setting_id',
|
||||
'user_group_settings' => 'user_group_setting_id',
|
||||
'user_settings' => 'user_setting_id',
|
||||
'site_settings' => 'site_setting_id',
|
||||
'static_page_settings' => 'static_page_setting_id', // PLUGIN
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of index data for tables that need their indexes rebuilt/renamed.
|
||||
*
|
||||
* @return array [table_name => [old_key_name, [list, of, columns], new_key_name, is_unique]
|
||||
*/
|
||||
public static function getIndexData(): array
|
||||
{
|
||||
return [
|
||||
'review_form_responses' => ['review_form_responses_pkey', ['review_form_element_id', 'review_id'], 'review_form_responses_unique', false],
|
||||
'review_round_files' => ['review_round_files_pkey', ['submission_id', 'review_round_id', 'submission_file_id'], 'review_round_files_unique', true],
|
||||
'review_files' => ['review_files_pkey', ['review_id', 'submission_file_id'], 'review_files_unique', true],
|
||||
'scheduled_tasks' => ['scheduled_tasks_pkey', ['class_name'], 'scheduled_tasks_unique', true],
|
||||
'announcement_type_settings' => ['announcement_type_settings_pkey', ['type_id', 'locale', 'setting_name'], 'announcement_type_settings_unique', true],
|
||||
'announcement_settings' => ['announcement_settings_pkey', ['announcement_id', 'locale', 'setting_name'], 'announcement_settings_unique', true],
|
||||
'category_settings' => ['category_settings_pkey', ['category_id', 'locale', 'setting_name'], 'category_settings_unique', true],
|
||||
'versions' => ['versions_pkey', ['product_type', 'product', 'major', 'minor', 'revision', 'build'], 'versions_unique', true],
|
||||
'site_settings' => ['site_settings_pkey', ['setting_name', 'locale'], 'site_settings_unique', true],
|
||||
'user_settings' => ['user_settings_pkey', ['user_id', 'locale', 'setting_name'], 'user_settings_unique', true],
|
||||
'notification_settings' => ['notification_settings_pkey', ['notification_id', 'locale', 'setting_name'], 'notification_settings_unique', true],
|
||||
'email_templates_default_data' => ['email_templates_default_data_pkey', ['email_key', 'locale'], 'email_templates_default_data_unique', true],
|
||||
'email_templates_settings' => ['email_settings_pkey', ['email_id', 'locale', 'setting_name'], 'email_templates_settings_unique', true],
|
||||
'oai_resumption_tokens' => ['oai_resumption_tokens_pkey', ['token'], 'oai_resumption_tokens_unique', true],
|
||||
'plugin_settings' => ['plugin_settings_pkey', ['plugin_name', 'context_id', 'setting_name'], 'plugin_settings_unique', true],
|
||||
'genre_settings' => ['genre_settings_pkey', ['genre_id', 'locale', 'setting_name'], 'genre_settings_unique', true],
|
||||
'library_file_settings' => ['library_file_settings_pkey', ['file_id', 'locale', 'setting_name'], 'library_file_settings_unique', true],
|
||||
'event_log_settings' => ['event_log_settings_pkey', ['log_id', 'setting_name'], 'event_log_settings_unique', true],
|
||||
'citation_settings' => ['citation_settings_pkey', ['citation_id', 'locale', 'setting_name'], 'citation_settings_unique', true],
|
||||
'filter_settings' => ['filter_settings_pkey', ['filter_id', 'locale', 'setting_name'], 'filter_settings_unique', true],
|
||||
'navigation_menu_item_settings' => ['navigation_menu_item_settings_pkey', ['navigation_menu_item_id', 'locale', 'setting_name'], 'navigation_menu_item_settings_unique', true],
|
||||
'navigation_menu_item_assignment_settings' => ['navigation_menu_item_assignment_settings_pkey', ['navigation_menu_item_assignment_id', 'locale', 'setting_name'], 'navigation_menu_item_assignment_settings_unique', true],
|
||||
'review_form_settings' => ['review_form_settings_pkey', ['review_form_id', 'locale', 'setting_name'], 'review_form_settings_unique', true],
|
||||
'review_form_element_settings' => ['review_form_element_settings_pkey', ['review_form_element_id', 'locale', 'setting_name'], 'review_form_element_settings_unique', true],
|
||||
'user_group_settings' => ['user_group_settings_pkey', ['user_group_id', 'locale', 'setting_name'], 'user_group_settings_unique', true],
|
||||
'user_user_groups' => ['user_user_groups_pkey', ['user_group_id', 'user_id'], 'user_user_groups_unique', true],
|
||||
'user_group_stage' => ['user_group_stage_pkey', ['context_id', 'user_group_id', 'stage_id'], 'user_group_stage_unique', true],
|
||||
'submission_file_settings' => ['submission_file_settings_pkey', ['submission_file_id', 'locale', 'setting_name'], 'submission_file_settings_unique', true],
|
||||
'submission_settings' => ['submission_settings_pkey', ['submission_id', 'locale', 'setting_name'], 'submission_settings_unique', true],
|
||||
'publication_settings' => ['publication_settings_pkey', ['publication_id', 'locale', 'setting_name'], 'publication_settings_unique', true],
|
||||
'author_settings' => ['author_settings_pkey', ['author_id', 'locale', 'setting_name'], 'author_settings_unique', true],
|
||||
'subeditor_submission_group' => ['section_editors_pkey', ['context_id', 'assoc_id', 'assoc_type', 'user_id'], 'section_editors_unique', true],
|
||||
'query_participants' => ['query_participants_pkey', ['query_id', 'user_id'], 'query_participants_unique', true],
|
||||
'submission_search_object_keywords' => ['submission_search_object_keywords_pkey', ['object_id', 'pos'], 'submission_search_object_keywords_unique', true],
|
||||
'data_object_tombstone_settings' => ['data_object_tombstone_settings_pkey', ['tombstone_id', 'locale', 'setting_name'], 'data_object_tombstone_settings_unique', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I4789_AddReviewerRequestResentColumns.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I4789_AddReviewerRequestResentColumns
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I4789_AddReviewerRequestResentColumns extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('review_assignments', function (Blueprint $table) {
|
||||
$table->smallInteger('request_resent')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('review_assignments', function (Blueprint $table) {
|
||||
$table->dropColumn('request_resent');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I5716_EmailTemplateAssignments.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I5716_EmailTemplateAssignments
|
||||
*
|
||||
* @brief Refactors relationship between Mailables and Email Templates
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use Exception;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\db\XMLDAO;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\migration\Migration;
|
||||
use stdClass;
|
||||
|
||||
abstract class I5716_EmailTemplateAssignments extends Migration
|
||||
{
|
||||
/** @var array $newEmailIds New emails created during the migration that should be removed on downgrade */
|
||||
public array $newEmailIds = [];
|
||||
|
||||
abstract protected function getContextTable(): string;
|
||||
abstract protected function getContextSettingsTable(): string;
|
||||
abstract protected function getContextIdColumn(): string;
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$contextIds = DB::table($this->getContextTable())
|
||||
->pluck($this->getContextIdColumn());
|
||||
|
||||
$this->moveDisabledEmailTemplateSettings($contextIds);
|
||||
$this->migrateNotificationCenterTemplates();
|
||||
|
||||
Schema::table('email_templates', function (Blueprint $table) {
|
||||
$table->dropColumn('enabled');
|
||||
$table->string('alternate_to', 255)->nullable();
|
||||
$table->index(['alternate_to'], 'email_templates_alternate_to');
|
||||
});
|
||||
|
||||
Schema::table('email_templates_default_data', function (Blueprint $table) {
|
||||
$table->string('name', 255)->nullable();
|
||||
});
|
||||
|
||||
$this->addDefaultTemplateNames();
|
||||
|
||||
Schema::table('email_templates_default_data', function (Blueprint $table) {
|
||||
$table->string('name', 255)->change();
|
||||
});
|
||||
|
||||
// The order below is important
|
||||
$this->assignIncludedAlternateTemplates($contextIds);
|
||||
$this->assignRemainingCustomTemplates($contextIds);
|
||||
$this->createAlternateTemplateNames($contextIds);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('email_templates', function (Blueprint $table) {
|
||||
$table->smallInteger('enabled')->default(1);
|
||||
$table->dropIndex('email_templates_alternate_to');
|
||||
$table->dropColumn('alternate_to');
|
||||
});
|
||||
|
||||
DB::table('email_templates_settings')
|
||||
->where('setting_name', 'name')
|
||||
->delete();
|
||||
|
||||
$this->downgradeEditorAssignTemplate();
|
||||
$this->downgradeAssignRemainingCustomTemplates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add context settings for disabled email templates
|
||||
*
|
||||
* This sets the `submissionAcknowledgement` and `editorialStatsEmail`
|
||||
* context settings based on whether the related email templates have
|
||||
* been disabled.
|
||||
*
|
||||
* From 3.4, emails will no longer be disabled at the email template
|
||||
* level.
|
||||
*/
|
||||
public function moveDisabledEmailTemplateSettings(Collection $contextIds): void
|
||||
{
|
||||
$contextIds->each(function (int $contextId) {
|
||||
if (
|
||||
DB::table('email_templates')
|
||||
->where('context_id', $contextId)
|
||||
->where('enabled', 0)
|
||||
->where('email_key', 'SUBMISSION_ACK')
|
||||
->exists()
|
||||
) {
|
||||
$submissionAckSetting = '';
|
||||
} elseif (
|
||||
DB::table('email_templates')
|
||||
->where('context_id', $contextId)
|
||||
->where('enabled', 0)
|
||||
->where('email_key', 'SUBMISSION_ACK_NOT_USER')
|
||||
->exists()
|
||||
) {
|
||||
$submissionAckSetting = 'submittingAuthor';
|
||||
} else {
|
||||
$submissionAckSetting = 'allAuthors';
|
||||
}
|
||||
|
||||
$statsReportSetting = !DB::table('email_templates')
|
||||
->where('context_id', $contextId)
|
||||
->where('enabled', 0)
|
||||
->where('email_key', 'STATISTICS_REPORT_NOTIFICATION')
|
||||
->exists();
|
||||
|
||||
DB::table($this->getContextSettingsTable())->insert([
|
||||
[
|
||||
$this->getContextIdColumn() => $contextId,
|
||||
'setting_name' => 'submissionAcknowledgement',
|
||||
'setting_value' => $submissionAckSetting,
|
||||
],
|
||||
[
|
||||
$this->getContextIdColumn() => $contextId,
|
||||
'setting_name' => 'editorialStatsEmail',
|
||||
'setting_value' => $statsReportSetting,
|
||||
],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates all custom NOTIFICATION_CENTER_DEFAULT templates
|
||||
*
|
||||
* This was the default template for the "Notify" feature
|
||||
* from the participants list in the workflow.
|
||||
*
|
||||
* We create a copy of the default template for each discussion
|
||||
* stage mailable. Then, if journals customized this template,
|
||||
* we update the keys for that custom template and make copies
|
||||
* of it for each stage mailable.
|
||||
*/
|
||||
protected function migrateNotificationCenterTemplates(): void
|
||||
{
|
||||
$alternateTos = $this->getDiscussionTemplates();
|
||||
|
||||
$newDefaultKeys = clone ($alternateTos);
|
||||
$newDefaultKey = $newDefaultKeys->shift();
|
||||
|
||||
DB::table('email_templates_default_data')
|
||||
->where('email_key', 'NOTIFICATION_CENTER_DEFAULT')
|
||||
->update(['email_key' => $newDefaultKey]);
|
||||
|
||||
$newDefaultKeys->each(function (string $key) use ($newDefaultKey) {
|
||||
DB::table('email_templates_default_data')
|
||||
->where('email_key', $newDefaultKey)
|
||||
->get()
|
||||
->each(function (stdClass $row) use ($key) {
|
||||
DB::table('email_templates_default_data')
|
||||
->insert([
|
||||
'email_key' => $key,
|
||||
'locale' => $row->locale,
|
||||
'subject' => $row->subject,
|
||||
'body' => $row->body,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
DB::table('email_templates')
|
||||
->where('email_key', 'NOTIFICATION_CENTER_DEFAULT')
|
||||
->get()
|
||||
->each(function (stdClass $row) use ($alternateTos) {
|
||||
$keys = clone $alternateTos;
|
||||
|
||||
DB::table('email_templates')
|
||||
->where('email_id', $row->email_id)
|
||||
->update(['email_key' => $keys->shift()]);
|
||||
|
||||
$settingsRows = DB::table('email_templates_settings')
|
||||
->where('email_id', $row->email_id)
|
||||
->get();
|
||||
|
||||
$keys->each(function (string $key) use ($row, $settingsRows) {
|
||||
DB::table('email_templates')
|
||||
->insert([
|
||||
'email_key' => $key,
|
||||
'context_id' => $row->context_id,
|
||||
]);
|
||||
$newEmailId = DB::getPdo()->lastInsertId();
|
||||
if ($settingsRows->count()) {
|
||||
DB::table('email_templates_settings')->insert(
|
||||
$settingsRows->map(fn (stdClass $settingsRow) => [
|
||||
'email_id' => $newEmailId,
|
||||
'locale' => $settingsRow->locale,
|
||||
'setting_name' => $settingsRow->setting_name,
|
||||
'setting_value' => $settingsRow->setting_value,
|
||||
])->toArray()
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds name values to the default email templates
|
||||
*
|
||||
* Localized names are defined in the emailTemplates.xml file.
|
||||
*
|
||||
* After setting the names, any default templates that don't have
|
||||
* a name will be given a name from the email key.
|
||||
*/
|
||||
protected function addDefaultTemplateNames(): void
|
||||
{
|
||||
$xmlDao = new XMLDAO();
|
||||
$data = $xmlDao->parseStruct('registry/emailTemplates.xml', ['email']);
|
||||
if (!isset($data['email'])) {
|
||||
throw new Exception('Failed to load or parse registry/emailTemplates.xml');
|
||||
}
|
||||
|
||||
$locales = json_decode(
|
||||
DB::table('site')
|
||||
->pluck('installed_locales')
|
||||
->first()
|
||||
);
|
||||
|
||||
$initialMissingLocaleKeyHandler = Locale::getMissingKeyHandler();
|
||||
Locale::setMissingKeyHandler(fn (string $key): string => '');
|
||||
|
||||
foreach ($locales as $locale) {
|
||||
Locale::setLocale($locale);
|
||||
|
||||
foreach ($data['email'] as $entry) {
|
||||
$key = $entry['attributes']['key'];
|
||||
$name = $entry['attributes']['name'];
|
||||
$name = __($name, [], $locale);
|
||||
DB::table('email_templates_default_data')
|
||||
->where('email_key', $key)
|
||||
->where('locale', $locale)
|
||||
->update(['name' => $name ? $name : $key]);
|
||||
}
|
||||
}
|
||||
|
||||
Locale::setMissingKeyHandler($initialMissingLocaleKeyHandler);
|
||||
|
||||
DB::table('email_templates_default_data')
|
||||
->whereNull('name')
|
||||
->get()
|
||||
->each(function (stdClass $row) {
|
||||
DB::table('email_templates_default_data')
|
||||
->where('email_key', $row->email_key)
|
||||
->where('locale', $row->locale)
|
||||
->update(['name' => $row->email_key]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates entries in email_templates for all of the alternate
|
||||
* templates that are included by default in the application
|
||||
*
|
||||
* These are the old email templates that would be available
|
||||
* in the "notify participant" feature.
|
||||
*
|
||||
* This migration will make them "alternate to" the appropriate
|
||||
* discussion mailables.
|
||||
*
|
||||
* It sets the alternate_to column and creates copies for
|
||||
* the discussion mailable in each stage.
|
||||
*/
|
||||
protected function assignIncludedAlternateTemplates(Collection $contextIds): void
|
||||
{
|
||||
$contextIds->each(function (int $contextId) {
|
||||
foreach ($this->mapIncludedAlternateTemplates() as $key => $alternateTo) {
|
||||
DB::table('email_templates')->updateOrInsert(
|
||||
[
|
||||
'email_key' => $key,
|
||||
'context_id' => $contextId,
|
||||
],
|
||||
[
|
||||
'alternate_to' => $alternateTo,
|
||||
]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$this->modifyEditorAssignTemplate($contextIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the new EDITOR_ASSIGN_<stage> templates
|
||||
*
|
||||
* Install the default templates. If a custom template has been
|
||||
* created for the EDITOR_ASSIGN template, copy this custom
|
||||
* template to the new EDITOR_ASSIGN_<stage> templates.
|
||||
*/
|
||||
protected function modifyEditorAssignTemplate(Collection $contextIds): void
|
||||
{
|
||||
$newTemplates = $this->mapEditorAssignTemplates();
|
||||
|
||||
$newTemplates->each(
|
||||
function (string $alternateTo, string $key) {
|
||||
Repo::emailTemplate()->dao->installEmailTemplates(
|
||||
Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(),
|
||||
[],
|
||||
$key
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$contextIds->each(function (int $contextId) use ($newTemplates) {
|
||||
$customTemplateId = DB::table('email_templates')
|
||||
->where('context_id', $contextId)
|
||||
->where('email_key', 'EDITOR_ASSIGN')
|
||||
->pluck('email_id')
|
||||
->first();
|
||||
|
||||
if (!$customTemplateId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = DB::table('email_templates_settings')
|
||||
->where('email_id', $customTemplateId)
|
||||
->get();
|
||||
|
||||
DB::table('email_templates')
|
||||
->where('context_id', $contextId)
|
||||
->whereIn('email_key', $newTemplates->keys())
|
||||
->pluck('email_id')
|
||||
->each(function (int $emailId) use ($rows) {
|
||||
DB::table('email_templates_settings')
|
||||
->insert(
|
||||
$rows->map(
|
||||
function (stdClass $row) use ($emailId) {
|
||||
return [
|
||||
'email_id' => $emailId,
|
||||
'locale' => $row->locale,
|
||||
'setting_name' => $row->setting_name,
|
||||
'setting_value' => $row->setting_value,
|
||||
];
|
||||
}
|
||||
)->toArray()
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the editor assign template to its original key
|
||||
*/
|
||||
protected function downgradeEditorAssignTemplate(): void
|
||||
{
|
||||
$emailIds = DB::table('email_templates')
|
||||
->where('email_key', 'EDITOR_ASSIGN_SUBMISSION')
|
||||
->orWhere('email_key', 'EDITOR_ASSIGN_REVIEW')
|
||||
->orWhere('email_key', 'EDITOR_ASSIGN_PRODUCTION')
|
||||
->pluck('email_id');
|
||||
|
||||
DB::table('email_templates')
|
||||
->whereIn('email_id', $emailIds->toArray())
|
||||
->delete();
|
||||
|
||||
DB::table('email_templates_settings')
|
||||
->whereIn('email_id', $emailIds->toArray())
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign all remaining unassigned custom templates to the
|
||||
* discussion stage mailables.
|
||||
*
|
||||
* Assigns the existing custom template to the first discussion
|
||||
* mailable that exists. Then creates copies of the template for
|
||||
* each of the remaining discussions.
|
||||
*/
|
||||
protected function assignRemainingCustomTemplates(Collection $contextIds): void
|
||||
{
|
||||
$contextIds->each(function (int $contextId) {
|
||||
DB::table('email_templates as et')
|
||||
->leftJoin('email_templates_default_data as etdd', 'et.email_key', '=', 'etdd.email_key')
|
||||
->where('et.context_id', $contextId)
|
||||
->whereNull('et.alternate_to')
|
||||
->whereNull('etdd.email_key')
|
||||
->get(['et.email_id', 'et.email_key'])
|
||||
->each(function (stdClass $row) use ($contextId) {
|
||||
$alternateTos = $this->getDiscussionTemplates();
|
||||
|
||||
DB::table('email_templates')
|
||||
->where('email_id', $row->email_id)
|
||||
->update(['alternate_to' => $alternateTos->shift()]);
|
||||
|
||||
if (!$alternateTos->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settingsRows = DB::table('email_templates_settings')
|
||||
->where('email_id', $row->email_id)
|
||||
->get();
|
||||
|
||||
$alternateTos->each(function (string $alternateTo) use ($row, $settingsRows, $contextId) {
|
||||
DB::table('email_templates')->insert([
|
||||
'email_key' => $row->email_key . '_' . $alternateTo,
|
||||
'context_id' => $contextId,
|
||||
'alternate_to' => $alternateTo,
|
||||
]);
|
||||
|
||||
$newEmailId = DB::getPdo()->lastInsertId();
|
||||
|
||||
$settingsRows->each(function (stdClass $settingsRow) use ($newEmailId) {
|
||||
DB::table('email_templates_settings')->insert([
|
||||
'email_id' => $newEmailId,
|
||||
'locale' => $settingsRow->locale,
|
||||
'setting_name' => $settingsRow->setting_name,
|
||||
'setting_value' => $settingsRow->setting_value,
|
||||
]);
|
||||
});
|
||||
|
||||
$this->newEmailIds[] = $newEmailId;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the extra copies of custom templates created
|
||||
* in self::assignRemainingCustomTemplates()
|
||||
*/
|
||||
protected function downgradeAssignRemainingCustomTemplates(): void
|
||||
{
|
||||
DB::table('email_templates')
|
||||
->whereIn('email_id', $this->newEmailIds)
|
||||
->delete();
|
||||
DB::table('email_templates_settings')
|
||||
->whereIn('email_id', $this->newEmailIds)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a name for all custom templates that are an alternate
|
||||
* to another template.
|
||||
*
|
||||
* Generates the name from the email key. Example:
|
||||
*
|
||||
* MY_EXAMPLE_KEY becomes MY EXAMPLE KEY
|
||||
*
|
||||
*/
|
||||
protected function createAlternateTemplateNames(Collection $contextIds): void
|
||||
{
|
||||
$primaryLocales = DB::table($this->getContextTable())
|
||||
->get([
|
||||
$this->getContextIdColumn() . ' as context_id',
|
||||
'primary_locale',
|
||||
]);
|
||||
|
||||
$contextIds->each(function (int $contextId) use ($primaryLocales) {
|
||||
$primaryLocale = $primaryLocales
|
||||
->first(fn ($row) => $row->context_id === $contextId)
|
||||
->primary_locale;
|
||||
|
||||
$nameRows = DB::table('email_templates')
|
||||
->where('context_id', $contextId)
|
||||
->whereNotNull('alternate_to')
|
||||
->get(['email_id', 'email_key'])
|
||||
->map(function ($row) use ($primaryLocale) {
|
||||
return [
|
||||
'email_id' => $row->email_id,
|
||||
'locale' => $primaryLocale,
|
||||
'setting_name' => 'name',
|
||||
'setting_value' => str_replace('_', ' ', $row->email_key),
|
||||
];
|
||||
});
|
||||
|
||||
DB::table('email_templates_settings')->insert($nameRows->toArray());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of the alternate templates to be reassigned
|
||||
*
|
||||
* @return [email_key => alternate_to]
|
||||
*/
|
||||
protected function mapIncludedAlternateTemplates(): array
|
||||
{
|
||||
return [
|
||||
'COPYEDIT_REQUEST' => 'DISCUSSION_NOTIFICATION_COPYEDITING',
|
||||
'LAYOUT_REQUEST' => 'DISCUSSION_NOTIFICATION_PRODUCTION',
|
||||
'LAYOUT_COMPLETE' => 'DISCUSSION_NOTIFICATION_PRODUCTION',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of the EDITOR_ASSIGN_<stage> templates
|
||||
*
|
||||
* @return [email_key => alternate_to]
|
||||
*/
|
||||
protected function mapEditorAssignTemplates(): Collection
|
||||
{
|
||||
return collect([
|
||||
'EDITOR_ASSIGN_SUBMISSION' => 'DISCUSSION_NOTIFICATION_SUBMISSION',
|
||||
'EDITOR_ASSIGN_REVIEW' => 'DISCUSSION_NOTIFICATION_REVIEW',
|
||||
'EDITOR_ASSIGN_PRODUCTION' => 'DISCUSSION_NOTIFICATION_PRODUCTION',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all discussion mailable temlate keys in this app
|
||||
*/
|
||||
abstract protected function getDiscussionTemplates(): Collection;
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I6093_AddForeignKeys.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 I6093_AddForeignKeys
|
||||
*
|
||||
* @brief Describe upgrade/downgrade operations for introducing foreign key definitions to existing database relationships.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
|
||||
abstract class I6093_AddForeignKeys extends \PKP\migration\Migration
|
||||
{
|
||||
abstract protected function getContextTable(): string;
|
||||
abstract protected function getContextSettingsTable(): string;
|
||||
abstract protected function getContextKeyField(): string;
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('announcement_types', function (Blueprint $table) {
|
||||
// Drop the old assoc_type column and assoc-based index
|
||||
$table->dropIndex('announcement_types_assoc');
|
||||
$table->dropColumn('assoc_type');
|
||||
|
||||
// Rename assoc_id to context_id and introduce foreign key constraint
|
||||
$table->renameColumn('assoc_id', 'context_id');
|
||||
$table->foreign('context_id')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
|
||||
// Introduce new index
|
||||
$table->index(['context_id'], 'announcement_types_context_id');
|
||||
});
|
||||
|
||||
Schema::table('announcement_type_settings', function (Blueprint $table) {
|
||||
$table->foreign('type_id')->references('type_id')->on('announcement_types')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('announcements', function (Blueprint $table) {
|
||||
$table->foreign('type_id')->references('type_id')->on('announcement_types')->onDelete('set null');
|
||||
$table->index(['type_id'], 'announcements_type_id');
|
||||
});
|
||||
|
||||
Schema::table('announcement_settings', function (Blueprint $table) {
|
||||
$table->foreign('announcement_id')->references('announcement_id')->on('announcements')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('category_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('setting_type');
|
||||
$table->foreign('category_id')->references('category_id')->on('categories')->onDelete('cascade');
|
||||
});
|
||||
// Permit nulls in categories.parent_id where previously 0 was used for "no parent"
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->bigInteger('parent_id')->nullable()->change();
|
||||
});
|
||||
DB::table('categories')->where('parent_id', '=', 0)->update(['parent_id' => null]);
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->dropIndex('category_context_id');
|
||||
$table->index(['context_id', 'parent_id'], 'category_context_parent_id');
|
||||
$table->foreign('context_id')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
$table->index(['context_id'], 'category_context_id');
|
||||
$table->foreign('parent_id')->references('category_id')->on('categories')->onDelete('set null');
|
||||
$table->index(['parent_id'], 'category_parent_id');
|
||||
});
|
||||
Schema::table('publication_categories', function (Blueprint $table) {
|
||||
$table->foreign('category_id')->references('category_id')->on('categories')->onDelete('cascade');
|
||||
$table->index(['category_id'], 'publication_categories_category_id');
|
||||
|
||||
$table->foreign('publication_id')->references('publication_id')->on('publications')->onDelete('cascade');
|
||||
$table->index(['publication_id'], 'publication_categories_publication_id');
|
||||
});
|
||||
Schema::table('genres', function (Blueprint $table) {
|
||||
$table->foreign('context_id')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
$table->index(['context_id'], 'genres_context_id');
|
||||
});
|
||||
Schema::table('genre_settings', function (Blueprint $table) {
|
||||
$table->foreign('genre_id')->references('genre_id')->on('genres')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('controlled_vocab_entries', function (Blueprint $table) {
|
||||
$table->foreign('controlled_vocab_id')->references('controlled_vocab_id')->on('controlled_vocabs')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('controlled_vocab_entry_settings', function (Blueprint $table) {
|
||||
$table->foreign('controlled_vocab_entry_id', 'c_v_e_s_entry_id')->references('controlled_vocab_entry_id')->on('controlled_vocab_entries')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('user_interests', function (Blueprint $table) {
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'user_interests_user_id');
|
||||
$table->foreign('controlled_vocab_entry_id')->references('controlled_vocab_entry_id')->on('controlled_vocab_entries')->onDelete('cascade');
|
||||
$table->index(['controlled_vocab_entry_id'], 'user_interests_controlled_vocab_entry_id');
|
||||
});
|
||||
Schema::table('user_settings', function (Blueprint $table) {
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('sessions', function (Blueprint $table) {
|
||||
$table->foreign('user_id', 'sessions_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('access_keys', function (Blueprint $table) {
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'access_keys_user_id');
|
||||
});
|
||||
Schema::table('notification_subscription_settings', function (Blueprint $table) {
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'notification_subscription_settings_user_id');
|
||||
$table->foreign('context')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
$table->index(['context'], 'notification_subscription_settings_context');
|
||||
});
|
||||
Schema::table('email_templates', function (Blueprint $table) {
|
||||
$table->foreign('context_id')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
$table->index(['context_id'], 'email_templates_context_id');
|
||||
});
|
||||
Schema::table('email_templates_settings', function (Blueprint $table) {
|
||||
$table->foreign('email_id', 'email_templates_settings_email_id')->references('email_id')->on('email_templates')->onDelete('cascade');
|
||||
$table->index(['email_id'], 'email_templates_settings_email_id');
|
||||
});
|
||||
|
||||
// Permit nullable submission_ids where previously 0 was used for context library
|
||||
Schema::table('library_files', function (Blueprint $table) {
|
||||
$table->bigInteger('submission_id')->nullable()->change();
|
||||
});
|
||||
DB::table('library_files')->where('submission_id', '=', 0)->update(['submission_id' => null]);
|
||||
Schema::table('library_files', function (Blueprint $table) {
|
||||
$table->foreign('context_id', 'library_files_context_id')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('library_file_settings', function (Blueprint $table) {
|
||||
$table->foreign('file_id')->references('file_id')->on('library_files')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('event_log', function (Blueprint $table) {
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'event_log_user_id');
|
||||
});
|
||||
Schema::table('event_log_settings', function (Blueprint $table) {
|
||||
$table->foreign('log_id', 'event_log_settings_log_id')->references('log_id')->on('event_log')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('email_log_users', function (Blueprint $table) {
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'email_log_users_user_id');
|
||||
|
||||
$table->foreign('email_log_id')->references('log_id')->on('email_log')->onDelete('cascade');
|
||||
$table->index(['email_log_id'], 'email_log_users_email_log_id');
|
||||
});
|
||||
Schema::table('citations', function (Blueprint $table) {
|
||||
$table->foreign('publication_id', 'citations_publication')->references('publication_id')->on('publications')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('citation_settings', function (Blueprint $table) {
|
||||
$table->foreign('citation_id', 'citation_settings_citation_id')->references('citation_id')->on('citations')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('filters', function (Blueprint $table) {
|
||||
$table->foreign('filter_group_id')->references('filter_group_id')->on('filter_groups')->onDelete('cascade');
|
||||
$table->index(['filter_group_id'], 'filters_filter_group_id');
|
||||
});
|
||||
Schema::table('filter_settings', function (Blueprint $table) {
|
||||
$table->foreign('filter_id')->references('filter_id')->on('filters')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('temporary_files', function (Blueprint $table) {
|
||||
$table->foreign('user_id', 'temporary_files_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('notes', function (Blueprint $table) {
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'notes_user_id');
|
||||
});
|
||||
Schema::table('navigation_menu_item_settings', function (Blueprint $table) {
|
||||
$table->foreign('navigation_menu_item_id', 'navigation_menu_item_settings_navigation_menu_id')->references('navigation_menu_item_id')->on('navigation_menu_items')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('navigation_menu_item_assignments', function (Blueprint $table) {
|
||||
$table->foreign('navigation_menu_id')->references('navigation_menu_id')->on('navigation_menus')->onDelete('cascade');
|
||||
$table->index(['navigation_menu_id'], 'navigation_menu_item_assignments_navigation_menu_id');
|
||||
$table->foreign('navigation_menu_item_id')->references('navigation_menu_item_id')->on('navigation_menu_items')->onDelete('cascade');
|
||||
$table->index(['navigation_menu_item_id'], 'navigation_menu_item_assignments_navigation_menu_item_id');
|
||||
});
|
||||
Schema::table('navigation_menu_item_assignment_settings', function (Blueprint $table) {
|
||||
$table->foreign('navigation_menu_item_assignment_id', 'assignment_settings_navigation_menu_item_assignment_id')->references('navigation_menu_item_assignment_id')->on('navigation_menu_item_assignments')->onDelete('cascade');
|
||||
});
|
||||
if (Schema::hasTable('review_form_elements')) {
|
||||
Schema::table('review_form_elements', function (Blueprint $table) {
|
||||
$table->foreign('review_form_id', 'review_form_elements_review_form_id')->references('review_form_id')->on('review_forms')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
if (Schema::hasTable('review_form_settings')) {
|
||||
Schema::table('review_form_settings', function (Blueprint $table) {
|
||||
$table->foreign('review_form_id', 'review_form_settings_review_form_id')->references('review_form_id')->on('review_forms')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
if (Schema::hasTable('review_form_element_settings')) {
|
||||
Schema::table('review_form_element_settings', function (Blueprint $table) {
|
||||
$table->foreign('review_form_element_id', 'review_form_element_settings_review_form_element_id')->references('review_form_element_id')->on('review_form_elements')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
if (Schema::hasTable('review_form_responses')) {
|
||||
Schema::table('review_form_responses', function (Blueprint $table) {
|
||||
$table->foreign('review_form_element_id')->references('review_form_element_id')->on('review_form_elements')->onDelete('cascade');
|
||||
$table->foreign('review_id')->references('review_id')->on('review_assignments')->onDelete('cascade');
|
||||
$table->index(['review_id'], 'review_form_responses_review_id');
|
||||
});
|
||||
}
|
||||
// The submissions_publication_id index was created for <3.3.0 but may be incorrect.
|
||||
$schemaManager = DB::getDoctrineSchemaManager();
|
||||
if (in_array('submissions_publication_id', array_keys($schemaManager->listTableIndexes('submissions')))) {
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
$table->dropIndex('submissions_publication_id');
|
||||
});
|
||||
}
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
$table->foreign('context_id', 'submissions_context_id')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
$table->foreign('current_publication_id', 'submissions_publication_id')->references('publication_id')->on('publications')->onDelete('set null');
|
||||
});
|
||||
Schema::table('submission_settings', function (Blueprint $table) {
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('publication_settings', function (Blueprint $table) {
|
||||
$table->foreign('publication_id')->references('publication_id')->on('publications')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('authors', function (Blueprint $table) {
|
||||
$table->foreign('publication_id')->references('publication_id')->on('publications')->onDelete('cascade');
|
||||
|
||||
$table->foreign('user_group_id')->references('user_group_id')->on('user_groups');
|
||||
$table->index(['user_group_id'], 'authors_user_group_id');
|
||||
});
|
||||
Schema::table('author_settings', function (Blueprint $table) {
|
||||
$table->foreign('author_id', 'author_settings_author_id')->references('author_id')->on('authors')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->foreign('editor_id', 'edit_decisions_editor_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->foreign('submission_id', 'edit_decisions_submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('submission_comments', function (Blueprint $table) {
|
||||
$table->foreign('submission_id', 'submission_comments_submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
|
||||
$table->foreign('author_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['author_id'], 'submission_comments_author_id');
|
||||
});
|
||||
Schema::table('subeditor_submission_group', function (Blueprint $table) {
|
||||
$table->foreign('context_id', 'section_editors_context_id')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
$table->foreign('user_id', 'subeditor_submission_group_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('submission_search_objects', function (Blueprint $table) {
|
||||
$table->foreign('submission_id', 'submission_search_object_submission')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('submission_search_object_keywords', function (Blueprint $table) {
|
||||
$table->foreign('object_id')->references('object_id')->on('submission_search_objects')->onDelete('cascade');
|
||||
$table->foreign('keyword_id', 'submission_search_object_keywords_keyword_id')->references('keyword_id')->on('submission_search_keyword_list')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('review_rounds', function (Blueprint $table) {
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('review_round_files', function (Blueprint $table) {
|
||||
$table->foreign('review_round_id')->references('review_round_id')->on('review_rounds')->onDelete('cascade');
|
||||
$table->index(['review_round_id'], 'review_round_files_review_round_id');
|
||||
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
$table->index(['submission_file_id'], 'review_round_files_submission_file_id');
|
||||
});
|
||||
Schema::table('user_user_groups', function (Blueprint $table) {
|
||||
$table->foreign('user_id', 'user_user_groups_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('user_group_stage', function (Blueprint $table) {
|
||||
$table->foreign('context_id', 'user_group_stage_context_id')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
});
|
||||
Schema::table('stage_assignments', function (Blueprint $table) {
|
||||
$table->foreign('user_group_id', 'stage_assignments_user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
$table->foreign('user_id', 'stage_assignments_user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->foreign('submission_id', 'submission_files_submission_id')->references('submission_id')->on('submissions')->onDelete('cascade');
|
||||
|
||||
$table->foreign('genre_id')->references('genre_id')->on('genres')->onDelete('set null');
|
||||
$table->index(['genre_id'], 'submission_files_genre_id');
|
||||
|
||||
$table->index(['file_id'], 'submission_files_file_id');
|
||||
|
||||
$table->foreign('uploader_user_id')->references('user_id')->on('users')->onDelete('set null');
|
||||
$table->index(['uploader_user_id'], 'submission_files_uploader_user_id');
|
||||
|
||||
$table->bigInteger('source_submission_file_id')->unsigned()->nullable()->change();
|
||||
$table->foreign('source_submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
$table->index(['source_submission_file_id'], 'submission_files_source_submission_file_id');
|
||||
});
|
||||
Schema::table('data_object_tombstone_settings', function (Blueprint $table) {
|
||||
$table->foreign('tombstone_id', 'data_object_tombstone_settings_tombstone_id')->references('tombstone_id')->on('data_object_tombstones')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('data_object_tombstone_oai_set_objects', function (Blueprint $table) {
|
||||
$table->foreign('tombstone_id', 'data_object_tombstone_oai_set_objects_tombstone_id')->references('tombstone_id')->on('data_object_tombstones')->onDelete('cascade');
|
||||
});
|
||||
Schema::table('submission_file_revisions', function (Blueprint $table) {
|
||||
$table->index(['submission_file_id'], 'submission_file_revisions_submission_file_id');
|
||||
$table->index(['file_id'], 'submission_file_revisions_file_id');
|
||||
});
|
||||
Schema::table($this->getContextSettingsTable(), function (Blueprint $table) {
|
||||
$table->foreign($this->getContextKeyField(), $this->getContextSettingsTable() . '_' . $this->getContextKeyField())->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
});
|
||||
Schema::table('review_files', function (Blueprint $table) {
|
||||
$table->foreign('review_id')->references('review_id')->on('review_assignments')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('notifications', function (Blueprint $table) {
|
||||
$table->bigInteger('context_id')->nullable()->change();
|
||||
$table->bigInteger('user_id')->nullable()->change();
|
||||
});
|
||||
DB::table('notifications')->where('context_id', '=', 0)->update(['context_id' => null]);
|
||||
DB::table('notifications')->where('user_id', '=', 0)->update(['user_id' => null]);
|
||||
Schema::table('notifications', function (Blueprint $table) {
|
||||
// Renaming indexes directly not supported until MariaDB 10.5.2
|
||||
$table->dropIndex('notifications_context_id');
|
||||
$table->index(['context_id', 'level'], 'notifications_context_id_level');
|
||||
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'notifications_user_id');
|
||||
|
||||
$table->foreign('context_id')->references($this->getContextKeyField())->on($this->getContextTable())->onDelete('cascade');
|
||||
$table->index(['context_id'], 'notifications_context_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I6241_RequiredGenres.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I6241_RequiredGenres
|
||||
*
|
||||
* @brief Add a required column to the `genres` table (file types)
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class I6241_RequiredGenres extends \PKP\migration\Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('genres', function (Blueprint $table) {
|
||||
$table->smallInteger('required')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('genres', function (Blueprint $table) {
|
||||
$table->dropColumn('required');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I6306_EnableCategories.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I6306_EnableCategories
|
||||
*
|
||||
* @brief Set the new context setting,`submitWithCategories`, to `true` for
|
||||
* existing journals to preserve the pre-existing behaviour.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
abstract class I6306_EnableCategories extends Migration
|
||||
{
|
||||
abstract protected function getContextTable(): string;
|
||||
abstract protected function getContextSettingsTable(): string;
|
||||
abstract protected function getContextIdColumn(): string;
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$contextIds = DB::table($this->getContextTable())
|
||||
->get([$this->getContextIdColumn()])
|
||||
->pluck($this->getContextIdColumn());
|
||||
|
||||
if (!$contextIds->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
DB::table($this->getContextSettingsTable())->insert(
|
||||
$contextIds->map(fn (int $id) => [
|
||||
$this->getContextIdColumn() => $id,
|
||||
'setting_name' => 'submitWithCategories',
|
||||
'setting_value' => '1',
|
||||
])
|
||||
->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table($this->getContextSettingsTable())
|
||||
->where('setting_name', 'submitWithCategories')
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I6782_CleanOldMetrics.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I6782_CleanOldMetrics
|
||||
*
|
||||
* @brief Clean the old metrics:
|
||||
* delete migrated entries with the given metric type from the DB table metrics,
|
||||
* move back the orphaned metrics from the temporary metrics_tmp,
|
||||
* rename or delete the DB table metrics,
|
||||
* delete DB table usage_stats_temporary_records.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
abstract class I6782_CleanOldMetrics extends Migration
|
||||
{
|
||||
abstract protected function getMetricType(): string;
|
||||
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Delete the entries with the metric type ojs::counter from the DB table metrics -> they were migrated in earlier scripts
|
||||
if (Schema::hasTable('metrics')) {
|
||||
DB::table('metrics')->where('metric_type', '=', $this->getMetricType())->delete();
|
||||
|
||||
// Move back the orphaned metrics form the temporary metrics_tmp
|
||||
$metricsColumns = Schema::getColumnListing('metrics_tmp');
|
||||
$metricsTmp = DB::table('metrics_tmp')->select($metricsColumns);
|
||||
DB::table('metrics')->insertUsing($metricsColumns, $metricsTmp);
|
||||
Schema::drop('metrics_tmp');
|
||||
|
||||
$metricsExist = DB::table('metrics')->count();
|
||||
// if table metrics is now not empty rename it, else delete it
|
||||
if ($metricsExist > 0) {
|
||||
Schema::rename('metrics', 'metrics_old');
|
||||
} else {
|
||||
Schema::drop('metrics');
|
||||
}
|
||||
}
|
||||
// Delete the old usage_stats_temporary_records table
|
||||
if (Schema::hasTable('usage_stats_temporary_records')) {
|
||||
Schema::drop('usage_stats_temporary_records');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I6782_MetricsContext.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I6782_MetricsContext
|
||||
*
|
||||
* @brief Migrate context stats data from the old DB table metrics into the new DB table metrics_context.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\config\Config;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
abstract class I6782_MetricsContext extends Migration
|
||||
{
|
||||
abstract protected function getMetricType(): string;
|
||||
abstract protected function getContextAssocType(): int;
|
||||
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$dayFormatSql = "DATE_FORMAT(STR_TO_DATE(m.day, '%Y%m%d'), '%Y-%m-%d')";
|
||||
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
|
||||
$dayFormatSql = "to_date(m.day, 'YYYYMMDD')";
|
||||
}
|
||||
|
||||
// The not existing foreign keys should already be moved to the metrics_tmp in I6782_OrphanedMetrics
|
||||
$selectContextMetrics = DB::table('metrics as m')
|
||||
->select(DB::raw("m.load_id, m.assoc_id, {$dayFormatSql}, m.metric"))
|
||||
->where('m.assoc_type', '=', $this->getContextAssocType())
|
||||
->where('m.metric_type', '=', $this->getMetricType());
|
||||
DB::table('metrics_context')->insertUsing(['load_id', 'context_id', 'date', 'metric'], $selectContextMetrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I6782_MetricsGeo.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I6782_MetricsGeo
|
||||
*
|
||||
* @brief Migrate submission stats Geo data from the old DB table metrics into the new DB table metrics_submission_geo_daily, then aggregate monthly.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\config\Config;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
abstract class I6782_MetricsGeo extends Migration
|
||||
{
|
||||
private const ASSOC_TYPE_SUBMISSION = 0x0100009;
|
||||
private const ASSOC_TYPE_SUBMISSION_FILE = 0x0000203;
|
||||
private const ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER = 0x0000213;
|
||||
|
||||
abstract protected function getMetricType(): string;
|
||||
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$dayFormatSql = "DATE_FORMAT(STR_TO_DATE(m.day, '%Y%m%d'), '%Y-%m-%d')";
|
||||
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
|
||||
$dayFormatSql = "to_date(m.day, 'YYYYMMDD')";
|
||||
}
|
||||
|
||||
// The not existing foreign keys should already be moved to the metrics_tmp in I6782_OrphanedMetrics
|
||||
|
||||
// Migrate Geo metrics -- no matter if the Geo usage stats are currently enabled
|
||||
// fix wrong entries in the DB table metrics
|
||||
// do all this first in order for groupBy to function properly
|
||||
DB::table('metrics')->where('city', '')->update(['city' => null]);
|
||||
DB::table('metrics')->where('region', '')->orWhere('region', '0')->update(['region' => null]);
|
||||
DB::table('metrics')->where('country_id', '')->update(['country_id' => null]);
|
||||
// in the GeoIP Legacy databases, several country codes were included that don't represent countries
|
||||
DB::table('metrics')->whereIn('country_id', ['AP', 'EU', 'A1', 'A2'])->update(['country_id' => null, 'region' => null, 'city' => null]);
|
||||
// some regions are missing the leading '0'
|
||||
DB::table('metrics')->update(['region' => DB::raw("LPAD(region, 2, '0')")]);
|
||||
|
||||
// Insert into metrics_submission_geo_daily table
|
||||
// metric = total views = abstracts + galley and supp files
|
||||
$selectGeoMetrics = DB::table('metrics as m')
|
||||
->select(DB::raw("m.load_id, m.context_id, m.submission_id, COALESCE(m.country_id, ''), COALESCE(m.region, ''), COALESCE(m.city, ''), {$dayFormatSql} as mday, SUM(m.metric), 0"))
|
||||
->whereIn('m.assoc_type', [self::ASSOC_TYPE_SUBMISSION, self::ASSOC_TYPE_SUBMISSION_FILE, self::ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER])
|
||||
->where('m.metric_type', '=', $this->getMetricType())
|
||||
->where(function ($q) {
|
||||
$q->whereNotNull('m.country_id')
|
||||
->orWhereNotNull('m.region')
|
||||
->orWhereNotNull('m.city');
|
||||
})
|
||||
->groupBy(DB::raw('m.load_id, m.context_id, m.submission_id, m.country_id, m.region, m.city, mday'));
|
||||
DB::table('metrics_submission_geo_daily')->insertUsing(['load_id', 'context_id', 'submission_id', 'country', 'region', 'city', 'date', 'metric', 'metric_unique'], $selectGeoMetrics);
|
||||
|
||||
// Migrate to metrics_submission_geo_monthly table
|
||||
$monthFormatSql = "CAST(DATE_FORMAT(gd.date, '%Y%m') AS UNSIGNED)";
|
||||
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
|
||||
$monthFormatSql = "to_char(gd.date, 'YYYYMM')::integer";
|
||||
}
|
||||
// use the table metrics_submission_geo_daily instead of table metrics to calculate the monthly numbers
|
||||
$selectSubmissionGeoDaily = DB::table('metrics_submission_geo_daily as gd')
|
||||
->select(DB::raw("gd.context_id, gd.submission_id, COALESCE(gd.country, ''), COALESCE(gd.region, ''), COALESCE(gd.city, ''), {$monthFormatSql} as gdmonth, SUM(gd.metric), SUM(gd.metric_unique)"))
|
||||
->groupBy(DB::raw('gd.context_id, gd.submission_id, gd.country, gd.region, gd.city, gdmonth'));
|
||||
DB::table('metrics_submission_geo_monthly')->insertUsing(['context_id', 'submission_id', 'country', 'region', 'city', 'month', 'metric', 'metric_unique'], $selectSubmissionGeoDaily);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I6782_OrphanedMetrics.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I6782_OrphanedMetrics
|
||||
*
|
||||
* @brief Migrate metrics data from objects that do not exist any more and from assoc types that are not considered in the upgrade into the temporary table.
|
||||
* These entries will be copied back and stay in the table metrics_old, s. I6782_CleanOldMetrics.
|
||||
* Consider only metric_type ojs/ops/omp::counter here, because these entries will be removed during the upgrade.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
abstract class I6782_OrphanedMetrics extends Migration
|
||||
{
|
||||
private const ASSOC_TYPE_SUBMISSION = 0x0100009;
|
||||
private const ASSOC_TYPE_SUBMISSION_FILE = 0x0000203;
|
||||
private const ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER = 0x0000213;
|
||||
|
||||
abstract protected function getMetricType(): string;
|
||||
abstract protected function getContextAssocType(): int;
|
||||
abstract protected function getContextTable(): string;
|
||||
abstract protected function getContextKeyField(): string;
|
||||
abstract protected function getRepresentationTable(): string;
|
||||
abstract protected function getRepresentationKeyField(): string;
|
||||
|
||||
/**
|
||||
* Get assoc types that will be considered in the upgrade
|
||||
*/
|
||||
protected function getAssocTypesToMigrate(): array
|
||||
{
|
||||
return [
|
||||
self::ASSOC_TYPE_SUBMISSION,
|
||||
self::ASSOC_TYPE_SUBMISSION_FILE,
|
||||
self::ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migration.
|
||||
*
|
||||
* assoc_object_type, assoc_object_id, and pkp_section_id will not be considered here, because they are not relevant for the upgrade
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->createMetricsTmpTable();
|
||||
|
||||
$metricsColumns = Schema::getColumnListing('metrics_tmp');
|
||||
|
||||
// Clean orphaned metrics context IDs. These IDs can be deleted.
|
||||
// as context_id
|
||||
$orphanedIds = DB::table('metrics AS m')->leftJoin($this->getContextTable() . ' AS c', 'm.context_id', '=', 'c.' . $this->getContextKeyField())->where('m.metric_type', '=', $this->getMetricType())->whereNull('c.' . $this->getContextKeyField())->distinct()->pluck('m.context_id');
|
||||
foreach ($orphanedIds as $contextId) {
|
||||
$this->_installer->log("Removing stats for context {$contextId} because no context with that ID could be found.");
|
||||
DB::table('metrics')->where('context_id', '=', $contextId)->delete();
|
||||
}
|
||||
// as assoc_id
|
||||
$orphanedIds = DB::table('metrics AS m')->leftJoin($this->getContextTable() . ' AS c', 'm.assoc_id', '=', 'c.' . $this->getContextKeyField())->where('m.assoc_type', '=', $this->getContextAssocType())->where('m.metric_type', '=', $this->getMetricType())->whereNull('c.' . $this->getContextKeyField())->distinct()->pluck('m.assoc_id');
|
||||
foreach ($orphanedIds as $contextId) {
|
||||
$this->_installer->log("Removing stats for context {$contextId} because no context with that ID could be found.");
|
||||
DB::table('metrics')->where('assoc_type', '=', $this->getContextAssocType())->where('assoc_id', '=', $contextId)->delete();
|
||||
}
|
||||
|
||||
// Clean orphaned metrics submission IDs
|
||||
// as submission_id
|
||||
$orphanedIds = DB::table('metrics AS m')->leftJoin('submissions AS s', 'm.submission_id', '=', 's.submission_id')->whereNotNull('m.submission_id')->whereNull('s.submission_id')->distinct()->pluck('m.submission_id');
|
||||
$orphandedSubmissions = DB::table('metrics')->select($metricsColumns)->whereIn('submission_id', $orphanedIds)->where('metric_type', '=', $this->getMetricType());
|
||||
DB::table('metrics_tmp')->insertUsing($metricsColumns, $orphandedSubmissions);
|
||||
DB::table('metrics')->whereIn('submission_id', $orphanedIds)->delete();
|
||||
|
||||
// as assoc_id
|
||||
$orphanedIds = DB::table('metrics AS m')->leftJoin('submissions AS s', 'm.assoc_id', '=', 's.submission_id')->where('m.assoc_type', '=', self::ASSOC_TYPE_SUBMISSION)->whereNull('s.submission_id')->distinct()->pluck('m.assoc_id');
|
||||
$orphandedSubmissionsAssocId = DB::table('metrics')->select($metricsColumns)->where('assoc_type', '=', self::ASSOC_TYPE_SUBMISSION)->whereIn('assoc_id', $orphanedIds)->where('metric_type', '=', $this->getMetricType());
|
||||
DB::table('metrics_tmp')->insertUsing($metricsColumns, $orphandedSubmissionsAssocId);
|
||||
DB::table('metrics')->where('assoc_type', '=', self::ASSOC_TYPE_SUBMISSION)->whereIn('assoc_id', $orphanedIds)->delete();
|
||||
|
||||
// Clean orphaned metrics submission file IDs
|
||||
$orphanedIds = DB::table('metrics AS m')->leftJoin('submission_files AS sf', 'm.assoc_id', '=', 'sf.submission_file_id')->where('m.assoc_type', '=', self::ASSOC_TYPE_SUBMISSION_FILE)->whereNull('sf.submission_file_id')->distinct()->pluck('m.assoc_id');
|
||||
$orphandedSubmissionFiles = DB::table('metrics')->select($metricsColumns)->where('assoc_type', '=', self::ASSOC_TYPE_SUBMISSION_FILE)->whereIn('assoc_id', $orphanedIds)->where('metric_type', '=', $this->getMetricType());
|
||||
DB::table('metrics_tmp')->insertUsing($metricsColumns, $orphandedSubmissionFiles);
|
||||
DB::table('metrics')->where('assoc_type', '=', self::ASSOC_TYPE_SUBMISSION_FILE)->whereIn('assoc_id', $orphanedIds)->delete();
|
||||
|
||||
// Clean orphaned metrics submission supp file IDs
|
||||
$orphanedIds = DB::table('metrics AS m')->leftJoin('submission_files AS sf', 'm.assoc_id', '=', 'sf.submission_file_id')->where('m.assoc_type', '=', self::ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER)->whereNull('sf.submission_file_id')->distinct()->pluck('m.assoc_id');
|
||||
$orphandedSubmissionSuppFiles = DB::table('metrics')->select($metricsColumns)->where('assoc_type', '=', self::ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER)->whereIn('assoc_id', $orphanedIds)->where('metric_type', '=', $this->getMetricType());
|
||||
DB::table('metrics_tmp')->insertUsing($metricsColumns, $orphandedSubmissionSuppFiles);
|
||||
DB::table('metrics')->where('assoc_type', '=', self::ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER)->whereIn('assoc_id', $orphanedIds)->delete();
|
||||
|
||||
// Clean orphaned metrics representation IDs
|
||||
$orphanedIds = DB::table('metrics AS m')->leftJoin($this->getRepresentationTable() . ' AS r', 'm.representation_id', '=', 'r.' . $this->getRepresentationKeyField())->whereNotNull('m.representation_id')->whereNull('r.' . $this->getRepresentationKeyField())->distinct()->pluck('m.representation_id');
|
||||
$orphandedRepresentations = DB::table('metrics')->select($metricsColumns)->whereIn('representation_id', $orphanedIds)->where('metric_type', '=', $this->getMetricType());
|
||||
DB::table('metrics_tmp')->insertUsing($metricsColumns, $orphandedRepresentations);
|
||||
DB::table('metrics')->whereIn('representation_id', $orphanedIds)->delete();
|
||||
|
||||
// Copy assoc types that will not be migrated to the metrics_tmp table
|
||||
$orphanedAssocTypes = DB::table('metrics AS m')->select($metricsColumns)->whereNotIn('m.assoc_type', $this->getAssocTypesToMigrate())->where('metric_type', '=', $this->getMetricType());
|
||||
DB::table('metrics_tmp')->insertUsing($metricsColumns, $orphanedAssocTypes);
|
||||
}
|
||||
|
||||
public function createMetricsTmpTable()
|
||||
{
|
||||
Schema::create('metrics_tmp', function (Blueprint $table) {
|
||||
$table->string('load_id', 255);
|
||||
$table->bigInteger('context_id');
|
||||
$table->bigInteger('pkp_section_id')->nullable();
|
||||
$table->bigInteger('assoc_object_type')->nullable();
|
||||
$table->bigInteger('assoc_object_id')->nullable();
|
||||
$table->bigInteger('submission_id')->nullable();
|
||||
$table->bigInteger('representation_id')->nullable();
|
||||
$table->bigInteger('assoc_type');
|
||||
$table->bigInteger('assoc_id');
|
||||
$table->string('day', 8)->nullable();
|
||||
$table->string('month', 6)->nullable();
|
||||
$table->smallInteger('file_type')->nullable();
|
||||
$table->string('country_id', 2)->nullable();
|
||||
$table->string('region', 2)->nullable();
|
||||
$table->string('city', 255)->nullable();
|
||||
$table->string('metric_type', 255);
|
||||
$table->integer('metric');
|
||||
$table->index(['load_id'], 'metrics_tmp_load_id');
|
||||
$table->index(['metric_type', 'context_id'], 'metrics_tmp_metric_type_context_id');
|
||||
$table->index(['metric_type', 'submission_id', 'assoc_type'], 'metrics_tmp_metric_type_submission_id_assoc_type');
|
||||
$table->index(['metric_type', 'context_id', 'assoc_type', 'assoc_id'], 'metrics_tmp_metric_type_submission_id_assoc');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I6782_UsageStatsSettings.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I6782_UsageStatsSettings
|
||||
*
|
||||
* @brief Migrate usage stats settings.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use APP\core\Services;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
use PKP\plugins\PluginRegistry;
|
||||
|
||||
class I6782_UsageStatsSettings extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Read old usage stats settings
|
||||
// Geo data stats settings
|
||||
$optionalColumns = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'usagestatsplugin')
|
||||
->where('setting_name', '=', 'optionalColumns')
|
||||
->value('setting_value');
|
||||
|
||||
$enableGeoUsageStats = 'disabled';
|
||||
$keepDailyUsageStats = false;
|
||||
if (!is_null($optionalColumns)) {
|
||||
$keepDailyUsageStats = true;
|
||||
if (str_contains($optionalColumns, 'city')) {
|
||||
$enableGeoUsageStats = 'country+region+city';
|
||||
} elseif (str_contains($optionalColumns, 'region')) {
|
||||
$enableGeoUsageStats = 'country+region';
|
||||
} else {
|
||||
$enableGeoUsageStats = 'country';
|
||||
}
|
||||
}
|
||||
// Compress archives settings
|
||||
$compressArchives = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'usagestatsplugin')
|
||||
->where('setting_name', '=', 'compressArchives')
|
||||
->value('setting_value');
|
||||
$compressStatsLogs = !is_null($compressArchives) ? $compressArchives : false;
|
||||
// Other, default site settings
|
||||
$enableInstitutionUsageStats = $isSiteSushiPlatform = false;
|
||||
$isSushiApiPublic = true;
|
||||
// Migrate site settings
|
||||
DB::table('site_settings')->insertOrIgnore([
|
||||
['setting_name' => 'compressStatsLogs', 'setting_value' => $compressStatsLogs],
|
||||
['setting_name' => 'enableGeoUsageStats', 'setting_value' => $enableGeoUsageStats],
|
||||
['setting_name' => 'keepDailyUsageStats', 'setting_value' => $keepDailyUsageStats],
|
||||
['setting_name' => 'enableInstitutionUsageStats', 'setting_value' => $enableInstitutionUsageStats],
|
||||
['setting_name' => 'isSushiApiPublic', 'setting_value' => $isSushiApiPublic],
|
||||
['setting_name' => 'isSiteSushiPlatform', 'setting_value' => $isSiteSushiPlatform]
|
||||
]);
|
||||
|
||||
// Display site settings
|
||||
$displayStatistics = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'usagestatsplugin')
|
||||
->where('setting_name', '=', 'displayStatistics')
|
||||
->where('context_id', '=', 0)
|
||||
->value('setting_value');
|
||||
$chartType = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'usagestatsplugin')
|
||||
->where('setting_name', '=', 'chartType')
|
||||
->where('context_id', '=', 0)
|
||||
->value('setting_value');
|
||||
// Migrate usage stats site display settings to the active site theme
|
||||
$siteThemePlugins = PluginRegistry::getPlugins('themes');
|
||||
$activeSiteTheme = null;
|
||||
foreach ($siteThemePlugins as $siteThemePlugin) {
|
||||
if ($siteThemePlugin->isActive()) {
|
||||
$activeSiteTheme = $siteThemePlugin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isset($activeSiteTheme)) {
|
||||
$siteUsageStatsDisplay = !$displayStatistics ? 'none' : $chartType;
|
||||
DB::table('plugin_settings')->insertOrIgnore([
|
||||
['plugin_name' => $activeSiteTheme->getName(), 'context_id' => 0, 'setting_name' => 'displayStats', 'setting_value' => $siteUsageStatsDisplay, 'setting_type' => 'string'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Migrate context settings
|
||||
// Get all, also disabled, contexts
|
||||
$contextIds = Services::get('context')->getIds();
|
||||
foreach ($contextIds as $contextId) {
|
||||
$contextDisplayStatistics = $contextChartType = null;
|
||||
$contextDisplayStatistics = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'usagestatsplugin')
|
||||
->where('setting_name', '=', 'displayStatistics')
|
||||
->where('context_id', '=', $contextId)
|
||||
->value('setting_value');
|
||||
$contextChartType = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'usagestatsplugin')
|
||||
->where('setting_name', '=', 'chartType')
|
||||
->where('context_id', '=', $contextId)
|
||||
->value('setting_value');
|
||||
// Migrate usage stats display settings to the active context theme
|
||||
$contextThemePlugins = PluginRegistry::loadCategory('themes', true, $contextId);
|
||||
$activeContextTheme = null;
|
||||
foreach ($contextThemePlugins as $contextThemePlugin) {
|
||||
if ($contextThemePlugin->isActive()) {
|
||||
$activeContextTheme = $contextThemePlugin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isset($activeContextTheme)) {
|
||||
$contextUsageStatsDisplay = !$contextDisplayStatistics ? 'none' : $contextChartType;
|
||||
DB::table('plugin_settings')->insertOrIgnore([
|
||||
['plugin_name' => $activeContextTheme->getName(), 'context_id' => $contextId, 'setting_name' => 'displayStats', 'setting_value' => $contextUsageStatsDisplay, 'setting_type' => 'string'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I6895_CreateNewInstitutionsTables.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I6895_CreateNewInstitutionsTables
|
||||
*
|
||||
* @brief Describe database table structures.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use APP\core\Application;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema as Schema;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I6895_CreateNewInstitutionsTables extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Institutions.
|
||||
Schema::create('institutions', function (Blueprint $table) {
|
||||
$table->bigInteger('institution_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$contextDao = Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'institutions_context_id');
|
||||
|
||||
$table->string('ror', 255)->nullable();
|
||||
$table->softDeletes('deleted_at', 0);
|
||||
});
|
||||
|
||||
// Locale-specific institution data
|
||||
Schema::create('institution_settings', function (Blueprint $table) {
|
||||
$table->bigIncrements('institution_setting_id');
|
||||
$table->bigInteger('institution_id');
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
$table->foreign('institution_id')->references('institution_id')->on('institutions')->onDelete('cascade');
|
||||
$table->index(['institution_id'], 'institution_settings_institution_id');
|
||||
$table->unique(['institution_id', 'locale', 'setting_name'], 'institution_settings_unique');
|
||||
});
|
||||
|
||||
// Institution IPs and IP ranges.
|
||||
Schema::create('institution_ip', function (Blueprint $table) {
|
||||
$table->bigInteger('institution_ip_id')->autoIncrement();
|
||||
$table->bigInteger('institution_id');
|
||||
$table->string('ip_string', 40);
|
||||
$table->bigInteger('ip_start');
|
||||
$table->bigInteger('ip_end')->nullable();
|
||||
$table->foreign('institution_id')->references('institution_id')->on('institutions')->onDelete('cascade');
|
||||
$table->index(['institution_id'], 'institution_ip_institution_id');
|
||||
$table->index(['ip_start'], 'institution_ip_start');
|
||||
$table->index(['ip_end'], 'institution_ip_end');
|
||||
});
|
||||
|
||||
if (Schema::hasTable('institutional_subscriptions') && Schema::hasColumn('institutional_subscriptions', 'institution_id')) {
|
||||
Schema::table('institutional_subscriptions', function (Blueprint $table) {
|
||||
$table->foreign('institution_id')->references('institution_id')->on('institutions');
|
||||
$table->index(['institution_id'], 'institutional_subscriptions_institution_ip');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7126_Galleys.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7126_Galleys
|
||||
*
|
||||
* @brief Update the galley class filters in the database
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use APP\core\Application;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class I7126_Galleys extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('filter_groups')
|
||||
->where('input_type', 'class::classes.article.ArticleGalley')
|
||||
->orWhere('input_type', 'class::classes.preprint.PreprintGalley')
|
||||
->update(['input_type' => 'class::lib.pkp.classes.galley.Galley']);
|
||||
DB::table('filter_groups')
|
||||
->where('output_type', 'class::classes.article.ArticleGalley')
|
||||
->orWhere('output_type', 'class::classes.preprint.PreprintGalley')
|
||||
->update(['output_type' => 'class::lib.pkp.classes.galley.Galley']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the upgrades
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
if (Application::get()->getName() === 'ojs2') {
|
||||
$class = 'class::classes.article.ArticleGalley';
|
||||
} elseif (Application::get()->getName() === 'ops') {
|
||||
$class = 'class::classes.preprint.PreprintGalley';
|
||||
} else {
|
||||
// Nothing to revert
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('filter_groups')
|
||||
->where('input_type', 'class::lib.pkp.classes.galley.Galley')
|
||||
->update(['input_type' => $class]);
|
||||
DB::table('filter_groups')
|
||||
->where('output_type', 'class::lib.pkp.classes.galley.Galley')
|
||||
->update(['input_type' => $class]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7190_UpdateFilters.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 I7190_UpdateFilters
|
||||
*
|
||||
* @brief Ensures filters based on the NativeImportFilter expect an array as output.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I7190_UpdateFilters extends Migration
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
DB::update(
|
||||
"UPDATE filter_groups
|
||||
SET output_type = CONCAT(output_type, '[]')
|
||||
WHERE
|
||||
symbolic LIKE 'native-xml=>%'
|
||||
AND output_type LIKE 'class::%'
|
||||
AND output_type NOT LIKE '%[]'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException('Downgrade unsupported due to updated data');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7191_EditorAssignments.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 I7191_EditorAssignments
|
||||
*
|
||||
* @brief Update the subeditor_submission_group table to accomodate new editor assignment settings
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
|
||||
abstract class I7191_EditorAssignments extends \PKP\migration\Migration
|
||||
{
|
||||
protected string $sectionDb;
|
||||
protected string $sectionIdColumn;
|
||||
protected string $contextColumn;
|
||||
|
||||
/**
|
||||
* Adds a user_group_id column to the subeditor_submission_group
|
||||
* table and adds initial data. Adds foreign keys where appropriate.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (empty($this->sectionDb) || empty($this->sectionIdColumn) || empty($this->contextColumn)) {
|
||||
throw new Exception('Upgrade could not be completed because required properties for the I7191_EditorAssignments migration are undefined.');
|
||||
}
|
||||
|
||||
Schema::table('subeditor_submission_group', function (Blueprint $table) {
|
||||
$table->bigInteger('user_group_id')->nullable();
|
||||
});
|
||||
|
||||
$this->setUserGroup();
|
||||
$this->deleteOrphanedAssignments();
|
||||
$this->addAutomatedAssignments();
|
||||
|
||||
Schema::table('subeditor_submission_group', function (Blueprint $table) {
|
||||
$table->bigInteger('user_group_id')->nullable(false)->change();
|
||||
$table->foreign('user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
$table->index(['user_group_id'], 'subeditor_submission_group_user_group_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('subeditor_submission_group', function (Blueprint $table) {
|
||||
$table->dropForeign('subeditor_submission_group_user_group_id_foreign');
|
||||
$table->dropColumn('user_group_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user group for all existing assignments
|
||||
*
|
||||
* Uses the default user group for Role::ROLE_ID_SUB_EDITOR
|
||||
* if one exists
|
||||
*/
|
||||
protected function setUserGroup(): void
|
||||
{
|
||||
$row = DB::table('user_groups')
|
||||
->where('role_id', '=', 17) // Role::ROLE_ID_SUB_EDITOR
|
||||
->orderBy('is_default')
|
||||
->get(['user_group_id', 'context_id'])
|
||||
->first();
|
||||
|
||||
if ($row) {
|
||||
DB::table('subeditor_submission_group')
|
||||
->whereNull('user_group_id')
|
||||
->where('context_id', '=', $row->context_id)
|
||||
->update(['user_group_id' => $row->user_group_id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete any orphaned records
|
||||
*
|
||||
* 1. Editorial assignments without a user group. They wouldn't have been
|
||||
* assigned anyway so the cleanup should not impact existing workflows.
|
||||
* 2. Editorial assignments without a matching user record.
|
||||
*/
|
||||
protected function deleteOrphanedAssignments(): void
|
||||
{
|
||||
DB::table('subeditor_submission_group')
|
||||
->whereNull('user_group_id')
|
||||
->delete();
|
||||
|
||||
DB::table('subeditor_submission_group as ssg')
|
||||
->leftJoin('users as u', 'u.user_id', '=', 'ssg.user_id')
|
||||
->whereNull('u.user_id')
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add assignment records for any editors who would have
|
||||
* been automatically assigned in previous versions.
|
||||
*
|
||||
* When a context had only one user in a user group with
|
||||
* the following roles, that user would automatically be
|
||||
* assigned to every submission.
|
||||
*
|
||||
* Role::ROLE_ID_MANAGER, Role::ROLE_ID_ASSISTANT
|
||||
*
|
||||
* This migration assigns such users to every section in
|
||||
* the context
|
||||
*/
|
||||
protected function addAutomatedAssignments(): void
|
||||
{
|
||||
$userGroups = DB::table('user_groups')
|
||||
->whereIn('role_id', [16, 4097]) // [Role::ROLE_ID_MANAGER, Role::ROLE_ID_ASSISTANT]
|
||||
->get(['user_group_id', 'context_id']);
|
||||
|
||||
$userGroups->each(function ($userGroup) {
|
||||
$userIds = DB::table('user_user_groups')
|
||||
->where('user_group_id', '=', $userGroup->user_group_id)
|
||||
->pluck('user_id');
|
||||
if ($userIds->count() !== 1) {
|
||||
return;
|
||||
}
|
||||
$newRows = DB::table($this->sectionDb)
|
||||
->where($this->contextColumn, '=', $userGroup->context_id)
|
||||
->pluck($this->sectionIdColumn)
|
||||
->map(function (int $sectionId) use ($userGroup, $userIds) {
|
||||
return [
|
||||
'context_id' => $userGroup->context_id,
|
||||
'assoc_type' => 530, // ASSOC_TYPE_SECTION
|
||||
'assoc_id' => $sectionId,
|
||||
'user_id' => $userIds->first(),
|
||||
'user_group_id' => $userGroup->user_group_id,
|
||||
];
|
||||
});
|
||||
if ($newRows->count()) {
|
||||
DB::table('subeditor_submission_group')->insertOrIgnore($newRows->toArray());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7191_InstallSubmissionHelpDefaults.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 I7191_InstallSubmissionHelpDefaults
|
||||
*
|
||||
* @brief Install new localized defaults for submission help text
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use APP\core\Application;
|
||||
use Exception;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\facades\Locale;
|
||||
use stdClass;
|
||||
|
||||
abstract class I7191_InstallSubmissionHelpDefaults extends \PKP\migration\Migration
|
||||
{
|
||||
protected string $CONTEXT_TABLE = '';
|
||||
protected string $CONTEXT_SETTINGS_TABLE = '';
|
||||
protected string $CONTEXT_COLUMN = '';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (empty($this->CONTEXT_TABLE) || empty($this->CONTEXT_SETTINGS_TABLE) || empty($this->CONTEXT_COLUMN)) {
|
||||
throw new Exception('Upgrade could not be completed because required properties for the I7191_InstallSubmissionHelpDefaults migration are undefined.');
|
||||
}
|
||||
|
||||
$initialLocale = Locale::getLocale();
|
||||
|
||||
DB::table($this->CONTEXT_TABLE . ' as ct')
|
||||
->leftJoin($this->CONTEXT_SETTINGS_TABLE . ' as cst', function (JoinClause $join) {
|
||||
$join->on('ct.' . $this->CONTEXT_COLUMN, '=', 'cst.' . $this->CONTEXT_COLUMN)
|
||||
->where('cst.setting_name', '=', 'supportedLocales');
|
||||
})
|
||||
->get([
|
||||
'ct.' . $this->CONTEXT_COLUMN,
|
||||
'ct.path',
|
||||
'ct.primary_locale',
|
||||
'cst.setting_value as supportedLocales'])
|
||||
->each(function (stdClass $row) {
|
||||
foreach (json_decode($row->supportedLocales) as $locale) {
|
||||
Locale::setLocale($locale);
|
||||
$localizationParams = $this->getLocalizationParams($row, $locale);
|
||||
DB::table($this->CONTEXT_SETTINGS_TABLE)
|
||||
->insert(
|
||||
$this->getNewSettings()->map(
|
||||
function ($localeKey, $settingName) use ($row, $locale, $localizationParams) {
|
||||
return [
|
||||
$this->CONTEXT_COLUMN => $row->{$this->CONTEXT_COLUMN},
|
||||
'locale' => $locale,
|
||||
'setting_name' => $settingName,
|
||||
'setting_value' => __($localeKey, $localizationParams),
|
||||
];
|
||||
}
|
||||
)->toArray()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Locale::setLocale($initialLocale);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table($this->CONTEXT_SETTINGS_TABLE)
|
||||
->whereIn('setting_name', $this->getNewSettings()->keys())
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection [settingName => localeKey]
|
||||
*/
|
||||
protected function getNewSettings(): Collection
|
||||
{
|
||||
return collect([
|
||||
'beginSubmissionHelp' => 'default.submission.step.beforeYouBegin',
|
||||
'contributorsHelp' => 'default.submission.step.contributors',
|
||||
'detailsHelp' => 'default.submission.step.details',
|
||||
'forTheEditorsHelp' => 'default.submission.step.forTheEditors',
|
||||
'reviewHelp' => 'default.submission.step.review',
|
||||
'uploadFilesHelp' => 'default.submission.step.uploadFiles',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the params needed to localize some of the default settings
|
||||
*/
|
||||
protected function getLocalizationParams(stdClass $row, string $locale): array
|
||||
{
|
||||
return [
|
||||
'submissionGuidelinesUrl' => Application::get()->getDispatcher()->url(
|
||||
Application::get()->getRequest(),
|
||||
Application::ROUTE_PAGE,
|
||||
$row->path,
|
||||
'about',
|
||||
'submissions'
|
||||
),
|
||||
'contextName' => $this->getContextName($row->{$this->CONTEXT_COLUMN}, $locale, $row->primary_locale),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of a context
|
||||
*
|
||||
* Sorts all names with preferred locale first and primary locale
|
||||
* second. Chooses top name after the sort.
|
||||
*/
|
||||
protected function getContextName(int $contextId, string $preferredLocale, string $primaryLocale): string
|
||||
{
|
||||
$rows = DB::table($this->CONTEXT_SETTINGS_TABLE)
|
||||
->where($this->CONTEXT_COLUMN, $contextId)
|
||||
->where('setting_name', 'name')
|
||||
->whereNotNull('setting_value')
|
||||
->get(['locale', 'setting_value'])
|
||||
->toArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
throw new Exception('Upgrade failed because no name was found for context ' . $contextId);
|
||||
}
|
||||
|
||||
usort($rows, function ($a, $b) use ($preferredLocale, $primaryLocale) {
|
||||
return $a->locale === $preferredLocale
|
||||
|| ($a->locale === $primaryLocale && $b->locale !== $preferredLocale);
|
||||
});
|
||||
|
||||
return $rows[0]->setting_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7191_ResubscribeSubeditors.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7191_ResubscribeSubeditors
|
||||
*
|
||||
* @brief Resubscribe subeditors to certain email notifications.
|
||||
*
|
||||
* In previous versions, subeditors received two emails
|
||||
* with a new submission: NOTIFICATION_TYPE_SUBMISSION_SUBMITTED and a copy
|
||||
* of the email sent to the author. The NOTIFICATION_TYPE_SUBMISSION_SUBMITTED
|
||||
* email is more appropriate, but they could not opt out of the author copy, so
|
||||
* many editors unsubscribed from the NOTIFICATION_TYPE_SUBMISSION_SUBMITTED
|
||||
* email.
|
||||
*
|
||||
* In 3.4, this was fixed so that subeditors will not receive two emails.
|
||||
* However, to ensure they continue receiving at least one email, we need to
|
||||
* resubscribe them to the NOTIFICATION_TYPE_SUBMISSION_SUBMITTED email. If
|
||||
* they want, they can unsubscribe again.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Enumerable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class I7191_ResubscribeSubeditors extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* PKP\notification\Notification::NOTIFICATION_TYPE_SUBMISSION_SUBMITTED
|
||||
*/
|
||||
public const NOTIFICATION_TYPE = 0x1000001;
|
||||
|
||||
/**
|
||||
* PKP\security\Role::ROLE_ID_SUB_EDITOR
|
||||
*/
|
||||
public const SUBEDITOR_ROLE = 0x00000011;
|
||||
|
||||
protected Enumerable $removedRows;
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$this->removedRows = DB::table('notification_subscription_settings as nss')
|
||||
->leftJoin('user_user_groups as uug', 'nss.user_id', '=', 'uug.user_id')
|
||||
->leftJoin('user_groups as ug', function (JoinClause $join) {
|
||||
$join->on('ug.user_group_id', '=', 'uug.user_group_id')
|
||||
->on('ug.context_id', '=', 'nss.context')
|
||||
->where('ug.role_id', '=', self::SUBEDITOR_ROLE);
|
||||
})
|
||||
->distinct()
|
||||
->where('nss.setting_name', 'blocked_emailed_notification')
|
||||
->where('nss.setting_value', self::NOTIFICATION_TYPE)
|
||||
->whereNotNull('ug.user_group_id')
|
||||
->get(['nss.*', 'nss.context']);
|
||||
|
||||
DB::table('notification_subscription_settings')
|
||||
->whereIn(
|
||||
'setting_id',
|
||||
$this->removedRows->map(fn ($row) => $row->setting_id)
|
||||
)
|
||||
->delete();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('notification_subscription_settings')
|
||||
->insert(
|
||||
$this
|
||||
->removedRows
|
||||
->map(fn ($row) => (array) $row)
|
||||
->toArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7191_SubmissionChecklistMigration.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 I7191_SubmissionChecklistMigration
|
||||
*
|
||||
* @brief Migrate the submissionChecklist setting from an array to a HTML string
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use stdClass;
|
||||
|
||||
abstract class I7191_SubmissionChecklistMigration extends \PKP\migration\Migration
|
||||
{
|
||||
protected string $CONTEXT_SETTINGS_TABLE = '';
|
||||
protected string $CONTEXT_COLUMN = '';
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (empty($this->CONTEXT_SETTINGS_TABLE) || empty($this->CONTEXT_COLUMN)) {
|
||||
throw new Exception('Upgrade could not be completed because required properties for the I7191_SubmissionChecklistMigration migration are undefined.');
|
||||
}
|
||||
|
||||
$initialLocale = Locale::getLocale();
|
||||
|
||||
DB::table($this->CONTEXT_SETTINGS_TABLE)
|
||||
->where('setting_name', 'submissionChecklist')
|
||||
->get()
|
||||
->each(function (stdClass $row) {
|
||||
if (empty($row->setting_value)) {
|
||||
DB::table($this->CONTEXT_SETTINGS_TABLE)
|
||||
->where($this->CONTEXT_COLUMN, $row->{$this->CONTEXT_COLUMN})
|
||||
->where('locale', $row->locale)
|
||||
->where('setting_name', 'submissionChecklist')
|
||||
->where('setting_value', $row->setting_value)
|
||||
->delete();
|
||||
return;
|
||||
}
|
||||
$checklist = json_decode($row->setting_value, true);
|
||||
|
||||
usort($checklist, fn ($a, $b) => ($a['order'] ?? 0) <=> ($b['order'] ?? 0));
|
||||
|
||||
$textList = [];
|
||||
foreach ($checklist as $item) {
|
||||
if (!isset($item['content'])) {
|
||||
continue;
|
||||
}
|
||||
$textList[] = $item['content'];
|
||||
}
|
||||
|
||||
Locale::setLocale($row->locale);
|
||||
|
||||
$newValue = '<p>'
|
||||
. __('submission.submit.submissionChecklistDescription')
|
||||
. '</p>'
|
||||
. '<ul><li>'
|
||||
. join('</li><li>', $textList)
|
||||
. '</li></ul>';
|
||||
|
||||
DB::table($this->CONTEXT_SETTINGS_TABLE)
|
||||
->where($this->CONTEXT_COLUMN, $row->{$this->CONTEXT_COLUMN})
|
||||
->where('locale', $row->locale)
|
||||
->where('setting_name', 'submissionChecklist')
|
||||
->update([
|
||||
'setting_value' => $newValue,
|
||||
]);
|
||||
});
|
||||
|
||||
Locale::setLocale($initialLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7191_SubmissionProgressType.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 I7191_SubmissionProgressType
|
||||
*
|
||||
* @brief Change the submission_progress setting from an int to a string to match
|
||||
* the new step ids
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class I7191_SubmissionProgressType extends \PKP\migration\Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
$table->string('submission_progress_temp', 50)->default('start');
|
||||
});
|
||||
|
||||
foreach ($this->getStepMap() as $oldValue => $newValue) {
|
||||
DB::table('submissions')
|
||||
->where('submission_progress', $oldValue)
|
||||
->update([
|
||||
'submission_progress_temp' => $newValue,
|
||||
]);
|
||||
}
|
||||
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
$table->dropColumn('submission_progress');
|
||||
$table->renameColumn('submission_progress_temp', 'submission_progress');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
$table->smallInteger('submission_progress_temp')->default(1);
|
||||
});
|
||||
|
||||
foreach ($this->getStepMap() as $oldValue => $newValue) {
|
||||
DB::table('submissions')
|
||||
->where('submission_progress', $newValue)
|
||||
->update([
|
||||
'submission_progress_temp' => $oldValue,
|
||||
]);
|
||||
}
|
||||
|
||||
Schema::table('submissions', function (Blueprint $table) {
|
||||
$table->dropColumn('submission_progress');
|
||||
$table->renameColumn('submission_progress_temp', 'submission_progress');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array [oldValue => newValue]
|
||||
*/
|
||||
protected function getStepMap(): array
|
||||
{
|
||||
return [
|
||||
0 => '',
|
||||
1 => 'files',
|
||||
2 => 'contributors',
|
||||
3 => 'review',
|
||||
];
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7245_UpdateUserLocaleStringToParsableJsonString.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7245_UpdateUserLocaleStringToParsableJsonString
|
||||
*
|
||||
* @brief Update the users locales columns where multiple locales are presented as simple string separated by colon(:)
|
||||
*
|
||||
* @see https://github.com/pkp/pkp-lib/issues/7245
|
||||
*/
|
||||
|
||||
// en:fr_CA:fr_FR
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I7245_UpdateUserLocaleStringToParsableJsonString extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('users')
|
||||
->select(['user_id', 'locales'])
|
||||
->where('locales', '<>', '[]')
|
||||
->cursor()
|
||||
->each(function ($user) {
|
||||
$parsedLocales = @json_decode($user->locales, true);
|
||||
|
||||
if (!is_array($parsedLocales)) {
|
||||
$locales = explode(':', $user->locales);
|
||||
|
||||
if (is_array($locales)) {
|
||||
DB::table('users')
|
||||
->where('user_id', $user->user_id)
|
||||
->update(['locales' => json_encode($locales)]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7249_UpdateUsersUniqueIndex.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7249_UpdateUsersUniqueIndex
|
||||
*
|
||||
* @brief Update the users table constraints to reflect case sensitive username, email indexes for postgres DB (For ver. >= 3.3.0)
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I7249_UpdateUsersUniqueIndex extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
switch (DB::getDriverName()) {
|
||||
case 'pgsql':
|
||||
DB::unprepared('ALTER TABLE users DROP CONSTRAINT users_username;');
|
||||
DB::unprepared('ALTER TABLE users DROP CONSTRAINT users_email;');
|
||||
|
||||
DB::unprepared('CREATE UNIQUE INDEX users_username on users (LOWER(username));');
|
||||
DB::unprepared('CREATE UNIQUE INDEX users_email on users (LOWER(email));');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
switch (DB::getDriverName()) {
|
||||
case 'pgsql':
|
||||
DB::unprepared('DROP INDEX IF EXISTS users_username;');
|
||||
DB::unprepared('DROP INDEX IF EXISTS users_email;');
|
||||
|
||||
Schema::table('users', function ($table) {
|
||||
$table->unique(['username'], 'users_username');
|
||||
$table->unique(['email'], 'users_email');
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7249_UpdateUsersUniqueIndex_v3_1.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7249_UpdateUsersUniqueIndex_v3_1
|
||||
*
|
||||
* @brief Update the users table constraints to reflect case sensitive username, email indexes for postgres DB (For ver. 3.1.0, 3.2.0, 3.2.1)
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I7249_UpdateUsersUniqueIndex_v3_1 extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
switch (DB::getDriverName()) {
|
||||
case 'pgsql':
|
||||
DB::unprepared('DROP INDEX IF EXISTS users_username;');
|
||||
DB::unprepared('DROP INDEX IF EXISTS users_email;');
|
||||
|
||||
DB::unprepared('CREATE UNIQUE INDEX users_username on users (LOWER(username));');
|
||||
DB::unprepared('CREATE UNIQUE INDEX users_email on users (LOWER(email));');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
switch (DB::getDriverName()) {
|
||||
case 'pgsql':
|
||||
DB::unprepared('DROP INDEX IF EXISTS users_username;');
|
||||
DB::unprepared('DROP INDEX IF EXISTS users_email;');
|
||||
|
||||
DB::unprepared('CREATE UNIQUE INDEX users_username on users (username);');
|
||||
DB::unprepared('CREATE UNIQUE INDEX users_email on users (email);');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7264_UpdateEmailTemplates.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 I7264_UpdateEmailTemplates
|
||||
*
|
||||
* @brief Describe upgrade/downgrade operations for DB table email_templates.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
abstract class I7264_UpdateEmailTemplates extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Rename email template keys
|
||||
foreach ([
|
||||
'email_templates',
|
||||
'email_templates_default',
|
||||
'email_templates_default_data'
|
||||
] as $tableName) {
|
||||
DB::table($tableName)->where('email_key', 'USER_VALIDATE')
|
||||
->update(['email_key' => 'USER_VALIDATE_CONTEXT']);
|
||||
|
||||
DB::table($tableName)->where('email_key', 'PUBLISH_NOTIFY')
|
||||
->update(['email_key' => 'ISSUE_PUBLISH_NOTIFY']);
|
||||
|
||||
DB::table($tableName)->where('email_key', 'REVIEW_REQUEST_REMIND_AUTO')
|
||||
->update(['email_key' => 'REVIEW_RESPONSE_OVERDUE_AUTO']);
|
||||
|
||||
DB::table($tableName)->where('email_key', 'REVIEW_REQUEST_REMIND_AUTO_ONECLICK')
|
||||
->update(['email_key' => 'REVIEW_RESPONSE_OVERDUE_AUTO_ONECLICK']);
|
||||
}
|
||||
|
||||
// Add new template for email which is sent to a user registered from a site
|
||||
DB::table('email_templates_default')->insert([
|
||||
'email_key' => 'USER_VALIDATE_SITE',
|
||||
'can_disable' => 0,
|
||||
]);
|
||||
|
||||
DB::table('email_templates_default_data')->insertUsing([
|
||||
'email_key',
|
||||
'locale',
|
||||
'subject',
|
||||
'body',
|
||||
'description'
|
||||
], function (Builder $q) {
|
||||
$q->selectRaw('? as email_key', ['USER_VALIDATE_SITE'])
|
||||
->addSelect('locale', 'subject', 'body', 'description')
|
||||
->from('email_templates_default_data')
|
||||
->where('email_key', '=', 'USER_VALIDATE_CONTEXT');
|
||||
});
|
||||
|
||||
// Replace all template variables
|
||||
$oldNewVariablesMap = $this->oldNewVariablesMap();
|
||||
$this->renameTemplateVariables($oldNewVariablesMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Revert variables renaming
|
||||
$newOldVariablesMap = [];
|
||||
foreach ($this->oldNewVariablesMap() as $emailKey => $variablesMap) {
|
||||
$newOldVariablesMap[$emailKey] = array_flip($variablesMap);
|
||||
}
|
||||
$this->renameTemplateVariables($newOldVariablesMap);
|
||||
|
||||
// Revert renaming email template keys
|
||||
foreach ([
|
||||
'email_templates',
|
||||
'email_templates_default',
|
||||
'email_templates_default_data'
|
||||
] as $tableName) {
|
||||
DB::table($tableName)->where('email_key', 'USER_VALIDATE_CONTEXT')
|
||||
->update(['email_key' => 'USER_VALIDATE']);
|
||||
|
||||
DB::table($tableName)->where('email_key', 'ISSUE_PUBLISH_NOTIFY')
|
||||
->update(['email_key' => 'PUBLISH_NOTIFY']);
|
||||
|
||||
DB::table($tableName)->where('email_key', 'REVIEW_RESPONSE_OVERDUE_AUTO')
|
||||
->update(['email_key' => 'REVIEW_REQUEST_REMIND_AUTO']);
|
||||
|
||||
DB::table($tableName)->where('email_key', 'REVIEW_RESPONSE_OVERDUE_AUTO_ONECLICK')
|
||||
->update(['email_key' => 'REVIEW_REQUEST_REMIND_AUTO_ONECLICK']);
|
||||
|
||||
DB::table($tableName)->where('email_key', 'USER_VALIDATE_SITE')->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces email template variables in templates' subject and body
|
||||
*/
|
||||
protected function renameTemplateVariables(array $oldNewVariablesMap): void
|
||||
{
|
||||
foreach ($oldNewVariablesMap as $emailKey => $variablesMap) {
|
||||
$variables = [];
|
||||
$replacements = [];
|
||||
foreach ($variablesMap as $key => $value) {
|
||||
$variables[] = '/\{\$' . $key . '\}/';
|
||||
$replacements[] = '{$' . $value . '}';
|
||||
}
|
||||
|
||||
// Default templates
|
||||
$data = DB::table('email_templates_default_data')->where('email_key', $emailKey)->get();
|
||||
$data->each(function (object $entry) use ($variables, $replacements) {
|
||||
$subject = preg_replace($variables, $replacements, $entry->subject);
|
||||
$body = preg_replace($variables, $replacements, $entry->body);
|
||||
DB::table('email_templates_default_data')
|
||||
->where('email_key', $entry->{'email_key'})
|
||||
->where('locale', $entry->{'locale'})
|
||||
->update(['subject' => $subject, 'body' => $body]);
|
||||
});
|
||||
|
||||
// Custom templates
|
||||
$customData = DB::table('email_templates')->where('email_key', $emailKey)->get();
|
||||
$customData->each(function (object $customEntry) use ($variables, $replacements) {
|
||||
$emailRows = DB::table('email_templates_settings')->where('email_id', $customEntry->{'email_id'})->get();
|
||||
foreach ($emailRows as $emailRow) {
|
||||
$value = preg_replace($variables, $replacements, $emailRow->{'setting_value'});
|
||||
DB::table('email_templates_settings')
|
||||
->where('email_id', $emailRow->{'email_id'})
|
||||
->where('locale', $emailRow->{'locale'})
|
||||
->where('setting_name', $emailRow->{'setting_name'})
|
||||
->update(['setting_value' => $value]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array [email_key => [old_variable => new_variable]]
|
||||
*/
|
||||
protected function oldNewVariablesMap(): array
|
||||
{
|
||||
return [
|
||||
'NOTIFICATION' => [
|
||||
'url' => 'notificationUrl',
|
||||
'principalContactSignature' => 'contextSignature',
|
||||
'siteTitle' => 'contextName',
|
||||
],
|
||||
'NOTIFICATION_CENTER_DEFAULT' => [
|
||||
'contextName' => 'contextName',
|
||||
],
|
||||
'PASSWORD_RESET_CONFIRM' => [
|
||||
'url' => 'passwordResetUrl',
|
||||
'principalContactSignature' => 'siteContactName',
|
||||
],
|
||||
'USER_REGISTER' => [
|
||||
'userFullName' => 'recipientName',
|
||||
'principalContactSignature' => 'contextSignature',
|
||||
'contextName' => 'contextName',
|
||||
'username' => 'recipientUsername',
|
||||
],
|
||||
// new template from USER_VALIDATE
|
||||
'USER_VALIDATE_CONTEXT' => [
|
||||
'userFullName' => 'recipientName',
|
||||
'principalContactSignature' => 'contextSignature',
|
||||
'contextName' => 'contextName',
|
||||
],
|
||||
// new template from USER_VALIDATE
|
||||
'USER_VALIDATE_SITE' => [
|
||||
'userFullName' => 'recipientName',
|
||||
'principalContactSignature' => 'siteContactName',
|
||||
'contextName' => 'siteTitle',
|
||||
],
|
||||
'REVIEWER_REGISTER' => [
|
||||
'contextName' => 'contextName',
|
||||
'principalContactSignature' => 'contextSignature',
|
||||
'username' => 'recipientUsername'
|
||||
],
|
||||
// renamed from PUBLISH_NOTIFY
|
||||
'ISSUE_PUBLISH_NOTIFY' => [
|
||||
'contextName' => 'contextName',
|
||||
'contextUrl' => 'contextUrl',
|
||||
'editorialContactSignature' => 'signature',
|
||||
],
|
||||
'LOCKSS_EXISTING_ARCHIVE' => [
|
||||
'contextName' => 'contextName',
|
||||
'contextUrl' => 'contextUrl',
|
||||
'principalContactSignature' => 'signature',
|
||||
],
|
||||
'LOCKSS_NEW_ARCHIVE' => [
|
||||
'contextName' => 'contextName',
|
||||
'contextUrl' => 'contextUrl',
|
||||
'principalContactSignature' => 'signature',
|
||||
],
|
||||
'SUBMISSION_ACK' => [
|
||||
'authorName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'authorUsername' => 'recipientUsername',
|
||||
'editorContactSignature' => 'contextSignature',
|
||||
'submissionUrl' => 'authorSubmissionUrl',
|
||||
],
|
||||
'SUBMISSION_ACK_NOT_USER' => [
|
||||
'contextName' => 'contextName',
|
||||
'editorialContactSignature' => 'contextSignature',
|
||||
],
|
||||
// submissionUrl and editorUsername/recipientUsername are used only in the old template
|
||||
'EDITOR_ASSIGN' => [
|
||||
'editorialContactName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'editorUsername' => 'recipientUsername',
|
||||
],
|
||||
'REVIEW_CANCEL' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
],
|
||||
'REVIEW_REINSTATE' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
],
|
||||
'REVIEW_REQUEST' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'contextUrl' => 'contextUrl',
|
||||
'passwordResetUrl' => 'passwordLostUrl',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'signature',
|
||||
],
|
||||
'REVIEW_REQUEST_SUBSEQUENT' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'contextUrl' => 'contextUrl',
|
||||
'passwordResetUrl' => 'passwordLostUrl',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'signature',
|
||||
],
|
||||
'REVIEW_REQUEST_ONECLICK' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'signature',
|
||||
],
|
||||
'REVIEW_REQUEST_ONECLICK_SUBSEQUENT' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'signature',
|
||||
],
|
||||
'REVIEW_REQUEST_ATTACHED' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'editorialContactSignature' => 'signature',
|
||||
],
|
||||
'REVIEW_REQUEST_ATTACHED_SUBSEQUENT' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'editorialContactSignature' => 'signature',
|
||||
],
|
||||
// renamed from REVIEW_REQUEST_REMIND_AUTO
|
||||
'REVIEW_RESPONSE_OVERDUE_AUTO' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'contextUrl' => 'contextUrl',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'contextSignature',
|
||||
],
|
||||
// renamed from REVIEW_REQUEST_REMIND_AUTO_ONECLICK
|
||||
'REVIEW_RESPONSE_OVERDUE_AUTO_ONECLICK' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'contextSignature',
|
||||
],
|
||||
'REVIEW_CONFIRM' => [
|
||||
'contextName' => 'contextName',
|
||||
'reviewerName' => 'senderName',
|
||||
],
|
||||
'REVIEW_DECLINE' => [
|
||||
'contextName' => 'contextName',
|
||||
'reviewerName' => 'senderName',
|
||||
],
|
||||
'REVIEW_ACK' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
],
|
||||
'REVIEW_REMIND' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'signature',
|
||||
'passwordResetUrl' => 'passwordLostUrl',
|
||||
],
|
||||
'REVIEW_REMIND_AUTO' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'contextSignature',
|
||||
],
|
||||
'REVIEW_REMIND_ONECLICK' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'signature',
|
||||
],
|
||||
'REVIEW_REMIND_AUTO_ONECLICK' => [
|
||||
'reviewerName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'submissionReviewUrl' => 'reviewAssignmentUrl',
|
||||
'editorialContactSignature' => 'contextSignature',
|
||||
],
|
||||
'EDITOR_DECISION_ACCEPT' => [
|
||||
'authorName' => 'authors',
|
||||
'contextName' => 'contextName',
|
||||
'submissionUrl' => 'authorSubmissionUrl',
|
||||
],
|
||||
'EDITOR_DECISION_SEND_TO_EXTERNAL' => [
|
||||
'authorName' => 'authors',
|
||||
'submissionUrl' => 'authorSubmissionUrl',
|
||||
],
|
||||
'EDITOR_DECISION_SEND_TO_PRODUCTION' => [
|
||||
'authorName' => 'authors',
|
||||
'submissionUrl' => 'authorSubmissionUrl',
|
||||
],
|
||||
'EDITOR_DECISION_REVISIONS' => [
|
||||
'authorName' => 'authors',
|
||||
'submissionUrl' => 'authorSubmissionUrl',
|
||||
],
|
||||
'EDITOR_DECISION_RESUBMIT' => [
|
||||
'authorName' => 'authors',
|
||||
'contextName' => 'contextName',
|
||||
'submissionUrl' => 'authorSubmissionUrl',
|
||||
],
|
||||
'EDITOR_DECISION_DECLINE' => [
|
||||
'authorName' => 'authors',
|
||||
'contextName' => 'contextName',
|
||||
'submissionUrl' => 'authorSubmissionUrl',
|
||||
],
|
||||
'EDITOR_DECISION_INITIAL_DECLINE' => [
|
||||
'authorName' => 'authors',
|
||||
'submissionUrl' => 'authorSubmissionUrl',
|
||||
],
|
||||
'EDITOR_RECOMMENDATION' => [
|
||||
'contextName' => 'contextName',
|
||||
],
|
||||
'COPYEDIT_REQUEST' => [
|
||||
'participantName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'contextUrl' => 'contextUrl',
|
||||
'participantUsername' => 'recipientUsername',
|
||||
'contextAcronym' => 'contextAcronym',
|
||||
],
|
||||
'LAYOUT_REQUEST' => [
|
||||
'participantName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'contextUrl' => 'contextUrl',
|
||||
'participantUsername' => 'recipientUsername',
|
||||
],
|
||||
'LAYOUT_COMPLETE' => [
|
||||
'editorialContactName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'participantName' => 'senderName', // OJS
|
||||
'signatureFullName' => 'senderName', // OMP
|
||||
],
|
||||
'EMAIL_LINK' => [
|
||||
'authorName' => 'authors',
|
||||
'contextName' => 'contextName',
|
||||
'articleUrl' => 'submissionUrl',
|
||||
'monographUrl' => 'submissionUrl',
|
||||
],
|
||||
'SUBSCRIPTION_NOTIFY' => [
|
||||
'subscriberName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'username' => 'recipientUsername',
|
||||
'subscriptionContactSignature' => 'subscriptionSignature',
|
||||
],
|
||||
'OPEN_ACCESS_NOTIFY' => [
|
||||
'contextName' => 'contextName',
|
||||
'contextUrl' => 'contextUrl',
|
||||
'editorialContactSignature' => 'contextSignature',
|
||||
],
|
||||
'SUBSCRIPTION_BEFORE_EXPIRY' => [
|
||||
'subscriberName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'username' => 'recipientUsername',
|
||||
'subscriptionContactSignature' => 'subscriptionSignature',
|
||||
],
|
||||
'SUBSCRIPTION_AFTER_EXPIRY' => [
|
||||
'subscriberName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'username' => 'recipientUsername',
|
||||
'subscriptionContactSignature' => 'subscriptionSignature',
|
||||
],
|
||||
'SUBSCRIPTION_AFTER_EXPIRY_LAST' => [
|
||||
'subscriberName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'username' => 'recipientUsername',
|
||||
'subscriptionContactSignature' => 'subscriptionSignature',
|
||||
],
|
||||
'SUBSCRIPTION_PURCHASE_INDL' => [
|
||||
'contextName' => 'contextName',
|
||||
'userDetails' => 'subscriberDetails',
|
||||
],
|
||||
'SUBSCRIPTION_PURCHASE_INSTL' => [
|
||||
'contextName' => 'contextName',
|
||||
'userDetails' => 'subscriberDetails',
|
||||
],
|
||||
'SUBSCRIPTION_RENEW_INDL' => [
|
||||
'contextName' => 'contextName',
|
||||
'userDetails' => 'subscriberDetails',
|
||||
],
|
||||
'SUBSCRIPTION_RENEW_INSTL' => [
|
||||
'contextName' => 'contextName',
|
||||
'userDetails' => 'subscriberDetails',
|
||||
],
|
||||
'CITATION_EDITOR_AUTHOR_QUERY' => [
|
||||
'authorFirstName' => 'recipientName',
|
||||
'userFirstName' => 'senderName',
|
||||
'contextName' => 'contextName',
|
||||
],
|
||||
'REVISED_VERSION_NOTIFY' => [
|
||||
'editorialContactSignature' => 'signature',
|
||||
'authorName' => 'submitterName',
|
||||
],
|
||||
'STATISTICS_REPORT_NOTIFICATION' => [
|
||||
'principalContactSignature' => 'contextSignature',
|
||||
'name' => 'recipientName',
|
||||
],
|
||||
'ANNOUNCEMENT' => [
|
||||
'title' => 'announcementTitle',
|
||||
'summary' => 'announcementSummary',
|
||||
'url' => 'announcementUrl',
|
||||
],
|
||||
'MANUAL_PAYMENT_NOTIFICATION' => [
|
||||
'itemName' => 'paymentName',
|
||||
'itemCost' => 'paymentAmount',
|
||||
'itemCurrencyCode' => 'paymentCurrencyCode',
|
||||
'userName' => 'senderUsername',
|
||||
'userFullName' => 'senderName',
|
||||
],
|
||||
// in OPS only
|
||||
'POSTED_ACK' => [
|
||||
'authorName' => 'recipientName',
|
||||
'publicationUrl' => 'submissionUrl',
|
||||
'editorialContactSignature' => 'signature'
|
||||
],
|
||||
'INDEX_REQUEST' => [
|
||||
'participantName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'participantUsername' => 'recipientUsername',
|
||||
'editorialContactSignature' => 'signature',
|
||||
'contextUrl' => 'contextUrl',
|
||||
],
|
||||
'INDEX_COMPLETE' => [
|
||||
'editorialContactName' => 'recipientName',
|
||||
'contextName' => 'contextName',
|
||||
'signatureFullName' => 'senderName',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7265_EditorialDecisions.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7265_EditorialDecisions
|
||||
*
|
||||
* @brief Database migrations for editorial decision refactor.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
abstract class I7265_EditorialDecisions extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->upReviewRounds();
|
||||
$this->upNotifyAllAuthors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->downReviewRounds();
|
||||
$this->downNotifyAllAuthors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use null instead of 0 for editorial decisions not in review rounds
|
||||
*/
|
||||
protected function upReviewRounds()
|
||||
{
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->bigInteger('review_round_id')->nullable()->change();
|
||||
$table->bigInteger('round')->nullable()->change();
|
||||
});
|
||||
|
||||
DB::table('edit_decisions')
|
||||
->where('review_round_id', '=', 0)
|
||||
->orWhere('round', '=', 0)
|
||||
->update([
|
||||
'review_round_id' => null,
|
||||
'round' => null
|
||||
]);
|
||||
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->foreign('review_round_id')->references('review_round_id')->on('review_rounds');
|
||||
$table->index(['review_round_id'], 'edit_decisions_review_round_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore 0 values instead of null for editorial decisions not in review rounds
|
||||
*/
|
||||
protected function downReviewRounds()
|
||||
{
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->dropForeign(['review_round_id']);
|
||||
});
|
||||
|
||||
DB::table('edit_decisions')
|
||||
->whereNull('review_round_id')
|
||||
->orWhereNull('round')
|
||||
->update([
|
||||
'review_round_id' => 0,
|
||||
'round' => 0
|
||||
]);
|
||||
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->bigInteger('review_round_id')->nullable(false)->change();
|
||||
$table->bigInteger('round')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the new context setting "notifyAllAuthors"
|
||||
*/
|
||||
protected function upNotifyAllAuthors()
|
||||
{
|
||||
DB::table($this->getContextSettingsTable())
|
||||
->insert(
|
||||
DB::table($this->getContextTable())
|
||||
->pluck($this->getContextIdColumn())
|
||||
->map(
|
||||
function (int $contextId) {
|
||||
return [
|
||||
$this->getContextIdColumn() => $contextId,
|
||||
'setting_name' => 'notifyAllAuthors',
|
||||
'setting_value' => 1,
|
||||
];
|
||||
}
|
||||
)->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the new context setting "notifyAllAuthors"
|
||||
*/
|
||||
protected function downNotifyAllAuthors()
|
||||
{
|
||||
DB::table($this->getContextSettingsTable())
|
||||
->where('setting_name', 'notifyAllAuthors')
|
||||
->delete();
|
||||
}
|
||||
|
||||
abstract protected function getContextTable(): string;
|
||||
abstract protected function getContextSettingsTable(): string;
|
||||
abstract protected function getContextIdColumn(): string;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7286_BatchesMigration.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7286_BatchesMigration
|
||||
*
|
||||
* @brief Database migrations for batches refactor, see https://github.com/illuminate/queue/blob/9.x/Console/stubs/batches.stub
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I7286_BatchesMigration extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->text('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the migrations
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('job_batches');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7287_RemoveEmailTemplatesDefault.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7287_RemoveEmailTemplatesDefault
|
||||
*
|
||||
* @brief Remove default email template table after data migrated to the Mailable class
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I7287_RemoveEmailTemplatesDefault extends Migration
|
||||
{
|
||||
protected Collection $emailTemplatesDefault;
|
||||
protected Collection $emailTemplateDefaultData;
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->emailTemplatesDefault = DB::table('email_templates_default')->get();
|
||||
$this->emailTemplateDefaultData = DB::table('email_templates_default_data')->get();
|
||||
Schema::drop('email_templates_default');
|
||||
Schema::table('email_templates_default_data', function (Blueprint $table) {
|
||||
$table->dropColumn('description');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the migrations
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Recreate email_templates_default table
|
||||
Schema::create('email_templates_default', function (Blueprint $table) {
|
||||
$table->bigInteger('email_id')->autoIncrement();
|
||||
$table->string('email_key', 255)->comment('Unique identifier for this email.');
|
||||
$table->smallInteger('can_disable')->default(0);
|
||||
$table->smallInteger('can_edit')->default(0);
|
||||
$table->bigInteger('from_role_id')->nullable();
|
||||
$table->bigInteger('to_role_id')->nullable();
|
||||
$table->bigInteger('stage_id')->nullable();
|
||||
$table->index(['email_key'], 'email_templates_default_email_key');
|
||||
});
|
||||
|
||||
$this->emailTemplatesDefault->each(function (\stdClass $row) {
|
||||
DB::table('email_templates_default')->insert([
|
||||
'email_key' => $row->{'email_key'},
|
||||
'can_disable' => $row->{'can_disable'},
|
||||
'can_edit' => $row->{'can_edit'},
|
||||
'from_role_id' => $row->{'from_role_id'},
|
||||
'to_role_id' => $row->{'to_role_id'},
|
||||
]);
|
||||
});
|
||||
|
||||
// Re-add description column to the email_templates_default_data table and populate with data
|
||||
Schema::table('email_templates_default_data', function (Blueprint $table) {
|
||||
$table->addColumn('text', 'description')->nullable();
|
||||
});
|
||||
$this->emailTemplateDefaultData->each(function (\stdClass $dataRow) {
|
||||
DB::table('email_templates_default_data')
|
||||
->where('email_key', $dataRow->{'email_key'})
|
||||
->where('locale', $dataRow->locale)
|
||||
->update(['description' => $dataRow->description]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7366_UpdateUserAPIKeySettings.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7366_UpdateUserAPIKeySettings
|
||||
*
|
||||
* @brief Describe upgrade/downgrade for updating user API related settings
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class I7366_UpdateUserAPIKeySettings extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$users = DB::select(
|
||||
DB::raw(
|
||||
"SELECT u.user_id FROM users u
|
||||
JOIN user_settings enabled_setting ON (enabled_setting.user_id = u.user_id AND enabled_setting.setting_name = 'apiKeyEnabled')
|
||||
LEFT JOIN user_settings key_setting ON (key_setting.user_id = u.user_id AND key_setting.setting_name = 'apiKey')
|
||||
WHERE key_setting.user_id IS NULL"
|
||||
)
|
||||
);
|
||||
|
||||
collect($users)
|
||||
->pluck('user_id')
|
||||
->chunk(1000)
|
||||
->each(
|
||||
fn ($ids) => DB::table('user_settings')
|
||||
->where('setting_name', 'apiKeyEnabled')
|
||||
->whereIn('user_id', $ids->toArray())
|
||||
->delete()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7463_LocaleColumn.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 I7463_LocaleColumn
|
||||
*
|
||||
* @brief Remove's locale column installed to publications table
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class I7463_LocaleColumn extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (Schema::hasColumn('publications', 'locale')) {
|
||||
Schema::table('publications', function (Blueprint $table) {
|
||||
$table->dropColumn('locale');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Don't restore this column on downgrade
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7474_UpdateMimetypes.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 I7474_UpdateMimetypes
|
||||
*
|
||||
* @brief Updates the mimetype of some files that were not detected properly
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class I7474_UpdateMimetypes extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('files')
|
||||
->where('mimetype', '=', 'video/x-ms-asf')
|
||||
->where('path', 'like', '%.wma')
|
||||
->update(['mimetype' => 'video/x-ms-wma']);
|
||||
DB::table('files')
|
||||
->where('mimetype', '=', 'video/x-ms-asf')
|
||||
->where('path', 'like', '%.wmv')
|
||||
->update(['mimetype' => 'video/x-ms-wmv']);
|
||||
DB::table('files')
|
||||
->where('mimetype', '=', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')
|
||||
->where('path', 'like', '%.docm')
|
||||
->update(['mimetype' => 'application/vnd.ms-word.document.macroEnabled.12']);
|
||||
DB::table('files')
|
||||
->where('mimetype', '=', 'text/plain')
|
||||
->where('path', 'like', '%.csv')
|
||||
->update(['mimetype' => 'text/csv']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Don't restore the old mimetypes on downgrade
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7486_RemoveItemViewsTable.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 I7486_RemoveItemViewsTable
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I7486_RemoveItemViewsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::drop('item_views');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::create('item_views', function (Blueprint $table) {
|
||||
$table->bigInteger('assoc_type');
|
||||
$table->bigInteger('assoc_id');
|
||||
|
||||
$table->bigInteger('user_id')->nullable();
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'item_views_user_id');
|
||||
|
||||
$table->datetime('date_last_viewed')->nullable();
|
||||
$table->unique(['assoc_type', 'assoc_id', 'user_id'], 'item_views_pkey');
|
||||
});
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7486_RenameUnconsideredColumnToConsidered.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 I7486_RenameUnconsideredColumnToConsidered
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I7486_RenameUnconsideredColumnToConsidered extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('review_assignments', function (Blueprint $table) {
|
||||
$table->renameColumn('unconsidered', 'considered');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('review_assignments', function (Blueprint $table) {
|
||||
$table->renameColumn('considered', 'unconsidered');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7592_RemoveUnusedEmailTemplates.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7592_RemoveUnusedEmailTemplates
|
||||
*
|
||||
* @brief Describe upgrade/downgrade for removing unused reviewer related email templates from the default data
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use stdClass;
|
||||
|
||||
class I7592_RemoveUnusedEmailTemplates extends \PKP\migration\Migration
|
||||
{
|
||||
protected ?Collection $templatesDefaultData;
|
||||
protected ?Collection $templatesDefault;
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$emailKeys = [
|
||||
'REVIEW_REMIND_AUTO_ONECLICK',
|
||||
'REVIEW_RESPONSE_OVERDUE_AUTO_ONECLICK',
|
||||
'REVIEW_REMIND_ONECLICK',
|
||||
'REVIEW_REQUEST_ATTACHED',
|
||||
'REVIEW_REQUEST_ATTACHED_SUBSEQUENT',
|
||||
'REVIEW_REQUEST_ONECLICK',
|
||||
'REVIEW_REQUEST_ONECLICK_SUBSEQUENT',
|
||||
'CITATION_EDITOR_AUTHOR_QUERY',
|
||||
];
|
||||
|
||||
$this->templatesDefault = DB::table('email_templates_default')
|
||||
->whereIn('email_key', $emailKeys)
|
||||
->get();
|
||||
|
||||
$this->templatesDefaultData = DB::table('email_templates_default_data')
|
||||
->whereIn('email_key', $emailKeys)
|
||||
->get();
|
||||
|
||||
DB::table('email_templates_default')
|
||||
->whereIn('email_key', $emailKeys)
|
||||
->delete();
|
||||
|
||||
DB::table('email_templates_default_data')
|
||||
->whereIn('email_key', $emailKeys)
|
||||
->delete();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->templatesDefault->each(function (stdClass $templateDefaultRow) {
|
||||
DB::table('email_templates_default')->insert((array) $templateDefaultRow);
|
||||
});
|
||||
|
||||
$this->templatesDefaultData->each(function (stdClass $templateDefaultDataRow) {
|
||||
DB::table('email_templates_default_data')->insert((array) $templateDefaultDataRow);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7624_StrftimeDeprecation.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 I7624_StrftimeDeprecation
|
||||
*
|
||||
* @brief Convert strftime-based date formats into DateTime::format instead.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use APP\core\Application;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\core\PKPString;
|
||||
|
||||
class I7624_StrftimeDeprecation extends \PKP\migration\Migration
|
||||
{
|
||||
private const DATETIME_SETTINGS = [
|
||||
'dateFormatShort',
|
||||
'dateFormatLong',
|
||||
'timeFormat',
|
||||
'datetimeFormatShort',
|
||||
'datetimeFormatLong',
|
||||
];
|
||||
|
||||
private const CONTEXT_SETTING_TABLE_NAMES = [
|
||||
'ojs2' => 'journal_settings',
|
||||
'omp' => 'press_settings',
|
||||
'ops' => 'server_settings',
|
||||
];
|
||||
|
||||
private const CONTEXT_SETTING_TABLE_KEYS = [
|
||||
'ojs2' => 'journal_id',
|
||||
'omp' => 'press_id',
|
||||
'ops' => 'server_id',
|
||||
];
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->convert('up');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->convert('down');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the date format settings.
|
||||
*
|
||||
* @param string Direction 'up'|'down'
|
||||
*/
|
||||
private function convert(string $direction)
|
||||
{
|
||||
$applicationName = Application::get()->getName();
|
||||
$contextIdColumnName = self::CONTEXT_SETTING_TABLE_KEYS[$applicationName];
|
||||
$dateSettingValues = DB::table(self::CONTEXT_SETTING_TABLE_NAMES[$applicationName])
|
||||
->whereIn('setting_name', self::DATETIME_SETTINGS)
|
||||
->select(['setting_value', $contextIdColumnName, 'setting_name', 'locale'])
|
||||
->get();
|
||||
$map = PKPString::getStrftimeConversion();
|
||||
switch ($direction) {
|
||||
case 'up': break;
|
||||
case 'down': $map = array_flip($map);
|
||||
break;
|
||||
default: throw new \Exception("Unknown direction {$direction}");
|
||||
}
|
||||
foreach ($dateSettingValues as $row) {
|
||||
DB::table(self::CONTEXT_SETTING_TABLE_NAMES[$applicationName])
|
||||
->where('setting_name', '=', $row->setting_name)
|
||||
->where('locale', '=', $row->locale)
|
||||
->where($contextIdColumnName, '=', $row->$contextIdColumnName)
|
||||
->update(
|
||||
[
|
||||
'setting_value' => strtr($row->setting_value, $map)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7725_DecisionConstantsUpdate.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7725_DecisionConstantsUpdate
|
||||
*
|
||||
* @brief Editorial decision constant sync up across all application
|
||||
*
|
||||
* @see https://github.com/pkp/pkp-lib/issues/7725
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
use Throwable;
|
||||
|
||||
abstract class I7725_DecisionConstantsUpdate extends Migration
|
||||
{
|
||||
/**
|
||||
* Get the decisions constants mappings
|
||||
*
|
||||
*/
|
||||
abstract public function getDecisionMappings(): array;
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->configureUpdatedAtColumn();
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
collect($this->getDecisionMappings())
|
||||
->each(
|
||||
fn ($decisionMapping) => DB::table('edit_decisions')
|
||||
->when(
|
||||
isset($decisionMapping['stage_id']) && !empty($decisionMapping['stage_id']),
|
||||
fn ($query) => $query->whereIn('stage_id', $decisionMapping['stage_id'])
|
||||
)
|
||||
->where('decision', $decisionMapping['current_value'])
|
||||
->whereNull('updated_at')
|
||||
->update([
|
||||
'decision' => $decisionMapping['updated_value'],
|
||||
'updated_at' => Carbon::now(),
|
||||
])
|
||||
);
|
||||
|
||||
DB::commit();
|
||||
} catch (Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
$this->_installer->log($exception->__toString());
|
||||
}
|
||||
|
||||
$this->removeUpdatedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->configureUpdatedAtColumn();
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
collect($this->getDecisionMappings())
|
||||
->each(
|
||||
fn ($decisionMapping) => DB::table('edit_decisions')
|
||||
->when(
|
||||
isset($decisionMapping['stage_id']) && !empty($decisionMapping['stage_id']),
|
||||
fn ($query) => $query->whereIn('stage_id', $decisionMapping['stage_id'])
|
||||
)
|
||||
->where('decision', $decisionMapping['updated_value'])
|
||||
->whereNull('updated_at')
|
||||
->update([
|
||||
'decision' => $decisionMapping['current_value'],
|
||||
'updated_at' => Carbon::now(),
|
||||
])
|
||||
);
|
||||
|
||||
DB::commit();
|
||||
} catch (Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
$this->_installer->log($exception->__toString());
|
||||
}
|
||||
|
||||
$this->removeUpdatedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the temporary updated_at column with NULL value
|
||||
*/
|
||||
protected function configureUpdatedAtColumn(): void
|
||||
{
|
||||
if (!Schema::hasColumn('edit_decisions', 'updated_at')) {
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->timestamp('updated_at')->nullable();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('edit_decisions')->update(['updated_at' => null]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the temporary updated_at column
|
||||
*/
|
||||
protected function removeUpdatedAtColumn(): void
|
||||
{
|
||||
if (Schema::hasColumn('edit_decisions', 'updated_at')) {
|
||||
Schema::table('edit_decisions', function (Blueprint $table) {
|
||||
$table->dropColumn('updated_at');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I7874_NotificationMetadataModifiedRemove.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I7874_NotificationMetadataModifiedRemove
|
||||
*
|
||||
* @brief Removes deprecated PKPNotification::NOTIFICATION_TYPE_METADATA_MODIFIED setting
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\migration\Migration;
|
||||
use stdClass;
|
||||
|
||||
class I7874_NotificationMetadataModifiedRemove extends Migration
|
||||
{
|
||||
protected Collection $subscribedToMetadataChangedNotification;
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$this->subscribedToMetadataChangedNotification = DB::table('notification_subscription_settings')
|
||||
->where('setting_value', '=', 0x1000002) // PKP\notification\PKPNotification::NOTIFICATION_TYPE_METADATA_MODIFIED
|
||||
->get();
|
||||
|
||||
$this->subscribedToMetadataChangedNotification->each(function (stdClass $row) {
|
||||
DB::table('notification_subscription_settings')
|
||||
->where('setting_id', '=', $row->{'setting_id'})
|
||||
->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$this->subscribedToMetadataChangedNotification->each(function (stdClass $row) {
|
||||
$values = (array) $row;
|
||||
unset($values['setting_id']);
|
||||
DB::table('notification_subscription_settings')->insert([$values]);
|
||||
});
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8060_UpdateUserLocalesDefaultToEmptyArrayFromNull.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I8060_UpdateUserLocalesDefaultToEmptyArray
|
||||
*
|
||||
* @brief Update the users table locales column default to empty array from NULL and update existing NULL ones to []
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I8060_UpdateUserLocalesDefaultToEmptyArrayFromNull extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('users')->whereNull('locales')->update(['locales' => '[]']);
|
||||
|
||||
Schema::table('users', function ($table) {
|
||||
$table->string('locales', 255)->nullable(false)->default('[]')->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function ($table) {
|
||||
$table->string('locales', 255)->nullable()->default('[]')->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8073_RemoveNotesWithoutQueriesAndRelatedObjects.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I8073_RemoveNotesWithoutQueriesAndRelatedObjects
|
||||
*
|
||||
* @brief Removes Notes without Queries and related objects
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use League\Flysystem\Filesystem;
|
||||
use League\Flysystem\FilesystemException;
|
||||
use League\Flysystem\Local\LocalFilesystemAdapter;
|
||||
use League\Flysystem\UnableToDeleteFile;
|
||||
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
|
||||
use PKP\config\Config;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I8073_RemoveNotesWithoutQueriesAndRelatedObjects extends Migration
|
||||
{
|
||||
private const ASSOC_TYPE_NOTE = 0x0000208; // PKPApplication::ASSOC_TYPE_NOTE
|
||||
private const ASSOC_TYPE_QUERY = 0x010000a; // PKPApplication::ASSOC_TYPE_QUERY
|
||||
private const FILE_MODE_MASK = 0666; // FileManager::FILE_MODE_MASK
|
||||
private const DIRECTORY_MODE_MASK = 0777; // FileManager::DIRECTORY_MODE_MASK
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
// Create a Filesystem object with the appropriate adapter to access the actual files
|
||||
$umask = Config::getVar('files', 'umask', 0022);
|
||||
$adapter = new LocalFilesystemAdapter(
|
||||
Config::getVar('files', 'files_dir'),
|
||||
PortableVisibilityConverter::fromArray([
|
||||
'file' => [
|
||||
'public' => self::FILE_MODE_MASK & ~$umask,
|
||||
'private' => self::FILE_MODE_MASK & ~$umask,
|
||||
],
|
||||
'dir' => [
|
||||
'public' => self::DIRECTORY_MODE_MASK & ~$umask,
|
||||
'private' => self::DIRECTORY_MODE_MASK & ~$umask,
|
||||
]
|
||||
]),
|
||||
LOCK_EX,
|
||||
LocalFilesystemAdapter::DISALLOW_LINKS
|
||||
);
|
||||
$filesystem = new Filesystem($adapter);
|
||||
|
||||
// Does not have the foreign key reference
|
||||
Schema::table('notification_settings', function (Blueprint $table) {
|
||||
$table->foreign('notification_id')->references('notification_id')->on('notifications')->onDelete('cascade');
|
||||
});
|
||||
|
||||
// Does have the foreign key reference but not the CASCADE
|
||||
if (DB::getDoctrineSchemaManager()->introspectTable('submission_files')->hasForeignKey('submission_files_file_id_foreign')) {
|
||||
Schema::table('submission_files', fn (Blueprint $table) => $table->dropForeign('submission_files_file_id_foreign'));
|
||||
}
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->foreign('file_id')->references('file_id')->on('files')->onDelete('cascade');
|
||||
});
|
||||
|
||||
// Does have the foreign key reference but not the CASCADE
|
||||
foreach (['submission_file_revisions_submission_file_id_foreign', 'submission_file_revisions_file_id_foreign'] as $foreignKeyName) {
|
||||
if (DB::getDoctrineSchemaManager()->introspectTable('submission_file_revisions')->hasForeignKey($foreignKeyName)) {
|
||||
Schema::table('submission_file_revisions', fn (Blueprint $table) => $table->dropForeign($foreignKeyName));
|
||||
}
|
||||
}
|
||||
Schema::table('submission_file_revisions', function (Blueprint $table) {
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
$table->foreign('file_id')->references('file_id')->on('files')->onDelete('cascade');
|
||||
});
|
||||
|
||||
// Does not have the foreign key reference
|
||||
Schema::table('submission_file_settings', function (Blueprint $table) {
|
||||
$table->foreignId('submission_file_id')->change();
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
});
|
||||
|
||||
// Does have the foreign key reference but not the CASCADE
|
||||
if (DB::getDoctrineSchemaManager()->introspectTable('review_files')->hasForeignKey('review_files_submission_file_id_foreign')) {
|
||||
Schema::table('review_files', fn (Blueprint $table) => $table->dropForeign('review_files_submission_file_id_foreign'));
|
||||
}
|
||||
Schema::table('review_files', function (Blueprint $table) {
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
$table->index(['submission_file_id'], 'review_files_submission_file_id');
|
||||
});
|
||||
|
||||
// Does have the foreign key reference but not the CASCADE
|
||||
if (DB::getDoctrineSchemaManager()->introspectTable('review_round_files')->hasForeignKey('review_round_files_submission_file_id_foreign')) {
|
||||
Schema::table('review_round_files', fn (Blueprint $table) => $table->dropForeign('review_round_files_submission_file_id_foreign'));
|
||||
}
|
||||
Schema::table('review_round_files', function (Blueprint $table) {
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files')->onDelete('cascade');
|
||||
});
|
||||
|
||||
// Does not have the foreign key reference
|
||||
Schema::table('query_participants', function (Blueprint $table) {
|
||||
$table->foreign('query_id')->references('query_id')->on('queries')->onDelete('cascade');
|
||||
$table->index(['query_id'], 'query_participants_query_id');
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'query_participants_user_id');
|
||||
});
|
||||
|
||||
$orphanedIds = DB::table('notes AS n')
|
||||
->leftJoin('queries AS q', 'n.assoc_id', '=', 'q.query_id')
|
||||
->where('n.assoc_type', '=', self::ASSOC_TYPE_QUERY)
|
||||
->whereNull('q.query_id')
|
||||
->pluck('n.note_id', 'n.assoc_id');
|
||||
|
||||
foreach ($orphanedIds as $neQueryId => $noteId) {
|
||||
$notesFileRows = DB::table('submission_files as sf')
|
||||
->join('files as f', 'sf.file_id', '=', 'f.file_id')
|
||||
->where('sf.assoc_type', '=', self::ASSOC_TYPE_NOTE)
|
||||
->where('sf.assoc_id', '=', $noteId)
|
||||
->get([
|
||||
'sf.submission_file_id as submissionFileId',
|
||||
'sf.file_id as fileId',
|
||||
'f.path as filePath'
|
||||
]);
|
||||
|
||||
$filesToCheckForDeletion = [];
|
||||
foreach ($notesFileRows as $submissionFileRow) {
|
||||
$submissionFileId = $submissionFileRow->submissionFileId;
|
||||
$submissionFileFileId = $submissionFileRow->fileId;
|
||||
$submissionFilePath = $submissionFileRow->filePath;
|
||||
|
||||
DB::table('submission_files')
|
||||
->where('submission_file_id', '=', $submissionFileId)
|
||||
->delete();
|
||||
|
||||
if (!array_key_exists($submissionFileFileId, $filesToCheckForDeletion)) {
|
||||
$filesToCheckForDeletion[$submissionFileFileId] = $submissionFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($filesToCheckForDeletion as $submissionFileFileId => $submissionFilePath) {
|
||||
$remainingSubmissionFilesCount = DB::table('submission_files')
|
||||
->where('file_id', '=', $submissionFileFileId)
|
||||
->count();
|
||||
|
||||
// If the file is not used by another SubmissionFile, it can be deleted.
|
||||
if ($remainingSubmissionFilesCount == 0) {
|
||||
if ($filesystem->has($submissionFilePath)) {
|
||||
try {
|
||||
$filesystem->delete($submissionFilePath);
|
||||
$this->_installer->log("A submission file that was attached to an orphaned note with ID {$noteId} at {$submissionFilePath} was successfully deleted.");
|
||||
} catch (FilesystemException | UnableToDeleteFile $exception) {
|
||||
$exceptionMessage = $exception->getMessage();
|
||||
$this->_installer->log("A submission file that was attached to an orphaned note with ID {$noteId} was found at {$submissionFilePath} but could not be deleted because of: {$exceptionMessage}.");
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('files')
|
||||
->where('file_id', '=', $submissionFileFileId)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$this->_installer->log("Removing orphaned note entry ID {$noteId} with nonexistent query {$neQueryId}");
|
||||
DB::table('notes')
|
||||
->where('note_id', '=', $noteId)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('notification_settings', function (Blueprint $table) {
|
||||
$table->dropForeign('notification_settings_notification_id_foreign');
|
||||
});
|
||||
|
||||
Schema::table('submission_files', function (Blueprint $table) {
|
||||
$table->dropForeign('submission_files_file_id_foreign');
|
||||
$table->foreign('file_id')->references('file_id')->on('files');
|
||||
});
|
||||
|
||||
Schema::table('submission_file_revisions', function (Blueprint $table) {
|
||||
$table->dropForeign('submission_file_revisions_submission_file_id_foreign');
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files');
|
||||
|
||||
$table->dropForeign('submission_file_revisions_file_id_foreign');
|
||||
$table->foreign('file_id')->references('file_id')->on('files');
|
||||
});
|
||||
|
||||
Schema::table('submission_file_settings', function (Blueprint $table) {
|
||||
$table->bigInteger('submission_file_id')->nullable(false)->unsigned()->change();
|
||||
$table->dropForeign('submission_file_settings_submission_file_id_foreign');
|
||||
});
|
||||
|
||||
Schema::table('review_files', function (Blueprint $table) {
|
||||
$table->dropForeign('review_files_submission_file_id_foreign');
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files');
|
||||
});
|
||||
|
||||
Schema::table('review_round_files', function (Blueprint $table) {
|
||||
$table->dropForeign('review_round_files_submission_file_id_foreign');
|
||||
$table->foreign('submission_file_id')->references('submission_file_id')->on('submission_files');
|
||||
});
|
||||
|
||||
Schema::table('query_participants', function (Blueprint $table) {
|
||||
$table->dropForeign('query_participants_query_id_foreign');
|
||||
$table->dropForeign('query_participants_user_id_foreign');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8093_UpdateUserGroupRelationTablesFK.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I8093_UpdateUserGroupRelationTablesFK
|
||||
*
|
||||
* @brief Update the foreign keys for UserGroup relation tables
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I8093_UpdateUserGroupRelationTablesFK extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('user_group_settings', function (Blueprint $table) {
|
||||
$table->foreign('user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('user_user_groups', function (Blueprint $table) {
|
||||
$table->foreign('user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('user_group_stage', function (Blueprint $table) {
|
||||
$table->foreign('user_group_id', 'user_group_stage_user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('user_group_settings', function (Blueprint $table) {
|
||||
$table->dropForeign('user_group_settings_user_group_id_foreign');
|
||||
});
|
||||
|
||||
Schema::table('user_user_groups', function (Blueprint $table) {
|
||||
$table->dropForeign('user_user_groups_user_group_id_foreign');
|
||||
});
|
||||
|
||||
Schema::table('user_group_stage', function (Blueprint $table) {
|
||||
$table->dropForeign('user_group_stage_user_group_id_foreign');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8151_ExtendSettingValues.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 I8151_ExtendSettingValues
|
||||
*
|
||||
* @brief Describe upgrade/downgrade operations for extending TEXT columns to MEDIUMTEXT
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class I8151_ExtendSettingValues extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('announcement_type_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('announcement_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('category_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('site_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('user_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('notification_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('notification_subscription_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('email_templates_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('plugin_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('controlled_vocab_entry_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('genre_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('library_file_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('event_log_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('citation_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('filter_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('navigation_menu_item_assignment_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('review_form_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('review_form_element_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('user_group_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('submission_file_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('publication_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('author_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('data_object_tombstone_settings', function (Blueprint $table) {
|
||||
$table->mediumText('setting_value')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// This downgrade is intentionally not implemented. Changing MEDIUMTEXT back to TEXT
|
||||
// may result in data truncation. Having MEDIUMTEXT in place of TEXT in an otherwise
|
||||
// downgraded database will not have side-effects.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8508_ConvertCurrentLogFile.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I8508_ConvertCurrentLogFile
|
||||
*
|
||||
* @brief Convert current and eventually the usage stats log file from yesterday into the new format.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use APP\statistics\StatisticsHelper;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\cliTool\traits\ConvertLogFile;
|
||||
use PKP\config\Config;
|
||||
use PKP\file\FileManager;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
use PKP\task\UpdateIPGeoDB;
|
||||
|
||||
class I8508_ConvertCurrentLogFile extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$fileManager = new FileManager();
|
||||
$convertCurrentUsageStatsLogFile = new ConvertCurrentUsageStatsLogFile();
|
||||
|
||||
// If Geo usage stats are enabled download the GeoIPDB
|
||||
$siteGeoUsageStatsSettings = DB::table('site_settings')
|
||||
->where('setting_name', '=', 'enableGeoUsageStats')
|
||||
->value('setting_value');
|
||||
if ($siteGeoUsageStatsSettings != null && $siteGeoUsageStatsSettings !== 'disabled') {
|
||||
$geoIPDBFile = StatisticsHelper::getGeoDBPath();
|
||||
if (!file_exists($geoIPDBFile)) {
|
||||
$geoIPDB = new UpdateIPGeoDB();
|
||||
$geoIPDB->execute();
|
||||
}
|
||||
}
|
||||
|
||||
$counterR5StartDate = date('Y-m-d');
|
||||
|
||||
$todayFileName = 'usage_events_' . date('Ymd') . '.log';
|
||||
if (file_exists($convertCurrentUsageStatsLogFile->getLogFileDir() . '/' . $todayFileName)) {
|
||||
$convertCurrentUsageStatsLogFile->convert($todayFileName);
|
||||
$oldFilePath = $convertCurrentUsageStatsLogFile->getLogFileDir() . '/usage_events_' . date('Ymd') . '_old.log';
|
||||
$oldFileRemoved = $fileManager->deleteByPath($oldFilePath);
|
||||
if ($oldFileRemoved) {
|
||||
$this->_installer->log("The old usage stats log file {$oldFilePath} was successfully deleted.");
|
||||
} else {
|
||||
$this->_installer->log("The old usage stats log file {$oldFilePath} could not be deleted.");
|
||||
}
|
||||
}
|
||||
|
||||
$yesterdayFileName = 'usage_events_' . date('Ymd', strtotime('-1 days')) . '.log';
|
||||
if (file_exists($convertCurrentUsageStatsLogFile->getLogFileDir() . '/' . $yesterdayFileName)) {
|
||||
$convertCurrentUsageStatsLogFile->convert($yesterdayFileName);
|
||||
$counterR5StartDate = date('Y-m-d', strtotime('-1 days'));
|
||||
$oldFilePath = $convertCurrentUsageStatsLogFile->getLogFileDir() . '/usage_events_' . date('Ymd', strtotime('-1 days')) . '_old.log';
|
||||
$oldFileRemoved = $fileManager->deleteByPath($oldFilePath);
|
||||
if ($oldFileRemoved) {
|
||||
$this->_installer->log("The old usage stats log file {$oldFilePath} was successfully deleted.");
|
||||
} else {
|
||||
$this->_installer->log("The old usage stats log file {$oldFilePath} could not be deleted.");
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('site_settings')->insert(['setting_name' => 'counterR5StartDate', 'setting_value' => $counterR5StartDate]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
class ConvertCurrentUsageStatsLogFile
|
||||
{
|
||||
use ConvertLogFile;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->__constructTrait();
|
||||
}
|
||||
|
||||
public function getLogFileDir(): string
|
||||
{
|
||||
return StatisticsHelper::getUsageStatsDirPath() . '/usageEventLogs';
|
||||
}
|
||||
|
||||
public function getParseRegex(): string
|
||||
{
|
||||
return '/^(?P<ip>\S+) \S+ \S+ "(?P<date>.*?)" (?P<url>\S+) (?P<returnCode>\S+) "(?P<userAgent>.*?)"/';
|
||||
}
|
||||
|
||||
public function getPhpDateTimeFormat(): string
|
||||
{
|
||||
return 'Y-m-d H:i:s';
|
||||
}
|
||||
|
||||
public function isPathInfoDisabled(): bool
|
||||
{
|
||||
return Config::getVar('general', 'disable_path_info') ? true : false;
|
||||
}
|
||||
|
||||
public function isApacheAccessLogFile(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8592_SiteNotificationSubscriptions.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I8592_SiteNotificationSubscriptions
|
||||
*
|
||||
* @brief Allow notification subscriptions to have no context for site-wide subscriptions
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class I8592_SiteNotificationSubscriptions extends \PKP\migration\Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('notification_subscription_settings', function (Blueprint $table) {
|
||||
$table->bigInteger('context')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('notification_subscription_settings', function (Blueprint $table) {
|
||||
$table->bigInteger('context')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8737_SectionEditorsUniqueIndexUpdate.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I8737_SectionEditorsUniqueIndexUpdate
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I8737_SectionEditorsUniqueIndexUpdate extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('subeditor_submission_group', function (Blueprint $table) {
|
||||
$table->dropUnique('section_editors_unique');
|
||||
$table->unique(['context_id', 'assoc_id', 'assoc_type', 'user_id', 'user_group_id'], 'section_editors_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('subeditor_submission_group', function (Blueprint $table) {
|
||||
$table->dropUnique('section_editors_unique');
|
||||
$table->unique(['context_id', 'assoc_id', 'assoc_type', 'user_id'], 'section_editors_unique');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8866_DispatchRegionCodesFixingJobs.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I8866_DispatchRegionCodesFixingJobs
|
||||
*
|
||||
* @brief Dispatches the jobs (a job per country) that shell fix the old region codes, if needed i.e. if any region exists.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Bus\Batch;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\core\Core;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
use PKP\migration\upgrade\v3_4_0\jobs\FixRegionCodes;
|
||||
|
||||
class I8866_DispatchRegionCodesFixingJobs extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (DB::table('metrics_submission_geo_monthly')->whereNotNull('region')->exists() ||
|
||||
DB::table('metrics_submission_geo_daily')->whereNotNull('region')->exists()) {
|
||||
// create a temporary table for the FIPS-ISO mapping
|
||||
if (!Schema::hasTable('region_mapping_tmp')) {
|
||||
Schema::create('region_mapping_tmp', function (Blueprint $table) {
|
||||
$table->string('country', 2);
|
||||
$table->string('fips', 3);
|
||||
$table->string('iso', 3)->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
// temporary create index on the column country and region, in order to be able to update the region codes in a reasonable time
|
||||
Schema::table('metrics_submission_geo_daily', function (Blueprint $table) {
|
||||
$sm = Schema::getConnection()->getDoctrineSchemaManager();
|
||||
$indexesFound = $sm->listTableIndexes('metrics_submission_geo_daily');
|
||||
if (!array_key_exists('metrics_submission_geo_daily_tmp_index', $indexesFound)) {
|
||||
$table->index(['country', 'region'], 'metrics_submission_geo_daily_tmp_index');
|
||||
}
|
||||
});
|
||||
Schema::table('metrics_submission_geo_monthly', function (Blueprint $table) {
|
||||
$sm = Schema::getConnection()->getDoctrineSchemaManager();
|
||||
$indexesFound = $sm->listTableIndexes('metrics_submission_geo_monthly');
|
||||
if (!array_key_exists('metrics_submission_geo_monthly_tmp_index', $indexesFound)) {
|
||||
$table->index(['country', 'region'], 'metrics_submission_geo_monthly_tmp_index');
|
||||
}
|
||||
});
|
||||
|
||||
// read the FIPS to ISO mappings and displatch a job per country
|
||||
$mappings = include Core::getBaseDir() . '/' . PKP_LIB_PATH . '/lib/regionMapping.php';
|
||||
$jobs = [];
|
||||
foreach (array_keys($mappings) as $country) {
|
||||
$jobs[] = new FixRegionCodes($country);
|
||||
}
|
||||
|
||||
Bus::batch($jobs)
|
||||
->then(function (Batch $batch) {
|
||||
// drop the temporary index
|
||||
Schema::table('metrics_submission_geo_daily', function (Blueprint $table) {
|
||||
$sm = Schema::getConnection()->getDoctrineSchemaManager();
|
||||
$indexesFound = $sm->listTableIndexes('metrics_submission_geo_daily');
|
||||
if (array_key_exists('metrics_submission_geo_daily_tmp_index', $indexesFound)) {
|
||||
$table->dropIndex(['tmp']);
|
||||
}
|
||||
});
|
||||
Schema::table('metrics_submission_geo_monthly', function (Blueprint $table) {
|
||||
$sm = Schema::getConnection()->getDoctrineSchemaManager();
|
||||
$indexesFound = $sm->listTableIndexes('metrics_submission_geo_monthly');
|
||||
if (array_key_exists('metrics_submission_geo_monthly_tmp_index', $indexesFound)) {
|
||||
$table->dropIndex(['tmp']);
|
||||
}
|
||||
});
|
||||
|
||||
// drop the temporary table
|
||||
if (Schema::hasTable('region_mapping_tmp')) {
|
||||
Schema::drop('region_mapping_tmp');
|
||||
}
|
||||
})
|
||||
->dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8933_EventLogLocalized.php
|
||||
*
|
||||
* Copyright (c) 2023 Simon Fraser University
|
||||
* Copyright (c) 2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I8933_EventLogLocalized.php
|
||||
*
|
||||
* @brief Adds a column to the event_log_settings table to store localized data such as a file name and drops setting_type column.
|
||||
* In the event_log table allows null values for userId.
|
||||
* Fixes the issue with duplicate event types and renames conflicting setting names
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\PostgresConnection;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
abstract class I8933_EventLogLocalized extends Migration
|
||||
{
|
||||
public const CHUNK_SIZE = 10000;
|
||||
|
||||
abstract protected function getContextTable(): string;
|
||||
|
||||
abstract protected function getContextIdColumn(): string;
|
||||
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('event_log_settings', function (Blueprint $table) {
|
||||
$table->string('locale', 14)->default('')->after('log_id');
|
||||
$table->dropUnique('event_log_settings_unique');
|
||||
$table->unique(['log_id', 'locale', 'setting_name'], 'event_log_settings_unique');
|
||||
$table->dropColumn('setting_type');
|
||||
});
|
||||
|
||||
// Events can be triggered without a user, e.g., in schedule tasks
|
||||
Schema::table('event_log', function (Blueprint $table) {
|
||||
$table->dropForeign('event_log_user_id_foreign');
|
||||
$table->dropIndex('event_log_user_id');
|
||||
$table->bigInteger('user_id')->nullable()->change();
|
||||
$table->foreign('user_id')->references('user_id')->on('users')->onDelete('cascade');
|
||||
$table->index(['user_id'], 'event_log_user_id');
|
||||
if (DB::connection() instanceof PostgresConnection) {
|
||||
DB::unprepared('ALTER TABLE event_log ALTER is_translated TYPE bool USING CASE WHEN COALESCE(is_translated, 0) = 0 THEN FALSE ELSE TRUE END');
|
||||
} else {
|
||||
$table->boolean('is_translated')->nullable()->change();
|
||||
}
|
||||
});
|
||||
|
||||
$this->fixConflictingSubmissionLogConstants();
|
||||
|
||||
// Rename ambiguous settings
|
||||
$this->renameSettings();
|
||||
|
||||
// Localize existing submission file name entries
|
||||
$sitePrimaryLocale = DB::table('site')->value('primary_locale');
|
||||
$contexts = DB::table($this->getContextTable())->get([$this->getContextIdColumn(), 'primary_locale']);
|
||||
$contextIdPrimaryLocaleMap = [];
|
||||
foreach ($contexts as $context) {
|
||||
$contextIdPrimaryLocaleMap[$context->{$this->getContextIdColumn()}] = $context->primary_locale;
|
||||
}
|
||||
|
||||
DB::table('event_log_settings AS es')
|
||||
->join('event_log AS e', 'es.log_id', '=', 'e.log_id')
|
||||
->where('es.setting_name', 'filename')
|
||||
->whereIn('e.event_type', [
|
||||
0x50000001, // SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_UPLOAD,
|
||||
0x50000010, // SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_EDIT,
|
||||
0x50000008, // SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_REVISION_UPLOAD,
|
||||
])
|
||||
->orderBy('event_log_setting_id')
|
||||
->chunk(self::CHUNK_SIZE, function (Collection $logChunks) use ($sitePrimaryLocale, $contextIdPrimaryLocaleMap) {
|
||||
$mapLocaleWithSettingIds = [];
|
||||
foreach ($logChunks as $row) {
|
||||
// Get locale based on a submission file ID log entry
|
||||
$locale = $this->getContextPrimaryLocale($row, $sitePrimaryLocale, $contextIdPrimaryLocaleMap);
|
||||
if (!$locale) {
|
||||
continue;
|
||||
}
|
||||
$mapLocaleWithSettingIds[$locale] ??= [];
|
||||
$mapLocaleWithSettingIds[$locale][] = $row->event_log_setting_id;
|
||||
}
|
||||
|
||||
// Update by chunks for each locale
|
||||
foreach ($mapLocaleWithSettingIds as $locale => $settingIds) {
|
||||
DB::table('event_log_settings')
|
||||
->whereIn('event_log_setting_id', $settingIds)
|
||||
->update(['locale' => $locale]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* FIX event types with identical values
|
||||
* 0x40000020: SUBMISSION_LOG_DECISION_EMAIL_SENT => 0x30000007, SUBMISSION_LOG_REVIEW_REMIND => 0x40000020, SUBMISSION_LOG_REVIEW_REMIND_AUTO => 0x40000021
|
||||
*/
|
||||
protected function fixConflictingSubmissionLogConstants(): void
|
||||
{
|
||||
DB::table('event_log')->where('event_type', 0x40000020)->lazyById(1000, 'log_id')->each(function (object $row) {
|
||||
if (
|
||||
DB::table('event_log_settings')
|
||||
->where('log_id', $row->log_id)
|
||||
->whereIn('setting_name', ['recipientCount', 'subject'])
|
||||
->count() === 2
|
||||
) {
|
||||
DB::table('event_log')
|
||||
->where('log_id', $row->log_id)
|
||||
->update(['event_type' => 0x30000007]); // SUBMISSION_LOG_DECISION_EMAIL_SENT
|
||||
} elseif (
|
||||
!DB::table('event_log_settings')
|
||||
->where('log_id', $row->log_id)
|
||||
->whereIn('setting_name', ['senderId', 'senderName'])
|
||||
->exists()
|
||||
) {
|
||||
DB::table('event_log')
|
||||
->where('log_id', $row->log_id)
|
||||
->update(['event_type' => 0x40000021]); // SUBMISSION_LOG_REVIEW_REMIND_AUTO
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the primary locale of the context associated with a given submission file
|
||||
*/
|
||||
protected function getContextPrimaryLocale(object $row, string $sitePrimaryLocale, array $contextIdPrimaryLocaleMap): ?string
|
||||
{
|
||||
// Try to determine submission/submission file ID based on the assoc type
|
||||
if ($row->assoc_type == 0x0000203) { // ASSOC_TYPE_SUBMISSION_FILE
|
||||
$submissionFileId = $row->assoc_id;
|
||||
} elseif ($row->assoc_type == 0x0100009) { // ASSOC_TYPE_SUBMISSION
|
||||
$submissionId = $row->assoc_id;
|
||||
} else {
|
||||
throw new \Exception('Unsupported assoc_type in the event log: ' . $row->assoc_type);
|
||||
}
|
||||
|
||||
// Get submission from the file ID
|
||||
if (!isset($submissionId)) {
|
||||
$submissionId = DB::table('submission_files')
|
||||
->where('submission_file_id', $submissionFileId)
|
||||
->value('submission_id');
|
||||
}
|
||||
|
||||
// Assuming submission file was removed
|
||||
if (!$submissionId) {
|
||||
return $sitePrimaryLocale;
|
||||
}
|
||||
|
||||
$contextId = DB::table('submissions')->where('submission_id', $submissionId)->value('context_id');
|
||||
|
||||
if (!$contextId) {
|
||||
return $sitePrimaryLocale;
|
||||
}
|
||||
|
||||
return $contextIdPrimaryLocaleMap[$contextId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename setting name to avoid ambiguity in the event log schema
|
||||
*/
|
||||
protected function renameSettings()
|
||||
{
|
||||
// First remove 'originalFileName' setting where 'name' setting exists
|
||||
$idsToDelete = DB::table('event_log_settings AS es')
|
||||
->join('event_log AS e', 'es.log_id', '=', 'e.log_id')
|
||||
->whereIn('e.event_type', [
|
||||
0x50000008, // SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_REVISION_UPLOAD,
|
||||
0x50000010, // SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_EDIT
|
||||
])
|
||||
->where('es.setting_name', 'name')
|
||||
->pluck('e.log_id');
|
||||
|
||||
foreach ($idsToDelete->chunk(self::CHUNK_SIZE) as $ids) {
|
||||
DB::table('event_log_settings')
|
||||
->whereIn('log_id', $ids->toArray())
|
||||
->where('setting_name', 'originalFileName')
|
||||
->delete();
|
||||
}
|
||||
|
||||
// Perform setting renaming
|
||||
foreach ($this->mapSettings() as $eventType => $settings) {
|
||||
$idsToUpdate = DB::table('event_log')
|
||||
->where('event_type', $eventType)
|
||||
->pluck('log_id');
|
||||
|
||||
foreach ($settings as $oldSettingName => $newSettingName) {
|
||||
foreach ($idsToUpdate->chunk(self::CHUNK_SIZE) as $ids) {
|
||||
DB::table('event_log_settings')
|
||||
->whereIn('log_id', $ids->toArray())
|
||||
->where('setting_name', $oldSettingName)
|
||||
->update(['setting_name' => $newSettingName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of new setting names for the event log
|
||||
* event type => [
|
||||
* old setting => new setting
|
||||
* ]
|
||||
*/
|
||||
protected function mapSettings(): Collection
|
||||
{
|
||||
return collect([
|
||||
0x10000009 => [ // PKPSubmissionEventLogEntry::SUBMISSION_LOG_COPYRIGHT_AGREED
|
||||
'name' => 'userFullName'
|
||||
],
|
||||
0x40000019 => [ // PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_CONFIRMED
|
||||
'userName' => 'editorName'
|
||||
],
|
||||
0x40000011 => [ // PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_SET_DUE_DATE
|
||||
'dueDate' => 'reviewDueDate'
|
||||
],
|
||||
0x50000001 => [ // SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_UPLOAD
|
||||
'originalFileName' => 'filename'
|
||||
],
|
||||
/**
|
||||
* 'originalFileName' and 'name' are duplicate entries in some events, the former arises from the times before
|
||||
* submission files refactoring, where it had pointed to the name of the original name of the uploaded file
|
||||
* rather than the user defined localized name.
|
||||
* Keep the 'name' where it exists, otherwise preserve 'originalFileName'
|
||||
*/
|
||||
0x50000008 => [ // SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_REVISION_UPLOAD
|
||||
'name' => 'filename',
|
||||
'originalFileName' => 'filename'
|
||||
],
|
||||
0x50000010 => [ // SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_EDIT
|
||||
'name' => 'filename',
|
||||
'originalFileName' => 'filename'
|
||||
],
|
||||
0x10000003 => [ // PKPSubmissionEventLogEntry::SUBMISSION_LOG_ADD_PARTICIPANT
|
||||
'name' => 'userFullName'
|
||||
],
|
||||
0x10000004 => [ // PKPSubmissionEventLogEntry::SUBMISSION_LOG_REMOVE_PARTICIPANT
|
||||
'name' => 'userFullName'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I8992_FixEmptyUrlPaths.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I8992_FixEmptyUrlPaths
|
||||
*
|
||||
* @brief Standardize the url column to hold NULL instead of NULL/empty string.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
|
||||
abstract class I8992_FixEmptyUrlPaths extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Retrieve the tables and fields to update
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getFieldset(): array
|
||||
{
|
||||
return [['publications', 'url_path']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
foreach ($this->getFieldset() as [$table, $column]) {
|
||||
DB::table($table)->whereRaw("TRIM({$column}) = ''")->update([$column => null]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I9039_DropDeprecatedFields.php
|
||||
*
|
||||
* Copyright (c) 2023 Simon Fraser University
|
||||
* Copyright (c) 2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I9039_DropDeprecatedFields
|
||||
*
|
||||
* @brief Drop deprecated fields
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I9039_DropDeprecatedFields extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$fieldMap = [
|
||||
'user_settings' => ['assoc_id', 'assoc_type'],
|
||||
'review_assignments' => ['reviewer_file_id']
|
||||
];
|
||||
foreach ($fieldMap as $entity => $columns) {
|
||||
foreach ($columns as $column) {
|
||||
if (Schema::hasColumn($entity, $column)) {
|
||||
Schema::dropColumns($entity, $column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I9040_DropSettingType.php
|
||||
*
|
||||
* Copyright (c) 2023 Simon Fraser University
|
||||
* Copyright (c) 2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I9040_DropSettingType
|
||||
*
|
||||
* @brief Drop not needed setting_type fields
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I9040_DropSettingType extends Migration
|
||||
{
|
||||
/**
|
||||
* Retrieve the affected entities
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getEntities(): array
|
||||
{
|
||||
return ['announcement_settings', 'submission_file_settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
foreach ($this->getEntities() as $table) {
|
||||
if (Schema::hasColumn($table, 'setting_type')) {
|
||||
Schema::dropColumns($table, 'setting_type');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I9136_MigrateUniqueSiteId.php
|
||||
*
|
||||
* Copyright (c) 2023 Simon Fraser University
|
||||
* Copyright (c) 2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I9136_MigrateUniqueSiteId
|
||||
*
|
||||
* @brief Migrate UsageEvent plugin setting 'uniqueSiteId' to the site_settings table.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\core\PKPString;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
|
||||
class I9136_MigrateUniqueSiteId extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$newUniqueSiteId = DB::table('site_settings')
|
||||
->where('setting_name', '=', 'uniqueSiteId')
|
||||
->value('setting_value');
|
||||
if (is_null($newUniqueSiteId)) {
|
||||
$oldUniqueSiteId = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'usageeventplugin')
|
||||
->where('setting_name', '=', 'uniqueSiteId')
|
||||
->value('setting_value');
|
||||
$uniqueSiteId = is_null($oldUniqueSiteId) || strlen($oldUniqueSiteId) == 0 ? PKPString::generateUUID() : $oldUniqueSiteId;
|
||||
DB::table('site_settings')->insert([
|
||||
'setting_name' => 'uniqueSiteId',
|
||||
'setting_value' => $uniqueSiteId
|
||||
]);
|
||||
DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'usageeventplugin')
|
||||
->where('setting_name', '=', 'uniqueSiteId')
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I9535_FixEmptyFileStage.php
|
||||
*
|
||||
* Copyright (c) 2024 Simon Fraser University
|
||||
* Copyright (c) 2024 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I9535_FixEmptyFileStage.php
|
||||
*
|
||||
* @brief Redirect empty file stages to the SUBMISSION_FILE_SUBMISSION.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I9535_FixEmptyFileStage extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('submission_files')
|
||||
// empty file_stage
|
||||
->where('file_stage', 0)
|
||||
// To \PKP\submissionFile\SubmissionFile::SUBMISSION_FILE_SUBMISSION
|
||||
->update(['file_stage' => 2]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/I9627_AddUsageStatsTemporaryTablesIndexes.php
|
||||
*
|
||||
* Copyright (c) 2024 Simon Fraser University
|
||||
* Copyright (c) 2024 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I9627_AddUsageStatsTemporaryTablesIndexes
|
||||
*
|
||||
* @brief Add an index to temporary usage stats tables to fix/improve the removeDoubleClicks and compileUniqueClicks query.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I9627_AddUsageStatsTemporaryTablesIndexes extends Migration
|
||||
{
|
||||
public $ipTables = [
|
||||
'usage_stats_total_temporary_records',
|
||||
'usage_stats_unique_item_investigations_temporary_records',
|
||||
'usage_stats_unique_item_requests_temporary_records',
|
||||
];
|
||||
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Decrease the size of the column ip to 64 characters
|
||||
foreach ($this->ipTables as $ipTable) {
|
||||
Schema::table($ipTable, function (Blueprint $table) {
|
||||
$table->string('ip', 64)->change();
|
||||
});
|
||||
}
|
||||
|
||||
$sm = Schema::getConnection()->getDoctrineSchemaManager();
|
||||
Schema::table('usage_stats_total_temporary_records', function (Blueprint $table) use ($sm) {
|
||||
$indexesFound = $sm->listTableIndexes('usage_stats_total_temporary_records');
|
||||
if (!array_key_exists('ust_load_id_context_id_ip', $indexesFound)) {
|
||||
$table->index(['load_id', 'context_id', 'ip'], 'ust_load_id_context_id_ip');
|
||||
}
|
||||
});
|
||||
Schema::table('usage_stats_unique_item_investigations_temporary_records', function (Blueprint $table) use ($sm) {
|
||||
$indexesFound = $sm->listTableIndexes('usage_stats_unique_item_investigations_temporary_records');
|
||||
if (!array_key_exists('usii_load_id_context_id_ip', $indexesFound)) {
|
||||
$table->index(['load_id', 'context_id', 'ip'], 'usii_load_id_context_id_ip');
|
||||
}
|
||||
});
|
||||
Schema::table('usage_stats_unique_item_requests_temporary_records', function (Blueprint $table) use ($sm) {
|
||||
$indexesFound = $sm->listTableIndexes('usage_stats_unique_item_requests_temporary_records');
|
||||
if (!array_key_exists('usir_load_id_context_id_ip', $indexesFound)) {
|
||||
$table->index(['load_id', 'context_id', 'ip'], 'usir_load_id_context_id_ip');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/InstallEmailTemplates.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class InstallEmailTemplates
|
||||
*
|
||||
* @brief Install all new email templates for 3.4.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use APP\core\Services;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\db\XMLDAO;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
abstract class InstallEmailTemplates extends Migration
|
||||
{
|
||||
protected array $emailTemplatesInstalled = [];
|
||||
|
||||
abstract protected function getEmailTemplateKeys(): array;
|
||||
abstract protected function getAppVariableNames(): array;
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$xmlDao = new XMLDAO();
|
||||
|
||||
$data = $xmlDao->parseStruct('registry/emailTemplates.xml', ['email']);
|
||||
|
||||
if (!isset($data['email'])) {
|
||||
throw new Exception('Unable to find <email> entries in registry/emailTemplates.xml.');
|
||||
}
|
||||
|
||||
$contextIds = Services::get('context')->getIds();
|
||||
$locales = json_decode(
|
||||
DB::table('site')->pluck('installed_locales')->first()
|
||||
);
|
||||
|
||||
foreach ($data['email'] as $entry) {
|
||||
$attrs = $entry['attributes'];
|
||||
|
||||
if (!in_array($attrs['key'], $this->getEmailTemplateKeys())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DB::table('email_templates_default_data')->where('email_key', $attrs['key'])->exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $attrs['name'] ?? null;
|
||||
$subject = $attrs['subject'] ?? null;
|
||||
$body = $attrs['body'] ?? null;
|
||||
|
||||
if (!$name || !$subject || !$body) {
|
||||
throw new Exception('Failed to install email template ' . $attrs['key'] . '. Missing name, subject or body attribute.');
|
||||
}
|
||||
|
||||
$previous = Locale::getMissingKeyHandler();
|
||||
Locale::setMissingKeyHandler(fn (string $key): string => '');
|
||||
|
||||
foreach ($locales as $locale) {
|
||||
$translatedName = $name ? __($name, [], $locale) : $attrs['key'];
|
||||
$translatedSubject = __($subject, [], $locale);
|
||||
$translatedBody = __($body, [], $locale);
|
||||
|
||||
DB::table('email_templates_default_data')->insert([
|
||||
'email_key' => $attrs['key'],
|
||||
'locale' => $locale,
|
||||
'name' => $translatedName,
|
||||
'subject' => $this->renameApplicationVariables($translatedSubject),
|
||||
'body' => $this->renameApplicationVariables($translatedBody),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->emailTemplatesInstalled[] = $attrs['key'];
|
||||
|
||||
Locale::setMissingKeyHandler($previous);
|
||||
|
||||
if (isset($attrs['alternateTo'])) {
|
||||
$exists = DB::table('email_templates_default_data')
|
||||
->where('email_key', $attrs['alternateTo'])
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
throw new Exception('Tried to install email template `' . $attrs['key'] . '` as an alternate to `' . $attrs['alternateTo'] . '`, but no default template exists with this key.');
|
||||
}
|
||||
|
||||
foreach ($contextIds as $contextId) {
|
||||
DB::table('email_templates')->insert([
|
||||
'email_key' => $attrs['key'],
|
||||
'context_id' => $contextId,
|
||||
'alternate_to' => $attrs['alternateTo'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('email_templates_default_data')
|
||||
->whereIn('email_key', $this->emailTemplatesInstalled)
|
||||
->delete();
|
||||
|
||||
DB::table('email_templates')
|
||||
->whereIn('email_key', $this->emailTemplatesInstalled)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the email template variables that are app-specific
|
||||
*
|
||||
* @param string $string Email template subject or body to be modified
|
||||
*/
|
||||
protected function renameApplicationVariables(string $string): string
|
||||
{
|
||||
$map = $this->getAppVariableNames();
|
||||
if (empty($map)) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
$variables = [];
|
||||
$replacements = [];
|
||||
foreach ($map as $key => $value) {
|
||||
$variables[] = '/\{\$' . $key . '\}/';
|
||||
$replacements[] = '{$' . $value . '}';
|
||||
}
|
||||
|
||||
return preg_replace($variables, $replacements, $string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/MergeLocalesMigration.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 MergeLocalesMigration
|
||||
*
|
||||
* @brief Change Locales from locale_countryCode localization folder notation to locale localization folder notation
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\install\DowngradeNotSupportedException;
|
||||
|
||||
abstract class MergeLocalesMigration extends \PKP\migration\Migration
|
||||
{
|
||||
protected string $CONTEXT_TABLE = '';
|
||||
protected string $CONTEXT_SETTINGS_TABLE = '';
|
||||
protected string $CONTEXT_COLUMN = '';
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// This change fixes the locale column length on the static_page_settings
|
||||
// which in 3.1.0 version was varchar(5).
|
||||
if (Schema::hasTable('static_page_settings')) {
|
||||
Schema::table('static_page_settings', function (Blueprint $table) {
|
||||
$table->string('locale', 14)->change();
|
||||
});
|
||||
}
|
||||
|
||||
if (empty($this->CONTEXT_TABLE) || empty($this->CONTEXT_SETTINGS_TABLE) || empty($this->CONTEXT_COLUMN)) {
|
||||
throw new Exception('Upgrade could not be completed because required properties for the MergeLocalesMigration migration are undefined.');
|
||||
}
|
||||
|
||||
// All _settings tables.
|
||||
foreach ($this->getSettingsTables() as $tableName => [$entityIdColumnName, $primaryKeyColumnName]) {
|
||||
if (!Schema::hasTable($tableName) || !Schema::hasColumn($tableName, 'locale')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (self::getAffectedLocales() as $source => $target) {
|
||||
DB::table($tableName)
|
||||
->where('locale', $source)
|
||||
->update(['locale' => $target]);
|
||||
}
|
||||
}
|
||||
|
||||
// Tables
|
||||
// site
|
||||
$site = DB::table('site')
|
||||
->select(['supported_locales', 'installed_locales', 'primary_locale'])
|
||||
->first();
|
||||
|
||||
$this->updateArrayLocaleNoId($site->supported_locales, 'site', 'supported_locales');
|
||||
$this->updateArrayLocaleNoId($site->installed_locales, 'site', 'installed_locales');
|
||||
$this->updateSingleValueLocaleNoId($site->primary_locale, 'site', 'primary_locale');
|
||||
|
||||
Schema::table('site', function (Blueprint $table) {
|
||||
$table->string('installed_locales')->default('en')->change();
|
||||
});
|
||||
|
||||
// users
|
||||
$migration = $this;
|
||||
$users = DB::table('users')->chunkById(1000, function ($users) use ($migration) {
|
||||
foreach ($users as $user) {
|
||||
$migration->updateArrayLocale($user->locales, 'users', 'locales', 'user_id', $user->user_id);
|
||||
}
|
||||
}, 'user_id');
|
||||
|
||||
// submissions
|
||||
$submissions = DB::table('submissions')->chunkById(1000, function ($submissions) use ($migration) {
|
||||
foreach ($submissions as $submission) {
|
||||
$migration->updateSingleValueLocale($submission->locale, 'submissions', 'locale', 'submission_id', $submission->submission_id);
|
||||
}
|
||||
}, 'submission_id');
|
||||
|
||||
|
||||
// email_templates_default_data
|
||||
$emailTemplatesDefaultData = DB::table('email_templates_default_data')
|
||||
->get();
|
||||
|
||||
foreach ($emailTemplatesDefaultData as $emailTemplatesDefaultDataCurrent) {
|
||||
$this->updateSingleValueLocaleEmailData($emailTemplatesDefaultDataCurrent->locale, 'email_templates_default_data', $emailTemplatesDefaultDataCurrent->email_key, $emailTemplatesDefaultData);
|
||||
}
|
||||
|
||||
Schema::table('email_templates_default_data', function (Blueprint $table) {
|
||||
$table->string('locale')->default('en')->change();
|
||||
});
|
||||
|
||||
// Context
|
||||
$contexts = DB::table($this->CONTEXT_TABLE)
|
||||
->get();
|
||||
|
||||
foreach ($contexts as $context) {
|
||||
$this->updateSingleValueLocale($context->primary_locale, $this->CONTEXT_TABLE, 'primary_locale', $this->CONTEXT_COLUMN, $context->{$this->CONTEXT_COLUMN});
|
||||
}
|
||||
|
||||
$contextSettingsFormLocales = DB::table($this->CONTEXT_SETTINGS_TABLE)
|
||||
->where('setting_name', '=', 'supportedFormLocales')
|
||||
->get();
|
||||
|
||||
foreach ($contextSettingsFormLocales as $contextSettingsFormLocale) {
|
||||
$this->updateArrayLocaleSetting($contextSettingsFormLocale->setting_value, $this->CONTEXT_SETTINGS_TABLE, 'supportedFormLocales', $this->CONTEXT_COLUMN, $contextSettingsFormLocale->{$this->CONTEXT_COLUMN});
|
||||
}
|
||||
|
||||
$contextSettingsFormLocales = DB::table($this->CONTEXT_SETTINGS_TABLE)
|
||||
->where('setting_name', '=', 'supportedLocales')
|
||||
->get();
|
||||
|
||||
foreach ($contextSettingsFormLocales as $contextSettingsFormLocale) {
|
||||
$this->updateArrayLocaleSetting($contextSettingsFormLocale->setting_value, $this->CONTEXT_SETTINGS_TABLE, 'supportedLocales', $this->CONTEXT_COLUMN, $contextSettingsFormLocale->{$this->CONTEXT_COLUMN});
|
||||
}
|
||||
|
||||
$contextSettingsFormLocales = DB::table($this->CONTEXT_SETTINGS_TABLE)
|
||||
->where('setting_name', '=', 'supportedSubmissionLocales')
|
||||
->get();
|
||||
|
||||
foreach ($contextSettingsFormLocales as $contextSettingsFormLocale) {
|
||||
$this->updateArrayLocaleSetting($contextSettingsFormLocale->setting_value, $this->CONTEXT_SETTINGS_TABLE, 'supportedSubmissionLocales', $this->CONTEXT_COLUMN, $contextSettingsFormLocale->{$this->CONTEXT_COLUMN});
|
||||
}
|
||||
|
||||
// plugin_settings
|
||||
// customBlockManager
|
||||
$blockPluginName = 'customblockmanagerplugin';
|
||||
$blockLocalizedSettingNames = ['blockTitle', 'blockContent'];
|
||||
|
||||
$contextIds = DB::table($this->CONTEXT_TABLE)
|
||||
->get()
|
||||
->pluck($this->CONTEXT_COLUMN);
|
||||
|
||||
foreach ($contextIds as $contextId) {
|
||||
$blocks = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', $blockPluginName)
|
||||
->where('setting_name', '=', 'blocks')
|
||||
->where('context_id', '=', $contextId)
|
||||
->get()
|
||||
->pluck('setting_value');
|
||||
|
||||
if (!$blocks->isEmpty()) {
|
||||
$blocksArray = json_decode($blocks[0], true);
|
||||
|
||||
foreach ($blocksArray as $block) {
|
||||
foreach ($blockLocalizedSettingNames as $blockLocalizedSettingName) {
|
||||
$blockLocalizedContent = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', $block)
|
||||
->where('setting_name', '=', $blockLocalizedSettingName)
|
||||
->where('context_id', '=', $contextId)
|
||||
->first();
|
||||
|
||||
if (isset($blockLocalizedContent)) {
|
||||
$this->updateArrayKeysLocaleSetting($blockLocalizedContent->setting_value, 'plugin_settings', 'plugin_setting_id', $blockLocalizedContent->plugin_setting_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pluginSettingsDao = DAORegistry::getDAO('PluginSettingsDAO'); /** @var PluginSettingsDAO $pluginSettingsDao */
|
||||
|
||||
$cache = $pluginSettingsDao->_getCache($contextId, $blockPluginName);
|
||||
$cache->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updateArrayLocaleNoId(string $dbLocales, string $table, string $column)
|
||||
{
|
||||
$siteSupportedLocales = json_decode($dbLocales) ?? [];
|
||||
|
||||
if ($siteSupportedLocales !== false) {
|
||||
$newLocales = [];
|
||||
foreach ($siteSupportedLocales as $siteSupportedLocale) {
|
||||
$updatedLocale = $this->getUpdatedLocale($siteSupportedLocale);
|
||||
|
||||
if (!is_null($updatedLocale)) {
|
||||
if (!in_array($updatedLocale, $newLocales)) {
|
||||
$newLocales[] = $updatedLocale;
|
||||
}
|
||||
} else {
|
||||
$newLocales[] = $siteSupportedLocale;
|
||||
}
|
||||
}
|
||||
|
||||
DB::table($table)
|
||||
->update([
|
||||
$column => $newLocales
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateArrayLocale(string $dbLocales, string $table, string $column, string $tableKeyColumn, int $id)
|
||||
{
|
||||
$siteSupportedLocales = json_decode($dbLocales) ?? [];
|
||||
|
||||
$newLocales = [];
|
||||
foreach ($siteSupportedLocales as $siteSupportedLocale) {
|
||||
$updatedLocale = $this->getUpdatedLocale($siteSupportedLocale);
|
||||
|
||||
if (!is_null($updatedLocale)) {
|
||||
if (!in_array($updatedLocale, $newLocales)) {
|
||||
$newLocales[] = $updatedLocale;
|
||||
}
|
||||
} else {
|
||||
$newLocales[] = $siteSupportedLocale;
|
||||
}
|
||||
}
|
||||
|
||||
DB::table($table)
|
||||
->where($tableKeyColumn, '=', $id)
|
||||
->update([
|
||||
$column => $newLocales
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateArrayLocaleSetting(string $dbLocales, string $table, string $settingValue, string $tableKeyColumn, int $id)
|
||||
{
|
||||
$siteSupportedLocales = json_decode($dbLocales) ?? [];
|
||||
|
||||
if ($siteSupportedLocales !== false) {
|
||||
$newLocales = [];
|
||||
foreach ($siteSupportedLocales as $siteSupportedLocale) {
|
||||
$updatedLocale = $this->getUpdatedLocale($siteSupportedLocale);
|
||||
|
||||
if (!is_null($updatedLocale)) {
|
||||
if (!in_array($updatedLocale, $newLocales)) {
|
||||
$newLocales[] = $updatedLocale;
|
||||
}
|
||||
} else {
|
||||
$newLocales[] = $siteSupportedLocale;
|
||||
}
|
||||
}
|
||||
|
||||
DB::table($table)
|
||||
->where($tableKeyColumn, '=', $id)
|
||||
->where('setting_name', '=', $settingValue)
|
||||
->update([
|
||||
'setting_value' => $newLocales
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateArrayKeysLocaleSetting(string $dbLocales, string $table, string $tableKeyColumn, int $id)
|
||||
{
|
||||
$siteSupportedLocales = json_decode($dbLocales, true) ?? [];
|
||||
|
||||
if ($siteSupportedLocales !== false) {
|
||||
$newLocales = [];
|
||||
foreach (array_keys($siteSupportedLocales) as $siteSupportedLocaleKey) {
|
||||
$updatedLocale = $this->getUpdatedLocale($siteSupportedLocaleKey);
|
||||
|
||||
if (!is_null($updatedLocale)) {
|
||||
$newLocales[$updatedLocale] = $siteSupportedLocales[$siteSupportedLocaleKey];
|
||||
} else {
|
||||
$newLocales[$siteSupportedLocaleKey] = $siteSupportedLocales[$siteSupportedLocaleKey];
|
||||
}
|
||||
}
|
||||
|
||||
$jsonString = json_encode($newLocales);
|
||||
|
||||
DB::table($table)
|
||||
->where($tableKeyColumn, '=', $id)
|
||||
->update([
|
||||
'setting_value' => $jsonString
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateSingleValueLocale(?string $localevalue, string $table, string $column, string $tableKeyColumn, int $id)
|
||||
{
|
||||
if ($localevalue === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$updatedLocale = $this->getUpdatedLocale($localevalue);
|
||||
|
||||
if (!is_null($updatedLocale)) {
|
||||
DB::table($table)
|
||||
->where($tableKeyColumn, '=', $id)
|
||||
->update([
|
||||
$column => $updatedLocale
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateSingleValueLocaleNoId(string $localevalue, string $table, string $column)
|
||||
{
|
||||
$updatedLocale = $this->getUpdatedLocale($localevalue);
|
||||
|
||||
if (!is_null($updatedLocale)) {
|
||||
DB::table($table)
|
||||
->update([
|
||||
$column => $updatedLocale
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateSingleValueLocaleEmailData(string $localevalue, string $table, string $email_key, Collection $allEmailTemplateData)
|
||||
{
|
||||
$localeRows = DB::table($table)
|
||||
->where('email_key', '=', $email_key)
|
||||
->where('locale', '=', $localevalue);
|
||||
|
||||
$stillExists = $localeRows->exists();
|
||||
|
||||
if ($stillExists) {
|
||||
$updatedLocale = $this->getUpdatedLocale($localevalue);
|
||||
|
||||
// if this is null we should do nothing - we are not handling this locale
|
||||
if (!is_null($updatedLocale)) {
|
||||
// if the updatedLocale is the same as the setting's locale we should do nothing
|
||||
// Check if the database already has an updated locale with the same value -
|
||||
$hasAlreadyExistingUpdatedLocale = DB::table($table)
|
||||
->where('email_key', '=', $email_key)
|
||||
->where('locale', '=', $updatedLocale)
|
||||
->exists();
|
||||
|
||||
// if so, it is safe to delete the currently processed value.
|
||||
if ($hasAlreadyExistingUpdatedLocale) {
|
||||
$localeRows->delete();
|
||||
} else {
|
||||
$localeRows->update(['locale' => $updatedLocale]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if no conversion is available or
|
||||
* a key value pair collection that the key is the output locale and the value is the defaultLocale.
|
||||
*/
|
||||
public function getUpdatedLocale(string $localeValue): ?string
|
||||
{
|
||||
$affectedLocales = $this->getAffectedLocales();
|
||||
|
||||
if ($affectedLocales->keys()->contains($localeValue)) {
|
||||
$localeTransformation = $affectedLocales[$localeValue];
|
||||
|
||||
return $localeTransformation;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of settings tables, keyed by table name. Values are [entity_id_column_name, settings_table_id_column_name].
|
||||
*/
|
||||
protected static function getSettingsTables(): Collection
|
||||
{
|
||||
return collect([
|
||||
'announcement_settings' => ['announcement_id', 'announcement_setting_id'],
|
||||
'announcement_type_settings' => ['type_id', 'announcement_type_setting_id'],
|
||||
'author_settings' => ['author_id', 'author_setting_id'],
|
||||
'category_settings' => ['category_id', 'category_setting_id'],
|
||||
'citation_settings' => ['citation_id', 'citation_setting_id'],
|
||||
'controlled_vocab_entry_settings' => ['controlled_vocab_entry_id', 'controlled_vocab_entry_setting_id'],
|
||||
'data_object_tombstone_settings' => ['tombstone_id', 'tombstone_setting_id'],
|
||||
'email_templates_settings' => ['email_id', 'email_template_setting_id'],
|
||||
'event_log_settings' => ['log_id', 'event_log_setting_id'],
|
||||
'filter_settings' => ['filter_id', 'filter_setting_id'],
|
||||
'genre_settings' => ['genre_id', 'genre_setting_id'],
|
||||
'library_file_settings' => ['file_id', 'library_file_setting_id'],
|
||||
'navigation_menu_item_assignment_settings' => ['navigation_menu_item_assignment_id', 'navigation_menu_item_assignment_setting_id'],
|
||||
'navigation_menu_item_settings' => ['navigation_menu_item_id', 'navigation_menu_item_setting_id'],
|
||||
'notification_settings' => ['notification_id', 'notification_setting_id'],
|
||||
'notification_subscription_settings' => ['setting_id', 'notification_subscription_setting_id'],
|
||||
'plugin_settings' => ['context_id', 'plugin_setting_id'],
|
||||
'publication_settings' => ['publication_id', 'publication_setting_id'],
|
||||
'review_form_element_settings' => ['review_form_element_id', 'review_form_element_setting_id'],
|
||||
'review_form_settings' => ['review_form_id', 'review_form_setting_id'],
|
||||
'submission_file_settings' => ['submission_file_id', 'submission_file_setting_id'],
|
||||
'submission_settings' => ['submission_id', 'submission_setting_id'],
|
||||
'user_group_settings' => ['user_group_id', 'user_group_setting_id'],
|
||||
'user_settings' => ['user_id', 'user_setting_id'],
|
||||
'site_settings' => [null, 'site_setting_id'],
|
||||
'funder_settings' => ['funder_id', 'funder_setting_id'],
|
||||
'funder_award_settings' => ['funder_award_id', 'funder_award_setting_id'],
|
||||
'static_page_settings' => ['static_page_id', 'static_page_setting_id'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effected locales along with the corresponding rename for each
|
||||
*/
|
||||
public static function getAffectedLocales(): Collection
|
||||
{
|
||||
return collect([
|
||||
'es_ES' => 'es',
|
||||
'en_US' => 'en',
|
||||
'sr_RS@cyrillic' => 'sr@cyrillic',
|
||||
'sr_RS@latin' => 'sr@latin',
|
||||
'el_GR' => 'el',
|
||||
'de_DE' => 'de',
|
||||
'da_DK' => 'da',
|
||||
'cs_CZ' => 'cs',
|
||||
'ca_ES' => 'ca',
|
||||
'bs_BA' => 'bs',
|
||||
'bg_BG' => 'bg',
|
||||
'be_BY@cyrillic' => 'be@cyrillic',
|
||||
'az_AZ' => 'az',
|
||||
'ar_IQ' => 'ar',
|
||||
'fa_IR' => 'fa',
|
||||
'fi_FI' => 'fi',
|
||||
'gd_GB' => 'gd',
|
||||
'gl_ES' => 'gl',
|
||||
'he_IL' => 'he',
|
||||
'hi_IN' => 'hi',
|
||||
'hr_HR' => 'hr',
|
||||
'hu_HU' => 'hu',
|
||||
'hy_AM' => 'hy',
|
||||
'id_ID' => 'id',
|
||||
'is_IS' => 'is',
|
||||
'it_IT' => 'it',
|
||||
'ja_JP' => 'ja',
|
||||
'ka_GE' => 'ka',
|
||||
'kk_KZ' => 'kk',
|
||||
'ko_KR' => 'ko',
|
||||
'ku_IQ' => 'ku',
|
||||
'lt_LT' => 'lt',
|
||||
'lv_LV' => 'lv',
|
||||
'mk_MK' => 'mk',
|
||||
'mn_MN' => 'mn',
|
||||
'ms_MY' => 'ms',
|
||||
'nb_NO' => 'nb',
|
||||
'nl_NL' => 'nl',
|
||||
'pl_PL' => 'pl',
|
||||
'ro_RO' => 'ro',
|
||||
'ru_RU' => 'ru',
|
||||
'si_LK' => 'si',
|
||||
'sk_SK' => 'sk',
|
||||
'sl_SI' => 'sl',
|
||||
'sv_SE' => 'sv',
|
||||
'tr_TR' => 'tr',
|
||||
'uk_UA' => 'uk',
|
||||
'ur_PK' => 'ur',
|
||||
'uz_UZ@cyrillic' => 'uz@cyrillic',
|
||||
'uz_UZ@latin' => 'uz@latin',
|
||||
'vi_VN' => 'vi',
|
||||
'eu_ES' => 'eu',
|
||||
'sw_KE' => 'sw',
|
||||
'zh_TW' => 'zh_Hant'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/PKPI7014_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 PKPI7014_DoiMigration
|
||||
*
|
||||
* @brief Migrate DOI related fields to the new structures
|
||||
*/
|
||||
|
||||
namespace PKP\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\install\DowngradeNotSupportedException;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
abstract class PKPI7014_DoiMigration extends Migration
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// DOIs
|
||||
Schema::create('dois', function (Blueprint $table) {
|
||||
$table->bigInteger('doi_id')->autoIncrement();
|
||||
|
||||
$table->bigInteger('context_id');
|
||||
$table->foreign('context_id')->references($this->getContextIdColumn())->on($this->getContextTable());
|
||||
$table->index(['context_id'], 'dois_context_id');
|
||||
|
||||
$table->string('doi');
|
||||
$table->smallInteger('status')->default(1);
|
||||
});
|
||||
|
||||
// Settings
|
||||
Schema::create('doi_settings', function (Blueprint $table) {
|
||||
$table->bigIncrements('doi_setting_id');
|
||||
$table->bigInteger('doi_id');
|
||||
$table->foreign('doi_id')->references('doi_id')->on('dois')->cascadeOnDelete();
|
||||
$table->index(['doi_id'], 'doi_settings_doi_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['doi_id', 'locale', 'setting_name'], 'doi_settings_unique');
|
||||
});
|
||||
|
||||
// Add doiId to publication
|
||||
Schema::table('publications', function (Blueprint $table) {
|
||||
$table->bigInteger('doi_id')->nullable();
|
||||
$table->foreign('doi_id')->references('doi_id')->on('dois')->nullOnDelete();
|
||||
$table->index(['doi_id'], 'publications_doi_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*
|
||||
* @throws DowngradeNotSupportedException
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
throw new DowngradeNotSupportedException();
|
||||
}
|
||||
|
||||
protected function migrateExistingDataUp(): void
|
||||
{
|
||||
$this->_migrateDoiSettingsToContext();
|
||||
$this->_transferDefaultPatterns();
|
||||
$this->_migratePublicationDoisUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move DOI settings from plugin_settings to Context (Journal/Press/Server) settings
|
||||
*/
|
||||
protected function _migrateDoiSettingsToContext(): void
|
||||
{
|
||||
// Get plugin_based settings
|
||||
$q = DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'doipubidplugin')
|
||||
->where('context_id', '<>', 0)
|
||||
->select(['context_id','setting_name', 'setting_value']);
|
||||
$results = $q->get();
|
||||
|
||||
$data = new \stdClass();
|
||||
$data->enabledDois = [];
|
||||
$data->doiCreationTime = [];
|
||||
$data->enabledDoiTypes = [];
|
||||
$data->doiPrefix = [];
|
||||
$data->doiSuffixType = [];
|
||||
$data->doiPublicationSuffixPattern = [];
|
||||
$data->doiRepresentationSuffixPattern = [];
|
||||
|
||||
$data = $this->addSuffixPatternsData($data);
|
||||
|
||||
// Map to context-based settings
|
||||
$results->reduce(function ($carry, $item) {
|
||||
switch ($item->setting_name) {
|
||||
case 'enabled':
|
||||
$carry->enabledDois[] = [
|
||||
$this->getContextIdColumn() => $item->context_id,
|
||||
'setting_name' => 'enableDois',
|
||||
'setting_value' => (int) $item->setting_value,
|
||||
];
|
||||
$carry->doiCreationTime[] = [
|
||||
$this->getContextIdColumn() => $item->context_id,
|
||||
'setting_name' => 'doiCreationTime',
|
||||
'setting_value' => 'copyEditCreationTime',
|
||||
];
|
||||
return $carry;
|
||||
case 'enablePublicationDoi':
|
||||
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') {
|
||||
array_push($carry->enabledDoiTypes[$item->context_id]['setting_value'], 'publication');
|
||||
}
|
||||
return $carry;
|
||||
case 'enableRepresentationDoi':
|
||||
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') {
|
||||
array_push($carry->enabledDoiTypes[$item->context_id]['setting_value'], 'representation');
|
||||
}
|
||||
return $carry;
|
||||
case 'doiSuffix':
|
||||
$value = '';
|
||||
switch ($item->setting_value) {
|
||||
case 'default':
|
||||
$value = 'default';
|
||||
break;
|
||||
case 'pattern':
|
||||
$value = 'customPattern';
|
||||
break;
|
||||
case 'customId':
|
||||
$value = 'customId';
|
||||
break;
|
||||
}
|
||||
$carry->doiSuffixType[] = [
|
||||
$this->getContextIdColumn() => $item->context_id,
|
||||
'setting_name' => 'doiSuffixType',
|
||||
'setting_value' => $value,
|
||||
];
|
||||
return $carry;
|
||||
case 'doiPrefix':
|
||||
case 'doiPublicationSuffixPattern':
|
||||
case 'doiRepresentationSuffixPattern':
|
||||
$carry->{$item->setting_name}[] = [
|
||||
$this->getContextIdColumn() => $item->context_id,
|
||||
'setting_name' => $item->setting_name,
|
||||
'setting_value' => $item->setting_value,
|
||||
];
|
||||
return $carry;
|
||||
default:
|
||||
$carry = $this->insertSuffixPatternsData($carry, $item);
|
||||
$carry = $this->insertEnabledDoiTypes($carry, $item);
|
||||
return $carry;
|
||||
}
|
||||
}, $data);
|
||||
|
||||
// Prepare insert statements
|
||||
$insertData = [];
|
||||
foreach ($data->enabledDois as $item) {
|
||||
array_push($insertData, $item);
|
||||
}
|
||||
foreach ($data->doiCreationTime as $item) {
|
||||
array_push($insertData, $item);
|
||||
}
|
||||
foreach ($data->enabledDoiTypes as $item) {
|
||||
$item['setting_value'] = json_encode($item['setting_value']);
|
||||
array_push($insertData, $item);
|
||||
}
|
||||
foreach ($data->doiPrefix as $item) {
|
||||
array_push($insertData, $item);
|
||||
}
|
||||
foreach ($data->doiSuffixType as $item) {
|
||||
array_push($insertData, $item);
|
||||
}
|
||||
foreach ($data->doiPublicationSuffixPattern as $item) {
|
||||
array_push($insertData, $item);
|
||||
}
|
||||
foreach ($data->doiRepresentationSuffixPattern as $item) {
|
||||
array_push($insertData, $item);
|
||||
}
|
||||
|
||||
$insertData = $this->prepareSuffixPatternsForInsert($data, $insertData);
|
||||
|
||||
DB::table($this->getContextSettingsTable())->insert($insertData);
|
||||
|
||||
// Add minimum required DOI settings to context if DOI plugin not previously enabled
|
||||
$missingDoiSettingsInsertStatement = DB::table($this->getContextTable())
|
||||
->select($this->getContextIdColumn())
|
||||
->whereNotIn($this->getContextIdColumn(), function (Builder $q) {
|
||||
$q->select($this->getContextIdColumn())
|
||||
->from($this->getContextSettingsTable())
|
||||
->where('setting_name', '=', 'enableDois');
|
||||
})
|
||||
->get()
|
||||
->reduce(function ($carry, $item) {
|
||||
$carry[] = [
|
||||
$this->getContextIdColumn() => $item->{$this->getContextIdColumn()},
|
||||
'setting_name' => 'enableDois',
|
||||
'setting_value' => 0,
|
||||
];
|
||||
$carry[] = [
|
||||
$this->getContextIdColumn() => $item->{$this->getContextIdColumn()},
|
||||
'setting_name' => 'doiCreationTime',
|
||||
'setting_value' => 'copyEditCreationTime'
|
||||
];
|
||||
$carry[] = [
|
||||
$this->getContextIdColumn() => $item->{$this->getContextIdColumn()},
|
||||
'setting_name' => 'useDefaultDoiSuffix',
|
||||
'setting_value' => 1
|
||||
];
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
DB::table($this->getContextSettingsTable())->insert($missingDoiSettingsInsertStatement);
|
||||
|
||||
// Cleanup old DOI plugin settings
|
||||
DB::table('plugin_settings')
|
||||
->where('plugin_name', '=', 'doipubidplugin')
|
||||
->delete();
|
||||
DB::table('versions')
|
||||
->where('product_type', '=', 'plugins.pubIds')
|
||||
->where('product', '=', 'doi')
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies default patterns to custom pattern fields if no custom patterns have been supplied
|
||||
*/
|
||||
private function _transferDefaultPatterns(): void
|
||||
{
|
||||
// Collect list of all possible custom suffix types for each context
|
||||
$suffixPatternStatuses = DB::table($this->getContextTable())
|
||||
->select($this->getContextIdColumn())
|
||||
->get()
|
||||
->reduce(function ($carry, $item) {
|
||||
$carry[$item->{$this->getContextIdColumn()}] = [];
|
||||
foreach ($this->getSuffixPatternNames() as $suffixPatternName) {
|
||||
$carry[$item->{$this->getContextIdColumn()}][$suffixPatternName] = false;
|
||||
}
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
// Flag those suffix types for each context that are already stored in the database
|
||||
DB::table($this->getContextSettingsTable())
|
||||
->whereIn('setting_name', function (Builder $q) {
|
||||
$q->select('setting_name')
|
||||
->from($this->getContextSettingsTable())
|
||||
->whereIn('setting_name', $this->getSuffixPatternNames())
|
||||
// We don't want to flag null suffix patterns as existing, otherwise the default
|
||||
// pattern would not be added
|
||||
->whereNotNull('setting_value');
|
||||
})
|
||||
->get()
|
||||
->each(function ($item) use (&$suffixPatternStatuses) {
|
||||
$suffixPatternStatuses[$item->{$this->getContextIdColumn()}][$item->setting_name] = true;
|
||||
});
|
||||
|
||||
// Prepare insert statement to add default suffix patterns as custom patterns
|
||||
// where no previous custom pattern existed
|
||||
$insertStatements = [];
|
||||
foreach ($suffixPatternStatuses as $contextId => $suffixPatternStatus) {
|
||||
foreach ($suffixPatternStatus as $suffixPattern => $status) {
|
||||
if ($status) {
|
||||
continue;
|
||||
}
|
||||
$insertStatements[] = [
|
||||
$this->getContextIdColumn() => $contextId,
|
||||
'setting_name' => $suffixPattern,
|
||||
'setting_value' => $this->getSuffixPatternValue($suffixPattern),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, save custom patterns to DB
|
||||
if (!empty($insertStatements)) {
|
||||
DB::table($this->getContextSettingsTable())->upsert($insertStatements, [$this->getContextIdColumn(), 'locale', 'setting_name'], ['setting_value']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move publication DOIs from publication_settings table to DOI objects
|
||||
*/
|
||||
private function _migratePublicationDoisUp(): void
|
||||
{
|
||||
$q = DB::table('submissions', 's')
|
||||
->select(['s.context_id', 'p.publication_id', 'p.doi_id', 'pss.setting_name', 'pss.setting_value'])
|
||||
->leftJoin('publications as p', 'p.submission_id', '=', 's.submission_id')
|
||||
->leftJoin('publication_settings as pss', 'pss.publication_id', '=', 'p.publication_id')
|
||||
->where('pss.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 publication
|
||||
if ($item->doi_id === null) {
|
||||
$doiId = $this->_addDoi($item->context_id, $item->setting_value);
|
||||
|
||||
// Add association to newly created DOI to publication
|
||||
DB::table('publications')
|
||||
->where('publication_id', '=', $item->publication_id)
|
||||
->update(['doi_id' => $doiId]);
|
||||
} else {
|
||||
// Otherwise, update existing DOI object
|
||||
$this->_updateDoi($item->doi_id, $item->context_id, $item->setting_value);
|
||||
}
|
||||
}
|
||||
}, 'p.publication_id', 'publication_id');
|
||||
|
||||
// Remove pub-id::doi settings entry
|
||||
DB::table('publication_settings')
|
||||
->where('setting_name', '=', 'pub-id::doi')
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DOI object for a given context ID and DOI
|
||||
*/
|
||||
protected function _addDoi(string $contextId, string $doi): int
|
||||
{
|
||||
return DB::table('dois')
|
||||
->insertGetId(
|
||||
[
|
||||
'context_id' => $contextId,
|
||||
'doi' => $doi,
|
||||
], 'doi_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the context ID and doi for a given DOI object
|
||||
*/
|
||||
protected function _updateDoi(int $doiId, string $contextId, string $doi): int
|
||||
{
|
||||
return DB::table('dois')
|
||||
->where('doi_id', '=', $doiId)
|
||||
->update(
|
||||
[
|
||||
'context_id' => $contextId,
|
||||
'doi' => $doi
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets app-specific context table name, e.g. journals
|
||||
*/
|
||||
abstract protected function getContextTable(): string;
|
||||
|
||||
/**
|
||||
* Gets app-specific context_id column, e.g. journal_id
|
||||
*/
|
||||
abstract protected function getContextIdColumn(): string;
|
||||
|
||||
/**
|
||||
* Gets app-specific context settings table, e.g. journal_settings
|
||||
*/
|
||||
abstract protected function getContextSettingsTable(): string;
|
||||
|
||||
/**
|
||||
* Adds app-specific suffix patterns to data collector stdClass
|
||||
*/
|
||||
abstract protected function addSuffixPatternsData(\stdClass $data): \stdClass;
|
||||
|
||||
/**
|
||||
* Adds suffix pattern settings from DB into reducer's data
|
||||
*/
|
||||
abstract protected function insertSuffixPatternsData(\stdClass $carry, \stdClass $item): \stdClass;
|
||||
|
||||
/**
|
||||
* Adds insert-ready statements for all applicable suffix pattern items
|
||||
*/
|
||||
abstract protected function prepareSuffixPatternsForInsert(\stdClass $processedData, array $insertData): array;
|
||||
|
||||
/**
|
||||
* Add app-specific enabled DOI types for insert into DB
|
||||
*/
|
||||
abstract protected function insertEnabledDoiTypes(\stdClass $carry, \stdClass $item): \stdClass;
|
||||
|
||||
/**
|
||||
* Get an array with the keys for each suffix pattern type
|
||||
*/
|
||||
abstract protected function getSuffixPatternNames(): array;
|
||||
|
||||
/**
|
||||
* Returns the default pattern for the given suffix pattern type
|
||||
*/
|
||||
abstract protected function getSuffixPatternValue(string $suffixPatternName): string;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/UpgradeMigration.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 UpgradeMigration
|
||||
*
|
||||
* @brief Describe upgrade/downgrade operations from 3.3.x to 3.4.0.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpgradeMigration extends \PKP\migration\Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// pkp/pkp-lib#6093: Delete review_assignment entries that correspond to nonexistent submissions.
|
||||
$orphanedIds = DB::table('review_assignments AS ra')->leftJoin('submissions AS s', 'ra.submission_id', '=', 's.submission_id')->whereNull('s.submission_id')->pluck('ra.submission_id', 'ra.review_id');
|
||||
foreach ($orphanedIds as $reviewId => $submissionId) {
|
||||
$this->_installer->log("Removing orphaned review_assignments entry ID {$reviewId} with submission_id {$submissionId}");
|
||||
DB::table('review_assignments')->where('review_id', '=', $reviewId)->delete();
|
||||
}
|
||||
|
||||
// pkp/pkp-lib#6093: Delete review_assignment entries that correspond to nonexistent reviewers.
|
||||
$orphanedIds = DB::table('review_assignments AS ra')->leftJoin('users AS u', 'ra.reviewer_id', '=', 'u.user_id')->whereNull('u.user_id')->pluck('ra.reviewer_id', 'ra.review_id');
|
||||
foreach ($orphanedIds as $reviewId => $userId) {
|
||||
$this->_installer->log("Removing orphaned review_assignments entry ID {$reviewId} with reviewer_id {$userId}");
|
||||
DB::table('review_assignments')->where('review_id', '=', $reviewId)->delete();
|
||||
}
|
||||
|
||||
// pkp/pkp-lib#6093: Delete review_assignment entries that correspond to nonexistent review rounds.
|
||||
$orphanedIds = DB::table('review_assignments AS ra')->leftJoin('review_rounds AS rr', 'ra.review_round_id', '=', 'rr.review_round_id')->whereNull('rr.review_round_id')->pluck('ra.review_round_id', 'ra.review_id');
|
||||
foreach ($orphanedIds as $reviewId => $reviewRoundId) {
|
||||
$this->_installer->log("Removing orphaned review_assignments entry ID {$reviewId} with review_round_id {$reviewRoundId}");
|
||||
DB::table('review_assignments')->where('review_id', '=', $reviewId)->delete();
|
||||
}
|
||||
|
||||
// pkp/pkp-lib#6093: Delete review_assignment entries that correspond to nonexistent review forms.
|
||||
$orphanedIds = DB::table('review_assignments AS ra')->leftJoin('review_forms AS rf', 'ra.review_form_id', '=', 'rf.review_form_id')->whereNull('rf.review_form_id')->whereNotNull('ra.review_form_id')->pluck('ra.review_form_id', 'ra.review_id');
|
||||
foreach ($orphanedIds as $reviewId => $reviewFormId) {
|
||||
$this->_installer->log("Using default review form for review with ID {$reviewId} which refers to nonexistent review_form_id {$reviewFormId}");
|
||||
DB::table('review_assignments')->where('review_id', '=', $reviewId)->update(['review_form_id' => null]);
|
||||
}
|
||||
|
||||
// pkp/pkp-lib#6093: Set up foreign key constraints
|
||||
Schema::table('review_assignments', function (Blueprint $table) {
|
||||
$table->foreign('submission_id')->references('submission_id')->on('submissions');
|
||||
$table->foreign('reviewer_id')->references('user_id')->on('users');
|
||||
$table->foreign('review_round_id')->references('review_round_id')->on('review_rounds');
|
||||
$table->foreign('review_form_id')->references('review_form_id')->on('review_forms');
|
||||
|
||||
// Normally reviewer can't be assigned twice on the same review round.
|
||||
// HOWEVER, if two reviewer user accounts are subsequently merged, both will keep
|
||||
// separate review assignments but the reviewer_id will become the same!
|
||||
// (https://github.com/pkp/pkp-lib/issues/7678)
|
||||
$table->index(['review_round_id', 'reviewer_id'], 'review_assignment_reviewer_round');
|
||||
});
|
||||
|
||||
// pkp/pkp-lib#6685: Drop old tombstones table in OJS and OPS
|
||||
Schema::dropIfExists('submission_tombstones');
|
||||
|
||||
// pkp/pkp-lib#7246: Allow default null values for the last login date
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dateTime('date_last_login')->nullable()->change();
|
||||
});
|
||||
|
||||
// pkp/pkp-lib#7246: Remove setting_type in user_settings
|
||||
if (Schema::hasColumn('user_settings', 'setting_type')) {
|
||||
Schema::table('user_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('setting_type');
|
||||
});
|
||||
}
|
||||
|
||||
// pkp/pkp-lib#8093: Remove setting_type in user_group_settings
|
||||
if (Schema::hasColumn('user_group_settings', 'setting_type')) {
|
||||
Schema::table('user_group_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('setting_type');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the downgrades
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('review_assignments', function (Blueprint $table) {
|
||||
$table->dropForeign(['reviewer_id']);
|
||||
$table->dropForeign(['submission_id']);
|
||||
$table->dropForeign(['review_round_id']);
|
||||
$table->dropForeign(['review_form_id']);
|
||||
});
|
||||
Schema::table('review_assignments', function (Blueprint $table) {
|
||||
$table->dropIndex('review_assignment_reviewer_round');
|
||||
});
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dateTime('date_last_login')->nullable(false)->default(null)->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_4_0/jobs/FixRegionCodes.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class FixRegionCodes
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Loads the FIPS-ISO mapping for the given country to the temporary table and fixes the wrong region codes, using the temporary table of FIPS-ISO mapping.
|
||||
* If it is the last job to run, it removes the temporary indexes and the temporary table.
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_4_0\jobs;
|
||||
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\config\Config;
|
||||
use PKP\core\Core;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class FixRegionCodes extends BaseJob
|
||||
{
|
||||
use Batchable;
|
||||
|
||||
/** The country ISO code */
|
||||
protected string $country;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(string $country)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->country = $country;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// read the FIPS to ISO mappings for the given country and isert them into the temporary table
|
||||
$mappings = include Core::getBaseDir() . '/' . PKP_LIB_PATH . '/lib/regionMapping.php';
|
||||
foreach ($mappings[$this->country] as $fips => $iso) {
|
||||
DB::table('region_mapping_tmp')->insert([
|
||||
'country' => $this->country,
|
||||
'fips' => $fips,
|
||||
'iso' => $iso
|
||||
]);
|
||||
}
|
||||
|
||||
// update region code from FIPS to ISP, according to the entries in the table region_mapping_tmp
|
||||
// Laravel join+update does not work well with PostgreSQL, so use the direct SQLs
|
||||
// daily
|
||||
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
|
||||
DB::statement('
|
||||
UPDATE metrics_submission_geo_daily AS gd
|
||||
SET region = rm.iso
|
||||
FROM region_mapping_tmp AS rm
|
||||
WHERE gd.country = rm.country AND gd.region = rm.fips
|
||||
');
|
||||
} else {
|
||||
DB::statement('
|
||||
UPDATE metrics_submission_geo_daily gd
|
||||
INNER JOIN region_mapping_tmp rm ON (rm.country = gd.country AND rm.fips = gd.region)
|
||||
SET gd.region = rm.iso
|
||||
');
|
||||
}
|
||||
// montly
|
||||
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
|
||||
DB::statement('
|
||||
UPDATE metrics_submission_geo_monthly AS gm
|
||||
SET region = rm.iso
|
||||
FROM region_mapping_tmp AS rm
|
||||
WHERE gm.country = rm.country AND gm.region = rm.fips
|
||||
');
|
||||
} else {
|
||||
DB::statement('
|
||||
UPDATE metrics_submission_geo_monthly gm
|
||||
INNER JOIN region_mapping_tmp rm ON (rm.country = gm.country AND rm.fips = gm.region)
|
||||
SET gm.region = rm.iso
|
||||
');
|
||||
}
|
||||
|
||||
// clear region_mapping_tmp table
|
||||
DB::table('region_mapping_tmp')->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_5_0/I9253_SiteAnnouncements.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I9253_SiteAnnouncements
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_5_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I9253_SiteAnnouncements extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('announcements', function (Blueprint $table) {
|
||||
$table->bigInteger('assoc_id')->nullable()->change();
|
||||
});
|
||||
Schema::table('announcement_types', function (Blueprint $table) {
|
||||
$table->bigInteger('context_id')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('announcements', function (Blueprint $table) {
|
||||
$table->bigInteger('assoc_id')->nullable(false)->change();
|
||||
});
|
||||
Schema::table('announcement_types', function (Blueprint $table) {
|
||||
$table->bigInteger('context_id')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/migration/upgrade/v3_5_0/I9262_Highlights.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class I9262_Highlights
|
||||
*/
|
||||
|
||||
namespace PKP\migration\upgrade\v3_5_0;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use PKP\migration\Migration;
|
||||
|
||||
class I9262_Highlights extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('highlights', function (Blueprint $table) {
|
||||
$table->comment('Highlights are featured items that can be presented to users, for example on the homepage.');
|
||||
$table->bigInteger('highlight_id')->autoIncrement();
|
||||
$table->bigInteger('context_id')->nullable();
|
||||
$contextDao = \APP\core\Application::getContextDAO();
|
||||
$table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade');
|
||||
$table->index(['context_id'], 'highlights_context_id');
|
||||
|
||||
$table->bigInteger('sequence');
|
||||
$table->string('url', 2047);
|
||||
});
|
||||
|
||||
Schema::create('highlight_settings', function (Blueprint $table) {
|
||||
$table->comment('More data about highlights, including localized properties like title and description.');
|
||||
$table->bigIncrements('highlight_setting_id');
|
||||
$table->bigInteger('highlight_id');
|
||||
$table->foreign('highlight_id')->references('highlight_id')->on('highlights')->onDelete('cascade');
|
||||
$table->index(['highlight_id'], 'highlight_settings_highlight_id');
|
||||
|
||||
$table->string('locale', 14)->default('');
|
||||
$table->string('setting_name', 255);
|
||||
$table->mediumText('setting_value')->nullable();
|
||||
|
||||
$table->unique(['highlight_id', 'locale', 'setting_name'], 'highlight_settings_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('highlight_settings');
|
||||
Schema::drop('highlights');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user