Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion civicrm.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,54 @@
define('CIVICRM_INSTALLED', FALSE);
}

/**
* Determine whether CiviCRM's database schema has actually been bootstrapped.
*
* In Bedrock-style deployments, civicrm.settings.php may be shipped as a
* template (driven by env vars) before the database has ever been initialized.
* In that case CIVICRM_INSTALLED is TRUE but the schema is empty. Without this
* check, the activation hook would skip the installer redirect and admin
* bootstrap would later fail trying to read from a missing schema.
*
* Probes the civicrm_domain table on CIVICRM_DSN. Cached per-request.
*/
if (!function_exists('civicrm_schema_is_installed')) {
function civicrm_schema_is_installed() {
static $cached = NULL;
if ($cached !== NULL) {
return $cached;
}
if (!CIVICRM_INSTALLED) {
return $cached = FALSE;
}
if (!defined('CIVICRM_DSN')) {
@include_once CIVICRM_SETTINGS_PATH;
}
if (!defined('CIVICRM_DSN')) {
return $cached = FALSE;
}
$dsn = @parse_url(CIVICRM_DSN);
if (empty($dsn['host']) || empty($dsn['user']) || empty($dsn['path'])) {
return $cached = FALSE;
}
$db_name = ltrim($dsn['path'], '/');
$port = !empty($dsn['port']) ? (int) $dsn['port'] : 3306;
try {
$mysqli = @new mysqli($dsn['host'], $dsn['user'], $dsn['pass'] ?? '', $db_name, $port);
if ($mysqli->connect_errno) {
return $cached = FALSE;
}
$result = @$mysqli->query("SHOW TABLES LIKE 'civicrm_domain'");
$found = ($result && $result->num_rows > 0);
$mysqli->close();
return $cached = (bool) $found;
}
catch (\Throwable $e) {
return $cached = FALSE;
}
}
}

