civicrm.users.php 18.1 KB
Newer Older
1
2
3
<?php
/*
 +--------------------------------------------------------------------+
4
 | Copyright CiviCRM LLC. All rights reserved.                        |
5
 |                                                                    |
6
7
8
 | 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       |
9
 +--------------------------------------------------------------------+
10
 */
11
12
13
14

/**
 *
 * @package CRM
15
 * @copyright CiviCRM LLC https://civicrm.org/licensing
16
17
18
 *
 */

19
// This file must not accessed directly.
20
21
22
if (!defined('ABSPATH')) {
  exit;
}
23
24

/**
25
26
27
 * Define CiviCRM_For_WordPress_Users Class.
 *
 * @since 4.6
28
29
30
31
 */
class CiviCRM_For_WordPress_Users {

  /**
32
   * @var object
33
34
35
   * Plugin object reference.
   * @since 4.6
   * @access public
36
37
   */
  public $civi;
38

39
40
41
42
43
44
45
46
  /**
   * @var string
   * Custom role name.
   * @since 5.52
   * @access private
   */
  private $custom_role_name = 'civicrm_admin';

47
  /**
48
   * Instance constructor.
49
   *
50
   * @since 4.6
51
   */
52
  public function __construct() {
53

54
    // Store reference to CiviCRM plugin object.
55
    $this->civi = civi_wp();
56

57
    // Always listen for activation action.
58
    add_action('civicrm_activate', [$this, 'activate']);
59
60
61
62
63
64
65
66
67
68

  }

  /**
   * Plugin activation tasks.
   *
   * @since 5.6
   */
  public function activate() {

69
    /*
70
     * Assign minimum capabilities to all WordPress roles and create
71
72
     * 'anonymous_user' role.
     */
73
74
    $this->set_wp_user_capabilities();

75
76
77
  }

  /**
78
   * Register hooks.
79
   *
80
   * @since 4.6
81
82
   */
  public function register_hooks() {
83

84
    // Add CiviCRM access capabilities to WordPress roles.
85
    $this->set_access_capabilities();
86

87
    // Do not hook into user updates if CiviCRM not installed yet.
88
89
90
    if (!CIVICRM_INSTALLED) {
      return;
    }
91

92
    // Synchronise users on insert and update.
93
94
    add_action('user_register', [$this, 'update_user']);
    add_action('profile_update', [$this, 'update_user']);
95

96
    // Delete ufMatch record when a WordPress user is deleted.
97
    add_action('deleted_user', [$this, 'delete_user_ufmatch']);
98

99
100
101
  }

  /**
102
103
   * Check permissions.
   *
104
105
106
107
108
109
110
111
112
   * 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()
113
   *
114
115
116
117
   * @since 4.6
   *
   * @param array $args The page arguments array.
   * @return bool True if authenticated, false otherwise.
118
   */
119
  public function check_permission($args) {
120

121
    if ($args[0] != 'civicrm') {
122
123
124
125
126
      return FALSE;
    }

    $config = CRM_Core_Config::singleton();

127
    // Set frontend true.
128
129
130
    $config->userFrameworkFrontend = TRUE;

    require_once 'CRM/Utils/Array.php';
131

132
    // All profile and file URLs, as well as user dashboard and tell-a-friend are valid.
133
    $arg1 = CRM_Utils_Array::value(1, $args);
134
    $invalidPaths = ['admin'];
135
    if (in_array($arg1, $invalidPaths)) {
136
137
138
139
      return FALSE;
    }

    return TRUE;
140

141
142
  }

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;

  }

170
  /**
171
172
   * Get "permission denied" text.
   *
173
174
   * Called when authentication fails in basepage_register_hooks()
   *
175
176
177
   * @since 4.6
   *
   * @return string Warning message.
178
179
   */
  public function get_permission_denied() {
180
    return __('You do not have permission to access this content.', 'civicrm');
181
182
183
  }

  /**
184
185
   * Handle WordPress user events.
   *
186
187
   * Callback function for 'user_register' hook.
   * Callback function for 'profile_update' hook.
188
   *
189
190
   * CMW: seems to (wrongly) create new CiviCRM Contact every time a user changes
   * their first_name or last_name attributes in WordPress.
191
   *
192
193
   * @since 4.6
   *
194
   * @param int $user_id The numeric ID of the WordPress user.
195
   */
196
  public function update_user($user_id) {
197

198
199
200
    $user = get_userdata($user_id);
    if ($user) {
      $this->sync_user($user);
haystack's avatar
haystack committed
201
202
203
    }

  }
204

haystack's avatar
haystack committed
205
  /**
206
207
208
   * Keep WordPress user synced with CiviCRM Contact.
   *
   * @since 4.6
haystack's avatar
haystack committed
209
   *
210
   * @param object $user The WordPress user object.
haystack's avatar
haystack committed
211
   */
