PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / trunk
GiveWP – Donation Plugin and Fundraising Platform vtrunk
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 1 month 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 6 months ago RegisterCampaignIdRestField.php 1 year ago RegisterCampaignShortcodes.php 10 months ago RenderDonateButton.php 9 months ago ReplaceGiveFormsCptLabels.php 1 year ago UnarchiveCampaignFormAsPublishStatus.php 1 year ago
AllowGiveRolesToEditCampaignPages.php
207 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.15.3 Handle null $cap gracefully for better compatibility.
34 * @since 4.14.0
35 */
36 public function mapMetaCap(array $caps, ?string $cap, int $userId, array $args): array
37 {
38 // Fast check: only handle specific meta capabilities
39 static $pageMetaCaps = ['edit_post' => true, 'delete_post' => true, 'publish_post' => true, 'read_post' => true];
40 if (!is_string($cap) || !isset($pageMetaCaps[$cap])) {
41 return $caps;
42 }
43
44 // We need a post ID to check
45 if (empty($args[0])) {
46 return $caps;
47 }
48
49 // Check if user has Give capability first (cheaper than DB lookups)
50 if (!user_can($userId, 'edit_give_forms')) {
51 return $caps;
52 }
53
54 // Check if this is a campaign page (uses cache)
55 if (!$this->isCampaignPage((int)$args[0])) {
56 return $caps;
57 }
58
59 // Grant full access to campaign pages
60 return [];
61 }
62
63 /**
64 * Dynamically grant page capabilities when working with a campaign page.
65 *
66 * The block editor and REST API check primitive capabilities like 'publish_pages'
67 * directly (not through map_meta_cap), so we need to dynamically add them.
68 *
69 * Hooked to 'user_has_cap' filter.
70 *
71 * @since 4.14.0
72 */
73 public function grantPublishCapability(array $allcaps, array $caps, array $args, WP_User $user): array
74 {
75 // Fast check: skip if not in admin or REST context
76 if (!is_admin() && !wp_is_serving_rest_request()) {
77 return $allcaps;
78 }
79
80 // Fast check: only process if specific page caps are requested
81 static $pageCaps = ['publish_pages' => true, 'edit_others_pages' => true, 'edit_published_pages' => true, 'delete_others_pages' => true];
82 $requestedPageCaps = array_filter($caps, static function ($cap) use ($pageCaps) {
83 return is_string($cap) && isset($pageCaps[$cap]);
84 });
85 if (empty($requestedPageCaps)) {
86 return $allcaps;
87 }
88
89 // User must have edit_give_forms capability (check from allcaps, no DB query)
90 if (empty($allcaps['edit_give_forms'])) {
91 return $allcaps;
92 }
93
94 // Get the post being edited (cached)
95 $postId = $this->getCurrentEditingPostId();
96 if (!$postId) {
97 return $allcaps;
98 }
99
100 // Check if this is a campaign page (uses cache)
101 if (!$this->isCampaignPage($postId)) {
102 return $allcaps;
103 }
104
105 // Grant the requested page capabilities
106 foreach ($requestedPageCaps as $cap) {
107 $allcaps[$cap] = true;
108 }
109
110 return $allcaps;
111 }
112
113 /**
114 * Check if a post is a campaign page (with caching).
115 *
116 * @since 4.14.0
117 */
118 private function isCampaignPage(int $postId): bool
119 {
120 if (isset(self::$campaignPageCache[$postId])) {
121 return self::$campaignPageCache[$postId];
122 }
123
124 $post = get_post($postId);
125 if (!$post || $post->post_type !== 'page') {
126 self::$campaignPageCache[$postId] = false;
127 return false;
128 }
129
130 $campaignId = get_post_meta($postId, CampaignPageMetaKeys::CAMPAIGN_ID, true);
131 self::$campaignPageCache[$postId] = !empty($campaignId);
132
133 return self::$campaignPageCache[$postId];
134 }
135
136 /**
137 * Get the post ID currently being edited (with static caching).
138 *
139 * @since 4.14.0
140 */
141 private function getCurrentEditingPostId(): ?int
142 {
143 static $cachedPostId = null;
144 static $checked = false;
145
146 if ($checked) {
147 return $cachedPostId;
148 }
149 $checked = true;
150
151 // Check for post ID in query string (standard edit screen)
152 if (!empty($_GET['post'])) {
153 $cachedPostId = (int)$_GET['post'];
154 return $cachedPostId;
155 }
156
157 // Check REST API for post ID (query param or route)
158 if (wp_is_serving_rest_request()) {
159 if (!empty($_GET['post_id'])) {
160 $cachedPostId = (int)$_GET['post_id'];
161 return $cachedPostId;
162 }
163
164 $cachedPostId = $this->getPostIdFromRestRoute();
165 if ($cachedPostId) {
166 return $cachedPostId;
167 }
168 }
169
170 // Check global $post
171 global $post;
172 if ($post instanceof \WP_Post) {
173 $cachedPostId = $post->ID;
174 return $cachedPostId;
175 }
176
177 return null;
178 }
179
180 /**
181 * Extract post ID from REST API route.
182 *
183 * @since 4.14.0
184 */
185 private function getPostIdFromRestRoute(): ?int
186 {
187 static $cachedRestPostId = null;
188 static $restChecked = false;
189
190 if ($restChecked) {
191 return $cachedRestPostId;
192 }
193 $restChecked = true;
194
195 global $wp;
196 $restRoute = $wp->query_vars['rest_route'] ?? '';
197
198 // Match routes like /wp/v2/pages/123
199 if (preg_match('#/wp/v2/pages/(\d+)#', $restRoute, $matches)) {
200 $cachedRestPostId = (int)$matches[1];
201 }
202
203 return $cachedRestPostId;
204 }
205 }
206
207