PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.12.2
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.12.2
1.8.13.1 1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 All 212 releases
charitable / includes / api / class-charitable-api-route-campaign-css.php

class-charitable-api-route-campaign-css.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.12.2, at includes/api/class-charitable-api-route-campaign-css.php

298 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sets up the /campaign-css/ API route for serving campaign theme CSS.
4 *
5 * This endpoint serves campaign template CSS with dynamic color values,
6 * avoiding direct PHP file access which can fail on some server configurations.
7 *
8 * @package Charitable/Classes/Charitable_API_Route_Campaign_CSS
9 * @author WP Charitable LLC
10 * @copyright Copyright (c) 2024, WP Charitable LLC
11 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
12 * @since 1.8.9.2
13 * @version 1.8.9.2
14 */
15
16 // Exit if accessed directly.
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 if ( ! class_exists( 'Charitable_API_Route_Campaign_CSS' ) ) :
22
23 /**
24 * Charitable_API_Route_Campaign_CSS
25 *
26 * @since 1.8.9.2
27 */
28 class Charitable_API_Route_Campaign_CSS extends Charitable_API_Route {
29
30 /**
31 * Route base.
32 *
33 * @since 1.8.9.2
34 *
35 * @var string
36 */
37 protected $base;
38
39 /**
40 * Set up class instance.
41 *
42 * @since 1.8.9.2
43 *
44 * @return void
45 */
46 public function __construct() {
47 parent::__construct();
48
49 $this->base = 'campaign-css';
50 }
51
52 /**
53 * Register the routes for this controller.
54 *
55 * @since 1.8.9.2
56 */
57 public function register_routes() {
58 $args = array(
59 'template' => array(
60 'required' => true,
61 'type' => 'string',
62 'sanitize_callback' => 'sanitize_file_name',
63 'description' => __( 'The campaign template slug (e.g., youth-sports, basic).', 'charitable' ),
64 ),
65 'p' => array(
66 'type' => 'string',
67 'sanitize_callback' => array( $this, 'sanitize_hex_color' ),
68 'description' => __( 'Primary color (hex without #).', 'charitable' ),
69 ),
70 's' => array(
71 'type' => 'string',
72 'sanitize_callback' => array( $this, 'sanitize_hex_color' ),
73 'description' => __( 'Secondary color (hex without #).', 'charitable' ),
74 ),
75 't' => array(
76 'type' => 'string',
77 'sanitize_callback' => array( $this, 'sanitize_hex_color' ),
78 'description' => __( 'Tertiary color (hex without #).', 'charitable' ),
79 ),
80 'b' => array(
81 'type' => 'string',
82 'sanitize_callback' => array( $this, 'sanitize_hex_color' ),
83 'description' => __( 'Button color (hex without #).', 'charitable' ),
84 ),
85 'mw' => array(
86 'type' => 'integer',
87 'sanitize_callback' => 'absint',
88 'default' => 800,
89 'description' => __( 'Mobile breakpoint width in pixels.', 'charitable' ),
90 ),
91 );
92
93 // Frontend CSS route
94 register_rest_route(
95 $this->namespace,
96 '/' . $this->base . '/(?P<template>[a-zA-Z0-9-_]+)',
97 array(
98 'methods' => WP_REST_Server::READABLE,
99 'callback' => array( $this, 'get_campaign_css' ),
100 'permission_callback' => '__return_true', // Public endpoint - CSS needs to be accessible.
101 'args' => $args,
102 )
103 );
104
105 // Admin CSS route (added in 1.8.9.2)
106 register_rest_route(
107 $this->namespace,
108 '/' . $this->base . '-admin/(?P<template>[a-zA-Z0-9-_]+)',
109 array(
110 'methods' => WP_REST_Server::READABLE,
111 'callback' => array( $this, 'get_admin_css' ),
112 'permission_callback' => '__return_true', // Public endpoint - CSS needs to be accessible.
113 'args' => $args,
114 )
115 );
116 }
117
118 /**
119 * Sanitize a hex color value (without the # prefix).
120 *
121 * @since 1.8.9.2
122 *
123 * @param string $color The color value to sanitize.
124 * @return string The sanitized color value.
125 */
126 public function sanitize_hex_color( $color ) {
127 return preg_replace( '/[^A-Fa-f0-9]/', '', $color );
128 }
129
130 /**
131 * Get the campaign CSS for a specific template.
132 *
133 * @since 1.8.9.2
134 *
135 * @param WP_REST_Request $request The API request object.
136 * @return void Outputs CSS directly and exits.
137 */
138 public function get_campaign_css( $request ) {
139 $template = $request->get_param( 'template' );
140
141 // Build the path to the template CSS file.
142 $template_file = charitable()->get_path( 'includes' ) . 'css-templates/frontend/' . $template . '.php';
143
144 // Validate template exists.
145 if ( ! file_exists( $template_file ) ) {
146 status_header( 404 );
147 header( 'Content-Type: text/css; charset=UTF-8' );
148 echo '/* Template not found: ' . esc_html( $template ) . ' */';
149 exit;
150 }
151
152 // Get color parameters with defaults.
153 $primary = $request->get_param( 'p' );
154 $secondary = $request->get_param( 's' );
155 $tertiary = $request->get_param( 't' );
156 $button = $request->get_param( 'b' );
157 $mobile_width = $request->get_param( 'mw' );
158
159 // Set up variables that the template file expects.
160 // These are accessed via $_GET in the original PHP files,
161 // so we'll set them up the same way for compatibility.
162 $_GET['p'] = $primary;
163 $_GET['s'] = $secondary;
164 $_GET['t'] = $tertiary;
165 $_GET['b'] = $button;
166 $_GET['mw'] = $mobile_width;
167
168 // Generate CSS using output buffering.
169 ob_start();
170
171 // Save current working directory.
172 $original_cwd = getcwd();
173
174 /*
175 * Change to the template directory. The templates now resolve their shared
176 * helper from their own directory instead of the working directory, so this is
177 * belt and braces for any third-party template that still uses a relative
178 * path. It must not become load-bearing again: if chdir() is disabled or
179 * restricted by open_basedir, a template relying on it fatals and this route
180 * returns a PHP error instead of CSS, which the browser rejects - leaving the
181 * campaign with only the base stylesheet's default colours.
182 */
183 chdir( dirname( $template_file ) );
184
185 /*
186 * Campaign Hero base CSS, emitted for every template.
187 *
188 * The hero block can be added to a campaign built on any template, but the
189 * bespoke hero CSS lives in partials/beacon-hero.php scoped to the Beacon
190 * wrappers. Without this, a hero on any other template gets an unstyled
191 * `__banner` — which is absolutely positioned and so collapses to zero height,
192 * making the background image invisible.
193 *
194 * Emitted *before* the template so a template's own rules always take
195 * precedence, and every selector in the partial is deliberately one class less
196 * specific than its Beacon counterpart.
197 */
198 include charitable()->get_path( 'includes' ) . 'css-templates/frontend/partials/campaign-hero-base.php';
199
200 // Include the template file - it will output CSS.
201 include $template_file;
202
203 // Restore original working directory.
204 chdir( $original_cwd );
205
206 $css = ob_get_clean();
207
208 // Generate ETag for cache validation.
209 $etag = md5( $template . $primary . $secondary . $tertiary . $button . $mobile_width );
210
211 // Set headers for CSS response.
212 header( 'Content-Type: text/css; charset=UTF-8' );
213 header( 'Cache-Control: public, max-age=31536000' ); // 1 year.
214 header( 'ETag: "' . $etag . '"' );
215
216 // Output CSS and exit to prevent REST API from processing further.
217 echo $css;
218 exit;
219 }
220
221 /**
222 * Get the admin CSS for a specific template.
223 *
224 * @since 1.8.9.2
225 *
226 * @param WP_REST_Request $request The API request object.
227 * @return void Outputs CSS directly and exits.
228 */
229 public function get_admin_css( $request ) {
230 $template = $request->get_param( 'template' );
231
232 // Build the path to the admin template CSS file.
233 $template_file = charitable()->get_path( 'includes' ) . 'css-templates/admin/' . $template . '.php';
234
235 // Validate template exists.
236 if ( ! file_exists( $template_file ) ) {
237 status_header( 404 );
238 header( 'Content-Type: text/css; charset=UTF-8' );
239 echo '/* Admin template not found: ' . esc_html( $template ) . ' */';
240 exit;
241 }
242
243 // Get color parameters with defaults.
244 $primary = $request->get_param( 'p' );
245 $secondary = $request->get_param( 's' );
246 $tertiary = $request->get_param( 't' );
247 $button = $request->get_param( 'b' );
248 $mobile_width = $request->get_param( 'mw' );
249
250 // Set up variables that the template file expects.
251 // These are accessed via $_GET in the original PHP files,
252 // so we'll set them up the same way for compatibility.
253 $_GET['p'] = $primary;
254 $_GET['s'] = $secondary;
255 $_GET['t'] = $tertiary;
256 $_GET['b'] = $button;
257 $_GET['mw'] = $mobile_width;
258
259 // Generate CSS using output buffering.
260 ob_start();
261
262 // Save current working directory.
263 $original_cwd = getcwd();
264
265 /*
266 * Change to the template directory. The templates now resolve their shared
267 * helper from their own directory instead of the working directory, so this is
268 * belt and braces for any third-party template that still uses a relative
269 * path. It must not become load-bearing again: if chdir() is disabled or
270 * restricted by open_basedir, a template relying on it fatals and this route
271 * returns a PHP error instead of CSS, which the browser rejects - leaving the
272 * campaign with only the base stylesheet's default colours.
273 */
274 chdir( dirname( $template_file ) );
275
276 // Include the template file - it will output CSS.
277 include $template_file;
278
279 // Restore original working directory.
280 chdir( $original_cwd );
281
282 $css = ob_get_clean();
283
284 // Generate ETag for cache validation.
285 $etag = md5( 'admin-' . $template . $primary . $secondary . $tertiary . $button . $mobile_width );
286
287 // Set headers for CSS response.
288 header( 'Content-Type: text/css; charset=UTF-8' );
289 header( 'Cache-Control: public, max-age=31536000' ); // 1 year.
290 header( 'ETag: "' . $etag . '"' );
291
292 // Output CSS and exit to prevent REST API from processing further.
293 echo $css;
294 exit;
295 }
296 }
297
298 endif;