PluginProbe
CoolClock / trunk
CoolClock vtrunk
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 +349 -117 4.2trunk View file →
@@ -5,32 +5,29 @@
5 5 */
6 6
7 7 class CoolClock {
8 8
9 - static $plugin_version;
9 + static ?string $plugin_version;
10 10
11 - static $script_version = '3.1.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 17 private static $min = '.min';
18 18
19 19 static $add_script = false;
20 20
21 - static $add_moreskins = false;
22 -
23 - private static $done_excanvas = false;
24 -
25 21 static $defaults = array(
26 - 'skin' => 'swissRail',
22 + 'skin' => 'swissrail',
27 23 'radius' => 100,
28 - 'noseconds' => false, // Hide seconds
24 + 'noseconds' => false, // true to hide second hand
29 25 'gmtoffset' => '', // GMT offset
30 - 'showdigital' => '', // Show digital time or date
31 - 'scale' => 'linear', // Define type of clock linear/logarithmic/log reversed
32 - 'digitalcolor' => '#333'
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'
33 30 );
34 31
35 32 static $showdigital_options = array(
36 33 '' => '',
@@ -36,113 +33,275 @@
36 33 '' => '',
37 34 'digital12' => 'showDigital'
38 35 );
39 36
40 - static $advanced = false;
41 -
42 37 static $advanced_defaults = array(
43 38 'subtext' => '',
44 39 'align' => 'center'
45 40 );
46 41
47 - static $default_skins = array('swissRail','chunkySwiss','chunkySwissOnBlack');
42 + static $more_skins_config = array();
48 43
49 - static $more_skins = array('fancy','machine','simonbaird_com','classic','modern','simple','securephp','Tes2','Lev','Sand','Sun','Tor','Cold','Babosa','Tumb','Stone','Disc','watermelon','mister');
44 + static $advanced_skins_config = array();
50 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();
51 54 static $advanced_skins = array();
55 + static $advanced = false;
56 + /* end unused arguments */
52 57
53 - static $advanced_skins_config = array();
54 -
55 58 static $clock_types = array(
56 59 'linear' => '',
57 - 'logClock' => 'logClock',
58 - 'logClockRev' => 'logClockRev'
60 + 'logclock' => 'logClock',
61 + 'logclockrev' => 'logClockRev'
59 62 );
60 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 +
61 75 /**
62 - * MAIN
76 + * Constructor
77 + *
78 + * @since 4.3
79 + *
80 + * @param string $plugin_file Plugin file path
81 + * @param string $plugin_version Plugin version
63 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;
64 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 + */
65 125 public static function canvas( $atts ) {
66 - extract( $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 + */
67 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
68 172 $output = '';
69 173
70 - if ( ! self::$done_excanvas ){
71 - $output .= '<!--[if lte IE 8]>';
72 - $output .= '<script type=\'text/javascript\' src=\'' . self::$plugin_url . 'js/excanvas' . self::$min . '.js\'></script>';
73 - $output .= '<![endif]-->' . PHP_EOL;
74 - 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;
75 201 }
76 202
77 - $output .= '<div class="coolclock';
203 + $style = ! empty($style_arr) ? ' style="' . implode( ';', $style_arr ) . '"' : '';
78 204
79 - // align class ans style
80 - $output .= ( $align ) ? ' align' . $align : '';
81 - $output .= '" style="width:' . 2 * $radius . 'px;max-width:100%;height:auto">';
82 - // canvas parameters
83 - $output .= '<canvas class="CoolClock:' . $skin . ':' . $radius . ':';
84 - $output .= ( $noseconds == 'true' || $noseconds == '1' ) ? 'noSeconds:' : ':';
85 - $output .= $gmtoffset;
205 + return $style;
206 + }
86 207
87 - // show digital
88 - if ( $showdigital == 'true' || $showdigital == '1' )
89 - $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 );
90 221
91 - if ( isset(self::$showdigital_options[$showdigital]) )
92 - $output .= ':'.self::$showdigital_options[$showdigital];
93 - else
94 - $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 + }
95 227
96 - // set type
97 - if ( isset(self::$clock_types[$scale]) )
98 - $output .= ':'.self::$clock_types[$scale];
99 - else
100 - $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 + }
101 233
102 - // set font
103 - if ( isset($font) )
104 - $output .= ':'.$font;
105 - else
106 - $output .= ':';
234 + // search in the more_skins and advanced_skins arrays
235 + $all_skins = self::get_all_skins();
236 + $skin_array = array();
107 237
108 - // set font color
109 - if ( isset($digitalcolor) )
110 - $output .= ':'.$digitalcolor;
111 - else
112 - $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 );
113 244
114 - $output .= '"></canvas>';
115 - $output .= ( $subtext ) ? '<div style="width:100%;text-align:center;padding-bottom:10px">' . $subtext . '</div></div>' : '</div>';
245 + if ( empty( $skin_array ) ) {
246 + // set faulty skin name
247 + return 'no_skin_found';
248 + }
249 + }
116 250
117 - return $output;
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;
118 256 }
119 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 + */
120 279 public static function enqueue_scripts() {
280 + // bail if we don't need script
121 281 if ( ! self::$add_script )
122 282 return;
123 283
124 - wp_enqueue_script( 'coolclock', self::$plugin_url . 'js/coolclock' . self::$min . '.js', array('jquery'), self::$script_version, true );
284 + $script = '';
125 285
126 - if ( self::$add_moreskins )
127 - 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 ) ) {
128 287
129 - if ( is_array( self::$advanced_skins_config ) && !empty( self::$advanced_skins_config ) ) {
130 - $script = 'jQuery.extend(CoolClock.config.skins, {' . PHP_EOL;
131 - // loop through plugin custom skins
132 - foreach (self::$advanced_skins_config as $key => $value)
133 - $script .= $key.':{'.$value.'},' . PHP_EOL;
134 - $script .= '});';
288 + $skins = wp_json_encode( self::$skins_config );
289 + if ( false === $skins ) {
290 + $skins = '{} /* skin parse error, please fix your custom skin */';
291 + }
135 292
136 - if ( self::$add_moreskins )
137 - wp_add_inline_script( 'coolclock-moreskins', $script );
138 - else
139 - wp_add_inline_script( 'coolclock', $script );
293 + $script .= 'CoolClock.config.skins = ' . $skins . ';';
140 294 }
141 - }
142 295
143 - public static function textdomain() {
144 - 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 );
145 304 }
146 305
147 306 public static function register_widget() {
148 307 register_widget("CoolClock_Widget");
@@ -147,12 +306,17 @@
147 306 public static function register_widget() {
148 307 register_widget("CoolClock_Widget");
149 308 }
150 309
151 - // add links to plugin's description
152 - public static function plugin_meta_links($links, $file) {
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 ) {
153 317 $support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/coolclock/">' . __('Support','coolclock') . '</a>';
154 - $rate_link = '<a target="_blank" href="https://wordpress.org/support/plugin/coolclock/reviews/?filter=5#new-post">' . __('Rate ★★★★★','coolclock') . '</a>';
318 + $rate_link = '<a target="_blank" href="https://wordpress.org/support/plugin/coolclock/reviews/">' . __('Rate this plugin','coolclock') . '</a>';
155 319
156 320 if ( $file == self::$plugin_basename ) {
157 321 $links[] = $support_link;
158 322 $links[] = $rate_link;
@@ -160,56 +324,124 @@
160 324
161 325 return $links;
162 326 }
163 327
164 - public static function colorval( $color )
165 - {
166 - $color = strip_tags( $color );
167 - $color = trim( $color );
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 + }
168 350
169 - if (substr($color, 0, 1) == '#') {
170 - if ( ctype_xdigit(substr($color, 1)) )
171 - return $color;
351 + // fist attempt to decode json string
352 + $skin_array = json_decode( '{' . $sanitized . '}', true );
172 353
173 - return substr($color, 1);
174 - }
354 + // not valid json? then do it the hard way
355 + if ( null === $skin_array ) :
175 356
176 - return ctype_xdigit($color) ? '#'.$color : $color;
177 - }
357 + // remove all spaces and tabs
358 + $sanitized = str_replace( array( ' ', "\t", '"', "'" ), '', $sanitized );
178 359
179 - /**
180 - * INIT
181 - */
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 );
182 363
183 - public function __construct( $plugin_file, $plugin_version ) {
184 - // VARS
185 - self::$plugin_url = plugins_url( '/', $plugin_file );
186 - self::$plugin_basename = plugin_basename( $plugin_file );
187 - self::$plugin_version = $plugin_version;
364 + // start fresh
365 + $skin_array = array();
188 366
189 - if ( defined('WP_DEBUG') && WP_DEBUG ) {
190 - self::$min = '';
191 - }
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 + }
192 384
193 - // text domain
194 - add_action( 'plugins_loaded', array( __CLASS__, 'textdomain' ) );
385 + endif;
195 386
196 - // widgets
197 - add_action( 'widgets_init', array( __CLASS__, 'register_widget' ) );
387 + return $skin_array;
388 + }
198 389
199 - // enqueue scripts but only if shortcode or widget has been used
200 - // so it has to be done as late as the wp_footer action
201 - add_action( 'wp_footer', array( __CLASS__, 'enqueue_scripts' ), 1 );
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] );
202 405
203 - add_filter( 'plugin_row_meta', array( __CLASS__, 'plugin_meta_links' ), 10, 2);
406 + if ( substr($color, 0, 1) == '#' ) {
407 + if ( ctype_xdigit( substr( $color, 1 ) ) )
408 + return $color;
204 409
205 - /**************
206 - * SHORTCODE
207 - **************/
410 + return substr( $color, 1 );
411 + }
208 412
209 - add_shortcode( 'coolclock', array( 'CoolClock_Shortcode', 'handle_shortcode' ) );
413 + return ctype_xdigit( $color ) ? '#'.$color : $color;
414 + }
210 415
211 - // prevent texturizing shortcode content
212 - add_filter( 'no_texturize_shortcodes', array( 'CoolClock_Shortcode', 'no_wptexturize') );
213 - }
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 + }
214 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 + }
215 447 }