PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
booking / includes / publish / class-wpbc-booking-form-publisher.php

class-wpbc-booking-form-publisher.php in Booking Calendar 11.9, at includes/publish/class-wpbc-booking-form-publisher.php

317 lines 12.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Presentation-neutral Booking Form publishing service.
4 *
5 * @package Booking Calendar
6 * @since 11.6.0
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 require_once dirname( __DIR__ ) . '/_functions/class-wpbc-environment-policy.php';
14
15 /**
16 * Publish one Booking Form shortcode into a new or existing WordPress page.
17 *
18 * This service owns request-independent validation and delegates the canonical
19 * content write to wpbc_add_shortcode_into_page(). UI-specific AJAX and modal
20 * controllers remain thin consumers of this neutral boundary.
21 */
22 final class WPBC_Booking_Form_Publisher {
23
24 /**
25 * Determine whether page publishing must be blocked on this website.
26 *
27 * This compatibility method delegates to the shared environment policy. The
28 * policy owns host identity, exact public-demo classification, internal test
29 * exceptions, and the released publishing filter for every consumer.
30 *
31 * @return bool True when page discovery and page mutations must be blocked.
32 */
33 public static function is_demo_restricted() {
34 return WPBC_Environment_Policy::is_page_publishing_restricted();
35 }
36
37 /**
38 * Publish a normalized Booking Form request.
39 *
40 * @param array $publish_request Untrusted publish values from an authorized controller.
41 *
42 * @return array|WP_Error Published-page response or a safe validation error.
43 */
44 public function publish( $publish_request ) {
45 $publish_request = is_array( $publish_request ) ? $publish_request : array();
46
47 if ( self::is_demo_restricted() ) {
48 return new WP_Error( 'wpbc_publish_demo_restricted', __( 'In the demo versions this operation is not allowed.', 'booking' ) );
49 }
50
51 if ( ! function_exists( 'wpbc_add_shortcode_into_page' ) ) {
52 return new WP_Error( 'wpbc_publish_helper_unavailable', __( 'Publishing helper is not available.', 'booking' ) );
53 }
54
55 $publish_mode = isset( $publish_request['publish_mode'] ) ? sanitize_key( $publish_request['publish_mode'] ) : '';
56 $resource_id = isset( $publish_request['resource_id'] ) ? absint( $publish_request['resource_id'] ) : 0;
57 $form_name = $this->normalize_form_name(
58 isset( $publish_request['form_name'] ) ? $publish_request['form_name'] : '',
59 isset( $publish_request['shortcode_raw'] ) ? $publish_request['shortcode_raw'] : ''
60 );
61 $page_id = isset( $publish_request['page_id'] ) ? absint( $publish_request['page_id'] ) : 0;
62 $page_title = isset( $publish_request['page_title'] ) ? sanitize_text_field( $publish_request['page_title'] ) : '';
63 $shortcode_raw = $this->normalize_booking_shortcode(
64 isset( $publish_request['shortcode_raw'] ) ? $publish_request['shortcode_raw'] : '',
65 $resource_id,
66 $form_name
67 );
68
69 if ( ! in_array( $publish_mode, array( 'create', 'edit' ), true ) ) {
70 return new WP_Error( 'wpbc_publish_invalid_mode', __( 'Unknown publish mode.', 'booking' ) );
71 }
72
73 if ( ! $resource_id ) {
74 return new WP_Error( 'wpbc_publish_invalid_resource', __( 'The selected Booking Resource is invalid.', 'booking' ) );
75 }
76
77 $capability_error = $this->validate_page_capability( $publish_mode, $page_id );
78 if ( is_wp_error( $capability_error ) ) {
79 return $capability_error;
80 }
81
82 $helper_params = array(
83 'shortcode' => $this->wrap_shortcode_for_editor( $shortcode_raw ),
84 'check_exist_shortcode' => $this->get_duplicate_check_list( $resource_id, $shortcode_raw, $form_name ),
85 'resource_id' => $resource_id,
86 );
87
88 if ( 'create' === $publish_mode ) {
89 if ( '' === $page_title ) {
90 return new WP_Error( 'wpbc_publish_missing_title', __( 'Please enter a page title.', 'booking' ) );
91 }
92 $helper_params['post_title'] = $page_title;
93 $helper_params['page_post_name'] = sanitize_title( $page_title );
94 } else {
95 $page = get_post( $page_id );
96 if ( ! $page_id ) {
97 return new WP_Error( 'wpbc_publish_missing_page', __( 'Please select an existing page.', 'booking' ) );
98 }
99 if ( ! $page || 'page' !== $page->post_type ) {
100 return new WP_Error( 'wpbc_publish_page_missing', __( 'The selected page does not exist.', 'booking' ) );
101 }
102 $helper_params['page_id'] = $page_id;
103 }
104
105 /**
106 * Filter canonical page-helper parameters for neutral Booking Form publishing.
107 *
108 * @param array $helper_params Canonical wpbc_add_shortcode_into_page() parameters.
109 * @param string $publish_mode Create or edit mode.
110 * @param int $resource_id Booking Resource ID.
111 * @param string $shortcode_raw Normalized raw shortcode.
112 * @param string $form_name Normalized Booking Form name.
113 */
114 $helper_params = apply_filters(
115 'wpbc_publish_booking_form_request_params',
116 $helper_params,
117 $publish_mode,
118 $resource_id,
119 $shortcode_raw,
120 $form_name
121 );
122
123 $publish_result = wpbc_add_shortcode_into_page( $helper_params );
124 if ( ! is_array( $publish_result ) || empty( $publish_result['result'] ) ) {
125 $message = is_array( $publish_result ) && ! empty( $publish_result['message'] )
126 ? wp_kses_post( $publish_result['message'] )
127 : __( 'Unable to publish the booking form into the selected page.', 'booking' );
128 return new WP_Error( 'wpbc_publish_failed', $message );
129 }
130
131 $post_id = $this->resolve_post_id( $publish_result, $helper_params );
132 $view_url = $post_id ? get_permalink( $post_id ) : '';
133 $edit_url = $post_id ? get_edit_post_link( $post_id, '' ) : '';
134 $post_title = $post_id ? get_the_title( $post_id ) : '';
135
136 if ( $view_url ) {
137 $view_url .= '#bklnk' . $resource_id;
138 }
139
140 return array(
141 'message' => ! empty( $publish_result['message'] ) ? wp_kses_post( $publish_result['message'] ) : __( 'Booking form has been published.', 'booking' ),
142 'post_id' => $post_id,
143 'post_title' => $post_title,
144 'view_url' => $view_url,
145 'edit_url' => $edit_url,
146 'form_name' => $form_name,
147 );
148 }
149
150 /**
151 * Normalize a Booking Form name to a stable key.
152 *
153 * @param mixed $form_name Raw Booking Form name.
154 * @param mixed $shortcode_raw Optional shortcode used to recover its form type.
155 *
156 * @return string Normalized name.
157 */
158 private function normalize_form_name( $form_name, $shortcode_raw = '' ) {
159 $form_name = sanitize_key( (string) $form_name );
160 if ( '' === $form_name && preg_match( '/\bform_type\s*=\s*(?:"([^"]*)"|\'([^\']*)\'|([^\s\]]+))/i', (string) $shortcode_raw, $matches ) ) {
161 $form_name = sanitize_key( $matches[1] ? $matches[1] : ( $matches[2] ? $matches[2] : $matches[3] ) );
162 }
163 return '' !== $form_name ? $form_name : 'standard';
164 }
165
166 /**
167 * Normalize a raw Booking Form shortcode for one Resource and form.
168 *
169 * @param mixed $shortcode_raw Raw shortcode value.
170 * @param int $resource_id Booking Resource ID.
171 * @param string $form_name Booking Form name.
172 *
173 * @return string Normalized raw shortcode.
174 */
175 private function normalize_booking_shortcode( $shortcode_raw, $resource_id, $form_name ) {
176 $shortcode_raw = preg_replace( '/<!--\s*\/?wp:shortcode\s*-->/', '', (string) $shortcode_raw );
177 $shortcode_raw = trim( wp_strip_all_tags( $shortcode_raw ) );
178
179 if ( ! preg_match( '/^\[booking(?:\s[^\]]*)?\]$/i', $shortcode_raw ) ) {
180 $shortcode_raw = "[booking resource_id={$resource_id} form_type='{$form_name}']";
181 }
182
183 $shortcode_raw = $this->upsert_shortcode_attribute( $shortcode_raw, 'resource_id', (string) $resource_id );
184 $shortcode_raw = $this->upsert_shortcode_attribute( $shortcode_raw, 'form_type', $form_name, '\'' );
185
186 return trim( $shortcode_raw );
187 }
188
189 /**
190 * Insert or replace one Booking shortcode attribute.
191 *
192 * @param string $shortcode_raw Shortcode being normalized.
193 * @param string $attribute Attribute name.
194 * @param string $attribute_value Attribute value.
195 * @param string $quote_character Optional quote character.
196 *
197 * @return string Updated shortcode.
198 */
199 private function upsert_shortcode_attribute( $shortcode_raw, $attribute, $attribute_value, $quote_character = '' ) {
200 $replacement_value = $quote_character ? $quote_character . $attribute_value . $quote_character : $attribute_value;
201 $replacement = $attribute . '=' . $replacement_value;
202 $pattern = '/\b' . preg_quote( $attribute, '/' ) . '\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s\]]+)/i';
203
204 if ( preg_match( $pattern, $shortcode_raw ) ) {
205 return preg_replace( $pattern, $replacement, $shortcode_raw, 1 );
206 }
207
208 return ']' === substr( $shortcode_raw, -1 )
209 ? substr( $shortcode_raw, 0, -1 ) . ' ' . $replacement . ']'
210 : $shortcode_raw . ' ' . $replacement;
211 }
212
213 /**
214 * Wrap a raw shortcode in the WordPress Shortcode block comments.
215 *
216 * @param string $shortcode_raw Raw shortcode.
217 *
218 * @return string Block-editor content.
219 */
220 private function wrap_shortcode_for_editor( $shortcode_raw ) {
221 return '<!-- wp:shortcode -->' . $shortcode_raw . '<!-- /wp:shortcode -->';
222 }
223
224 /**
225 * Build duplicate-detection signatures for the canonical page helper.
226 *
227 * @param int $resource_id Booking Resource ID.
228 * @param string $shortcode_raw Normalized raw shortcode.
229 * @param string $form_name Booking Form name.
230 *
231 * @return array Duplicate signatures.
232 */
233 private function get_duplicate_check_list( $resource_id, $shortcode_raw, $form_name ) {
234 $signatures = array(
235 $shortcode_raw,
236 "[booking resource_id={$resource_id} form_type='{$form_name}']",
237 '[booking resource_id=' . $resource_id . ' form_type="' . $form_name . '"]',
238 );
239
240 if ( 'standard' === $form_name ) {
241 $signatures[] = '[booking resource_id=' . $resource_id . ' ';
242 $signatures[] = '[booking resource_id=' . $resource_id . ']';
243 $signatures[] = '[booking type=' . $resource_id . ' ';
244 $signatures[] = '[booking type=' . $resource_id . ']';
245 if ( 1 === $resource_id ) {
246 $signatures[] = '[booking]';
247 }
248 }
249
250 return array_values( array_unique( array_filter( $signatures ) ) );
251 }
252
253 /**
254 * Validate WordPress page capabilities for the requested operation.
255 *
256 * @param string $publish_mode Create or edit mode.
257 * @param int $page_id Existing page ID for edit mode.
258 *
259 * @return true|WP_Error True when allowed, otherwise a safe error.
260 */
261 private function validate_page_capability( $publish_mode, $page_id ) {
262 if ( 'create' === $publish_mode && ! current_user_can( 'publish_pages' ) ) {
263 return new WP_Error( 'wpbc_publish_create_forbidden', __( 'You do not have permission to create pages.', 'booking' ) );
264 }
265 if ( 'edit' === $publish_mode && ! current_user_can( 'edit_pages' ) ) {
266 return new WP_Error( 'wpbc_publish_edit_forbidden', __( 'You do not have permission to edit pages.', 'booking' ) );
267 }
268 if ( 'edit' === $publish_mode && $page_id && ! current_user_can( 'edit_post', $page_id ) ) {
269 return new WP_Error( 'wpbc_publish_page_forbidden', __( 'You do not have permission to edit the selected page.', 'booking' ) );
270 }
271 return true;
272 }
273
274 /**
275 * Resolve the affected page ID from a canonical helper response.
276 *
277 * @param array $publish_result Canonical helper response.
278 * @param array $helper_params Canonical helper request.
279 *
280 * @return int Affected page ID, or zero when it cannot be resolved.
281 */
282 private function resolve_post_id( $publish_result, $helper_params ) {
283 if ( ! empty( $helper_params['page_id'] ) ) {
284 return absint( $helper_params['page_id'] );
285 }
286 if ( ! empty( $publish_result['post_id'] ) ) {
287 return absint( $publish_result['post_id'] );
288 }
289 if ( ! empty( $publish_result['relative_url'] ) ) {
290 $absolute_url = function_exists( 'wpbc_make_link_absolute' )
291 ? wpbc_make_link_absolute( $publish_result['relative_url'] )
292 : home_url( $publish_result['relative_url'] );
293 $post_id = url_to_postid( $absolute_url );
294 if ( $post_id ) {
295 return absint( $post_id );
296 }
297 }
298 if ( ! empty( $helper_params['page_post_name'] ) ) {
299 $page = get_page_by_path( $helper_params['page_post_name'], OBJECT, 'page' );
300 return $page ? absint( $page->ID ) : 0;
301 }
302 return 0;
303 }
304 }
305
306 /**
307 * Determine whether Booking Form page publishing is restricted on this site.
308 *
309 * This compatibility boundary keeps Catalog, Form Builder, legacy Resources,
310 * and the canonical page helper on the same host-aware demo policy.
311 *
312 * @return bool True when page discovery and mutations must be blocked.
313 */
314 function wpbc_is_booking_form_publishing_restricted() {
315 return WPBC_Booking_Form_Publisher::is_demo_restricted();
316 }
317