PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.14.2
GiveWP – Donation Plugin and Fundraising Platform v4.14.2
4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / Campaigns / Actions / AllowGiveRolesToEditCampaignPages.php
give / src / Campaigns / Actions Last commit date
AddCampaignFormFromRequest.php 1 year ago AddNewBadgeToAdminMenuItem.php 1 year ago AllowGiveRolesToEditCampaignPages.php 5 months ago ArchiveCampaignFormsAsDraftStatus.php 1 year ago ArchiveCampaignPagesAsDraftStatus.php 1 year ago AssignDuplicatedFormToCampaign.php 1 year ago AssociateCampaignPageWithCampaign.php 1 year ago CacheCampaignData.php 7 months ago ConvertQueryDataToCampaign.php 1 year ago CreateCampaignPage.php 1 year ago CreateDefaultCampaignForm.php 4 months ago CreateDefaultLayoutForCampaignPage.php 1 year ago DuplicateCampaign.php 7 months ago EnqueueCampaignPageEditorAssets.php 1 year ago FormInheritsCampaignGoal.php 1 year ago LoadCampaignAdminOptions.php 11 months ago LoadCampaignDetailsAssets.php 1 year ago LoadCampaignPublicOptions.php 11 months ago LoadCampaignsListTableAssets.php 11 months ago PreventDeleteDefaultForm.php 1 year ago RedirectLegacyCreateFormToCreateCampaign.php 4 months ago RegisterCampaignBlocks.php 7 months ago RegisterCampaignIdRestField.php 1 year ago RegisterCampaignShortcodes.php 10 months ago RenderDonateButton.php 10 months ago ReplaceGiveFormsCptLabels.php 1 year ago UnarchiveCampaignFormAsPublishStatus.php 1 year ago
AllowGiveRolesToEditCampaignPages.php
206 lines
1 <?php
2
3 namespace Give\Campaigns\Actions;
4
5 use Give\Campaigns\ValueObjects\CampaignPageMetaKeys;
6 use WP_User;
7
8 /**
9 * Allow users with Give roles to edit and publish campaign landing pages.
10 *
11 * Campaign pages are standard WordPress pages (post_type = 'page') that have
12 * the give_campaign_id meta key. Give roles like give_worker and give_manager
13 * have edit_pages but not edit_others_pages or publish_pages, so this action
14 * maps the meta capabilities to allow full management of campaign pages
15 * for users with edit_give_forms capability.
16 *
17 * @since 4.14.0
18 */
19 class AllowGiveRolesToEditCampaignPages
20 {
21 /**
22 * Cache for campaign page checks to avoid repeated DB queries.
23 *
24 * @var array<int, bool>
25 */
26 private static array $campaignPageCache = [];
27
28 /**
29 * Filter meta capabilities for campaign pages.
30 *
31 * Hooked to 'map_meta_cap' filter.
32 *
33 * @since 4.14.0
34 */
35 public function mapMetaCap(array $caps, string $cap, int $userId, array $args): array
36 {
37 // Fast check: only handle specific meta capabilities
38 static $pageMetaCaps = ['edit_post' => true, 'delete_post' => true, 'publish_post' => true, 'read_post' => true];
39 if (!isset($pageMetaCaps[$cap])) {
40 return $caps;
41 }
42
43 // We need a post ID to check
44 if (empty($args[0])) {
45 return $caps;
46 }
47
48 // Check if user has Give capability first (cheaper than DB lookups)
49 if (!user_can($userId, 'edit_give_forms')) {
50 return $caps;
51 }
52
53 // Check if this is a campaign page (uses cache)
54 if (!$this->isCampaignPage((int)$args[0])) {
55 return $caps;
56 }
57
58 // Grant full access to campaign pages
59 return [];
60 }
61
62 /**
63 * Dynamically grant page capabilities when working with a campaign page.
64 *
65 * The block editor and REST API check primitive capabilities like 'publish_pages'
66 * directly (not through map_meta_cap), so we need to dynamically add them.
67 *
68 * Hooked to 'user_has_cap' filter.
69 *
70 * @since 4.14.0
71 */
72 public function grantPublishCapability(array $allcaps, array $caps, array $args, WP_User $user): array
73 {
74 // Fast check: skip if not in admin or REST context
75 if (!is_admin() && !wp_is_serving_rest_request()) {
76 return $allcaps;
77 }
78
79 // Fast check: only process if specific page caps are requested
80 static $pageCaps = ['publish_pages' => true, 'edit_others_pages' => true, 'edit_published_pages' => true, 'delete_others_pages' => true];
81 $requestedPageCaps = array_filter($caps, static function ($cap) use ($pageCaps) {
82 return is_string($cap) && isset($pageCaps[$cap]);
83 });
84 if (empty($requestedPageCaps)) {
85 return $allcaps;
86 }
87
88 // User must have edit_give_forms capability (check from allcaps, no DB query)
89 if (empty($allcaps['edit_give_forms'])) {
90 return $allcaps;
91 }
92
93 // Get the post being edited (cached)
94 $postId = $this->getCurrentEditingPostId();
95 if (!$postId) {
96 return $allcaps;
97 }
98
99 // Check if this is a campaign page (uses cache)
100 if (!$this->isCampaignPage($postId)) {
101 return $allcaps;
102 }
103
104 // Grant the requested page capabilities
105 foreach ($requestedPageCaps as $cap) {
106 $allcaps[$cap] = true;
107 }
108
109 return $allcaps;
110 }
111
112 /**
113 * Check if a post is a campaign page (with caching).
114 *
115 * @since 4.14.0
116 */
117 private function isCampaignPage(int $postId): bool
118 {
119 if (isset(self::$campaignPageCache[$postId])) {
120 return self::$campaignPageCache[$postId];
121 }
122
123 $post = get_post($postId);
124 if (!$post || $post->post_type !== 'page') {
125 self::$campaignPageCache[$postId] = false;
126 return false;
127 }
128
129 $campaignId = get_post_meta($postId, CampaignPageMetaKeys::CAMPAIGN_ID, true);
130 self::$campaignPageCache[$postId] = !empty($campaignId);
131
132 return self::$campaignPageCache[$postId];
133 }
134
135 /**
136 * Get the post ID currently being edited (with static caching).
137 *
138 * @since 4.14.0
139 */
140 private function getCurrentEditingPostId(): ?int
141 {
142 static $cachedPostId = null;
143 static $checked = false;
144
145 if ($checked) {
146 return $cachedPostId;
147 }
148 $checked = true;
149
150 // Check for post ID in query string (standard edit screen)
151 if (!empty($_GET['post'])) {
152 $cachedPostId = (int)$_GET['post'];
153 return $cachedPostId;
154 }
155
156 // Check REST API for post ID (query param or route)
157 if (wp_is_serving_rest_request()) {
158 if (!empty($_GET['post_id'])) {
159 $cachedPostId = (int)$_GET['post_id'];
160 return $cachedPostId;
161 }
162
163 $cachedPostId = $this->getPostIdFromRestRoute();
164 if ($cachedPostId) {
165 return $cachedPostId;
166 }
167 }
168
169 // Check global $post
170 global $post;
171 if ($post instanceof \WP_Post) {
172 $cachedPostId = $post->ID;
173 return $cachedPostId;
174 }
175
176 return null;
177 }
178
179 /**
180 * Extract post ID from REST API route.
181 *
182 * @since 4.14.0
183 */
184 private function getPostIdFromRestRoute(): ?int
185 {
186 static $cachedRestPostId = null;
187 static $restChecked = false;
188
189 if ($restChecked) {
190 return $cachedRestPostId;
191 }
192 $restChecked = true;
193
194 global $wp;
195 $restRoute = $wp->query_vars['rest_route'] ?? '';
196
197 // Match routes like /wp/v2/pages/123
198 if (preg_match('#/wp/v2/pages/(\d+)#', $restRoute, $matches)) {
199 $cachedRestPostId = (int)$matches[1];
200 }
201
202 return $cachedRestPostId;
203 }
204 }
205
206