-
Kevin Cristiano authoredKevin Cristiano authored
civicrm.admin.php 19.35 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
* 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() {
// If settings file does not exist, show notice with link to installer.
if (!CIVICRM_INSTALLED) {
if (isset($_GET['page']) && $_GET['page'] == 'civicrm-install') {
// Set install type.
$_GET['civicrm_install_type'] = 'wordpress';
}
else {
// Show notice.
add_action('admin_notices', [$this, 'show_setup_warning']);
}
}
// Listen for changes to the basepage setting.
add_action('civicrm_postSave_civicrm_setting', [$this, 'settings_change'], 10);
// Prevent auto-updates.
add_filter('plugin_auto_update_setting_html', [$this, 'auto_update_prevent'], 10, 3);
// Set page title.
add_filter('admin_title', [$this, 'set_admin_title']);
// 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);
/**
* Broadcast that this object has registered its callbacks.
*
* @since 5.34
*/
do_action('civicrm/admin/hooks/registered');
}
// ---------------------------------------------------------------------------
// Installation
// ---------------------------------------------------------------------------
/**
* Callback method for missing settings file in register_hooks().
*
* @since 4.4
* @since 5.33 Moved to this class.
*/
public function show_setup_warning() {
echo '<div id="civicrm-warning" class="updated fade">';
echo '<p><strong>' . __('CiviCRM is almost ready.', 'civicrm') . '</strong> ';
echo sprintf(
__('You must <a href="%s">configure CiviCRM</a> for it to work.', 'civicrm'),
admin_url('options-general.php?page=civicrm-install')
);
echo '</p></div>';
}
/**
* Callback method for add_options_page() that runs the CiviCRM installer.
*
* @since 4.4
*/
public function run_installer() {
$this->assert_php_support();
$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' => admin_url('options-general.php?page=civicrm-install'),
'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'));
}
/**
* Check that the PHP version is supported. If not, raise a fatal error with
* a pointed message.
*
* One should check this before bootstrapping CiviCRM - after we start the
* class-loader, the PHP-compatibility errors will become more ugly.
*
* @since 5.18
* @since 5.33 Moved to this class.
*/
protected function assert_php_support() {
if (version_compare(PHP_VERSION, CIVICRM_WP_PHP_MINIMUM) < 0) {
echo '<p>';
echo sprintf(
__('CiviCRM requires PHP version %1$s or greater. You are running PHP version %2$s', 'civicrm'),
CIVICRM_WP_PHP_MINIMUM,
PHP_VERSION
);
echo '<p>';
exit();
}
}
// ---------------------------------------------------------------------------
// Initialisation
// ---------------------------------------------------------------------------
/**
* Initialize CiviCRM.
*
* @since 4.4
*
* @return bool $success True if CiviCRM is initialized, false otherwise.
*/
public function initialize() {
static $initialized = FALSE;
static $failure = FALSE;
if ($failure) {
return FALSE;
}
if (!$initialized) {
$this->assert_php_support();
// Check for settings.
if (!CIVICRM_INSTALLED) {
$error = FALSE;
}
elseif (file_exists(CIVICRM_SETTINGS_PATH)) {
$error = include_once CIVICRM_SETTINGS_PATH;
}
// Autoload.
require_once CIVICRM_PLUGIN_DIR . 'civicrm/CRM/Core/ClassLoader.php';
CRM_Core_ClassLoader::singleton()->register();
// Get ready for problems.
$installLink = admin_url('options-general.php?page=civicrm-install');
$docLinkInstall = "https://docs.civicrm.org/installation/en/latest/wordpress/";
$docLinkTrouble = "https://docs.civicrm.org/sysadmin/en/latest/troubleshooting/";
$forumLink = "https://civicrm.stackexchange.com/";
// Construct message.
$errorMsgAdd = sprintf(
__('Please review the <a href="%s">WordPress Installation Guide</a> and the <a href="%s">Trouble-shooting page</a> for assistance. If you still need help installing, you can often find solutions to your issue by searching for the error message in the <a href="%s">installation support section of the community forum</a>.', 'civicrm'),
$docLinkInstall,
$docLinkTrouble,
$forumLink
);
// Does install message get used?
$installMessage = sprintf(
__('Click <a href="%s">here</a> for fresh install.', 'civicrm'),
$installLink
);
if ($error == FALSE) {
wp_redirect(admin_url('options-general.php?page=civicrm-install'));
exit;
}
// Access global defined in civicrm.settings.php.
global $civicrm_root;
// This does pretty much all of the CiviCRM initialization.
if (!file_exists($civicrm_root . 'CRM/Core/Config.php')) {
$error = FALSE;
}
else {
$error = include_once 'CRM/Core/Config.php';
}
// Have we got it?
if ($error == FALSE) {
// Set static flag.
$failure = TRUE;
// FIX ME - why?
wp_die(
"<strong><p class='error'>" .
sprintf(
__('Oops! - The path for including CiviCRM code files is not set properly. Most likely there is an error in the <em>civicrm_root</em> setting in your CiviCRM settings file (%s).', 'civicrm'),
CIVICRM_SETTINGS_PATH
) .
"</p><p class='error'> » " .
sprintf(
__('civicrm_root is currently set to: <em>%s</em>.', 'civicrm'),
$civicrm_root
) .
"</p><p class='error'>" . $errorMsgAdd . "</p></strong>"
);
// Won't reach here!
return FALSE;
}
// Set static flag.
$initialized = TRUE;
// 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!
return TRUE;
}
/**
* 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');
// Check for settings file.
if (CIVICRM_INSTALLED) {
// Add 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 {
// 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);
*/
}
}
/**
* 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();
}
}