| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* CoolClock Class |
| 5 |
*/ |
| 6 |
|
| 7 |
class CoolClock { |
| 8 |
|
| 9 |
static ?string $plugin_version; |
| 10 |
|
| 11 |
static $script_version = '3.2.2'; |
| 12 |
|
| 13 |
private static ?string $plugin_url; |
| 14 |
|
| 15 |
private static ?string $plugin_basename; |
| 16 |
|
| 17 |
private static $min = '.min'; |
| 18 |
|
| 19 |
static $add_script = false; |
| 20 |
|
| 21 |
static $defaults = array( |
| 22 |
'skin' => 'swissrail', |
| 23 |
'radius' => 100, |
| 24 |
'noseconds' => false, // true to hide second hand |
| 25 |
'gmtoffset' => '', // GMT offset |
| 26 |
'showdigital' => '', // show digital time or date |
| 27 |
'scale' => '', // Define alternative clock type: 'logClock' logarithmic or 'logClockRev' reversed |
| 28 |
'font' => '', // Define font size and family for digital time, default '15px monospace' |
| 29 |
'fontcolor' => '' // Define font color for digital time, default '#333' |
| 30 |
); |
| 31 |
|
| 32 |
static $showdigital_options = array( |
| 33 |
'' => '', |
| 34 |
'digital12' => 'showDigital' |
| 35 |
); |
| 36 |
|
| 37 |
static $advanced_defaults = array( |
| 38 |
'subtext' => '', |
| 39 |
'align' => 'center' |
| 40 |
); |
| 41 |
|
| 42 |
static $more_skins_config = array(); |
| 43 |
|
| 44 |
static $advanced_skins_config = array(); |
| 45 |
|
| 46 |
static $skins_config = array(); |
| 47 |
|
| 48 |
/** |
| 49 |
* DEPRICATED ARGUMENTS |
| 50 |
* keep these for backward compatibility with old advanced extension |
| 51 |
*/ |
| 52 |
static $default_skins = array('swissRail'); |
| 53 |
static $more_skins = array(); |
| 54 |
static $advanced_skins = array(); |
| 55 |
static $advanced = false; |
| 56 |
/* end unused arguments */ |
| 57 |
|
| 58 |
static $clock_types = array( |
| 59 |
'linear' => '', |
| 60 |
'logclock' => 'logClock', |
| 61 |
'logclockrev' => 'logClockRev' |
| 62 |
); |
| 63 |
|
| 64 |
static $allowed_tags = array( |
| 65 |
'a' => array( |
| 66 |
'href' => array(), |
| 67 |
'title' => array(), |
| 68 |
'target' => array() |
| 69 |
), |
| 70 |
'br' => array(), |
| 71 |
'em' => array(), |
| 72 |
'strong' => array(), |
| 73 |
); |
| 74 |
|
| 75 |
/** |
| 76 |
* Constructor |
| 77 |
* |
| 78 |
* @since 4.3 |
| 79 |
* |
| 80 |
* @param string $plugin_file Plugin file path |
| 81 |
* @param string $plugin_version Plugin version |
| 82 |
*/ |
| 83 |
public function __construct( string $plugin_file, string $plugin_version ) { |
| 84 |
// VARS |
| 85 |
self::$plugin_url = plugins_url( '/', $plugin_file ); |
| 86 |
self::$plugin_basename = plugin_basename( $plugin_file ); |
| 87 |
self::$plugin_version = $plugin_version; |
| 88 |
|
| 89 |
if ( defined('WP_DEBUG') && WP_DEBUG ) { |
| 90 |
self::$min = ''; |
| 91 |
} |
| 92 |
|
| 93 |
// widgets |
| 94 |
add_action( 'widgets_init', array( __CLASS__, 'register_widget' ) ); |
| 95 |
|
| 96 |
// enqueue scripts but only if shortcode or widget has been used |
| 97 |
// so it has to be done as late as the wp_footer action |
| 98 |
add_action( 'wp_footer', array( __CLASS__, 'enqueue_scripts' ), 1 ); |
| 99 |
|
| 100 |
add_filter( 'plugin_row_meta', array( __CLASS__, 'plugin_meta_links' ), 10, 2); |
| 101 |
|
| 102 |
// backward compat |
| 103 |
add_filter( 'coolclock_shortcode', array( __CLASS__, 'filter_shortcode'), 10, 3 ); |
| 104 |
add_filter( 'coolclock_widget', array( __CLASS__, 'filter_widget'), 10, 3 ); |
| 105 |
|
| 106 |
/************** |
| 107 |
* SHORTCODE |
| 108 |
**************/ |
| 109 |
|
| 110 |
add_shortcode( 'coolclock', array( 'CoolClock_Shortcode', 'handle_shortcode' ) ); |
| 111 |
|
| 112 |
// prevent texturizing shortcode content |
| 113 |
add_filter( 'no_texturize_shortcodes', array( 'CoolClock_Shortcode', 'no_wptexturize') ); |
| 114 |
} |
| 115 |
|
| 116 |
/** |
| 117 |
* Build canvas output |
| 118 |
* |
| 119 |
* @since 2.0 |
| 120 |
* |
| 121 |
* @param array $atts Array of sanitized attributes |
| 122 |
* |
| 123 |
* @return string Canvas tag |
| 124 |
*/ |
| 125 |
public static function canvas( $atts ) { |
| 126 |
/** |
| 127 |
* ARRAY VALUES |
| 128 |
* skin @param string Skin ID. Must be one of these: 'swissRail' (default skin), 'chunkySwiss', 'chunkySwissOnBlack', 'fancy', 'machine', 'simonbaird_com', 'classic', 'modern', 'simple', 'securephp', 'Tes2', 'Lev', 'Sand', 'Sun', 'Tor', 'Cold', 'Babosa', 'Tumb', 'Stone', 'Disc', 'watermelon' or 'mister'. |
| 129 |
* If the Advanced extension is activated, there is also 'minimal' available. |
| 130 |
* radius @param int Define the clock radius. |
| 131 |
* noseconds @param bool True to hide the second hand. |
| 132 |
* gmtoffset @param float Timezone offset relative the Greenwhich Mean Time |
| 133 |
* showdigital @param string|bool Set to 'digital12' to show the time in 12h digital format (with am/pm). |
| 134 |
* font @param string Set to a font size, family and style for the digital time |
| 135 |
* fontcolor @param string Set to a color value to change the digital time color |
| 136 |
* scale @param string Optional alternative clock scale 'logClock' or 'logClockRev' |
| 137 |
* subtext @param string Optional text, centered below the clock |
| 138 |
* align @param string Sets floating of the clock: 'left', 'right' or 'center' |
| 139 |
*/ |
| 140 |
|
| 141 |
// get defaults for missing attributes |
| 142 |
$defaults = array_merge( self::$defaults, self::$advanced_defaults ); |
| 143 |
|
| 144 |
$atts = apply_filters( 'coolclock_atts', $atts, $defaults ); |
| 145 |
|
| 146 |
// CoolClock fields array |
| 147 |
$fields = array(); |
| 148 |
$fields[] = 'CoolClock'; |
| 149 |
// skin id |
| 150 |
$fields[] = !empty( $atts['skin'] ) ? $atts['skin'] : $defaults['skin']; |
| 151 |
// radius |
| 152 |
$fields[] = !empty( $atts['radius'] ) && is_numeric($atts['radius']) ? (int) $atts['radius'] : $defaults['radius']; |
| 153 |
// noseconds |
| 154 |
$noseconds = isset( $atts['noseconds'] ) ? (bool) $atts['noseconds'] : $defaults['noseconds']; |
| 155 |
$fields[] = $noseconds ? 'noSeconds' : ''; |
| 156 |
// gmt offset |
| 157 |
$fields[] = isset( $atts['gmtoffset'] ) && is_numeric( $atts['gmtoffset'] ) ? (float) $atts['gmtoffset'] : $defaults['gmtoffset']; |
| 158 |
// show digital |
| 159 |
$showdigital = isset($atts['showdigital']) ? $atts['showdigital'] : $defaults['showdigital']; |
| 160 |
if ( true === $showdigital ) |
| 161 |
$showdigital = 'digital12'; |
| 162 |
$fields[] = isset( self::$showdigital_options[$showdigital] ) ? self::$showdigital_options[$showdigital] : ''; |
| 163 |
// clock type |
| 164 |
$scale = isset( $atts['scale'] ) ? strtolower( $atts['scale'] ) : $defaults['scale']; |
| 165 |
$fields[] = isset( self::$clock_types[$scale] ) ? self::$clock_types[$scale] : ''; |
| 166 |
// set font color |
| 167 |
$fields[] = isset( $atts['fontcolor'] ) ? $atts['fontcolor'] : $defaults['fontcolor']; |
| 168 |
|
| 169 |
$fields = apply_filters( 'coolclock_fields_array', $fields, $atts, $defaults ); |
| 170 |
|
| 171 |
// build output |
| 172 |
$output = ''; |
| 173 |
|
| 174 |
$styles = apply_filters( 'coolclock_canvas_styles', array(), $atts, $defaults ); |
| 175 |
|
| 176 |
// canvas parameters |
| 177 |
$output .= '<canvas class="' . implode( ':', array_map('esc_attr', $fields) ) . '"' . CoolClock::inline_style( $styles ) . '></canvas>'; |
| 178 |
|
| 179 |
// sub text |
| 180 |
$subtext = ( isset( $atts['subtext'] ) ) ? $atts['subtext'] : $defaults['subtext']; |
| 181 |
if ( !empty( $subtext ) ) |
| 182 |
$output .= '<div class="coolclock-subtext">' . $subtext . '</div>'; |
| 183 |
|
| 184 |
return $output; |
| 185 |
} |
| 186 |
|
| 187 |
/** |
| 188 |
* Create inline style attribute from array |
| 189 |
* |
| 190 |
* @since 4.3 |
| 191 |
* |
| 192 |
* @param array $styles array with style parameters and their values |
| 193 |
* |
| 194 |
* @return string inline style attribute style="..." complete with leading space |
| 195 |
*/ |
| 196 |
public static function inline_style( $styles ) { |
| 197 |
$style_arr = array(); |
| 198 |
|
| 199 |
foreach ( $styles as $key => $value ) { |
| 200 |
$style_arr[] = $key . ':' . $value; |
| 201 |
} |
| 202 |
|
| 203 |
$style = ! empty($style_arr) ? ' style="' . implode( ';', $style_arr ) . '"' : ''; |
| 204 |
|
| 205 |
return $style; |
| 206 |
} |
| 207 |
|
| 208 |
/** |
| 209 |
* Parse skin name and user skin parameters |
| 210 |
* |
| 211 |
* @since 3.2.0 |
| 212 |
* |
| 213 |
* @param string $skin_name skin name |
| 214 |
* @param string $skin_parms skin parameters, preferably in JSON format |
| 215 |
* |
| 216 |
* @return string either found matching skin name or 'invalid_or_missing_skin' on failure |
| 217 |
*/ |
| 218 |
public static function parse_skin( $skin_name, $skin_parms = '' ) { |
| 219 |
$skin = strtolower( $skin_name ); // user input, sanitize! |
| 220 |
$skin = preg_replace( '/[^a-z0-9_-]/', '', $skin ); |
| 221 |
|
| 222 |
// check for empty or default skin first |
| 223 |
if ( empty($skin) || $skin === self::$defaults['skin'] ) { |
| 224 |
// return the matching skin name |
| 225 |
return self::$defaults['skin']; |
| 226 |
} |
| 227 |
|
| 228 |
// short-circuit if skin name is already in the config array |
| 229 |
if ( array_key_exists( $skin, self::$skins_config ) ) { |
| 230 |
// return the matching skin name |
| 231 |
return $skin; |
| 232 |
} |
| 233 |
|
| 234 |
// search in the more_skins and advanced_skins arrays |
| 235 |
$all_skins = self::get_all_skins(); |
| 236 |
$skin_array = array(); |
| 237 |
|
| 238 |
if ( array_key_exists( $skin, $all_skins ) ) { |
| 239 |
// fetch parameters from skins array |
| 240 |
$skin_array = is_array( $all_skins[$skin] ) ? $all_skins[$skin] : self::skin_array( $all_skins[$skin] ); |
| 241 |
} else { |
| 242 |
// try to build a skin config from passed parameters (user input, sanitize!) |
| 243 |
$skin_array = self::skin_array( $skin_parms ); |
| 244 |
|
| 245 |
if ( empty( $skin_array ) ) { |
| 246 |
// set faulty skin name |
| 247 |
return 'no_skin_found'; |
| 248 |
} |
| 249 |
} |
| 250 |
|
| 251 |
// add found skin parameters to the config array |
| 252 |
self::$skins_config[$skin] = $skin_array; |
| 253 |
|
| 254 |
// return the matching skin name |
| 255 |
return $skin; |
| 256 |
} |
| 257 |
|
| 258 |
/** |
| 259 |
* Get all available skins |
| 260 |
* |
| 261 |
* @since 3.2.0 |
| 262 |
* |
| 263 |
* @return array array of all skins |
| 264 |
*/ |
| 265 |
public static function get_all_skins() { |
| 266 |
|
| 267 |
if ( empty( self::$more_skins_config ) ) { |
| 268 |
self::$more_skins_config = include COOLCLOCK_DIR . 'includes/moreskins.php'; |
| 269 |
} |
| 270 |
|
| 271 |
return array_merge( self::$more_skins_config, self::$advanced_skins_config ); |
| 272 |
} |
| 273 |
|
| 274 |
/** |
| 275 |
* Enqueue scripts |
| 276 |
* |
| 277 |
* @since 3.2.0 |
| 278 |
*/ |
| 279 |
public static function enqueue_scripts() { |
| 280 |
// bail if we don't need script |
| 281 |
if ( ! self::$add_script ) |
| 282 |
return; |
| 283 |
|
| 284 |
$script = ''; |
| 285 |
|
| 286 |
if ( !empty( self::$skins_config ) ) { |
| 287 |
|
| 288 |
$skins = wp_json_encode( self::$skins_config ); |
| 289 |
if ( false === $skins ) { |
| 290 |
$skins = '{} /* skin parse error, please fix your custom skin */'; |
| 291 |
} |
| 292 |
|
| 293 |
$script .= 'CoolClock.config.skins = ' . $skins . ';'; |
| 294 |
} |
| 295 |
|
| 296 |
$script .= PHP_EOL . 'if(document.readyState!="loading"&&document.addEventListener){document.addEventListener("DOMContentLoaded",function(){CoolClock.findAndCreateClocks();})}else{CoolClock.findAndCreateClocks();};'; |
| 297 |
|
| 298 |
wp_enqueue_script( 'coolclock', self::$plugin_url . 'js/coolclock' . self::$min . '.js', false, self::$script_version, true ); |
| 299 |
|
| 300 |
wp_add_inline_script( 'coolclock', $script ); |
| 301 |
|
| 302 |
// called late so should end up in the footer |
| 303 |
wp_enqueue_style( 'coolclock', self::$plugin_url . 'css/coolclock' . self::$min . '.css', array(), self::$script_version ); |
| 304 |
} |
| 305 |
|
| 306 |
public static function register_widget() { |
| 307 |
register_widget("CoolClock_Widget"); |
| 308 |
} |
| 309 |
|
| 310 |
/** |
| 311 |
* Add links to plugin's description in the plugins list |
| 312 |
* |
| 313 |
* @param array $links Array of links to display |
| 314 |
* @param string $file Plugin file path |
| 315 |
*/ |
| 316 |
public static function plugin_meta_links( array $links, string $file ) { |
| 317 |
$support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/coolclock/">' . __('Support','coolclock') . '</a>'; |
| 318 |
$rate_link = '<a target="_blank" href="https://wordpress.org/support/plugin/coolclock/reviews/">' . __('Rate this plugin','coolclock') . '</a>'; |
| 319 |
|
| 320 |
if ( $file == self::$plugin_basename ) { |
| 321 |
$links[] = $support_link; |
| 322 |
$links[] = $rate_link; |
| 323 |
} |
| 324 |
|
| 325 |
return $links; |
| 326 |
} |
| 327 |
|
| 328 |
/** |
| 329 |
* Turn custom skin user input into an array |
| 330 |
* |
| 331 |
* @since 2.0 |
| 332 |
* |
| 333 |
* @param string $skin |
| 334 |
* |
| 335 |
* @return array array of skin parameters |
| 336 |
*/ |
| 337 |
public static function skin_array( $skin ) { |
| 338 |
// remove everything following a ; to thwart any script injection |
| 339 |
$parts = explode( ';', $skin, 2 ); |
| 340 |
$sanitized = $parts[0]; |
| 341 |
// strip all kind of whitespace |
| 342 |
$sanitized = preg_replace("/\s+/", '', $sanitized);; |
| 343 |
$sanitized = ltrim( $sanitized, '{' ); |
| 344 |
$sanitized = rtrim( $sanitized, '}' ) . '}'; |
| 345 |
// by now, the string should start with a double quote |
| 346 |
if ( 0 !== strpos( $sanitized, '"' ) ) { |
| 347 |
// regex for (re)wrapping all keys in double quotes |
| 348 |
$sanitized = preg_replace( "/(['\"])?([a-zA-Z0-9_]+)(['\"])?:([^\/])/", '"$2":$4', $sanitized ); |
| 349 |
} |
| 350 |
|
| 351 |
// fist attempt to decode json string |
| 352 |
$skin_array = json_decode( '{' . $sanitized . '}', true ); |
| 353 |
|
| 354 |
// not valid json? then do it the hard way |
| 355 |
if ( null === $skin_array ) : |
| 356 |
|
| 357 |
// remove all spaces and tabs |
| 358 |
$sanitized = str_replace( array( ' ', "\t", '"', "'" ), '', $sanitized ); |
| 359 |
|
| 360 |
// break it all up in little parts |
| 361 |
// this converts valid skin javascript object into json, but messes up all else :/ |
| 362 |
$elements = explode( '},', $sanitized ); |
| 363 |
|
| 364 |
// start fresh |
| 365 |
$skin_array = array(); |
| 366 |
|
| 367 |
// and build array |
| 368 |
foreach( $elements as $element ) { |
| 369 |
$pair = explode( ':{', $element ); |
| 370 |
// validate key |
| 371 |
$part = str_replace( '{', '', $pair[0] ); |
| 372 |
if ( ! in_array( $part, array('outerBorder','smallIndicator','largeIndicator','hourHand','minuteHand','secondHand','secondDecoration') ) ) |
| 373 |
continue; |
| 374 |
// constuct value |
| 375 |
$value = array(); |
| 376 |
$parameters = explode( ',', $pair[1] ); |
| 377 |
for( $i=0; $i < count( $parameters ); $i++ ) { |
| 378 |
$pairs = explode( ':', $parameters[$i] ); |
| 379 |
$parm_val = str_replace( '}', '', $pairs[1] ); |
| 380 |
$value[$pairs[0]] = is_numeric($parm_val) ? (float) $parm_val : (string) $parm_val; |
| 381 |
} |
| 382 |
$skin_array[$part] = $value; |
| 383 |
} |
| 384 |
|
| 385 |
endif; |
| 386 |
|
| 387 |
return $skin_array; |
| 388 |
} |
| 389 |
|
| 390 |
/** |
| 391 |
* Sanitize color value user input |
| 392 |
* |
| 393 |
* @since 4.2 |
| 394 |
* |
| 395 |
* @param string $color |
| 396 |
* |
| 397 |
* @return string hex color value or text for named color |
| 398 |
*/ |
| 399 |
public static function colorval( $color ) { |
| 400 |
$color = wp_strip_all_tags( $color ); |
| 401 |
$color = trim( $color ); |
| 402 |
$color = str_replace( array( '"', '\'', ':'), '', $color ); |
| 403 |
$color_arr = explode( ' ', $color, 2 ); |
| 404 |
$color = esc_attr( $color_arr[0] ); |
| 405 |
|
| 406 |
if ( substr($color, 0, 1) == '#' ) { |
| 407 |
if ( ctype_xdigit( substr( $color, 1 ) ) ) |
| 408 |
return $color; |
| 409 |
|
| 410 |
return substr( $color, 1 ); |
| 411 |
} |
| 412 |
|
| 413 |
return ctype_xdigit( $color ) ? '#'.$color : $color; |
| 414 |
} |
| 415 |
|
| 416 |
/** |
| 417 |
* Filter shortcode output |
| 418 |
* |
| 419 |
* @since 4.3 |
| 420 |
* |
| 421 |
* @param string $output output |
| 422 |
* @param array $atts array of shortcode attributes |
| 423 |
* @param string $content shortcode content |
| 424 |
* |
| 425 |
* @return string output |
| 426 |
*/ |
| 427 |
public static function filter_shortcode( $output, $atts, $content ) { |
| 428 |
// add backward compat filter |
| 429 |
return apply_filters( 'coolclock_shortcode_advanced', $output, $atts, $content ); |
| 430 |
} |
| 431 |
|
| 432 |
/** |
| 433 |
* Filter widget output |
| 434 |
* |
| 435 |
* @since 4.3 |
| 436 |
* |
| 437 |
* @param string $output output |
| 438 |
* @param array $args array of widget parameters |
| 439 |
* @param array $instance widget instance array parameters |
| 440 |
* |
| 441 |
* @return string output |
| 442 |
*/ |
| 443 |
public static function filter_widget( $output, $args, $instance ) { |
| 444 |
// add backward compat filter |
| 445 |
return apply_filters( 'coolclock_widget_advanced', $output, $args, $instance ); |
| 446 |
} |
| 447 |
} |
| 448 |
|