/**
* Setting this to 'TRUE' will replace all mailing URLs calls to 'extern/url.php'
* and 'extern/open.php' with their REST counterpart 'civicrm/v3/url' and
Expand Down Expand Up @@ -435,7 +483,7 @@ public function activation() {
// When installed via the WordPress UI, try and redirect to the Installer page.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$activate_multi = isset($_GET['activate-multi']) ? sanitize_text_field(wp_unslash($_GET['activate-multi'])) : '';
if (!is_multisite() && empty($activate_multi) && !CIVICRM_INSTALLED) {
if (!is_multisite() && empty($activate_multi) && (!CIVICRM_INSTALLED || !civicrm_schema_is_installed())) {
wp_safe_redirect(admin_url('admin.php?page=civicrm-install'));
exit;
}
Expand Down
112 changes: 108 additions & 4 deletions includes/civicrm.admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,98 @@ public function run_installer() {

}

$vendor_setup_paths = [];
$filtered_paths = array_values(array_filter(explode(DIRECTORY_SEPARATOR, CIVICRM_PLUGIN_DIR)));
$potential_vendor_paths = [];
$count = count($filtered_paths);
for ($i = 0; $i <= $count; $i++) {
if ($i < $count) {
$slice = array_slice($filtered_paths, 0, $count - $i);
$potential_vendor_paths[] = '/' . implode('/', $slice);
}
}
foreach ($potential_vendor_paths as $potential_path) {
$civicrm_core_path = implode(DIRECTORY_SEPARATOR, array_merge(explode(DIRECTORY_SEPARATOR, $potential_path), ['vendor', 'civicrm', 'civicrm-core']));
$civicrm_setup_autoload_path = $civicrm_core_path . DIRECTORY_SEPARATOR . 'setup' . DIRECTORY_SEPARATOR . 'civicrm-setup-autoload.php';
$civicrm_classloader_path = $civicrm_core_path . DIRECTORY_SEPARATOR . 'CRM' . DIRECTORY_SEPARATOR . 'Core' . DIRECTORY_SEPARATOR . 'ClassLoader.php';
if (file_exists($civicrm_setup_autoload_path)) {
require_once $civicrm_setup_autoload_path;
require_once $civicrm_classloader_path;
CRM_Core_ClassLoader::singleton()->register();
\Civi\Setup::assertProtocolCompatibility(1.0);
\Civi\Setup::init([
'cms' => 'WordPress',
'srcPath' => $civicrm_core_path,
'doNotCreateSettingsFile' => TRUE,
]);
// Bedrock-style deployments ship civicrm.settings.php as a template,
// so its presence does not imply CiviCRM has been installed. Force
// setSettingInstalled(FALSE) so only the database check gates whether
// SetupController::runStart() short-circuits to the "finished" page.
\Civi\Setup::dispatcher()->addListener(
'civi.setup.checkInstalled',
function (\Civi\Setup\Event\CheckInstalledEvent $e) {
$e->setSettingInstalled(FALSE);
},
-1000
);
// The default checkRequirements listener errors when the settings file
// path isn't writable. When doNotCreateSettingsFile is set, the file is
// managed externally and writability is irrelevant — overwrite the
// 'settingsWritable' message (keyed by name) with an info entry.
\Civi\Setup::dispatcher()->addListener(
'civi.setup.checkRequirements',
function (\Civi\Setup\Event\CheckRequirementsEvent $e) {
$m = $e->getModel();
if (!empty($m->doNotCreateSettingsFile)) {
$e->addInfo('system', 'settingsWritable', sprintf('The settings file "%s" is managed externally; writability is not checked.', $m->settingsPath));
}
},
-1000
);
// The default WordPress init listener sets $model->db to the WP
// database creds. When CIVICRM_DSN is already defined (Bedrock-style
// templated settings file), prefer that — the CiviCRM database is
// typically separate from the WordPress one. The civi.setup.init event
// is dispatched inside \Civi\Setup::init() before listeners can be
// attached, so mutate the model directly.
if (!defined('CIVICRM_DSN') && file_exists(CIVICRM_SETTINGS_PATH)) {
@include_once CIVICRM_SETTINGS_PATH;
}
if (defined('CIVICRM_DSN')) {
$civi_dsn_parsed = \Civi\Setup\DbUtil::parseDsn(CIVICRM_DSN);
\Civi\Setup::instance()->getModel()->db = $civi_dsn_parsed;
\Civi\Setup::instance()->getModel()->extras['advanced']['db'] = \Civi\Setup\DbUtil::encodeDsn($civi_dsn_parsed);
}
// Keep the editable "advanced.db" field in sync with $model->db when
// the user hasn't typed their own value yet — the default advanced
// listener at PRIORITY_LATE forces the input back to a placeholder, so
// run after it (PRIORITY_END = -2000).
\Civi\Setup::dispatcher()->addListener(
'civi.setupui.run',
function (\Civi\Setup\UI\Event\UIBootEvent $e) {
$model = $e->getModel();
$placeholder = 'mysql://USER:PASS@HOST/DB';
$values = $e->getField('advanced', []);
if (empty($values['db']) || $values['db'] === $placeholder) {
$model->extras['advanced']['db'] = \Civi\Setup\DbUtil::encodeDsn($model->db);
}
},
\Civi\Setup::PRIORITY_END
);
$ctrl = \Civi\Setup::instance()->createController()->getCtrl();
$ctrl->setUrls([
'ctrl' => menu_page_url('civicrm-install', FALSE),
'res' => CIVICRM_PLUGIN_URL . 'civicrm/core/setup/res/',
'jquery.js' => CIVICRM_PLUGIN_URL . 'civicrm/core/bower_components/jquery/dist/jquery.min.js',
'font-awesome.css' => CIVICRM_PLUGIN_URL . 'civicrm/core/bower_components/font-awesome/css/all.min.css',
'finished' => admin_url('admin.php?page=CiviCRM&q=civicrm&reset=1'),
]);
\Civi\Setup\BasicRunner::run($ctrl);
return;
}
}

wp_die(__('Installer unavailable. Failed to locate CiviCRM libraries.', 'civicrm'));

}
Expand Down Expand Up @@ -378,6 +470,18 @@ public function initialize() {
return FALSE;
}

/*
* Settings file exists, but the database schema may not have been
* bootstrapped yet (e.g. Bedrock layouts that ship a templated settings
* file driven by env vars). In that case route to the installer rather
* than attempting to bootstrap CiviCRM against an empty schema.
*/
if (function_exists('civicrm_schema_is_installed') && !civicrm_schema_is_installed()) {
$this->error_flag = 'settings-missing';
$initialized = FALSE;
return FALSE;
}

// Check PHP version in case of upgrade.
if (!$this->assert_php_support()) {
$initialized = FALSE;
Expand Down Expand Up @@ -438,13 +542,13 @@ public function initialize() {
return FALSE;
}

// Initialize the Class Loader.
require_once CIVICRM_PLUGIN_DIR . 'civicrm/CRM/Core/ClassLoader.php';
CRM_Core_ClassLoader::singleton()->register();

// Access global defined in "civicrm.settings.php".
global $civicrm_root;

// Initialize the Class Loader.
require_once $civicrm_root . 'CRM/Core/ClassLoader.php';
CRM_Core_ClassLoader::singleton()->register();

// Bail if the config file isn't found.
if (!file_exists($civicrm_root . 'CRM/Core/Config.php')) {
$this->error_flag = 'config-missing';
Expand Down