| 1 |
<?php |
| 2 |
/** |
| 3 |
* Plugin Name: Gutenify Countdown Block |
| 4 |
* Plugin URI: https://gutenify.com |
| 5 |
* Description: Gutenify Countdown - stores settings in post meta |
| 6 |
* Version: 1.0.0 |
| 7 |
* Author: Gutenify |
| 8 |
* |
| 9 |
* @package gutenify |
| 10 |
*/ |
| 11 |
|
| 12 |
defined( 'ABSPATH' ) || exit; |
| 13 |
|
| 14 |
/** |
| 15 |
* Register post meta for countdown timer settings. |
| 16 |
*/ |
| 17 |
function gutenify_countdown_timer_register_meta() { |
| 18 |
$meta_fields = array( |
| 19 |
'_gutenify_countdown_timer_type' => array( |
| 20 |
'type' => 'string', |
| 21 |
'default' => 'fixed', |
| 22 |
), |
| 23 |
'_gutenify_countdown_timer_evergreen_hours' => array( |
| 24 |
'type' => 'integer', |
| 25 |
'default' => 24, |
| 26 |
), |
| 27 |
'_gutenify_countdown_timer_evergreen_minutes' => array( |
| 28 |
'type' => 'integer', |
| 29 |
'default' => 0, |
| 30 |
), |
| 31 |
'_gutenify_countdown_timer_date' => array( |
| 32 |
'type' => 'string', |
| 33 |
'default' => '', |
| 34 |
), |
| 35 |
'_gutenify_countdown_timer_timezone' => array( |
| 36 |
'type' => 'string', |
| 37 |
'default' => 'US/Eastern', |
| 38 |
), |
| 39 |
'_gutenify_countdown_timer_end_action' => array( |
| 40 |
'type' => 'string', |
| 41 |
'default' => 'none', |
| 42 |
), |
| 43 |
'_gutenify_countdown_timer_hide_selector' => array( |
| 44 |
'type' => 'string', |
| 45 |
'default' => '', |
| 46 |
), |
| 47 |
'_gutenify_countdown_timer_redirect_url' => array( |
| 48 |
'type' => 'string', |
| 49 |
'default' => '', |
| 50 |
), |
| 51 |
); |
| 52 |
|
| 53 |
foreach ( $meta_fields as $meta_key => $args ) { |
| 54 |
register_post_meta( |
| 55 |
'', |
| 56 |
$meta_key, |
| 57 |
array( |
| 58 |
'show_in_rest' => true, |
| 59 |
'single' => true, |
| 60 |
'type' => $args['type'], |
| 61 |
'default' => $args['default'], |
| 62 |
'auth_callback' => function () { |
| 63 |
return current_user_can( 'edit_posts' ); |
| 64 |
}, |
| 65 |
) |
| 66 |
); |
| 67 |
} |
| 68 |
} |
| 69 |
add_action( 'init', 'gutenify_countdown_timer_register_meta' ); |
| 70 |
|
| 71 |
/** |
| 72 |
* Register site-wide settings for countdown timer. |
| 73 |
*/ |
| 74 |
function gutenify_countdown_timer_register_settings() { |
| 75 |
register_setting( |
| 76 |
'options', |
| 77 |
'_gutenify_countdown_timer_site_data', |
| 78 |
array( |
| 79 |
'type' => 'object', |
| 80 |
'show_in_rest' => array( |
| 81 |
'schema' => array( |
| 82 |
'type' => 'object', |
| 83 |
'properties' => array( |
| 84 |
'countdownTimerType' => array( 'type' => 'string' ), |
| 85 |
'evergreenHours' => array( 'type' => 'integer' ), |
| 86 |
'evergreenMinutes' => array( 'type' => 'integer' ), |
| 87 |
'date' => array( 'type' => 'string' ), |
| 88 |
'timezone' => array( 'type' => 'string' ), |
| 89 |
'endAction' => array( 'type' => 'string' ), |
| 90 |
'hideSelector' => array( 'type' => 'string' ), |
| 91 |
'redirectUrl' => array( 'type' => 'string' ), |
| 92 |
'evergreenRestart' => array( 'type' => 'boolean' ), |
| 93 |
), |
| 94 |
), |
| 95 |
), |
| 96 |
'default' => array( |
| 97 |
'countdownTimerType' => 'fixed', |
| 98 |
'evergreenHours' => 24, |
| 99 |
'evergreenMinutes' => 0, |
| 100 |
'evergreenRestart' => false, |
| 101 |
'date' => '', |
| 102 |
'timezone' => 'US/Eastern', |
| 103 |
'endAction' => 'none', |
| 104 |
'hideSelector' => '', |
| 105 |
'redirectUrl' => '', |
| 106 |
), |
| 107 |
) |
| 108 |
); |
| 109 |
} |
| 110 |
add_action( 'init', 'gutenify_countdown_timer_register_settings' ); |
| 111 |
|
| 112 |
/** |
| 113 |
* Registers the block. |
| 114 |
*/ |
| 115 |
function gutenify_countdown_timer_register_block() { |
| 116 |
register_block_type( |
| 117 |
__DIR__, |
| 118 |
array( |
| 119 |
'render_callback' => 'gutenify_countdown_timer_render', |
| 120 |
) |
| 121 |
); |
| 122 |
} |
| 123 |
add_action( 'init', 'gutenify_countdown_timer_register_block' ); |
| 124 |
|
| 125 |
/** |
| 126 |
* Converts a font size value to a fluid clamp() value. |
| 127 |
* Min = 75% of value, viewport range 320px–1920px. |
| 128 |
*/ |
| 129 |
function gutenify_get_fluid_font_size( $font_size ) { |
| 130 |
if ( empty( $font_size ) ) { |
| 131 |
return $font_size; |
| 132 |
} |
| 133 |
|
| 134 |
if ( ! preg_match( '/^([\d.]+)(.+)$/', $font_size, $matches ) ) { |
| 135 |
return $font_size; |
| 136 |
} |
| 137 |
|
| 138 |
$value = (float) $matches[1]; |
| 139 |
$unit = $matches[2]; |
| 140 |
|
| 141 |
$value_px = $value; |
| 142 |
if ( 'rem' === $unit || 'em' === $unit ) { |
| 143 |
$value_px = $value * 16; |
| 144 |
} |
| 145 |
|
| 146 |
$min_px = $value_px * 0.75; |
| 147 |
$min_viewport = 320; |
| 148 |
$max_viewport = 1920; |
| 149 |
$slope = ( $value_px - $min_px ) / ( $max_viewport - $min_viewport ); |
| 150 |
$intercept = $min_px - $slope * $min_viewport; |
| 151 |
$slope_vw = round( $slope * 100, 4 ); |
| 152 |
$intercept_rem = round( $intercept / 16, 4 ); |
| 153 |
|
| 154 |
$min = 'px' === $unit |
| 155 |
? $min_px . 'px' |
| 156 |
: round( $value * 0.75, 4 ) . $unit; |
| 157 |
|
| 158 |
return "clamp({$min}, {$intercept_rem}rem + {$slope_vw}vw, {$font_size})"; |
| 159 |
} |
| 160 |
|
| 161 |
/** |
| 162 |
* Render callback for the block - outputs HTML with post meta as data attributes. |
| 163 |
* |
| 164 |
* @param array $attributes Block attributes. |
| 165 |
* @param string $content Block content. |
| 166 |
* @param WP_Block $block Block instance. |
| 167 |
* @return string Rendered block HTML. |
| 168 |
*/ |
| 169 |
function gutenify_countdown_timer_render( $attributes, $content, $block ) { |
| 170 |
$post_id = $block->context['postId'] ?? get_the_ID(); |
| 171 |
|
| 172 |
if ( ! $post_id ) { |
| 173 |
return $content; |
| 174 |
} |
| 175 |
|
| 176 |
$storage_level = $attributes['storageLevel'] ?? 'postMeta'; |
| 177 |
|
| 178 |
$type = $attributes['countdownTimerType'] ?? 'fixed'; |
| 179 |
|
| 180 |
if ( 'blockLevel' === $storage_level ) { |
| 181 |
$evg_hours = (int) ( $attributes['evergreenHours'] ?? 24 ); |
| 182 |
$evg_minutes = (int) ( $attributes['evergreenMinutes'] ?? 0 ); |
| 183 |
$date = $attributes['date'] ?? ''; |
| 184 |
$timezone = wp_timezone_string(); |
| 185 |
$end_action = $attributes['endAction'] ?? 'none'; |
| 186 |
$hide_selector = $attributes['hideSelector'] ?? ''; |
| 187 |
$redirect_url = $attributes['redirectUrl'] ?? ''; |
| 188 |
} elseif ( 'siteLevel' === $storage_level ) { |
| 189 |
$site_data = get_option( '_gutenify_countdown_timer_site_data', array() ); |
| 190 |
$evg_hours = (int) ( $site_data['evergreenHours'] ?? 24 ); |
| 191 |
$evg_minutes = (int) ( $site_data['evergreenMinutes'] ?? 0 ); |
| 192 |
$date = $site_data['date'] ?? ''; |
| 193 |
$timezone = wp_timezone_string(); |
| 194 |
$end_action = $site_data['endAction'] ?? 'none'; |
| 195 |
$hide_selector = $site_data['hideSelector'] ?? ''; |
| 196 |
$redirect_url = $site_data['redirectUrl'] ?? ''; |
| 197 |
} else { |
| 198 |
$evg_hours = get_post_meta( $post_id, '_gutenify_countdown_timer_evergreen_hours', true ); |
| 199 |
$evg_hours = $evg_hours !== '' ? (int) $evg_hours : 24; |
| 200 |
$evg_minutes = get_post_meta( $post_id, '_gutenify_countdown_timer_evergreen_minutes', true ); |
| 201 |
$evg_minutes = $evg_minutes !== '' ? (int) $evg_minutes : 0; |
| 202 |
$date = get_post_meta( $post_id, '_gutenify_countdown_timer_date', true ); |
| 203 |
$timezone = wp_timezone_string(); |
| 204 |
$end_action = get_post_meta( $post_id, '_gutenify_countdown_timer_end_action', true ); |
| 205 |
$hide_selector = get_post_meta( $post_id, '_gutenify_countdown_timer_hide_selector', true ); |
| 206 |
$redirect_url = get_post_meta( $post_id, '_gutenify_countdown_timer_redirect_url', true ); |
| 207 |
$evg_restart = get_post_meta( $post_id, '_gutenify_countdown_timer_evergreen_restart', true ); |
| 208 |
} |
| 209 |
|
| 210 |
$evg_restart = 'siteLevel' === $storage_level ? ( $site_data['evergreenRestart'] ?? false ) : ( 'blockLevel' === $storage_level ? ( $attributes['evergreenRestart'] ?? false ) : $evg_restart ); |
| 211 |
$evg_restart = filter_var( $evg_restart, FILTER_VALIDATE_BOOLEAN ); |
| 212 |
|
| 213 |
if ( empty( $hide_selector ) && 'hide' === $end_action ) { |
| 214 |
$hide_selector = '.gutenify-countdown-timer-hide'; |
| 215 |
} |
| 216 |
|
| 217 |
if ( 'fixed' === $type && empty( $date ) ) { |
| 218 |
return $content; |
| 219 |
} |
| 220 |
|
| 221 |
// Default to US/Eastern if no timezone is set. |
| 222 |
if ( empty( $timezone ) ) { |
| 223 |
$timezone = 'US/Eastern'; |
| 224 |
} |
| 225 |
|
| 226 |
$timestamp = 0; |
| 227 |
|
| 228 |
if ( 'evergreen' === $type ) { |
| 229 |
// Calculate the full duration relative to now so the initial server render |
| 230 |
// shows the maximum time before the frontend JS kicks in. |
| 231 |
$timestamp = current_time( 'timestamp', true ) + ( $evg_hours * 3600 ) + ( $evg_minutes * 60 ); |
| 232 |
} else { |
| 233 |
try { |
| 234 |
// Create the DateTime object without timezone first to parse the stored date. |
| 235 |
$dt = new DateTime( $date ); |
| 236 |
|
| 237 |
// Re-interpret the date in the selected timezone |
| 238 |
// (strip any offset from the stored date and apply the selected timezone). |
| 239 |
$tz = new DateTimeZone( $timezone ); |
| 240 |
$dt = new DateTime( $dt->format( 'Y-m-d H:i:s' ), $tz ); |
| 241 |
|
| 242 |
$timestamp = $dt->getTimestamp(); |
| 243 |
} catch ( Exception $e ) { |
| 244 |
return $content; |
| 245 |
} |
| 246 |
} |
| 247 |
|
| 248 |
// Compute initial countdown values server-side to avoid flash of empty content. |
| 249 |
$remaining = $timestamp - current_time( 'timestamp', true ); |
| 250 |
if ( $remaining > 0 ) { |
| 251 |
$init_days = str_pad( floor( $remaining / 86400 ), 2, '0', STR_PAD_LEFT ); |
| 252 |
$init_hours = str_pad( floor( ( $remaining % 86400 ) / 3600 ), 2, '0', STR_PAD_LEFT ); |
| 253 |
$init_minutes = str_pad( floor( ( $remaining % 3600 ) / 60 ), 2, '0', STR_PAD_LEFT ); |
| 254 |
$init_seconds = str_pad( $remaining % 60, 2, '0', STR_PAD_LEFT ); |
| 255 |
} else { |
| 256 |
$init_days = '00'; |
| 257 |
$init_hours = '00'; |
| 258 |
$init_minutes = '00'; |
| 259 |
$init_seconds = '00'; |
| 260 |
} |
| 261 |
|
| 262 |
$labels = array( |
| 263 |
'days' => $attributes['daysLabel'] ?? 'DAYS', |
| 264 |
'hours' => $attributes['hoursLabel'] ?? 'HOURS', |
| 265 |
'minutes' => $attributes['minutesLabel'] ?? 'MINUTES', |
| 266 |
'seconds' => $attributes['secondsLabel'] ?? 'SECONDS', |
| 267 |
); |
| 268 |
|
| 269 |
$label_style = ''; |
| 270 |
|
| 271 |
if ( ! empty( $attributes['labelColor'] ) ) { |
| 272 |
$label_style .= 'color:' . esc_attr( $attributes['labelColor'] ) . ';'; |
| 273 |
} |
| 274 |
if ( ! empty( $attributes['labelFontSize'] ) ) { |
| 275 |
$label_style .= 'font-size:' . esc_attr( gutenify_get_fluid_font_size( $attributes['labelFontSize'] ) ) . ';'; |
| 276 |
} |
| 277 |
if ( ! empty( $attributes['labelLineHeight'] ) ) { |
| 278 |
$label_style .= 'line-height:' . esc_attr( $attributes['labelLineHeight'] ) . ';'; |
| 279 |
} |
| 280 |
|
| 281 |
$label_style_attr = $label_style ? ' style="' . $label_style . '"' : ''; |
| 282 |
|
| 283 |
$wrapper_attributes = get_block_wrapper_attributes( array( |
| 284 |
'data-type' => esc_attr( $type ), |
| 285 |
'data-post-id' => esc_attr( $post_id ), |
| 286 |
'data-evergreen-hours' => esc_attr( $evg_hours ), |
| 287 |
'data-evergreen-minutes' => esc_attr( $evg_minutes ), |
| 288 |
'data-timestamp' => esc_attr( $timestamp ), |
| 289 |
'data-end-action' => esc_attr( $end_action ), |
| 290 |
'data-hide-selector' => esc_attr( $hide_selector ), |
| 291 |
'data-redirect-url' => esc_attr( $redirect_url ), |
| 292 |
'data-evergreen-restart' => esc_attr( $evg_restart ? 'true' : 'false' ), |
| 293 |
) ); |
| 294 |
|
| 295 |
$separator = $attributes['separator'] ?? ''; |
| 296 |
$item_gap = $attributes['itemGap'] ?? ''; |
| 297 |
$ul_style = $item_gap ? ' style="gap:' . esc_attr( $item_gap ) . ';"' : ''; |
| 298 |
|
| 299 |
$item_border = $attributes['itemBorder'] ?? array(); |
| 300 |
$item_border_style = ''; |
| 301 |
if ( ! empty( $item_border['color'] ) ) { |
| 302 |
$item_border_style .= 'border-color:' . esc_attr( $item_border['color'] ) . ';'; |
| 303 |
} |
| 304 |
if ( ! empty( $item_border['width'] ) ) { |
| 305 |
$item_border_style .= 'border-width:' . esc_attr( $item_border['width'] ) . ';'; |
| 306 |
} |
| 307 |
if ( ! empty( $item_border['style'] ) ) { |
| 308 |
$item_border_style .= 'border-style:' . esc_attr( $item_border['style'] ) . ';'; |
| 309 |
} |
| 310 |
if ( ! empty( $attributes['itemBorderRadius'] ) ) { |
| 311 |
$item_border_style .= 'border-radius:' . esc_attr( $attributes['itemBorderRadius'] ) . ';'; |
| 312 |
} |
| 313 |
if ( ! empty( $attributes['itemPadding'] ) ) { |
| 314 |
$item_border_style .= 'padding:' . esc_attr( $attributes['itemPadding'] ) . ';'; |
| 315 |
} |
| 316 |
$item_style_attr = $item_border_style ? ' style="' . $item_border_style . '"' : ''; |
| 317 |
|
| 318 |
ob_start(); |
| 319 |
?> |
| 320 |
<div <?php echo wp_kses_post( $wrapper_attributes ); ?>> |
| 321 |
<ul<?php echo $ul_style; ?>> |
| 322 |
<li<?php echo $item_style_attr; ?>> |
| 323 |
<span class="days"><?php echo esc_html( $init_days ); ?></span> |
| 324 |
<small<?php echo $label_style_attr; ?>><?php echo esc_html( $labels['days'] ); ?></small> |
| 325 |
</li> |
| 326 |
<?php if ( $separator ) : ?> |
| 327 |
<li class="separator"><?php echo esc_html( $separator ); ?></li> |
| 328 |
<?php endif; ?> |
| 329 |
<li<?php echo $item_style_attr; ?>> |
| 330 |
<span class="hours"><?php echo esc_html( $init_hours ); ?></span> |
| 331 |
<small<?php echo $label_style_attr; ?>><?php echo esc_html( $labels['hours'] ); ?></small> |
| 332 |
</li> |
| 333 |
<?php if ( $separator ) : ?> |
| 334 |
<li class="separator"><?php echo esc_html( $separator ); ?></li> |
| 335 |
<?php endif; ?> |
| 336 |
<li<?php echo $item_style_attr; ?>> |
| 337 |
<span class="minutes"><?php echo esc_html( $init_minutes ); ?></span> |
| 338 |
<small<?php echo $label_style_attr; ?>><?php echo esc_html( $labels['minutes'] ); ?></small> |
| 339 |
</li> |
| 340 |
<?php if ( $separator ) : ?> |
| 341 |
<li class="separator"><?php echo esc_html( $separator ); ?></li> |
| 342 |
<?php endif; ?> |
| 343 |
<li<?php echo $item_style_attr; ?>> |
| 344 |
<span class="seconds"><?php echo esc_html( $init_seconds ); ?></span> |
| 345 |
<small<?php echo $label_style_attr; ?>><?php echo esc_html( $labels['seconds'] ); ?></small> |
| 346 |
</li> |
| 347 |
</ul> |
| 348 |
</div> |
| 349 |
<?php |
| 350 |
return ob_get_clean(); |
| 351 |
} |
| 352 |
|