<?php
/**
* @file classes/install/Installer.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 Installer
*
* @ingroup install
*
* @brief Base class for install and upgrade scripts.
*/
namespace PKP\install;
use adoSchema;
use APP\core\Application;
use APP\facades\Repo;
use APP\file\LibraryFileManager;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use PKP\cache\CacheManager;
use PKP\config\Config;
use PKP\context\Context;
use PKP\context\LibraryFile;
use PKP\core\Core;
use PKP\core\PKPContainer;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\db\DAOResultFactory;
use PKP\db\DBDataXMLParser;
use PKP\db\XMLDAO;
use PKP\facades\Locale;
use PKP\file\FileManager;
use PKP\filter\FilterHelper;
use PKP\navigationMenu\NavigationMenuDAO;
use PKP\notification\PKPNotification;
use PKP\plugins\Hook;
use PKP\plugins\PluginRegistry;
use PKP\security\Role;
use PKP\site\SiteDAO;
use PKP\site\Version;
use PKP\site\VersionCheck;
use PKP\site\VersionDAO;
use PKP\xml\PKPXMLParser;
use PKP\xml\XMLNode;
class Installer
{
// Installer error codes
public const INSTALLER_ERROR_GENERAL = 1;
public const INSTALLER_ERROR_DB = 2;
public const INSTALLER_DATA_DIR = 'dbscripts/xml';
public const INSTALLER_DEFAULT_LOCALE = 'en';
/** @var string descriptor path (relative to INSTALLER_DATA_DIR) */
public $descriptor;
/** @var bool indicates if a plugin is being installed (thus modifying the descriptor path) */
public $isPlugin;
/** @var array installation parameters */
public $params;
/** @var Version currently installed version */
public $currentVersion;
/** @var Version version after installation */
public $newVersion;
/** @var string default locale */
public $locale;
/** @var array available locales */
public $installedLocales;
/** @var DBDataXMLParser database data parser */
public $dataXMLParser;
/** @var array installer actions to be performed */
public $actions;
/** @var array SQL statements for database installation */
public $sql;
/** @var array installation notes */
public $notes;
/** @var string contents of the updated config file */
public $configContents;
/** @var bool indicating if config file was written or not */
public $wroteConfig;
/** @var int error code (null | INSTALLER_ERROR_GENERAL | INSTALLER_ERROR_DB) */
public $errorType;
/** @var string the error message, if an installation error has occurred */
public $errorMsg;
/** @var object logging object */
public $logger;
/** @var array List of migrations executed already */
public $migrations = [];
/** @var array List of email template variables to rename. App-specific */
protected $appEmailTemplateVariableNames = [];
/**
* Constructor.
*
* @param string $descriptor descriptor path
* @param array $params installer parameters
* @param bool $isPlugin true iff a plugin is being installed
*/
public function __construct($descriptor, $params = [], $isPlugin = false)
{
// Load all plugins. If any of them use installer hooks,
// they'll need to be loaded here.
PluginRegistry::loadAllPlugins();
$this->isPlugin = $isPlugin;
// Give the Hook registry the opportunity to override this
// method or alter its parameters.
if (!Hook::call('Installer::Installer', [$this, &$descriptor, &$params])) {
$this->descriptor = $descriptor;
$this->params = $params;
$this->actions = [];
$this->sql = [];
$this->notes = [];
$this->wroteConfig = true;
}
}
/**
* Returns true iff this is an upgrade process.
*/
public function isUpgrade()
{
exit('ABSTRACT CLASS');
}
/**
* Destroy / clean-up after the installer.
*/
public function destroy()
{
Hook::call('Installer::destroy', [$this]);
}
/**
* Pre-installation.
*
* @return bool
*/
public function preInstall()
{
$this->log('pre-install');
if (!isset($this->currentVersion)) {
// Retrieve the currently installed version
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */
$this->currentVersion = $versionDao->getCurrentVersion();
}
if (!isset($this->locale)) {
$this->locale = Locale::getLocale();
}
if (!isset($this->installedLocales)) {
$this->installedLocales = array_keys(Locale::getLocales());
}
if (!isset($this->dataXMLParser)) {
$this->dataXMLParser = new DBDataXMLParser();
}
$result = true;
Hook::call('Installer::preInstall', [$this, &$result]);
return $result;
}
/**
* Installation.
*
* @return bool
*/
public function execute()
{
// Ensure that the installation will not get interrupted if it takes
// longer than max_execution_time (php.ini). Note that this does not
// work under safe mode.
@set_time_limit(0);
if (!$this->preInstall()) {
return false;
}
if (!$this->parseInstaller()) {
return false;
}
if (!$this->executeInstaller()) {
return false;
}
if (!$this->postInstall()) {
return false;
}
return $this->updateVersion();
}
/**
* Post-installation.
*
* @return bool
*/
public function postInstall()
{
$this->log('post-install');
$result = true;
Hook::call('Installer::postInstall', [$this, &$result]);
return $result;
}
/**
* Record message to installation log.
*
* @param string $message
*/
public function log($message)
{
if (isset($this->logger)) {
call_user_func([$this->logger, 'log'], $message);
}
}
//
// Main actions
//
/**
* Parse the installation descriptor XML file.
*
* @return bool
*/
public function parseInstaller()
{
// Read installation descriptor file
$this->log(sprintf('load: %s', $this->descriptor));
$xmlParser = new PKPXMLParser();
$installPath = $this->isPlugin ? $this->descriptor : self::INSTALLER_DATA_DIR . "/{$this->descriptor}";
$installTree = $xmlParser->parse($installPath);
if (!$installTree) {
// Error reading installation file
$this->setError(self::INSTALLER_ERROR_GENERAL, 'installer.installFileError');
return false;
}
$versionString = $installTree->getAttribute('version');
if (isset($versionString)) {
$this->newVersion = Version::fromString($versionString);
} else {
$this->newVersion = $this->currentVersion;
}
// Parse descriptor
$this->parseInstallNodes($installTree);
$result = $this->getErrorType() == 0;
Hook::call('Installer::parseInstaller', [$this, &$result]);
return $result;
}
/**
* Execute the installer actions.
*
* @return bool
*/
public function executeInstaller()
{
$this->log(sprintf('version: %s', $this->newVersion->getVersionString(false)));
foreach ($this->actions as $action) {
if (!$this->executeAction($action)) {
return false;
}
}
$result = true;
Hook::call('Installer::executeInstaller', [$this, &$result]);
return $result;
}
/**
* Update the version number.
*
* @return bool
*/
public function updateVersion()
{
if ($this->newVersion->compare($this->currentVersion) > 0) {
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */
if (!$versionDao->insertVersion($this->newVersion)) {
return false;
}
}
$result = true;
Hook::call('Installer::updateVersion', [$this, &$result]);
return $result;
}
//
// Installer Parsing
//
/**
* Parse children nodes in the install descriptor.
*
* @param XMLNode $installTree
*/
public function parseInstallNodes($installTree)
{
foreach ($installTree->getChildren() as $node) {
switch ($node->getName()) {
case 'schema':
case 'data':
case 'code':
case 'migration':
case 'note':
$this->addInstallAction($node);
break;
case 'upgrade':
$minVersion = $node->getAttribute('minversion');
$maxVersion = $node->getAttribute('maxversion');
if ((!isset($minVersion) || $this->currentVersion->compare($minVersion) >= 0) && (!isset($maxVersion) || $this->currentVersion->compare($maxVersion) <= 0)) {
$this->parseInstallNodes($node);
}
break;
}
}
}
/**
* Add an installer action from the descriptor.
*
* @param XMLNode $node
*/
public function addInstallAction($node)
{
$fileName = $node->getAttribute('file');
if (!isset($fileName)) {
$this->actions[] = ['type' => $node->getName(), 'file' => null, 'attr' => $node->getAttributes()];
} elseif (strstr($fileName, '{$installedLocale}')) {
// Filename substitution for locales
foreach ($this->installedLocales as $thisLocale) {
$newFileName = str_replace('{$installedLocale}', $thisLocale, $fileName);
$this->actions[] = ['type' => $node->getName(), 'file' => $newFileName, 'attr' => $node->getAttributes()];
}
} else {
$newFileName = str_replace('{$locale}', $this->locale, $fileName);
if (!file_exists($newFileName)) {
// Use version from default locale if data file is not available in the selected locale
$newFileName = str_replace('{$locale}', self::INSTALLER_DEFAULT_LOCALE, $fileName);
}
$this->actions[] = ['type' => $node->getName(), 'file' => $newFileName, 'attr' => $node->getAttributes()];
}
}
//
// Installer Execution
//
/**
* Execute a single installer action.
*
* @param array $action
*
* @return bool
*/
public function executeAction($action)
{
switch ($action['type']) {
case 'schema':
$fileName = $action['file'];
$this->log(sprintf('schema: %s', $action['file']));
require_once('lib/pkp/lib/vendor/adodb/adodb-php/adodb.inc.php');
require_once('./lib/pkp/lib/vendor/adodb/adodb-php/adodb-xmlschema.inc.php');
$dbconn = ADONewConnection(Config::getVar('database', 'driver'));
$port = Config::getVar('database', 'port');
$dbconn->Connect(
Config::getVar('database', 'host') . ($port ? ':' . $port : ''),
Config::getVar('database', 'username'),
Config::getVar('database', 'password'),
Config::getVar('database', 'name')
);
$schemaXMLParser = new adoSchema($dbconn);
$dict = $schemaXMLParser->dict;
$sql = $schemaXMLParser->parseSchema($fileName);
$schemaXMLParser->destroy();
if ($sql) {
return $this->executeSQL($sql);
}
$this->setError(self::INSTALLER_ERROR_DB, str_replace('{$file}', $fileName, __('installer.installParseDBFileError')));
return false;
case 'data':
$fileName = $action['file'];
$condition = $action['attr']['condition'] ?? null;
$includeAction = true;
if ($condition) {
// Create a new scope to evaluate the condition
$evalFunction = function ($installer, $action) use ($condition) {
return eval($condition);
};
$includeAction = $evalFunction($this, $action);
}
$this->log('data: ' . $action['file'] . ($includeAction ? '' : ' (skipped)'));
if (!$includeAction) {
break;
}
$sql = $this->dataXMLParser->parseData($fileName);
// We might get an empty SQL if the upgrade script has
// been executed before.
if ($sql) {
return $this->executeSQL($sql);
}
break;
case 'migration':
assert(isset($action['attr']['class']));
$fullClassName = $action['attr']['class'];
if (strpos($fullClassName, '\\') !== false) {
// Migration is specified fully-qualified PHP class name; allow autoloading
$this->log(sprintf('migration: %s', $fullClassName));
$migration = new $fullClassName($this, $action['attr']);
} else {
// Migration is specified using old-style class.name.like.this
// This behaviour is DEPRECATED as of 3.4.0
import($fullClassName);
$shortClassName = substr($fullClassName, strrpos($fullClassName, '.') + 1);
$this->log(sprintf('migration: %s', $shortClassName));
$migration = new $shortClassName($this, $action['attr']);
}
try {
$migration->up();
$this->migrations[] = $migration;
} catch (Exception $e) {
// Log an error message
$this->setError(
self::INSTALLER_ERROR_DB,
Config::getVar('debug', 'show_stacktrace') ? (string) $e : $e->getMessage()
);
// Back out already-executed migrations.
while ($previousMigration = array_pop($this->migrations)) {
try {
$this->log(sprintf('revert migration: %s', get_class($previousMigration)));
$previousMigration->down();
} catch (DowngradeNotSupportedException $e) {
$this->log(sprintf('downgrade for "%s" unsupported: %s', get_class($previousMigration), $e->getMessage()));
break;
} catch (Exception $e) {
$this->log(sprintf('error while downgrading "%s": %s', get_class($previousMigration), Config::getVar('debug', 'show_stacktrace') ? (string) $e : $e->getMessage()));
break;
}
}
return false;
}
return true;
case 'code':
$condition = $action['attr']['condition'] ?? null;
$includeAction = true;
if ($condition) {
// Create a new scope to evaluate the condition
$evalFunction = function ($installer, $action) use ($condition) {
return eval($condition);
};
$includeAction = $evalFunction($this, $action);
}
$this->log(sprintf('code: %s %s::%s' . ($includeAction ? '' : ' (skipped)'), $action['file'] ?? 'Installer', $action['attr']['class'] ?? 'Installer', $action['attr']['function']));
if (!$includeAction) {
return true;
} // Condition not met; skip the action.
if (isset($action['file'])) {
require_once($action['file']);
}
if (isset($action['attr']['class'])) {
return call_user_func([$action['attr']['class'], $action['attr']['function']], $this, $action['attr']);
}
return call_user_func([$this, $action['attr']['function']], $this, $action['attr']);
case 'note':
$this->log(sprintf('note: %s', $action['file']));
$this->notes[] = join('', file($action['file']));
break;
}
return true;
}
/**
* Execute an SQL statement.
*
*
* @return bool
*/
public function executeSQL($sql)
{
if (is_array($sql)) {
foreach ($sql as $stmt) {
if (!$this->executeSQL($stmt)) {
return false;
}
}
} else {
try {
DB::affectingStatement($sql);
} catch (Exception $e) {
$this->setError(self::INSTALLER_ERROR_DB, $e->getMessage());
return false;
}
}
return true;
}
/**
* Update the specified configuration parameters.
*
* @param array $configParams
*
* @return bool
*/
public function updateConfig($configParams)
{
// Update config file
$configParser = new \PKP\config\ConfigParser();
if (!$configParser->updateConfig(Config::getConfigFileName(), $configParams)) {
// Error reading config file
$this->setError(self::INSTALLER_ERROR_GENERAL, 'installer.configFileError');
return false;
}
$this->configContents = $configParser->getFileContents();
if (!$configParser->writeConfig(Config::getConfigFileName())) {
$this->wroteConfig = false;
}
return true;
}
//
// Accessors
//
/**
* Get the value of an installation parameter.
*
* @param string $name
*/
public function getParam($name)
{
return $this->params[$name] ?? null;
}
/**
* Return currently installed version.
*
* @return Version
*/
public function getCurrentVersion()
{
return $this->currentVersion;
}
/**
* Return new version after installation.
*
* @return Version
*/
public function getNewVersion()
{
return $this->newVersion;
}
/**
* Get the set of SQL statements required to perform the install.
*
* @return array
*/
public function getSQL()
{
return $this->sql;
}
/**
* Get the set of installation notes.
*
* @return array
*/
public function getNotes()
{
return $this->notes;
}
/**
* Get the contents of the updated configuration file.
*
* @return string
*/
public function getConfigContents()
{
return $this->configContents;
}
/**
* Check if installer was able to write out new config file.
*
* @return bool
*/
public function wroteConfig()
{
return $this->wroteConfig;
}
/**
* Return the error code.
* Valid return values are:
* - 0 = no error
* - INSTALLER_ERROR_GENERAL = general installation error.
* - INSTALLER_ERROR_DB = database installation error
*
* @return int
*/
public function getErrorType()
{
return $this->errorType ?? 0;
}
/**
* The error message, if an error has occurred.
* In the case of a database error, an unlocalized string containing the error message is returned.
* For any other error, a localization key for the error message is returned.
*
* @return string
*/
public function getErrorMsg()
{
return $this->errorMsg;
}
/**
* Return the error message as a localized string.
*
* @return string.
*/
public function getErrorString()
{
switch ($this->getErrorType()) {
case self::INSTALLER_ERROR_DB:
return 'DB: ' . $this->getErrorMsg();
default:
return __($this->getErrorMsg());
}
}
/**
* Set the error type and messgae.
*
* @param int $type
* @param string $msg Text message (INSTALLER_ERROR_DB) or locale key (otherwise)
*/
public function setError($type, $msg)
{
$this->errorType = $type;
$this->errorMsg = $msg;
}
/**
* Set the logger for this installer.
*
* @param object $logger
*/
public function setLogger($logger)
{
$this->logger = $logger;
}
/**
* Clear the data cache files (needed because of direct tinkering
* with settings tables)
*
* @return bool
*/
public function clearDataCache()
{
// Clear the CacheManager's caches
$cacheManager = CacheManager::getManager();
$cacheManager->flush(null, CACHE_TYPE_FILE);
$cacheManager->flush(null, CACHE_TYPE_OBJECT);
//clear laravel cache
$cacheManager = PKPContainer::getInstance()['cache'];
$cacheManager->store()->flush();
return true;
}
/**
* Set the current version for this installer.
*
* @param Version $version
*/
public function setCurrentVersion($version)
{
$this->currentVersion = $version;
}
/**
* For upgrade: install email templates and data
*
* @deprecated 3.4 Do not use <code function="installEmailTemplate" ...>
* in upgrade scripts to 3.4 and later versions. This is no longer in
* sync with the PKP\emailTemplates\DAO methods to install email templates.
*
* @param object $installer
* @param array $attr Attributes: array containing
* 'key' => 'EMAIL_KEY_HERE',
* 'locales' => 'en,fr_CA,...'
*/
public function installEmailTemplate($installer, $attr)
{
$locales = explode(',', $attr['locales'] ?? '');
$emailKey = $attr['key'] ?? '';
if (!$emailKey) {
throw new Exception('Tried to install email template but no template key provided.');
}
$xmlDao = new XMLDAO();
$data = $xmlDao->parseStruct('registry/emailTemplates.xml', ['email']);
if (!isset($data['email'])) {
return false;
}
if (empty($locales)) {
$siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */
$site = $siteDao->getSite(); /** @var \PKP\site\Site $site */
$locales = $site->getInstalledLocales();
}
// filter out any invalid locales that is not supported by site
$allLocales = array_keys(Locale::getLocales());
if (!empty($invalidLocales = array_diff($locales, $allLocales))) {
$locales = array_diff($locales, $invalidLocales);
}
foreach ($data['email'] as $entry) {
$attrs = $entry['attributes'];
if ($emailKey && $emailKey != $attrs['key']) {
continue;
}
if (DB::table('email_templates_default_data')->where('email_key', $attrs['key'])->exists()) {
continue;
}
$subject = $attrs['subject'] ?? null;
$body = $attrs['body'] ?? null;
if ($subject && $body) {
foreach ($locales as $locale) {
DB::table('email_templates_default_data')
->where('email_key', $attrs['key'])
->where('locale', $locale)
->delete();
$previous = Locale::getMissingKeyHandler();
Locale::setMissingKeyHandler(fn (string $key): string => '');
$translatedSubject = __($subject, [], $locale);
$translatedBody = __($body, [], $locale);
Locale::setMissingKeyHandler($previous);
if ($translatedSubject !== null && $translatedBody !== null) {
DB::table('email_templates_default_data')->insert([
'email_key' => $attrs['key'],
'locale' => $locale,
'subject' => $this->renameEmailTemplateVariables($translatedSubject),
'body' => $this->renameEmailTemplateVariables($translatedBody),
]);
}
}
}
}
return true;
}
/**
* @deprecated 3.4
* @see self::installEmailTemplate()
*/
protected function renameEmailTemplateVariables($string): string
{
if (empty($this->appEmailTemplateVariableNames)) {
return $string;
}
$variables = [];
$replacements = [];
foreach ($this->appEmailTemplateVariableNames as $key => $value) {
$variables[] = '/\{\$' . $key . '\}/';
$replacements[] = '{$' . $value . '}';
}
return preg_replace($variables, $replacements, $string);
}
/**
* Install the given filter configuration file.
*
* @param string $filterConfigFile
*
* @return bool true when successful, otherwise false
*/
public function installFilterConfig($filterConfigFile)
{
static $filterHelper = false;
// Parse the filter configuration.
$xmlParser = new PKPXMLParser();
$tree = $xmlParser->parse($filterConfigFile);
// Validate the filter configuration.
if (!$tree) {
return false;
}
// Get the filter helper.
if ($filterHelper === false) {
$filterHelper = new FilterHelper();
}
// Are there any filter groups to be installed?
$filterGroupsNode = $tree->getChildByName('filterGroups');
if ($filterGroupsNode instanceof XMLNode) {
$filterHelper->installFilterGroups($filterGroupsNode);
}
// Are there any filters to be installed?
$filtersNode = $tree->getChildByName('filters');
if ($filtersNode instanceof XMLNode) {
foreach ($filtersNode->getChildren() as $filterNode) { /** @var XMLNode $filterNode */
$filterHelper->configureFilter($filterNode);
}
}
return true;
}
/**
* Check to see whether a column exists.
* Used in installer XML in conditional checks on <data> nodes.
*
* @param string $tableName
* @param string $columnName
*
* @return bool
*/
public function columnExists($tableName, $columnName)
{
$schema = DB::getDoctrineSchemaManager();
// Make sure the table exists
$tables = $schema->listTableNames();
if (!in_array($tableName, $tables)) {
return false;
}
return Schema::hasColumn($tableName, $columnName);
}
/**
* Check to see whether a table exists.
* Used in installer XML in conditional checks on <data> nodes.
*
* @param string $tableName
*
* @return bool
*/
public function tableExists($tableName)
{
$tables = DB::getDoctrineSchemaManager()->listTableNames();
return in_array($tableName, $tables);
}
/**
* Insert or update plugin data in versions
* and plugin_settings tables.
*
* @return bool
*/
public function addPluginVersions()
{
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */
$fileManager = new FileManager();
$categories = PluginRegistry::getCategories();
foreach ($categories as $category) {
PluginRegistry::loadCategory($category);
$plugins = PluginRegistry::getPlugins($category);
if (!empty($plugins)) {
foreach ($plugins as $plugin) {
$versionFile = $plugin->getPluginPath() . '/version.xml';
if ($fileManager->fileExists($versionFile)) {
$versionInfo = VersionCheck::parseVersionXML($versionFile);
$pluginVersion = $versionInfo['version'];
} else {
$pluginVersion = new Version(
1,
0,
0,
0, // Major, minor, revision, build
Core::getCurrentDate(), // Date installed
1, // Current
'plugins.' . $category, // Type
basename($plugin->getPluginPath()), // Product
'', // Class name
0, // Lazy load
$plugin->isSitePlugin() // Site wide
);
}
$versionDao->insertVersion($pluginVersion, true);
}
}
}
return true;
}
/**
* Fail the upgrade.
*
* @param Installer $installer
* @param array $attr Attributes
*
* @return bool
*/
public function abort($installer, $attr)
{
$installer->setError(self::INSTALLER_ERROR_GENERAL, $attr['message']);
return false;
}
/**
* For 3.1.0 upgrade. DefaultMenus Defaults
*
* @return bool Success/failure
*/
public function installDefaultNavigationMenus()
{
$contextDao = Application::getContextDAO();
$navigationMenuDao = DAORegistry::getDAO('NavigationMenuDAO'); /** @var NavigationMenuDAO $navigationMenuDao */
$contexts = $contextDao->getAll();
while ($context = $contexts->next()) {
$navigationMenuDao->installSettings($context->getId(), 'registry/navigationMenus.xml');
}
$navigationMenuDao->installSettings(PKPApplication::CONTEXT_ID_NONE, 'registry/navigationMenus.xml');
return true;
}
/**
* Check that the environment meets minimum PHP requirements.
*
* @return bool Success/failure
*/
public function checkPhpVersion()
{
if (version_compare(PKPApplication::PHP_REQUIRED_VERSION, PHP_VERSION) != 1) {
return true;
}
$this->setError(self::INSTALLER_ERROR_GENERAL, 'installer.unsupportedPhpError');
return false;
}
/**
* Migrate site locale settings to a serialized array in the database
*/
public function migrateSiteLocales()
{
$siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */
$result = $siteDao->retrieve('SELECT installed_locales, supported_locales FROM site');
$set = $params = [];
$row = (array) $result->current();
$type = 'array';
foreach ($row as $column => $value) {
if (!empty($value)) {
$set[] = $column . ' = ?';
$params[] = $siteDao->convertToDB(explode(':', $value), $type);
}
}
$siteDao->update('UPDATE site SET ' . join(',', $set), $params);
return true;
}
/**
* Migrate active sidebar blocks from plugin_settings to journal_settings
*
* @return bool
*/
public function migrateSidebarBlocks()
{
$siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */
$site = $siteDao->getSite();
$plugins = PluginRegistry::loadCategory('blocks');
if (empty($plugins)) {
return true;
}
// Sanitize plugin names for use in sql IN().
$sanitizedPluginNames = array_map(function ($name) {
return "'" . preg_replace('/[^A-Za-z0-9]/', '', $name) . "'";
}, array_keys($plugins));
$pluginSettingsDao = DAORegistry::getDAO('PluginSettingsDAO'); /** @var \PKP\plugins\PluginSettingsDAO $pluginSettingsDao */
$result = $pluginSettingsDao->retrieve(
'SELECT plugin_name, context_id, setting_value FROM plugin_settings WHERE plugin_name IN (' . join(',', $sanitizedPluginNames) . ') AND setting_name=\'context\';'
);
$sidebarSettings = [];
foreach ($result as $row) {
if ($row->setting_value != 1) {
continue;
} // BLOCK_CONTEXT_SIDEBAR
$seq = $pluginSettingsDao->getSetting($row->context_id, $row->plugin_name, 'seq');
if (!isset($sidebarSettings[$row->context_id])) {
$sidebarSettings[$row->context_id] = [];
}
$sidebarSettings[$row->context_id][(int) $seq] = $row->plugin_name;
}
foreach ($sidebarSettings as $contextId => $contextSetting) {
// Order by sequence
ksort($contextSetting);
$contextSetting = array_values($contextSetting);
if ($contextId) {
$contextDao = Application::getContextDAO();
$context = $contextDao->getById($contextId);
$context->setData('sidebar', $contextSetting);
$contextDao->updateObject($context);
} else {
$siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */
$site = $siteDao->getSite();
$site->setData('sidebar', $contextSetting);
$siteDao->updateObject($site);
}
}
$pluginSettingsDao->update('DELETE FROM plugin_settings WHERE plugin_name IN (' . join(',', $sanitizedPluginNames) . ') AND (setting_name=\'context\' OR setting_name=\'seq\');');
return true;
}
/**
* Migrate the metadata settings in the database to use a single row with one
* of the new constants
*/
public function migrateMetadataSettings()
{
$contextDao = Application::getContextDao();
$metadataSettings = [
'coverage',
'languages',
'rights',
'source',
'subjects',
'type',
'disciplines',
'keywords',
'agencies',
'citations',
];
$result = $contextDao->retrieve('SELECT ' . $contextDao->primaryKeyColumn . ' from ' . $contextDao->tableName);
$contextIds = [];
foreach ($result as $row) {
$row = (array) $row;
$contextIds[] = $row[$contextDao->primaryKeyColumn];
}
foreach ($metadataSettings as $metadataSetting) {
foreach ($contextIds as $contextId) {
$result = $contextDao->retrieve(
'SELECT *
FROM ' . $contextDao->settingsTableName . '
WHERE
' . $contextDao->primaryKeyColumn . ' = ?
AND (
setting_name = ?
OR setting_name = ?
OR setting_name = ?
)
',
[
$contextId,
$metadataSetting . 'EnabledWorkflow',
$metadataSetting . 'EnabledSubmission',
$metadataSetting . 'Required',
]
);
$value = Context::METADATA_DISABLE;
foreach ($result as $row) {
if ($row->setting_name === $metadataSetting . 'Required' && $row->setting_value) {
$value = Context::METADATA_REQUIRE;
} elseif ($row->setting_name === $metadataSetting . 'EnabledSubmission' && $row->setting_value && $value !== Context::METADATA_REQUIRE) {
$value = Context::METADATA_REQUEST;
} elseif ($row->setting_name === $metadataSetting . 'EnabledWorkflow' && $row->setting_value && $value !== Context::METADATA_REQUEST && $value !== Context::METADATA_REQUIRE) {
$value = Context::METADATA_ENABLE;
}
}
if ($value !== Context::METADATA_DISABLE) {
$contextDao->update(
'INSERT INTO ' . $contextDao->settingsTableName . ' (
' . $contextDao->primaryKeyColumn . ',
locale,
setting_name,
setting_value
) VALUES (?, ?, ?, ?)',
[
$contextId,
'',
$metadataSetting,
$value,
]
);
}
$contextDao->update(
'DELETE FROM ' . $contextDao->settingsTableName . ' WHERE
' . $contextDao->primaryKeyColumn . ' = ?
AND (
setting_name = ?
OR setting_name = ?
OR setting_name = ?
)
',
[
$contextId,
$metadataSetting . 'EnabledWorkflow',
$metadataSetting . 'EnabledSubmission',
$metadataSetting . 'Required',
]
);
}
}
return true;
}
/**
* Set the notification settings for journal managers and subeditors so
* that they are opted out of the monthly stats email.
*/
public function setStatsEmailSettings()
{
$roleIds = [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR];
$notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO'); /** @var \PKP\notification\NotificationSubscriptionSettingsDAO $notificationSubscriptionSettingsDao */
for ($contexts = Application::get()->getContextDAO()->getAll(true); $context = $contexts->next();) {
$users = Repo::user()->getCollector()
->filterByContextIds([$context->getId()])
->filterByRoleIds($roleIds)
->getMany();
foreach ($users as $user) {
$notificationSubscriptionSettingsDao->update(
'INSERT INTO notification_subscription_settings
(setting_name, setting_value, user_id, context, setting_type)
VALUES
(?, ?, ?, ?, ?)',
[
'blocked_emailed_notification',
PKPNotification::NOTIFICATION_TYPE_EDITORIAL_REPORT,
$user->getId(),
$context->getId(),
'int'
]
);
}
}
return true;
}
/**
* Fix library files, which were mistakenly named server-side using source filenames.
* See https://github.com/pkp/pkp-lib/issues/5471
*
* @return bool
*/
public function fixLibraryFiles()
{
// Fetch all library files (no method currently in LibraryFileDAO for this)
$libraryFileDao = DAORegistry::getDAO('LibraryFileDAO'); /** @var \PKP\context\LibraryFileDAO $libraryFileDao */
$result = $libraryFileDao->retrieve('SELECT * FROM library_files');
/** @var DAOResultFactory<LibraryFile> */
$libraryFiles = new DAOResultFactory($result, $libraryFileDao, '_fromRow', ['id']);
$wrongFiles = [];
while ($libraryFile = $libraryFiles->next()) {
$libraryFileManager = new LibraryFileManager($libraryFile->getContextId());
$wrongFilePath = $libraryFileManager->getBasePath() . $libraryFile->getOriginalFileName();
$rightFilePath = $libraryFile->getFilePath();
if (isset($wrongFiles[$wrongFilePath])) {
error_log('A potential collision was found between library files ' . $libraryFile->getId() . ' and ' . $wrongFiles[$wrongFilePath]->getId() . '. Please review the database entries and ensure that the associated files are correct.');
} else {
$wrongFiles[$wrongFilePath] = $libraryFile;
}
// For all files for which the "wrong" filename exists and the "right" filename doesn't,
// copy the "wrong" file over to the "right" one. This will leave the "wrong" file in
// place, and won't disambiguate cases for which files were clobbered.
if (file_exists($wrongFilePath) && !file_exists($rightFilePath)) {
$libraryFileManager->copyFile($wrongFilePath, $rightFilePath);
}
}
return true;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\install\Installer', '\Installer');
define('INSTALLER_ERROR_GENERAL', Installer::INSTALLER_ERROR_GENERAL);
define('INSTALLER_ERROR_DB', Installer::INSTALLER_ERROR_DB);
define('INSTALLER_DATA_DIR', Installer::INSTALLER_DATA_DIR);
define('INSTALLER_DEFAULT_LOCALE', Installer::INSTALLER_DEFAULT_LOCALE);
}
|