-
Kevin Cristiano authoredKevin Cristiano authored
civicrm.admin.php 23.60 KiB
<?php
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
/**
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
*
*/
// This file must not accessed directly.
if (!defined('ABSPATH')) {
exit;
}
/**
* Define CiviCRM_For_WordPress_Admin Class.
*
* @since 5.33
*/
class CiviCRM_For_WordPress_Admin {
/**
* @var object
* Plugin object reference.
* @since 5.33
* @access public
*/
public $civi;
/**
* @var object
* Settings page object.
* @since 5.34
* @access public
*/
public $page_options;
/**
* @var object
* Integration page object.
* @since 5.34
* @access public
*/
public $page_integration;
/**
* @var object
* Error Information page object.
* @since 5.40
* @access public
*/
public $page_error;
/**
* @var string
* Error handling flag to determine whether to show a troubleshooting page.
* @since 5.40
* @access public
*/
public $error_flag = '';
/**
* @var object
* Quick Add meta box object.
* @since 5.34
* @access public
*/
public $metabox_quick_add;
/**
* Instance constructor.
*
* @since 5.33
*/
public function __construct() {
// Store reference to CiviCRM plugin object.
$this->civi = civi_wp();
// Include class files and instantiate.
$this->include_files();
$this->setup_objects();
// Filter Heartbeat on CiviCRM admin pages as late as is practical.
add_filter('heartbeat_settings', [$this, 'heartbeat'], 1000, 1);
}
/**
* Include files.
*
* @since 5.34
*/
public function include_files() {
// Include class files.
include_once CIVICRM_PLUGIN_DIR . 'includes/admin-pages/civicrm.page.options.php';
include_once CIVICRM_PLUGIN_DIR . 'includes/admin-pages/civicrm.page.integration.php';
include_once CIVICRM_PLUGIN_DIR . 'includes/admin-metaboxes/civicrm.metabox.contact.add.php';
}
/**
* Instantiate objects.
*
* @since 5.34
*/
public function setup_objects() {
// Instantiate objects.
$this->page_options = new CiviCRM_For_WordPress_Admin_Page_Options();
$this->page_integration = new CiviCRM_For_WordPress_Admin_Page_Integration();
$this->metabox_quick_add = new CiviCRM_For_WordPress_Admin_Metabox_Contact_Add();
}
/**
* Register hooks on "init" action.
*
* @since 4.4
* @since 5.33 Moved to this class.
*/
public function register_hooks() {
// Prevent auto-updates.
add_filter('plugin_auto_update_setting_html', [$this, 'auto_update_prevent'], 10, 3);
// Modify the admin menu.
add_action('admin_menu', [$this, 'add_menu_items'], 9);
// Add CiviCRM's resources in the admin header.
add_action('admin_head', [$this->civi, 'wp_head'], 50);
// If settings file does not exist.
if (!CIVICRM_INSTALLED) {
// Maybe show notice with link to installer.
add_action('admin_notices', [$this, 'show_setup_warning']);
}
else {
// Listen for changes to the basepage setting.
add_action('civicrm_postSave_civicrm_setting', [$this, 'settings_change'], 10);
// Set page title.
add_filter('admin_title', [$this, 'set_admin_title']);
}
/**
* Broadcast that this object has registered its callbacks.
*
* @since 5.34
*/
do_action('civicrm/admin/hooks/registered');
}
// ---------------------------------------------------------------------------
// Installation
// ---------------------------------------------------------------------------
/**
* Show an admin notice on pages other than the CiviCRM Installer.
*
* @since 4.4
* @since 5.33 Moved to this class.
*/
public function show_setup_warning() {
// Check user permissions.
if (!current_user_can('manage_options')) {
return;
}
// Get current screen.
$screen = get_current_screen();
// Bail if it's not what we expect.
if (!($screen instanceof WP_Screen)) {
return;
}
// Bail if we are on our installer page.
if ($screen->id == 'toplevel_page_civicrm-install') {
return;
}
$message = sprintf(
__('%1$sCiviCRM is almost ready.%2$s You must %3$sconfigure CiviCRM%4$s for it to work.', 'civicrm'),
'<strong>',
'</strong>',
'<a href="' . menu_page_url('civicrm-install', FALSE) . '">',
'</a>'
);
echo '<div id="message" class="notice notice-warning">';
echo '<p>' . $message . '</p>';
echo '</div>';
}
/**
* Callback method for add_options_page() that runs the CiviCRM installer.
*
* @since 4.4
*/
public function run_installer() {
// Set install type.
$_GET['civicrm_install_type'] = 'wordpress';
$civicrmCore = CIVICRM_PLUGIN_DIR . 'civicrm';
$setupPaths = [
implode(DIRECTORY_SEPARATOR, ['vendor', 'civicrm', 'civicrm-setup']),
implode(DIRECTORY_SEPARATOR, ['packages', 'civicrm-setup']),
implode(DIRECTORY_SEPARATOR, ['setup']),
];
foreach ($setupPaths as $setupPath) {
$loader = implode(DIRECTORY_SEPARATOR, [$civicrmCore, $setupPath, 'civicrm-setup-autoload.php']);
if (file_exists($loader)) {
require_once $loader;
require_once implode(DIRECTORY_SEPARATOR, [$civicrmCore, 'CRM', 'Core', 'ClassLoader.php']);
CRM_Core_ClassLoader::singleton()->register();
\Civi\Setup::assertProtocolCompatibility(1.0);
\Civi\Setup::init([
'cms' => 'WordPress',
'srcPath' => $civicrmCore,
]);
$ctrl = \Civi\Setup::instance()->createController()->getCtrl();
$ctrl->setUrls([
'ctrl' => menu_page_url('civicrm-install', FALSE),
'res' => CIVICRM_PLUGIN_URL . 'civicrm/' . strtr($setupPath, DIRECTORY_SEPARATOR, '/') . '/res/',
'jquery.js' => CIVICRM_PLUGIN_URL . 'civicrm/bower_components/jquery/dist/jquery.min.js',
'font-awesome.css' => CIVICRM_PLUGIN_URL . 'civicrm/bower_components/font-awesome/css/font-awesome.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'));
}
// ---------------------------------------------------------------------------
// Pre-flight check
// ---------------------------------------------------------------------------
/**
* Show an admin notice when the PHP version isn't sufficient.
*
* @since 5.40
*/
public function show_php_warning() {
// Check user permissions.
if (!current_user_can('manage_options')) {
return;
}
// Get current screen.
$screen = get_current_screen();
// Bail if it's not what we expect.
if (!($screen instanceof WP_Screen)) {
return;
}
// Bail if we are on our error page.
if ($screen->id == 'toplevel_page_CiviCRM') {
return;
}
$message = sprintf(
__('%1$sCiviCRM needs your attention.%2$s Please visit the %3$sInformation Page%4$s for details.', 'civicrm'),
'<strong>',
'</strong>',
'<a href="' . menu_page_url('CiviCRM', FALSE) . '">',
'</a>'
);
echo '<div id="message" class="notice notice-warning">';
echo '<p>' . $message . '</p>';
echo '</div>';
}
/**
* Check that the PHP version is supported.
*
* If not, show an admin notice and enable the Error Page instead of CiviCRM's
* admin UI. This way WordPress is still usable while the issue is sorted out.
*
* This check is not necessary for fresh installs because we now have the
* "Requires PHP:" plugin header. It is, however, necessary for upgrades - but
* shouldn't render WordPress unusable.
*
* @since 5.18
* @since 5.33 Moved to this class.
*
* @return bool True if the PHP version is supported, false otherwise.
*/
protected function assert_php_support() {
if (version_compare(PHP_VERSION, CIVICRM_WP_PHP_MINIMUM) < 0) {
add_action('admin_notices', [$this, 'show_php_warning']);
$this->error_flag = 'php-version';
return FALSE;
}
return TRUE;
}
// ---------------------------------------------------------------------------
// Initialisation
// ---------------------------------------------------------------------------
/**
* Initialize CiviCRM.
*
* @since 4.4
*
* @return bool $success True if CiviCRM is initialized, false otherwise.
*/
public function initialize() {
static $initialized = NULL;
if (!is_null($initialized)) {
return $initialized;
}
/*
* CiviCRM must not be initialized if it's not installed. It's okay to
* return early because the admin notice will be displayed if, for some
* reason, the initial redirect on install hasn't occured.
*/
if (!CIVICRM_INSTALLED) {
$this->error_flag = 'settings-missing';
$initialized = FALSE;
return FALSE;
}
// Check PHP version in case of upgrade.
if (!$this->assert_php_support()) {
$initialized = FALSE;
return FALSE;
}
/*
* Checks from this point on are for cases where the install has become
* corrupted in some way. We are trying to fail as gracefully as we can.
* Since CIVICRM_INSTALLED is set based on the presence of the settings
* file, we now know it is there.
*/
// Include settings file - returns int(1) on success.
$error = include_once CIVICRM_SETTINGS_PATH;
/*
* Bail if the settings file returns something other than int(1).
* When this happens, we should show an admin page with troubleshooting
* instructions rather than dying and leaving WordPress unusable.
*
* Requires a page similar to "civicrm.page.options.php", which we can
* show instead of "invoking" CiviCRM itself. In order to do that, this
* method *must* have been called before "add_menu_items()" so that an
* alternative "add_menu_page()" call can be made. Usefully, this already
* happens because "register_hooks_clean_urls()" is called first.
*
* However, it looks like there may be little that can be done to mitigate
* path errors - e.g. when $civicrm_root is not set correctly - because
* including "civicrm.settings.php" will throw a fatal error if $civicrm_root
* is wrong.
*/
if ($error == FALSE) {
$this->error_flag = 'settings-include';
$initialized = FALSE;
return;
}
// 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;
// Bail if the config file isn't found.
if (!file_exists($civicrm_root . 'CRM/Core/Config.php')) {
$this->error_flag = 'config-missing';
$initialized = FALSE;
return;
}
// Include config file - returns int(1) on success.
$error = include_once 'CRM/Core/Config.php';
// Bail if the config file returns something other than int(1).
if ($error == FALSE) {
$this->error_flag = 'config-include';
$initialized = FALSE;
return FALSE;
}
// Initialize the system by creating a config object.
$config = CRM_Core_Config::singleton();
// Sync the logged-in WordPress user with CiviCRM.
global $current_user;
if ($current_user) {
// Sync procedure sets session values for logged in users.
require_once 'CRM/Core/BAO/UFMatch.php';
CRM_Core_BAO_UFMatch::synchronize(
// User object.
$current_user,
// Do not update.
FALSE,
// CMS.
'WordPress',
$this->civi->users->get_civicrm_contact_type('Individual')
);
}
/**
* Broadcast that CiviCRM is now initialized.
*
* @since 4.4
*/
do_action('civicrm_initialized');
// Success! Set and return static flag.
$initialized = TRUE;
return $initialized;
}
/**
* Slow down the frequency of WordPress heartbeat calls.
*
* Heartbeat is important to WordPress for a number of tasks - e.g. checking
* continued authentication whilst on a page - but it does consume server
* resources. Reducing the frequency of calls minimises the impact on servers
* and can make CiviCRM more responsive.
*
* @since 5.29
* @since 5.33 Moved to this class.
*
* @param array $settings The existing heartbeat settings.
* @return array $settings The modified heartbeat settings.
*/
public function heartbeat($settings) {
// Access script identifier.
global $pagenow;
// Bail if not admin.
if (!is_admin()) {
return $settings;
}
// Process the requested URL.
$requested_url = filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
if ($requested_url) {
$current_url = wp_unslash($requested_url);
}
else {
$current_url = admin_url();
}
$current_screen = wp_parse_url($current_url);
// Bail if entry is missing for some reason.
if (!isset($current_screen['query'])) {
return $settings;
}
// Bail if this is not CiviCRM admin.
if ($pagenow != 'admin.php' || FALSE === strpos($current_screen['query'], 'page=CiviCRM')) {
return $settings;
}
// Defer to any previously set value, but only if it's greater than ours.
if (!empty($settings['interval']) && intval($settings['interval']) > 120) {
return $settings;
}
// Slow down heartbeat.
$settings['interval'] = 120;
return $settings;
}
/**
* Force rewrite rules to be recreated.
*
* When CiviCRM settings are saved, the method is called post-save. It checks
* if it's the WordPress Base Page setting that has been saved and causes all
* rewrite rules to be flushed on the next page load.
*
* @since 5.14
* @since 5.33 Moved to this class.
*
* @param obj $dao The CiviCRM database access object.
*/
public function settings_change($dao) {
// Delete the option if conditions are met.
if ($dao instanceof CRM_Core_DAO_Setting) {
if (isset($dao->name) && $dao->name == 'wpBasePage') {
delete_option('civicrm_rules_flushed');
}
}
}
/**
* Prevent auto-updates of this plugin in WordPress 5.5.
*
* @link https://make.wordpress.org/core/2020/07/15/controlling-plugin-and-theme-auto-updates-ui-in-wordpress-5-5/
*
* @since 5.28
* @since 5.33 Moved to this class.
*/
public function auto_update_prevent($html, $plugin_file, $plugin_data) {
// Test for this plugin.
$this_plugin = plugin_basename(dirname(CIVICRM_PLUGIN_FILE) . '/civicrm.php');
if ($this_plugin === $plugin_file) {
$html = __('Auto-updates are not available for this plugin.', 'civicrm');
}
// --<
return $html;
}
/**
* Add CiviCRM's title to the header's <title> tag.
*
* @since 4.6
*
* @param string $title The title to set.
* @return string The computed title.
*/
public function set_admin_title($title) {
global $civicrm_wp_title;
if (!$civicrm_wp_title) {
return $title;
}
// Replace 1st occurance of "CiviCRM" in the title.
$pos = strpos($title, 'CiviCRM');
if ($pos !== FALSE) {
return substr_replace($title, $civicrm_wp_title, $pos, 7);
}
return $civicrm_wp_title;
}
/**
* Adds menu items to WordPress admin menu.
*
* Callback method for 'admin_menu' hook as set in register_hooks().
*
* @since 4.4
*/
public function add_menu_items() {
$civilogo = file_get_contents(CIVICRM_PLUGIN_DIR . 'assets/images/civilogo.svg.b64');
/**
* Filter the position of the CiviCRM menu item.
*
* Currently set to 3.9 + some random digits to reduce risk of conflict.
*
* @since 4.4
*
* @param str The default menu position expressed as a float.
* @return str The modified menu position expressed as a float.
*/
$position = apply_filters('civicrm_menu_item_position', '3.904981');
// Try and initialize CiviCRM.
$success = $this->initialize();
// If all went well.
if ($success) {
// Add the CiviCRM top level menu item.
$this->menu_page = add_menu_page(
__('CiviCRM', 'civicrm'),
__('CiviCRM', 'civicrm'),
'access_civicrm',
'CiviCRM',
[$this->civi, 'invoke'],
$civilogo,
$position
);
// Add core resources prior to page load.
add_action('load-' . $this->menu_page, [$this, 'admin_page_load']);
}
else {
/*
* Are we here because this is a fresh install or because something is broken?
*
* Let's inspect the "error_flag" property for help with the decision. Where
* the settings file is missing, there's not a lot we can do, so assume it's
* a fresh install.
*
* However, we may be able to detect signs of installs where CiviCRM has been
* installed but "civicrm.settings.php" can't be found.
*
* @see self::detect_existing_install()
*/
if ($this->error_flag === 'settings-missing') {
// Add top level menu item.
$this->menu_page = add_menu_page(
__('CiviCRM Installer', 'civicrm'),
__('CiviCRM Installer', 'civicrm'),
'manage_options',
'civicrm-install',
[$this, 'run_installer'],
$civilogo,
$position
);
/*
// Add scripts and styles like this.
add_action('admin_print_scripts-' . $this->menu_page, [$this, 'admin_installer_js']);
add_action('admin_print_styles-' . $this->menu_page, [$this, 'admin_installer_css']);
add_action('admin_head-' . $this->menu_page, [$this, 'admin_installer_head'], 50);
*/
}
else {
// Hand over to our Error Page to provide feedback.
$this->page_error_init($civilogo, $position);
}
}
}
/**
* Try and detect signs of the existence of CiviCRM.
*
* It's possible that CiviCRM has been installed but that something is broken
* with the current install. This method looks for tell-tale signs.
*
* This is not used as yet, but is included as-is to be completed later.
*
* @since 5.40
*
* @return bool $existing_install True if there are signs of an existing install.
*/
public function detect_existing_install() {
// Assume there's no evidence.
$existing_install = FALSE;
// This option is created on activation.
$existing_option = FALSE;
if ('fjwlws' != get_option('civicrm_activation_in_progress', 'fjwlws')) {
$existing_option = TRUE;
}
// Look for a directory in the standard location.
$existing_uploads_dir = FALSE;
$upload_dir = wp_upload_dir();
if (is_dir($upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm')) {
$existing_uploads_dir = TRUE;
}
// Look for a file in the legacy location.
$existing_legacy_file = FALSE;
if (file_exists(CIVICRM_PLUGIN_DIR . 'civicrm.settings.php')) {
$existing_legacy_file = TRUE;
}
return $existing_install;
}
/**
* Show Error Information page.
*
* In situations where something has gone wrong with the CiviCRM installation,
* show a page which will help people troubleshoot the problem.
*
* @since 5.40
*
* @param str $logo The CiviCRM logo.
* @param str $position The default menu position expressed as a float.
*/
public function page_error_init($logo, $position) {
// Include and init Error Page.
include_once CIVICRM_PLUGIN_DIR . 'includes/admin-pages/civicrm.page.error.php';
$this->page_error = new CiviCRM_For_WordPress_Admin_Page_Error($logo, $position);
}
/**
* Perform necessary stuff prior to CiviCRM's admin page being loaded.
*
* @since 4.6
* @since 5.33 Moved to this class.
*/
public function admin_page_load() {
// This is required for AJAX calls in WordPress admin.
$_REQUEST['noheader'] = $_GET['noheader'] = TRUE;
// Add resources for back end.
$this->civi->add_core_resources(FALSE);
// Check setting for path to wp-load.php.
$this->add_wpload_setting();
}
/**
* When CiviCRM is loaded in WordPress Admin, check for the existence of a
* setting which holds the path to wp-load.php. This is the only reliable way
* to bootstrap WordPress from CiviCRM.
*
* CMW: I'm not entirely happy with this approach, because the value will be
* different for different installs (e.g. when a dev site is migrated to live)
* A better approach would be to store this setting in civicrm.settings.php as
* a constant, but doing that involves a complicated process of getting a new
* setting registered in the installer.
*
* Also, it needs to be decided whether this value should be tied to a CiviCRM
* 'domain', since a single CiviCRM install could potentially be used by a
* number of WordPress installs. This is not relevant to its use in WordPress
* Multisite, because the path to wp-load.php is common to all sites on the
* network.
*
* My final concern is that the value will only be set *after* someone visits
* CiviCRM in the back end. I have restricted it to this so as not to add
* overhead to the front end, but there remains the possibility that the value
* could be missing. To repeat: this would be better in civicrm.settings.php.
*
* To get the path to wp-load.php, use:
* $path = CRM_Core_BAO_Setting::getItem('CiviCRM Preferences', 'wpLoadPhp');
*
* @since 4.6.3
* @since 5.33 Moved to this class.
*/
public function add_wpload_setting() {
if (!$this->civi->initialize()) {
return;
}
// Get path to wp-load.php.
$path = ABSPATH . 'wp-load.php';
// Get the setting, if it exists.
$setting = CRM_Core_BAO_Setting::getItem('CiviCRM Preferences', 'wpLoadPhp');
// If we don't have one, create it.
if (is_null($setting)) {
CRM_Core_BAO_Setting::setItem($path, 'CiviCRM Preferences', 'wpLoadPhp');
}
// Is it different to the one we've stored?
if ($setting !== $path) {
// Yes - set new path (this could be because we've changed server or location)
CRM_Core_BAO_Setting::setItem($path, 'CiviCRM Preferences', 'wpLoadPhp');
}
}
/**
* Get a CiviCRM admin link.
*
* @since 5.34
*
* @param str $path The CiviCRM path.
* @param str $params The CiviCRM parameters.
* @return string $link The URL of the CiviCRM page.
*/
public function get_admin_link($path = '', $params = NULL) {
// Init link.
$link = '';
if (!$this->civi->initialize()) {
return $link;
}
// Use CiviCRM to construct link.
$link = CRM_Utils_System::url(
$path,
$params,
TRUE,
NULL,
TRUE,
FALSE,
TRUE
);
// --<
return $link;
}
/**
* Clear CiviCRM caches.
*
* Another way to do this might be:
* CRM_Core_Invoke::rebuildMenuAndCaches(TRUE);
*
* @since 5.34
*/
public function clear_caches() {
if (!$this->civi->initialize()) {
return;
}
// Access config object.
$config = CRM_Core_Config::singleton();
// Clear database cache.
$config->clearDBCache();
// Cleanup the "templates_c" directory.
$config->cleanup(1, TRUE);
// Cleanup the session object.
$session = CRM_Core_Session::singleton();
$session->reset(1);
// Call system flush.
CRM_Utils_System::flushCache();
}
}