Newer
Older
/**
* Plugin Name: CiviCRM
* Description: CiviCRM - Growing and Sustaining Relationships
* Author: CiviCRM LLC
* Author URI: https://civicrm.org/
* Plugin URI: https://docs.civicrm.org/sysadmin/en/latest/install/wordpress/
* License: AGPL3
* Text Domain: civicrm
* Domain Path: /languages
*/
/*
+--------------------------------------------------------------------+
| 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 |
+--------------------------------------------------------------------+
* -----------------------------------------------------------------------------
* WordPress resources for developers
* -----------------------------------------------------------------------------
* Not that they're ever adhered to anywhere other than core, but people do their
* best to comply...
*
* WordPress core coding standards:
* https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/
*
* WordPress HTML standards:
* https://make.wordpress.org/core/handbook/best-practices/coding-standards/html/
*
* WordPress JavaScript standards:
* https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/
* -----------------------------------------------------------------------------
*/
// This file must not accessed directly.
if (!defined('ABSPATH')) {
exit;
}
// Set version here: when it changes, will force Javascript & CSS to reload.
// Store URL to this plugin's directory.
if (!defined('CIVICRM_PLUGIN_URL')) {
define('CIVICRM_PLUGIN_URL', plugin_dir_url(CIVICRM_PLUGIN_FILE));
// Store PATH to this plugin's directory.
if (!defined('CIVICRM_PLUGIN_DIR')) {
define('CIVICRM_PLUGIN_DIR', plugin_dir_path(CIVICRM_PLUGIN_FILE));
* Note: This duplicates `CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER`.
* The duplication helps avoid dependency issues. (Reading
* `CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER` requires loading
* `civicrm.settings.php`, but that triggers a parse-error on PHP 5.x.)
* @see CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER
* @see CiviWP\PhpVersionTest::testConstantMatch()
*/
* The constant `CIVICRM_SETTINGS_PATH` is also defined in `civicrm.config.php`
* and may already have been defined there - e.g. by cron or external scripts.
* Test where the settings file exists.
*
* If the settings file is found in the 4.6 and prior location, use that as
* `CIVICRM_SETTINGS_PATH`, otherwise use the new location.
$upload_dir = wp_upload_dir();
$wp_civi_settings = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm.settings.php';
$wp_civi_settings_deprectated = CIVICRM_PLUGIN_DIR . 'civicrm.settings.php';
if (file_exists($wp_civi_settings_deprectated)) {
define('CIVICRM_SETTINGS_PATH', $wp_civi_settings_deprectated);
else {
define('CIVICRM_SETTINGS_PATH', $wp_civi_settings);
// Test if CiviCRM is installed.
if (file_exists(CIVICRM_SETTINGS_PATH)) {
define('CIVICRM_INSTALLED', TRUE);
}
else {
define('CIVICRM_INSTALLED', FALSE);
// Prevent CiviCRM from rendering its own header.
define('CIVICRM_UF_HEAD', TRUE);
* 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
* 'civicrm/v3/open'.
* Use for test purposes, may affect mailing performance.
*
* @see CiviCRM_WP_REST\Plugin::replace_tracking_urls()
if (!defined('CIVICRM_WP_REST_REPLACE_MAILING_TRACKING')) {
define('CIVICRM_WP_REST_REPLACE_MAILING_TRACKING', FALSE);
* Define CiviCRM_For_WordPress Class.
*
* @since 4.4
* Plugin instance.
* @since 4.4
* @access private
* Plugin context (broad).
* @since 4.4
* @access public
* Plugin context (specific).
* @since 4.4
* @access public
*/
* Shortcodes management object.
* @since 4.4
* @access public
* Modal dialog management object.
* @since 4.4
* @access public
* Basepage management object.
* @since 4.4
* @access public
* User management object.
* @since 4.4
* @access public
* @since 5.24
* @access public
*/
public $compat;
/**
* @var object
* Admin object.
* @since 5.33
* @access public
*/
public $admin;
// ---------------------------------------------------------------------------
// Setup
// ---------------------------------------------------------------------------
/**
* Getter method which returns the CiviCRM instance and optionally creates one
* if it does not already exist. Standard CiviCRM singleton pattern.
*
* @since 4.4
*
* @return object CiviCRM_For_WordPress The CiviCRM plugin instance.
// If instance doesn't already exist.
if (!isset(self::$instance)) {
// Create instance.
self::$instance = new CiviCRM_For_WordPress();
// Delay setup until 'plugins_loaded' to allow other plugins to load as well.
add_action('plugins_loaded', [self::$instance, 'setup_instance']);
* Dummy instance constructor.
*
* @since 4.4
* Dummy magic method to prevent CiviCRM_For_WordPress from being cloned.
*
* @since 4.4
_doing_it_wrong(__FUNCTION__, __('Only one instance of CiviCRM_For_WordPress please', 'civicrm'), '4.4');
* Dummy magic method to prevent CiviCRM_For_WordPress from being unserialized.
*
* @since 4.4
_doing_it_wrong(__FUNCTION__, __('Please do not serialize CiviCRM_For_WordPress', 'civicrm'), '4.4');
* Plugin activation.
*
* This method is called only when CiviCRM plugin is activated. In order for
* other plugins to be able to interact with CiviCRM's activation, we wait until
* after the activation redirect to perform activation actions.
// Set a one-time-only option.
add_option('civicrm_activation_in_progress', 'true');
// Bail if not activating.
if (get_option('civicrm_activation_in_progress') !== 'true') {
// Bail if not in WordPress admin.
if (!is_admin()) {
/**
* Broadcast that activation actions need to happen now.
*
* @since 5.6
*/
// Change option so this action never fires again.
update_option('civicrm_activation_in_progress', 'false');
if (!is_multisite() && !isset($_GET['activate-multi']) && !CIVICRM_INSTALLED) {
wp_redirect(admin_url('options-general.php?page=civicrm-install'));
* Plugin deactivation.
*
* This method is called only when CiviCRM plugin is deactivated. In order for
* other plugins to be able to interact with CiviCRM's activation, we need to
* remove any options that are set in activate() above.
// Delete any options we hay have set.
delete_option('civicrm_activation_in_progress');
/**
* Broadcast that deactivation actions need to happen now.
*
* @since 5.6
*/
// ---------------------------------------------------------------------------
// Plugin set up
// ---------------------------------------------------------------------------
// Kick out if another instance is being inited.
if (isset(self::$in_wordpress)) {
wp_die(__('Only one instance of CiviCRM_For_WordPress please', 'civicrm'));
// Maybe start session.
$this->maybe_start_session();
/*
* AJAX calls do not set the 'cms.root' item, so make sure it is set here so
* the CiviCRM doesn't fall back on flaky directory traversal code.
*/
global $civicrm_paths;
if (empty($civicrm_paths['cms.root']['path'])) {
$civicrm_paths['cms.root']['path'] = untrailingslashit(ABSPATH);
}
if (empty($civicrm_paths['cms.root']['url'])) {
$civicrm_paths['cms.root']['url'] = home_url();
}
// Register all hooks on init.
add_action('init', [$this, 'register_hooks']);
/**
* Broadcast that this plugin is now loaded.
*
* @since 4.4
*/
/**
* Maybe start a session for CiviCRM.
*
* There is no session handling in WordPress so start it for CiviCRM pages.
*
* Not needed when running:
*
* - via WP-CLI
* - via wp-cron.php
* - via PHP on the command line
*
* none of which require sessions.
*
* @since 5.28
*/
public function maybe_start_session() {
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
$session_id = session_id();
// Check WordPress pseudo-cron.
$wp_cron = FALSE;
if (function_exists('wp_doing_cron') && wp_doing_cron()) {
$wp_cron = TRUE;
}
// Check WP-CLI.
$wp_cli = FALSE;
if (defined('WP_CLI') && WP_CLI) {
$wp_cli = TRUE;
}
// Check PHP on the command line - e.g. `cv`.
$php_cli = TRUE;
if (PHP_SAPI !== 'cli') {
$php_cli = FALSE;
}
// Maybe start session.
if (empty($session_id) && !$wp_cron && !$wp_cli && !$php_cli) {
session_start();
}
}
// Include class files.
include_once CIVICRM_PLUGIN_DIR . 'includes/civicrm.admin.php';
include_once CIVICRM_PLUGIN_DIR . 'includes/civicrm.users.php';
include_once CIVICRM_PLUGIN_DIR . 'includes/civicrm.shortcodes.php';
include_once CIVICRM_PLUGIN_DIR . 'includes/civicrm.shortcodes.modal.php';
include_once CIVICRM_PLUGIN_DIR . 'includes/civicrm.basepage.php';
include_once CIVICRM_PLUGIN_DIR . 'includes/civicrm.compat.php';
// Maybe include REST API autoloader class.
if (!class_exists('CiviCRM_WP_REST\Autoloader')) {
require_once CIVICRM_PLUGIN_DIR . 'wp-rest/Autoloader.php';
/**
* Instantiate objects.
*
* @since 5.33
*/
public function setup_objects() {
// Instantiate objects.
$this->admin = new CiviCRM_For_WordPress_Admin();
$this->users = new CiviCRM_For_WordPress_Users();
$this->shortcodes = new CiviCRM_For_WordPress_Shortcodes();
$this->modal = new CiviCRM_For_WordPress_Shortcodes_Modal();
$this->basepage = new CiviCRM_For_WordPress_Basepage();
$this->compat = new CiviCRM_For_WordPress_Compat();
/**
* Load translation files.
*
* A good reference on how to implement translation in WordPress:
* http://ottopress.com/2012/internationalization-youre-probably-doing-it-wrong/
*
* Also see:
* https://developer.wordpress.org/plugins/internationalization/
*
* @since 4.4
*/
public function enable_translation() {
// Load translations.
load_plugin_textdomain(
// Unique name.
'civicrm',
// Deprecated argument.
FALSE,
// Relative path to translation files.
dirname(plugin_basename(__FILE__)) . '/languages/'
);
// ---------------------------------------------------------------------------
// Context
// ---------------------------------------------------------------------------
* Setter for determining if CiviCRM is currently being displayed in WordPress.
* This becomes true whe CiviCRM is called in the following contexts:
*
* (a) in the WordPress back-end
* (b) when CiviCRM content is being displayed on the front-end via wpBasePage
* (c) when an AJAX request is made to CiviCRM
*
* It is NOT true when CiviCRM is called via a shortcode.
// Store.
self::$in_wordpress = ($page == 'CiviCRM') ? TRUE : FALSE;
}
/**
* Getter for testing if CiviCRM is currently being displayed in WordPress.
*
* @see $this->civicrm_in_wordpress_set()
*
* @since 4.4
*
* @return bool $in_wordpress True if CiviCRM is displayed in WordPress, false otherwise.
/**
* Allow broad context to be filtered.
*
* @since 4.4
*
* @param bool $in_wordpress True if CiviCRM is displayed in WordPress, false otherwise.
* @return bool $in_wordpress True if CiviCRM is displayed in WordPress, false otherwise.
*/
return apply_filters('civicrm_in_wordpress', self::$in_wordpress);
* Setter for determining how CiviCRM is currently being displayed in WordPress.
* This can be one of the following contexts:
*
* (a) in the WordPress back-end
* (b) when CiviCRM content is being displayed on the front-end via wpBasePage
* (c) when a "non-page" request is made to CiviCRM
* (d) when CiviCRM is called via a shortcode
*
* The following codes correspond to the different contexts:
*
* (a) 'admin'
* (b) 'basepage'
* (c) 'nonpage'
* (d) 'shortcode'
*
* @since 4.4
*
* @param string $context One of the four context codes above.
* Getter for determining how CiviCRM is currently being displayed in WordPress.
*
* @see $this->civicrm_context_set()
*
* @since 4.4
*
* @return string The context in which CiviCRM is displayed in WordPress.
/**
* Allow specific context to be filtered.
*
* @since 4.4
*
* @param bool $context The existing context in which CiviCRM is displayed in WordPress.
* @return bool $context The modified context in which CiviCRM is displayed in WordPress.
*/
return apply_filters('civicrm_context', self::$context);
}
// ---------------------------------------------------------------------------
// Hooks
// ---------------------------------------------------------------------------
/**
// When in WordPress admin.
if (is_admin()) {
// Set context.
$this->civicrm_context_set('admin');
// Handle WordPress Admin context.
$this->admin->register_hooks();
// Enable shortcode modal.
$this->modal->register_hooks();
// Go no further if CiviCRM not installed yet.
if (!CIVICRM_INSTALLED) {
return;
}
add_filter('request', [$this, 'maybe_replace_page_query_var']);
// Delay everything else until query has been parsed.
add_action('parse_query', [$this, 'register_hooks_front_end']);
}
/**
* Register hooks for the front end.
*
* @since 5.6
*
* @param WP_Query $query The WP_Query instance (passed by reference).
return;
}
// Bail if filters are suppressed on this query.
return;
}
$alreadyRegistered = TRUE;
if ('CiviCRM' == get_query_var('page') && 'CiviCRM' != get_query_var('civiwp')) {
$redirect_url = remove_query_arg('page', FALSE);
$redirect_url = add_query_arg('civiwp', 'CiviCRM', $redirect_url);
wp_redirect($redirect_url, 301);
// When embedded via wpBasePage or AJAX call.
if ($this->civicrm_in_wordpress()) {
* Directly output CiviCRM html only in a few cases and skip WordPress
* templating:
*
* (a) when a snippet is set
* (b) when there is an AJAX call
* (c) for an iCal feed (unless 'html' is specified)
* (d) for file download URLs
*/
// Set context.
$this->civicrm_context_set('nonpage');
// Add core resources for front end.
add_action('wp', [$this, 'front_end_page_load']);
// Echo all output when WordPress has been set up but nothing has been rendered.
add_action('wp', [$this, 'invoke']);
// Set context.
$this->civicrm_context_set('basepage');
// If we get here, we must be in a "wpBasePage" context.
$this->basepage->register_hooks();
return;
}
// Set context.
$this->civicrm_context_set('shortcode');
// That leaves us with handling shortcodes, should they exist.
$this->shortcodes->register_hooks();
}
/**
// Register hooks for clean URLs.
$this->register_hooks_clean_urls();
CiviCRM_WP_REST\Autoloader::add_source($source_path = trailingslashit(CIVICRM_PLUGIN_DIR . 'wp-rest'));
}
/**
* Register hooks to handle Clean URLs.
*
* @since 5.12
*/
public function register_hooks_clean_urls() {
// Bail if CiviCRM is not installed.
if (!CIVICRM_INSTALLED) {
return;
}
// Bail if we can't initialize CiviCRM.
if (!$this->initialize()) {
return;
}
if (!defined('CIVICRM_CLEANURL') || CIVICRM_CLEANURL !== 1) {
return;
}
// Have we flushed rewrite rules?
// Apply custom rewrite rules, then flush rules afterwards.
// Set a one-time-only option to flag that this has been done.
// Apply custom rewrite rules normally.
$this->rewrite_rules();
}
// ---------------------------------------------------------------------------
// Construction of URLs
// ---------------------------------------------------------------------------
/**
* Add our rewrite rules.
*
* @since 5.7
*
* @param bool $flush_rewrite_rules True if rules should be flushed, false otherwise.
*/
public function rewrite_rules($flush_rewrite_rules = FALSE) {
if (!$this->initialize()) {
return;
}
// Get basepage object.
$basepage = get_page_by_path($config->wpBasePage);
add_rewrite_rule(
'^' . $config->wpBasePage . '/([^?]*)?',
'index.php?page_id=' . $basepage->ID . '&civiwp=CiviCRM&q=civicrm%2F$matches[1]',
if ($flush_rewrite_rules) {
flush_rewrite_rules();
}
/**
* Broadcast the rewrite rules event.
*
* @since 5.7
* @since 5.24 Added $basepage parameter.
*
* @param bool $flush_rewrite_rules True if rules flushed, false otherwise.
* @param WP_Post $basepage The Basepage post object.
do_action('civicrm_after_rewrite_rules', $flush_rewrite_rules, $basepage);
}
/**
* Add our query vars.
*
* @since 5.7
*
* @param array $query_vars The existing query vars.
* @return array $query_vars The modified query vars.
*/
// Build our query vars.
$civicrm_query_vars = [
// URL query vars.
'civiwp', 'q', 'reset', 'id', 'html', 'snippet',
// Shortcode query vars.
'action', 'mode', 'cid', 'gid', 'sid', 'cs', 'force',
];
/**
* Filter the default CiviCRM query vars.
*
* Use in combination with `civicrm_query_vars_assigned` action to ensure
* that any other query vars are included in the assignment to the
* super-global arrays.
*
* @since 5.7
*
* @param array $civicrm_query_vars The default set of query vars.
* @return array $civicrm_query_vars The modified set of query vars.
*/
$civicrm_query_vars = apply_filters('civicrm_query_vars', $civicrm_query_vars);
// Now add them to WordPress.
foreach ($civicrm_query_vars as $civicrm_query_var) {
$query_vars[] = $civicrm_query_var;
}
return $query_vars;
}
* Filters the request right after WordPress has parsed it and replaces the
* 'page' query variable with 'civiwp' if applicable.
*
* Prevents old URLs like example.org/civicrm/?page=CiviCRM&q=what/ever/path&reset=1
* being redirected to example.org/civicrm/?civiwp=CiviCRM&q=what/ever/path&reset=1
*
* @see https://lab.civicrm.org/dev/wordpress/-/issues/49
*
* @since 5.26
*
* @param array $query_vars The existing query vars.
* @return array $query_vars The modified query vars.
*/
public function maybe_replace_page_query_var($query_vars) {
$civi_query_arg = array_search('CiviCRM', $query_vars);
if (FALSE === $civi_query_arg || $civi_query_arg !== 'page') {
return $query_vars;
}
$query_vars['civiwp'] = 'CiviCRM';
return $query_vars;
}
// ---------------------------------------------------------------------------
// CiviCRM Initialisation
// ---------------------------------------------------------------------------
/**
* This method has been moved to "includes/civicrm.admin.php"
* @since 5.33 Placeholder for backwards (and semantic) compatibility.
* @return bool True if CiviCRM is initialized, false otherwise.
// Pass to method in admin class.
return $this->admin->initialize();
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
/**
* Perform necessary stuff prior to CiviCRM being loaded on the front end.
* This needs to be a method because it can then be hooked into WordPress at
* the right time.
*/
public function front_end_page_load() {
static $frontend_loaded = FALSE;
// Add resources for front end.
$this->add_core_resources(TRUE);
// Merge CiviCRM's HTML header with the WordPress theme's header.
add_action('wp_head', [$this, 'wp_head']);
* This is needed because $this->front_end_page_load() is only called when
* there is a single CiviCRM entity present on a page or archive and, whilst
* we don't want all the Javascript to load, we do want stylesheets.
*
* @since 4.6
*/
public function front_end_css_load() {
static $frontend_css_loaded = FALSE;
return;
}
if (!$this->initialize()) {
return;