PluginProbe
CoolClock / 4.3.9
CoolClock v4.3.9
4.3.9 4.3.8 trunk 0.1 2.0 2.9.8 3.0 3.1 3.2 3.3 4.0 4.1 4.2 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7
← All changes | includes/class-coolclock.php +385 -90 4.04.3.9 View file →
@@ -5,131 +5,303 @@
5 5 */
6 6
7 7 class CoolClock {
8 8
9 - static $plugin_version = '4.0';
9 + static ?string $plugin_version;
10 10
11 - static $script_version = '3.0.0';
11 + static $script_version = '3.2.2';
12 12
13 - private static $plugin_url;
13 + private static ?string $plugin_url;
14 14
15 - private static $plugin_basename;
15 + private static ?string $plugin_basename;
16 16
17 - private static $min = '';
17 + private static $min = '.min';
18 18
19 19 static $add_script = false;
20 20
21 - static $add_moreskins = false;
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 + );
22 31
23 - private static $done_excanvas = false;
32 + static $showdigital_options = array(
33 + '' => '',
34 + 'digital12' => 'showDigital'
35 + );
24 36
25 - static $defaults = array (
26 - 'skin' => 'swissRail',
27 - 'radius' => 100,
28 - 'noseconds' => false, // Hide seconds
29 - 'gmtoffset' => '', // GMT offset
30 - 'showdigital' => '', // Show digital time or date
31 - 'scale' => 'linear' // Define type of clock linear/logarithmic/log reversed
32 - );
37 + static $advanced_defaults = array(
38 + 'subtext' => '',
39 + 'align' => 'center'
40 + );
33 41
34 - static $showdigital_options = array (
35 - '' => '',
36 - 'digital12' => 'showDigital'
37 - );
42 + static $more_skins_config = array();
38 43
39 - static $advanced;
44 + static $advanced_skins_config = array();
40 45
41 - static $advanced_defaults = array (
42 - 'subtext' => '',
43 - 'align' => 'center'
44 - );
46 + static $skins_config = array();
45 47
46 - static $default_skins = ['swissRail','chunkySwiss','chunkySwissOnBlack'];
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 */
47 57
48 - static $more_skins = ['fancy','machine','simonbaird_com','classic','modern','simple','securephp','Tes2','Lev','Sand','Sun','Tor','Cold','Babosa','Tumb','Stone','Disc','watermelon','mister'];
58 + static $clock_types = array(
59 + 'linear' => '',
60 + 'logclock' => 'logClock',
61 + 'logclockrev' => 'logClockRev'
62 + );
49 63
50 - static $advanced_skins = [];
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 + );
51 74
52 - static $advanced_skins_config = [];
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;
53 88
54 - static $clock_types = array (
55 - 'linear' => '',
56 - 'logClock' => 'logClock',
57 - 'logClockRev' => 'logClockRev'
58 - );
89 + if ( defined('WP_DEBUG') && WP_DEBUG ) {
90 + self::$min = '';
91 + }
59 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 +
60 116 /**
61 - * MAIN
62 - */
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 + */
63 140
64 - public static function canvas( $atts ) {
65 - extract( $atts );
141 + // get defaults for missing attributes
142 + $defaults = array_merge( self::$defaults, self::$advanced_defaults );
66 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
67 172 $output = '';
68 173
69 - if ( ! self::$done_excanvas ){
70 - $output .= '<!--[if lte IE 8]>';
71 - $output .= '<script type=\'text/javascript\' src=\'' . self::$plugin_url . 'js/excanvas' . self::$min . '.js\'></script>';
72 - $output .= '<![endif]-->' . PHP_EOL;
73 - self::$done_excanvas = true;
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;
74 201 }
75 202
76 - $output .= '<div class="coolclock';
203 + $style = ! empty($style_arr) ? ' style="' . implode( ';', $style_arr ) . '"' : '';
77 204
78 - // align class ans style
79 - $output .= ( $align ) ? ' align' . $align : '';
80 - $output .= '" style="width:' . 2 * $radius . 'px;max-width:100%;height:auto">';
81 - // canvas parameters
82 - $output .= '<canvas class="CoolClock:' . $skin . ':' . $radius . ':';
83 - $output .= ( $noseconds == 'true' || $noseconds == '1' ) ? 'noSeconds:' : ':';
84 - $output .= $gmtoffset;
205 + return $style;
206 + }
85 207
86 - // show digital
87 - if ( $showdigital == 'true' || $showdigital == '1' )
88 - $showdigital = 'digital12'; // backward compat
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 );
89 221
90 - if ( isset(self::$showdigital_options[$showdigital]) )
91 - $output .= ':'.self::$showdigital_options[$showdigital];
92 - else
93 - $output .= ':';
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 + }
94 227
95 - // set type
96 - if ( isset(self::$clock_types[$scale]) )
97 - $output .= ':'.self::$clock_types[$scale];
98 - else
99 - $output .= ':';
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 + }
100 233
101 - $output .= '"></canvas>';
102 - $output .= ( $subtext ) ? '<div style="width:100%;text-align:center;padding-bottom:10px">' . $subtext . '</div></div>' : '</div>';
234 + // search in the more_skins and advanced_skins arrays
235 + $all_skins = self::get_all_skins();
236 + $skin_array = array();
103 237
104 - return $output;
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;
105 256 }
106 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 + */
107 279 public static function enqueue_scripts() {
280 + // bail if we don't need script
108 281 if ( ! self::$add_script )
109 282 return;
110 283
111 - wp_enqueue_script( 'coolclock', self::$plugin_url . 'js/coolclock' . self::$min . '.js', array('jquery'), self::$script_version, true );
284 + $script = '';
112 285
113 - if ( self::$add_moreskins )
114 - wp_enqueue_script( 'coolclock-moreskins', self::$plugin_url . 'js/moreskins' . self::$min . '.js', array('coolclock'), self::$script_version, true );
286 + if ( !empty( self::$skins_config ) ) {
115 287
116 - if ( is_array( self::$advanced_skins_config ) && !empty( self::$advanced_skins_config ) ) {
117 - $script = 'jQuery.extend(CoolClock.config.skins, {' . PHP_EOL;
118 - // loop through plugin custom skins
119 - foreach (self::$advanced_skins_config as $key => $value)
120 - $script .= $key.':{'.$value.'},' . PHP_EOL;
121 - $script .= '});';
288 + $skins = wp_json_encode( self::$skins_config );
289 + if ( false === $skins ) {
290 + $skins = '{} /* skin parse error, please fix your custom skin */';
291 + }
122 292
123 - if ( self::$add_moreskins )
124 - wp_add_inline_script( 'coolclock-moreskins', $script );
125 - else
126 - wp_add_inline_script( 'coolclock', $script );
293 + $script .= 'CoolClock.config.skins = ' . $skins . ';';
127 294 }
128 - }
129 295
130 - public static function textdomain() {
131 - load_plugin_textdomain( 'coolclock', false, dirname(self::$plugin_basename).'/languages' );
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 );
132 304 }
133 305
134 306 public static function register_widget() {
135 307 register_widget("CoolClock_Widget");
@@ -135,18 +307,141 @@
135 307 register_widget("CoolClock_Widget");
136 308 }
137 309
138 310 /**
139 - * INIT
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
140 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>';
141 319
142 - public function __construct( $file ) {
143 - // VARS
144 - self::$plugin_url = plugins_url( '/', $file );
145 - self::$plugin_basename = plugin_basename( $file );
320 + if ( $file == self::$plugin_basename ) {
321 + $links[] = $support_link;
322 + $links[] = $rate_link;
323 + }
146 324
147 - if ( defined('WP_DEBUG') && WP_DEBUG ) {
148 - self::$min = '.min';
149 - }
150 - }
325 + return $links;
326 + }
151 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 + }
152 447 }