212
  public function sync_user($user = FALSE) {
213

214
215
    // Sanity check.
    if ($user === FALSE || !($user instanceof WP_User)) {
haystack's avatar
haystack committed
216
      return;
217
    }
218

haystack's avatar
haystack committed
219
220
221
    if (!$this->civi->initialize()) {
      return;
    }
222

haystack's avatar
haystack committed
223
    require_once 'CRM/Core/BAO/UFMatch.php';
224

225
226
227
228
    /*
     * 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.
     */
haystack's avatar
haystack committed
229
    CRM_Core_BAO_UFMatch::synchronize(
230
231
232
233
234
235
236
237
      // User object.
      $user,
      // Update = true.
      TRUE,
      // CMS.
      'WordPress',
      // Contact Type.
      'Individual'
haystack's avatar
haystack committed
238
    );
239

240
241
242
  }

  /**
243
   * When a WordPress user is deleted, delete the UFMatch record.
244
   *
245
   * Callback function for 'delete_user' hook.
246
   *
247
248
249
   * @since 4.6
   *
   * @param $user_id The numerical ID of the WordPress user.
250
   */
251
  public function delete_user_ufmatch($user_id) {
252

253
    if (!$this->civi->initialize()) {
254
255
256
      return;
    }

257
    // Delete the UFMatch record.
258
    require_once 'CRM/Core/BAO/UFMatch.php';
haystack's avatar
haystack committed
259
    CRM_Core_BAO_UFMatch::deleteUser($user_id);
260
261
262
263

  }

  /**
264
265
266
267
268
   * 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.
269
   *
270
271
   * The legacy global scope function civicrm_wp_set_capabilities() is called
   * from upgrade_4_3_alpha1()
272
   *
273
   * @since 4.6
274
275
276
   */
  public function set_wp_user_capabilities() {

277
    // Define minimum capabilities (CiviCRM permissions).
278
    $default_min_capabilities = [
279
      'access_all_custom_data' => 1,
280
      'access_civimail_subscribe_unsubscribe_pages' => 1,
281
282
283
284
285
286
287
      '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,
288
289
      'view_event_info' => 1,
      'view_my_invoices' => 1,
290
      'view_public_civimail_content' => 1,
291
    ];
292

293
294
295
296
297
298
299
    /**
     * Allow minimum capabilities to be filtered.
     *
     * @since 4.6
     *
     * @param array $default_min_capabilities The minimum capabilities.
     */
300
    $min_capabilities = apply_filters('civicrm_min_capabilities', $default_min_capabilities);
301

302
    // Assign the minimum capabilities to all WordPress roles.
303
    $wp_roles = wp_roles();
304
305
306
    foreach ($wp_roles->role_names as $role => $name) {
      $roleObj = $wp_roles->get_role($role);
      foreach ($min_capabilities as $capability_name => $capability_value) {
307
308
309
        if (!$roleObj->has_cap($capability_name)) {
          $roleObj->add_cap($capability_name);
        }
310
311
312
313
      }
    }

    // Add the 'anonymous_user' role with minimum capabilities.
314
    if (!in_array('anonymous_user', $wp_roles->roles)) {
315
      add_role('anonymous_user', __('Anonymous User', 'civicrm'), $min_capabilities);
316
317
318
319
320
    }

  }

  /**
321
322
   * Add CiviCRM access capabilities to WordPress roles.
   *
323
   * This is called in register_hooks().
324
325
326
327
   *
   * The legacy global scope function wp_civicrm_capability() is called by
   * postProcess() in civicrm/CRM/ACL/Form/WordPress/Permissions.php
   *
328
   * @since 4.6
329
330
331
   */
  public function set_access_capabilities() {

332
    $wp_roles = wp_roles();
333

334
335
336
337
338
339
340
341
342
343
    /**
     * 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.
     */
344
    $roles = apply_filters('civicrm_access_roles', ['super admin', 'administrator']);
345

346
    // Give access to CiviCRM to particular roles.
347
348
    foreach ($roles as $role) {
      $roleObj = $wp_roles->get_role($role);
349
      if (
350
351
352
        is_object($roleObj) &&
        is_array($roleObj->capabilities) &&
        !array_key_exists('access_civicrm', $wp_roles->get_role($role)->capabilities)
353
      ) {
354
        $wp_roles->add_cap($role, 'access_civicrm');
355
356
357
358
359
360
      }
    }

  }

  /**
361
   * Get CiviCRM Contact Type.
362
363
   *
   * @since 4.6
364
   *
365
366
   * @param string $default The requested Contact Type.
   * @return string $ctype The computed Contact Type.
367
   */
368
  public function get_civicrm_contact_type($default = NULL) {
369

370
371
372
373
    /*
     * Here we are creating a new Contact.
     * Get the Contact Type from the POST variables if any.
     */
374
    if (isset($_REQUEST['ctype'])) {
375
      $ctype = $_REQUEST['ctype'];
376
377
378
379
    }
    elseif (
      isset($_REQUEST['edit']) &&
      isset($_REQUEST['edit']['ctype'])
380
381
    ) {
      $ctype = $_REQUEST['edit']['ctype'];
382
383
    }
    else {
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
      $ctype = $default;
    }

    if (
      $ctype != 'Individual' &&
      $ctype != 'Organization' &&
      $ctype != 'Household'
    ) {
      $ctype = $default;
    }

    return $ctype;

  }

399
400
401
402
403
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
  // ---------------------------------------------------------------------------

  /**
   * 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']);

  }

732
}