Newer
Older
<?php
/*
+--------------------------------------------------------------------+
| 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 |
+--------------------------------------------------------------------+
// This file must not accessed directly.
if (!defined('ABSPATH')) {
exit;
}
* Define CiviCRM_For_WordPress_Users Class.
*
* @since 4.6
* Plugin object reference.
* @since 4.6
* @access public
/**
* @var string
* Custom role name.
* @since 5.52
* @access private
*/
private $custom_role_name = 'civicrm_admin';
add_action('civicrm_activate', [$this, 'activate']);
}
/**
* Plugin activation tasks.
*
* @since 5.6
*/
public function activate() {
* Assign minimum capabilities to all WordPress roles and create
// Do not hook into user updates if CiviCRM not installed yet.
if (!CIVICRM_INSTALLED) {
return;
}
// Synchronise users on insert and update.
add_action('user_register', [$this, 'update_user']);
add_action('profile_update', [$this, 'update_user']);
// Delete ufMatch record when a WordPress user is deleted.
add_action('deleted_user', [$this, 'delete_user_ufmatch']);
* This method only denies permission when the CiviCRM path that is requested
* begins with "civicrm/admin". Its intention seems to be to exclude admin
* requests from display on the front-end.
*
* Used internally by:
*
* - CiviCRM_For_WordPress_Basepage::basepage_handler()
* - CiviCRM_For_WordPress_Shortcodes::render_single()
* - civicrm_check_permission()
* @since 4.6
*
* @param array $args The page arguments array.
* @return bool True if authenticated, false otherwise.
return FALSE;
}
$config = CRM_Core_Config::singleton();
$config->userFrameworkFrontend = TRUE;
require_once 'CRM/Utils/Array.php';
// All profile and file URLs, as well as user dashboard and tell-a-friend are valid.
$invalidPaths = ['admin'];
if (in_array($arg1, $invalidPaths)) {
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/**
* Check a CiviCRM permission.
*
* @since 5.35
*
* @param str $permission The permission string.
* @return bool $permitted True if allowed, false otherwise.
*/
public function check_civicrm_permission($permission) {
// Always deny if CiviCRM is not initialised.
if (!$this->civi->initialize()) {
return FALSE;
}
// Deny by default.
$permitted = FALSE;
// Check CiviCRM permissions.
if (CRM_Core_Permission::check($permission)) {
$permitted = TRUE;
}
return $permitted;
}
* Called when authentication fails in basepage_register_hooks()
*
* @since 4.6
*
* @return string Warning message.
return __('You do not have permission to access this content.', 'civicrm');
* Callback function for 'user_register' hook.
* Callback function for 'profile_update' hook.
* CMW: seems to (wrongly) create new CiviCRM Contact every time a user changes
* their first_name or last_name attributes in WordPress.
* @param int $user_id The numeric ID of the WordPress user.
$user = get_userdata($user_id);
if ($user) {
$this->sync_user($user);
* Keep WordPress user synced with CiviCRM Contact.
*
* @since 4.6
* @param object $user The WordPress user object.
// Sanity check.
if ($user === FALSE || !($user instanceof WP_User)) {
return;
}
if (!$this->civi->initialize()) {
return;
}
require_once 'CRM/Core/BAO/UFMatch.php';
/*
* This does not return anything, so if we want to do anything further
* to the CiviCRM Contact, we have to search for it all over again.
*/
// User object.
$user,
// Update = true.
TRUE,
// CMS.
'WordPress',
// Contact Type.
'Individual'
* When a WordPress user is deleted, delete the UFMatch record.
* @param int $user_id The numerical ID of the WordPress user.
if (!$this->civi->initialize()) {
return;
}
require_once 'CRM/Core/BAO/UFMatch.php';
CRM_Core_BAO_UFMatch::deleteUser($user_id);
}
/**
* Create anonymous role and define capabilities.
*
* Function to create 'anonymous_user' role, if 'anonymous_user' role is not
* in the WordPress installation and assign minimum capabilities for all
* WordPress roles.
* The legacy global scope function civicrm_wp_set_capabilities() is called
* from upgrade_4_3_alpha1()
// Define minimum capabilities (CiviCRM permissions).
$default_min_capabilities = [
'access_civimail_subscribe_unsubscribe_pages' => 1,
'access_uploaded_files' => 1,
'make_online_contributions' => 1,
'profile_create' => 1,
'profile_edit' => 1,
'profile_view' => 1,
'register_for_events' => 1,
'sign_civicrm_petition' => 1,
'view_event_info' => 1,
'view_my_invoices' => 1,
/**
* Allow minimum capabilities to be filtered.
*
* @since 4.6
*
* @param array $default_min_capabilities The minimum capabilities.
*/
$min_capabilities = apply_filters('civicrm_min_capabilities', $default_min_capabilities);
// Assign the minimum capabilities to all WordPress roles.
foreach ($wp_roles->role_names as $role => $name) {
$roleObj = $wp_roles->get_role($role);
foreach ($min_capabilities as $capability_name => $capability_value) {
if (!$roleObj->has_cap($capability_name)) {
$roleObj->add_cap($capability_name);
}
}
}
// Add the 'anonymous_user' role with minimum capabilities.
add_role('anonymous_user', __('Anonymous User', 'civicrm'), $min_capabilities);
* Add CiviCRM access capabilities to WordPress roles.
*
*
* The legacy global scope function wp_civicrm_capability() is called by
* postProcess() in civicrm/CRM/ACL/Form/WordPress/Permissions.php
*
/**
* Filter the default roles with access to CiviCRM.
*
* The 'access_civicrm' capability is the most basic CiviCRM capability and
* is required to see the CiviCRM menu link in the WordPress Admin menu.
*
* @since 4.6
*
* @param array The default roles with access to CiviCRM.
*/
$roles = apply_filters('civicrm_access_roles', ['super admin', 'administrator']);
// Give access to CiviCRM to particular roles.
foreach ($roles as $role) {
$roleObj = $wp_roles->get_role($role);
is_object($roleObj) &&
is_array($roleObj->capabilities) &&
!array_key_exists('access_civicrm', $wp_roles->get_role($role)->capabilities)
* @param string $default The requested Contact Type.
* @return string $ctype The computed Contact Type.
public function get_civicrm_contact_type($default = NULL) {
// Nonce verification not necessary here.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
/*
* Here we are creating a new Contact.
* Get the Contact Type from the POST variables if any.
*/
if (isset($_REQUEST['ctype'])) {
$ctype = sanitize_text_field(wp_unslash($_REQUEST['ctype']));
}
elseif (
isset($_REQUEST['edit']) &&
isset($_REQUEST['edit']['ctype'])
$ctype = sanitize_text_field(wp_unslash($_REQUEST['edit']['ctype']));
// phpcs:enable WordPress.Security.NonceVerification.Recommended
$ctype !== 'Individual' &&
$ctype !== 'Organization' &&
$ctype !== 'Household'
) {
$ctype = $default;
}
return $ctype;
}
404
405
406
407
408
409
410
411
412
413
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
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
// ---------------------------------------------------------------------------
/**
* Refreshes all CiviCRM capabilities.
*
* @since 5.52
*/
public function refresh_capabilities() {
// Refresh capabilities assigned at plugin activation.
$this->set_wp_user_capabilities();
// Refresh access capabilities.
$this->set_access_capabilities();
/**
* Fires when CiviCRM has refreshed the WordPress capabilities.
*
* @since 5.52
*/
do_action('civicrm_capabilities_refreshed');
}
/**
* Applies all CiviCRM capabilities to the custom WordPress role.
*
* @since 5.52
*/
public function refresh_custom_role_capabilities() {
// Get the role to apply all CiviCRM permissions to.
$custom_role = $this->get_custom_role();
if (empty($custom_role)) {
return;
}
// Get all CiviCRM capabilities.
$capabilities = $this->get_all_civicrm_capabilities();
// Add the capabilities if not already added.
foreach ($capabilities as $capability) {
if (!$custom_role->has_cap($capability)) {
$custom_role->add_cap($capability);
}
}
// Delete capabilities that no longer exist.
$this->delete_missing_capabilities($capabilities);
/**
* Fires when CiviCRM has refreshed the WordPress capabilities.
*
* @since 5.52
*
* @param array $capabilities The array of CiviCRM permissions converted to WordPress capabilities.
* @param WP_Role $custom_role The WordPress role object.
*/
do_action('civicrm_custom_role_capabilities_refreshed', $capabilities, $custom_role);
}
// ---------------------------------------------------------------------------
/**
* Gets all CiviCRM permissions converted to WordPress capabilities.
*
* @since 5.52
*
* @return array $capabilities The array of capabilities.
*/
public function get_all_civicrm_capabilities() {
// Init return.
$capabilities = [];
// Bail if no CiviCRM.
if (!$this->civi->initialize()) {
return $capabilities;
}
// Get all CiviCRM permissions, excluding disabled components and descriptions.
$permissions = CRM_Core_Permission::basicPermissions(FALSE, FALSE);
// Convert to WordPress capabilities.
foreach ($permissions as $permission => $title) {
$capabilities[] = CRM_Utils_String::munge(strtolower($permission));
}
/**
* Filters the complete set of CiviCRM capabilities.
*
* @since 5.52
*
* @param array $capabilities The complete set of CiviCRM capabilities.
*/
return apply_filters('civicrm_all_capabilities', $capabilities);
}
/**
* Deletes CiviCRM capabilities when they no longer exist.
*
* This can happen when an Extension which had previously added permissions
* is disabled or uninstalled, for example.
*
* Things can get a bit complicated here because capabilities can appear and
* disappear (see above) and may have been assigned to other roles while they
* were present. Deleting missing capabilities may therefore have unintended
* consequences. Use the "civicrm_delete_missing_capabilities" filter if you
* are sure that you want to delete missing capabilities.
*
* @since 5.52
*
* @param array $capabilities The complete set of CiviCRM capabilities.
*/
public function delete_missing_capabilities($capabilities) {
/**
* Filters whether capabilities should be deleted.
*
* To enable deletion of capabilities, pass boolean true.
*
* @since 5.52
*
* @param bool $allow_delete False (disabled) by default.
*/
$allow_delete = apply_filters('civicrm_delete_missing_capabilities', FALSE);
if ($allow_delete === FALSE) {
return;
}
// Read the stored CiviCRM permissions array.
$stored = $this->get_saved_capabilities();
// Save and bail if we don't have any stored.
if (empty($stored)) {
$this->save_capabilities($capabilities);
return;
}
// Find the capabilities that are missing in the current CiviCRM data.
$not_in_current = array_diff($stored, $capabilities);
// Get the role to delete CiviCRM permissions from.
$custom_role = $this->get_custom_role();
if (empty($custom_role)) {
return;
}
// Delete the capabilities if not already deleted.
foreach ($capabilities as $capability) {
if ($custom_role->has_cap($capability)) {
$custom_role->remove_cap($capability);
}
}
// Overwrite the current permissions array.
$this->save_capabilities($capabilities);
}
/**
* Gets the stored array of CiviCRM permissions formatted as WordPress capabilities.
*
* @since 5.52
*
* @return array $capabilities The array of stored capabilities.
*/
public function get_saved_capabilities() {
// Get capabilities from option.
$capabilities = get_option('civicrm_permissions_sync_perms', 'false');
// If no option exists, cast return as array.
if ($capabilities === 'false') {
$capabilities = [];
}
return $capabilities;
}
/**
* Stores the array of CiviCRM permissions formatted as WordPress capabilities.
*
* @since 5.52
*
* @param array $capabilities The array of capabilities to store.
*/
public function save_capabilities($capabilities) {
update_option('civicrm_permissions_sync_perms', $capabilities);
}
// ---------------------------------------------------------------------------
/**
* Retrieves the config for the custom WordPress role.
*
* @since 5.52
*
* @return array $role_data The array of custom role data.
*/
public function get_custom_role_data() {
// Init default role data.
$role_data = [
'name' => $this->custom_role_name,
'title' => __('CiviCRM Admin', 'civicrm'),
];
/**
* Filters the default CiviCRM custom role data.
*
* @since 5.52
*
* @param array $role_data The array of default CiviCRM custom role data.
*/
$role_data = apply_filters('civicrm_custom_role_data', $role_data);
return $role_data;
}
/**
* Checks if the custom WordPress role exists.
*
* @since 5.52
*
* @return WP_Role|bool $custom_role The custom role if it exists, or false otherwise.
*/
public function has_custom_role() {
// Return the custom role if it already exists.
$custom_role = $this->get_custom_role();
if (!empty($custom_role)) {
return $custom_role;
}
return FALSE;
}
/**
* Retrieves the custom WordPress role.
*
* @since 5.52
*
* @return WP_Role|bool $custom_role The custom role, or false on failure.
*/
public function get_custom_role() {
// Get the default role data.
$role_data = $this->get_custom_role_data();
// Return the custom role if it exists.
$wp_roles = wp_roles();
if ($wp_roles->is_role($role_data['name'])) {
$custom_role = $wp_roles->get_role($role_data['name']);
return $custom_role;
}
return FALSE;
}
/**
* Creates the custom WordPress role.
*
* We need a role to which we add all CiviCRM permissions. This makes all the
* CiviCRM capabilities discoverable by other plugins.
*
* This method creates the role if it doesn't already exist by cloning the
* built-in WordPress "administrator" role.
*
* Note: it's unlikely that you will want to grant this role to any WordPress
* users - it is purely present to make capabilities discoverable.
*
* @since 5.52
*
* @return WP_Role|bool $custom_role The custom role, or false on failure.
*/
public function create_custom_role() {
// Return the custom role if it already exists.
$custom_role = $this->has_custom_role();
if (!empty($custom_role)) {
return $custom_role;
}
// Bail if the "administrator" role doesn't exist.
$wp_roles = wp_roles();
if (!$wp_roles->is_role('administrator')) {
return FALSE;
}
// Get the default role data.
$role_data = $this->get_custom_role_data();
// Add new role based on the "administrator" role.
$admin = $wp_roles->get_role('administrator');
$custom_role = add_role($role_data['name'], $role_data['title'], $admin->capabilities);
// Return false if something went wrong.
if (empty($custom_role)) {
return FALSE;
}
return $custom_role;
}
/**
* Deletes the custom WordPress role.
*
* @since 5.52
*/
public function delete_custom_role() {
// Bail if the custom role does not exist.
$custom_role = $this->has_custom_role();
if (empty($custom_role)) {
return;
}
// Get the default role data.
$role_data = $this->get_custom_role_data();
// Okay, remove it.
remove_role($role_data['name']);
}