Changeset 3646096
- Timestamp:
- 08/13/2026 07:43:04 PM (2 months ago)
- Location:
- current-year-shortcode
- Files:
-
- 10 added
- 8 edited
-
tags/2.6 (added)
-
tags/2.6/index.php (added)
-
tags/2.6/readme.txt (added)
-
tags/2.6/shortcodes (added)
-
tags/2.6/shortcodes/copyright.php (added)
-
tags/2.6/shortcodes/dates.php (added)
-
tags/2.6/shortcodes/index.php (added)
-
tags/2.6/shortcodes/ip.php (added)
-
tags/2.6/shortcodes/symbols.php (added)
-
tags/2.6/year-kgm.php (added)
-
trunk/index.php (modified) (1 diff)
-
trunk/readme.txt (modified) (11 diffs)
-
trunk/shortcodes/copyright.php (modified) (1 diff)
-
trunk/shortcodes/dates.php (modified) (5 diffs)
-
trunk/shortcodes/index.php (modified) (1 diff)
-
trunk/shortcodes/ip.php (modified) (2 diffs)
-
trunk/shortcodes/symbols.php (modified) (1 diff)
-
trunk/year-kgm.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
current-year-shortcode/trunk/index.php
r1971581 r3646096 1 1 <?php 2 // Silence is golden. 2 /** 3 * Silence is golden. 4 * 5 * @package Current_Year_Shortcode 6 */ -
current-year-shortcode/trunk/readme.txt
r3504475 r3646096 4 4 Tags: current year shortcode, copyright shortcode, copyright symbol, trademark shortcode, symbol shortcode 5 5 Requires at least: 2.5 6 Tested up to: 6.86 Tested up to: 7.0 7 7 Requires PHP: 5.6 8 Stable tag: 2. 58 Stable tag: 2.6 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 18 18 19 19 **How shortcode work:** [WordPress Codex](https://codex.wordpress.org/Shortcode "WordPress Codex") 20 21 **Full documentation and shortcode list:** [Documentation](http://uskgm.it/cysdoc "Documentation")22 20 23 21 Current year, copyright, symbols and user IP with shortcode. … … 51 49 For shortcode with month you can choose format with attribute format="". It support "F" - "m" - "M" - "n". 52 50 51 "F" full name (April) 52 "M" short name (Apr) 53 "m" number with leading zero (04) 54 "n" number without leading zero (4) 55 53 56 **Format Attribute for Day** 54 For shortcode with day you can choose format with attribute format="". It support "D" - "d" - "j" - "N" 57 For shortcode with day you can choose format with attribute format="". It support "d" - "D" - "j" - "N" - "S" - "w" - "z" - "t". 58 59 "d" number with leading zero (05) 60 "j" number without leading zero (5) 61 "D" short day name (Mon) 62 "S" ordinal suffix, to put after "j" (st, nd, rd, th) 63 "N" day of the week, 1 for Monday to 7 for Sunday 64 "w" day of the week, 0 for Sunday to 6 for Saturday 65 "z" day of the year (0 to 365) 66 "t" number of days in the current month 55 67 56 68 **Format Attribute for current date** 57 All php date function attributes. 69 For [dmy] you can use the letters of the php date function, plus space and - / . , : ; characters. 70 Escaped literal characters (the ones with a backslash) are not supported. 71 72 **Example** 73 [dmy format="d/m/Y"] **12/04/2026** 74 [dmy format="j F Y"] **12 April 2026** 75 [dmy format="Y-m-d"] **2026-04-12** 76 [dmy format="l"] **Sunday** 77 [dmy format="H:i"] **18:30** 78 79 Month and day names come out in the language of your site. 58 80 59 81 **Offset Attribute** 60 For day shortcode [d] you can add offset with attribute offset="". It support + or -. 61 For month shortcode [m] you can add offset with attribute offset="". It support + or -. 62 For year shortcode [y] you can add offset with attribute offset="". It support + or -. 63 For current date shortcode [dmy] you can add offset with attribute offset="". It support + or - in standard php date format, for ex. +1 year or -1 day. 82 For [y], [m] and [d] the offset is a plain number with + or -, from -1000 to 1000. 83 For [dmy] the number needs a unit after it: years, months, weeks, days, hours, minutes, seconds. Also "today", "yesterday" and "tomorrow" work. 64 84 65 85 **Example** … … 67 87 [m offset="+1"] **May** (if current month is April) 68 88 [y offset="+1"] **2027** (if current year is 2026) 89 [y offset="-3"] **2023** (if current year is 2026) 69 90 [dmy offset="+1 year"] **12/04/2027** 91 [dmy offset="-2 months"] **12/02/2026** 92 [dmy offset="tomorrow"] **13/04/2026** 93 94 Offsets follow the timezone of your site, the one in Settings > General. 70 95 71 96 … … 101 126 [show_user_ip] display ip of current user 102 127 103 **Full list here:** [Documentation](http://uskgm.it/cysdoc "Documentation") 128 The complete list of format letters and offsets is in the Description tab. 104 129 105 130 = What are the symbols shortcode? = … … 119 144 With IP shortcode you can retrieve your user IP. 120 145 146 = I use a CDN and [show_user_ip] shows the wrong address = 147 148 The shortcode reads a forwarded header only when the request reaches your site through a proxy it recognises. Local reverse proxies and Cloudflare are already in the list. For any other CDN, add its ranges: 149 150 `add_filter( 'cys_trusted_proxies', function ( $proxies ) { 151 $proxies[] = '203.0.113.0/24'; 152 return $proxies; 153 } );` 154 155 Ask your host or your CDN which ranges to use. Getting it wrong breaks nothing, the shortcode simply falls back to the address of whoever opened the connection. 156 157 = Why is the page with [show_user_ip] not cached? = 158 159 Because that shortcode prints something different for every visitor, so the page has to be built on each request. If you also cache at the edge, exclude the page there as well. 160 121 161 = How work year format attribute? = 122 162 … … 134 174 = How work day format attribute? = 135 175 136 For shortcode with day you can choose format with attribute format="". It support " D" - "d" - "j" - "N"176 For shortcode with day you can choose format with attribute format="". It support "d" - "D" - "j" - "N" - "S" - "w" - "z" - "t". The full list with the meaning of each letter is in the Description tab. 137 177 138 178 = How work year, month and day offset attribute? = 139 179 140 For day shortcode [d] you can add offset with attribute offset="". It support + or -. 141 For month shortcode [m] you can add offset with attribute offset="". It support + or -. 142 For year shortcode [y] you can add offset with attribute offset="". It support + or -. 143 For current date shortcode [dmy] you can add offset with attribute offset="". It support + or - in standard php date format, for ex. +1 year or -1 day. 180 For [y], [m] and [d] the offset is a plain number with + or -, from -1000 to 1000. 181 For [dmy] the number needs a unit after it: years, months, weeks, days, hours, minutes, seconds. Also "today", "yesterday" and "tomorrow" work. 182 Offsets follow the timezone of your site, the one in Settings > General. 144 183 145 184 **Example** … … 148 187 [y offset="+1"] **2027** (if current year is 2026) 149 188 [dmy offset="+1 year"] **12/04/2027** 189 [dmy offset="tomorrow"] **13/04/2026** 150 190 151 191 … … 160 200 161 201 == Changelog == 202 203 = 2.6 = 204 * Offsets now follow the timezone set in your WordPress settings, on dates and on times. 205 * More consistent handling of the year format: [cyy] and friends with format="y" show a single year when there is nothing to range, same as format="Y" already did. 206 * Stricter check on the year attribute. 207 * [show_user_ip] reads forwarded headers only when the request comes through a known proxy. Local reverse proxies and Cloudflare work out of the box, other CDNs can be added with a filter, see the FAQ. 208 * Pages using [show_user_ip] are kept out of full page caching. 209 * Code revision on the date and copyright shortcodes. 210 * Tested with WordPress 7.0. 162 211 163 212 = 2.5 = … … 269 318 == Upgrade Notice == 270 319 320 = 2.6 = 321 Offsets now follow your site timezone. If your site sits behind a CDN other than Cloudflare, add it with the cys_trusted_proxies filter, otherwise [show_user_ip] shows the address of the CDN instead of the one of your visitor. See the FAQ. 322 271 323 = 2.5 = 272 324 Security and accessibility improvements. Fixed negative offset bug. If you use the wpb_get_ip filter hook, please update to cys_get_ip (old hook still works for now). … … 364 416 = 0.1 = 365 417 Initial release. 366 367 `<?php code(); // goes in backticks ?>` -
current-year-shortcode/trunk/shortcodes/copyright.php
r3504475 r3646096 7 7 * License: GPLv2 or later 8 8 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 * 10 * @package Current_Year_Shortcode 9 11 */ 10 12 11 13 if ( ! defined( 'ABSPATH' ) ) { 12 exit; // Exit if accessed directly 13 } 14 15 /** 16 * Validate year parameter 17 * 18 * @param string $year The year to validate 19 * @return array|false Array with 'display' (original format) and 'numeric' (4-digit) or false if invalid 14 exit; // Exit if accessed directly. 15 } 16 17 /** 18 * Validate year parameter. 19 * 20 * @param string $year The year to validate. 21 * @return array|false Array with 'display' (original format) and 'numeric' (4-digit) or false if invalid. 20 22 */ 21 23 function cys_validate_year( $year ) { 22 // Check if year is numeric 23 if ( ! is_numeric( $year ) ) { 24 $year = trim( (string) $year ); 25 26 /* 27 * A plain run of one to four digits, nothing else. is_numeric() would also accept 28 * "2003.7", "2e3", ".5" and "+2003", and the display value is printed verbatim: 29 * those inputs used to reach the page as "© 2003.7-2026". 30 */ 31 if ( ! preg_match( '/^\d{1,4}$/', $year ) ) { 24 32 return false; 25 33 } 26 34 27 $original_year = $year; // Keep original format 28 $year = intval( $year ); 29 30 // Handle 2-digit years (00-99) 31 if ( $year >= 0 && $year <= 99 ) { 32 // Convert 2-digit year to 4-digit year for comparison 33 // Years 00-30 are considered 2000-2030 34 // Years 31-99 are considered 1931-1999 35 if ( $year <= 30 ) { 36 $numeric_year = $year + 2000; 37 } else { 38 $numeric_year = $year + 1900; 35 $original_year = $year; // Keep original format. 36 $numeric_year = (int) $year; 37 38 // Handle 2-digit years (00-99). 39 if ( $numeric_year <= 99 ) { 40 // Years 00-30 are considered 2000-2030, years 31-99 are considered 1931-1999. 41 $numeric_year += ( $numeric_year <= 30 ) ? 2000 : 1900; 42 } 43 44 /* 45 * No range check is needed here: the pattern above caps the input at four digits, 46 * so the normalized year is always between 100 and 9999. 47 */ 48 return array( 49 'display' => $original_year, // Original format for display. 50 'numeric' => $numeric_year, // 4-digit for comparison. 51 ); 52 } 53 54 /** 55 * Wrap the copyright text in the symbol markup used by [cy] and [cyy]. 56 * 57 * @param string $text The year or year range to display. 58 * @return string The copyright symbol with the given text, escaped for safe output. 59 */ 60 function cys_present_copyright_symbol( $text ) { 61 return '<span aria-label="' . esc_attr( 'Copyright ' . $text ) . '"><span aria-hidden="true">©</span> ' . esc_html( $text ) . '</span>'; 62 } 63 64 /** 65 * Wrap the copyright text in the long form used by [cyyl]. 66 * 67 * @param string $text The year or year range to display. 68 * @return string The "Copyright" text with the given text, escaped for safe output. 69 */ 70 function cys_present_copyright_long( $text ) { 71 return esc_html( 'Copyright ' . $text ); 72 } 73 74 /** 75 * Wrap the copyright text in the long form with symbol used by [cyyls]. 76 * 77 * @param string $text The year or year range to display. 78 * @return string The copyright symbol and "Copyright" text, escaped for safe output. 79 */ 80 function cys_present_copyright_long_symbol( $text ) { 81 return '<span aria-hidden="true">©</span> ' . esc_html( 'Copyright ' . $text ); 82 } 83 84 /** 85 * Per-shortcode settings for the copyright shortcodes. 86 * 87 * @return array Map of shortcode tag to whether it takes a year attribute and which 88 * presenter renders its markup. 89 */ 90 function cys_copyright_shortcode_config() { 91 return array( 92 'cy' => array( 93 'needs_year' => false, 94 'presenter' => 'cys_present_copyright_symbol', 95 ), 96 'cyy' => array( 97 'needs_year' => true, 98 'presenter' => 'cys_present_copyright_symbol', 99 ), 100 'cyyl' => array( 101 'needs_year' => true, 102 'presenter' => 'cys_present_copyright_long', 103 ), 104 'cyyls' => array( 105 'needs_year' => true, 106 'presenter' => 'cys_present_copyright_long_symbol', 107 ), 108 ); 109 } 110 111 /** 112 * Shared renderer behind every copyright shortcode. 113 * 114 * @param array $atts Shortcode attributes. 115 * @param string $tag Shortcode tag, used to look up the per-shortcode settings. 116 * @return string The rendered copyright or an error message. 117 */ 118 function cys_render_copyright_shortcode( $atts, $tag ) { 119 $config = cys_copyright_shortcode_config(); 120 $spec = $config[ $tag ]; 121 122 $defaults = array( 'format' => 'error' ); 123 if ( $spec['needs_year'] ) { 124 $defaults = array( 125 'year' => 'error enter first year, show guide', 126 'format' => 'error', 127 ); 128 } 129 130 $atts = shortcode_atts( $defaults, $atts, $tag ); 131 132 $atts = cys_sanitize_shortcode_atts( $atts ); 133 134 // The year is validated before the format, so a bad year wins over a bad format. 135 if ( $spec['needs_year'] ) { 136 $validated_year = cys_validate_year( $atts['year'] ); 137 if ( false === $validated_year ) { 138 return cys_error( 'Invalid year value!' ); 139 } 140 141 $year_display = $validated_year['display']; // Original format for display. 142 $year_numeric = $validated_year['numeric']; // 4-digit for comparison. 143 } 144 145 if ( 'error' !== $atts['format'] ) { 146 $format = cys_validate_date_format( $atts['format'], 'year' ); 147 if ( false === $format ) { 148 return cys_error( $atts['format'] . ' is not a valid year format!' ); 39 149 } 40 150 } else { 41 $numeric_year = $year; 42 } 43 44 // Check if final year is within reasonable range (1-9999) 45 if ( $numeric_year >= 1 && $numeric_year <= 9999 ) { 46 return array( 47 'display' => $original_year, // Original format for display 48 'numeric' => $numeric_year, // 4-digit for comparison 49 ); 50 } 51 52 return false; 53 } 54 55 /** 56 * Retrieve copyright symbol with current year 57 * 58 * @param array $atts Shortcode attributes 59 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format Y) 60 * @return string The copyright symbol with formatted year or error message 61 */ 151 $format = 'Y'; 152 } 153 154 $current_year = date_i18n( $format ); 155 156 if ( ! $spec['needs_year'] ) { 157 return call_user_func( $spec['presenter'], $current_year ); 158 } 159 160 /* 161 * The comparison runs on the canonical four-digit year, never on the formatted one: 162 * date_i18n( 'y' ) returns two digits while the validated year is always four, so 163 * with format="y" the two could never match and [cyy year="26" format="y"] used to 164 * render "© 26-26" in the year 2026 instead of collapsing to a single year. 165 */ 166 if ( (int) date_i18n( 'Y' ) === $year_numeric ) { 167 return call_user_func( $spec['presenter'], $current_year ); 168 } 169 170 return call_user_func( $spec['presenter'], $year_display . '-' . $current_year ); 171 } 172 173 /** 174 * Retrieve copyright symbol with current year. 175 * 176 * @param array $atts Shortcode attributes. 177 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format Y). 178 * @return string The copyright symbol with formatted year or error message. 179 */ 180 function cys_copy_year( $atts ) { 181 return cys_render_copyright_shortcode( $atts, 'cy' ); 182 } 62 183 add_shortcode( 'cy', 'cys_copy_year' ); 63 function cys_copy_year( $atts ) { 64 $atts = shortcode_atts( 65 array( 66 'format' => 'error', 67 ), 68 $atts, 69 'cy' 70 ); 71 72 if ( 'error' !== $atts['format'] ) { 73 $validated_format = cys_validate_date_format( $atts['format'], 'year' ); 74 if ( false !== $validated_format ) { 75 $year = date_i18n( $validated_format ); 76 return '<span aria-label="' . esc_attr( 'Copyright ' . $year ) . '"><span aria-hidden="true">©</span> ' . esc_html( $year ) . '</span>'; 77 } else { 78 return '<span role="alert">' . esc_html( sanitize_text_field( $atts['format'] ) . ' is not a valid year format!' ) . '</span>'; 79 } 80 } else { 81 $year = date_i18n( 'Y' ); 82 return '<span aria-label="' . esc_attr( 'Copyright ' . $year ) . '"><span aria-hidden="true">©</span> ' . esc_html( $year ) . '</span>'; 83 } 84 } 85 86 /** 87 * Retrieve copyright symbol with year range (first-year - last-year) 88 * 89 * @param array $atts Shortcode attributes 90 * - year (string) Required. The first year of copyright (1-9999, supports 2-digit years) 91 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format Y) 92 * @return string The copyright symbol with year range or error message 93 */ 184 185 /** 186 * Retrieve copyright symbol with year range (first-year - last-year). 187 * 188 * @param array $atts Shortcode attributes. 189 * - year (string) Required. The first year of copyright (1-9999, supports 2-digit years). 190 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format Y). 191 * @return string The copyright symbol with year range or error message. 192 */ 193 function cys_copy_year_year( $atts ) { 194 return cys_render_copyright_shortcode( $atts, 'cyy' ); 195 } 94 196 add_shortcode( 'cyy', 'cys_copy_year_year' ); 95 function cys_copy_year_year( $atts ) { 96 $atts = shortcode_atts( 97 array( 98 'year' => 'error enter first year, show guide', 99 'format' => 'error', 100 ), 101 $atts, 102 'cyy' 103 ); 104 105 // Validate year parameter 106 $validated_year = cys_validate_year( $atts['year'] ); 107 if ( false === $validated_year ) { 108 return '<span role="alert">' . esc_html( 'Invalid year value!' ) . '</span>'; 109 } 110 111 $year_display = $validated_year['display']; // Original format for display 112 $year_numeric = $validated_year['numeric']; // 4-digit for comparison 113 114 if ( 'error' !== $atts['format'] ) { 115 $validated_format = cys_validate_date_format( $atts['format'], 'year' ); 116 if ( false !== $validated_format ) { 117 $current_year = date_i18n( $validated_format ); 118 if ( (int) $current_year === $year_numeric ) { 119 return '<span aria-label="' . esc_attr( 'Copyright ' . $current_year ) . '"><span aria-hidden="true">©</span> ' . esc_html( $current_year ) . '</span>'; 120 } else { 121 return '<span aria-label="' . esc_attr( 'Copyright ' . $year_display . '-' . $current_year ) . '"><span aria-hidden="true">©</span> ' . esc_html( $year_display . '-' . $current_year ) . '</span>'; 122 } 123 } else { 124 return '<span role="alert">' . esc_html( sanitize_text_field( $atts['format'] ) . ' is not a valid year format!' ) . '</span>'; 125 } 126 } else { 127 $current_year = date_i18n( 'Y' ); 128 if ( (int) $current_year === $year_numeric ) { 129 return '<span aria-label="' . esc_attr( 'Copyright ' . $current_year ) . '"><span aria-hidden="true">©</span> ' . esc_html( $current_year ) . '</span>'; 130 } else { 131 return '<span aria-label="' . esc_attr( 'Copyright ' . $year_display . '-' . $current_year ) . '"><span aria-hidden="true">©</span> ' . esc_html( $year_display . '-' . $current_year ) . '</span>'; 132 } 133 } 134 } 135 136 /** 137 * Retrieve "Copyright" text with year range (first-year - last-year) 138 * 139 * @param array $atts Shortcode attributes 140 * - year (string) Required. The first year of copyright (1-9999, supports 2-digit years) 141 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format Y) 142 * @return string The "Copyright" text with year range or error message 143 */ 197 198 /** 199 * Retrieve "Copyright" text with year range (first-year - last-year). 200 * 201 * @param array $atts Shortcode attributes. 202 * - year (string) Required. The first year of copyright (1-9999, supports 2-digit years). 203 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format Y). 204 * @return string The "Copyright" text with year range or error message. 205 */ 206 function cys_copy_year_year_long( $atts ) { 207 return cys_render_copyright_shortcode( $atts, 'cyyl' ); 208 } 144 209 add_shortcode( 'cyyl', 'cys_copy_year_year_long' ); 145 function cys_copy_year_year_long( $atts ) { 146 $atts = shortcode_atts( 147 array( 148 'year' => 'error enter first year, show guide', 149 'format' => 'error', 150 ), 151 $atts, 152 'cyyl' 153 ); 154 155 // Validate year parameter 156 $validated_year = cys_validate_year( $atts['year'] ); 157 if ( false === $validated_year ) { 158 return '<span role="alert">' . esc_html( 'Invalid year value!' ) . '</span>'; 159 } 160 161 $year_display = $validated_year['display']; // Original format for display 162 $year_numeric = $validated_year['numeric']; // 4-digit for comparison 163 164 if ( 'error' !== $atts['format'] ) { 165 $validated_format = cys_validate_date_format( $atts['format'], 'year' ); 166 if ( false !== $validated_format ) { 167 $current_year = date_i18n( $validated_format ); 168 if ( (int) $current_year === $year_numeric ) { 169 return esc_html( 'Copyright ' . $current_year ); 170 } else { 171 return esc_html( 'Copyright ' . $year_display . '-' . $current_year ); 172 } 173 } else { 174 return '<span role="alert">' . esc_html( sanitize_text_field( $atts['format'] ) . ' is not a valid year format!' ) . '</span>'; 175 } 176 } else { 177 $current_year = date_i18n( 'Y' ); 178 if ( (int) $current_year === $year_numeric ) { 179 return esc_html( 'Copyright ' . $current_year ); 180 } else { 181 return esc_html( 'Copyright ' . $year_display . '-' . $current_year ); 182 } 183 } 184 } 185 186 /** 187 * Retrieve copyright symbol with "Copyright" text and year range (first-year - last-year) 188 * 189 * @param array $atts Shortcode attributes 190 * - year (string) Required. The first year of copyright (1-9999, supports 2-digit years) 191 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format Y) 192 * @return string The copyright symbol with "Copyright" text and year range or error message 193 */ 210 211 /** 212 * Retrieve copyright symbol with "Copyright" text and year range (first-year - last-year). 213 * 214 * @param array $atts Shortcode attributes. 215 * - year (string) Required. The first year of copyright (1-9999, supports 2-digit years). 216 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format Y). 217 * @return string The copyright symbol with "Copyright" text and year range or error message. 218 */ 219 function cys_copy_year_year_long_symbol( $atts ) { 220 return cys_render_copyright_shortcode( $atts, 'cyyls' ); 221 } 194 222 add_shortcode( 'cyyls', 'cys_copy_year_year_long_symbol' ); 195 function cys_copy_year_year_long_symbol( $atts ) { 196 $atts = shortcode_atts( 197 array( 198 'year' => 'error enter first year, show guide', 199 'format' => 'error', 200 ), 201 $atts, 202 'cyyls' 203 ); 204 205 // Validate year parameter 206 $validated_year = cys_validate_year( $atts['year'] ); 207 if ( false === $validated_year ) { 208 return '<span role="alert">' . esc_html( 'Invalid year value!' ) . '</span>'; 209 } 210 211 $year_display = $validated_year['display']; // Original format for display 212 $year_numeric = $validated_year['numeric']; // 4-digit for comparison 213 214 if ( 'error' !== $atts['format'] ) { 215 $validated_format = cys_validate_date_format( $atts['format'], 'year' ); 216 if ( false !== $validated_format ) { 217 $current_year = date_i18n( $validated_format ); 218 if ( (int) $current_year === $year_numeric ) { 219 return '<span aria-hidden="true">©</span> ' . esc_html( 'Copyright ' . $current_year ); 220 } else { 221 return '<span aria-hidden="true">©</span> ' . esc_html( 'Copyright ' . $year_display . '-' . $current_year ); 222 } 223 } else { 224 return '<span role="alert">' . esc_html( sanitize_text_field( $atts['format'] ) . ' is not a valid year format!' ) . '</span>'; 225 } 226 } else { 227 $current_year = date_i18n( 'Y' ); 228 if ( (int) $current_year === $year_numeric ) { 229 return '<span aria-hidden="true">©</span> ' . esc_html( 'Copyright ' . $current_year ); 230 } else { 231 return '<span aria-hidden="true">©</span> ' . esc_html( 'Copyright ' . $year_display . '-' . $current_year ); 232 } 233 } 234 } 235 236 /** 237 * Retrieve copyright symbol (©) 238 * 239 * @param array $atts Shortcode attributes (not used) 240 * @return string The copyright symbol, escaped for safe output 241 */ 223 224 /** 225 * Retrieve copyright symbol. 226 * 227 * @return string The copyright symbol, escaped for safe output. 228 */ 229 function cys_copy() { 230 return '<abbr aria-label="' . esc_attr( 'Copyright' ) . '">©</abbr>'; 231 } 242 232 add_shortcode( 'c', 'cys_copy' ); 243 function cys_copy( $atts ) { 244 return '<abbr aria-label="' . esc_attr( 'Copyright' ) . '">©</abbr>'; 245 } 246 247 /** 248 * Retrieve "Copyright" text 249 * 250 * @param array $atts Shortcode attributes (not used) 251 * @return string The "Copyright" text, escaped for safe output 252 */ 233 234 /** 235 * Retrieve "Copyright" text. 236 * 237 * @return string The "Copyright" text, escaped for safe output. 238 */ 239 function cys_copylong() { 240 return esc_html( 'Copyright' ); 241 } 253 242 add_shortcode( 'cc', 'cys_copylong' ); 254 function cys_copylong( $atts ) {255 return esc_html( 'Copyright' );256 } -
current-year-shortcode/trunk/shortcodes/dates.php
r3504475 r3646096 7 7 * License: GPLv2 or later 8 8 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 * 10 * @package Current_Year_Shortcode 9 11 */ 10 12 11 13 if ( ! defined( 'ABSPATH' ) ) { 12 exit; // Exit if accessed directly 13 } 14 15 /** 16 * Validate and sanitize offset parameter for date calculations 17 * 18 * @param string $offset The offset value to validate 19 * @param string $type The type of offset (years, months, days, or generic) 20 * @return string|false Sanitized offset or false if invalid 14 exit; // Exit if accessed directly. 15 } 16 17 /** 18 * Validate and sanitize offset parameter for date calculations. 19 * 20 * @param string $offset The offset value to validate. 21 * @param string $type The type of offset. 'generic' also accepts strtotime keywords and a time 22 * unit; any other value restricts the offset to a bare signed integer. 23 * @return string|false Sanitized offset, 'none' when there is no offset, or false if invalid. 21 24 */ 22 25 function cys_validate_offset( $offset, $type = 'years' ) { 23 // Remove any whitespace 26 // Remove any whitespace. 24 27 $offset = trim( $offset ); 25 28 26 // Check if offset is empty or 'none' 27 if ( empty( $offset )|| 'none' === $offset ) {29 // Check if offset is empty or 'none'. 30 if ( '' === $offset || 'none' === $offset ) { 28 31 return 'none'; 29 32 } 30 33 34 /* 35 * A run of digits long enough to overflow a float makes intval() answer 0, and a 0 36 * sails straight through the range check further down: 309 nines would be accepted 37 * as an offset and land on strtotime() as they are. The longest offset that means 38 * anything here is something like "-1000 seconds". 39 */ 40 if ( strlen( $offset ) > 32 ) { 41 return false; 42 } 43 31 44 if ( 'generic' === $type ) { 32 // For generic strtotime, allow only safe patterns 33 // Pattern 1: today/yesterday/tomorrow 45 // For generic strtotime, allow only safe patterns. 46 // Pattern 1: today/yesterday/tomorrow. 34 47 if ( preg_match( '/^(today|yesterday|tomorrow)$/i', $offset ) ) { 35 48 return $offset; 36 49 } 37 50 38 // Pattern 2: numeric offset with optional time unit 51 // Pattern 2: numeric offset with optional time unit. 39 52 if ( preg_match( '/^([+-]?\s*\d+)(\s*(years?|months?|days?|weeks?|hours?|minutes?|seconds?))?$/i', $offset, $matches ) ) { 40 53 $num_offset = intval( trim( $matches[1] ) ); … … 45 58 46 59 return false; 47 } else {48 // For specific types (years, months, days) 49 if ( preg_match( '/^[+-]?\s*\d+$/', $offset ) ) {50 $num_offset = intval( trim( $offset ) );51 // Extended range for more flexibility52 if ( $num_offset >= -1000 && $num_offset <= 1000 ) {53 return $offset;54 }60 } 61 62 // For specific types (years, months, days). 63 if ( preg_match( '/^[+-]?\s*\d+$/', $offset ) ) { 64 $num_offset = intval( trim( $offset ) ); 65 // Extended range for more flexibility. 66 if ( $num_offset >= -1000 && $num_offset <= 1000 ) { 67 return $offset; 55 68 } 69 } 70 71 return false; 72 } 73 74 /** 75 * Validate date format using flexible pattern-based approach. 76 * 77 * @param string $format The format to validate. 78 * @param string $type The type of format (year, month, day, date). 79 * @return string|false Valid format or false if invalid. 80 */ 81 function cys_validate_date_format( $format, $type = 'year' ) { 82 $format = trim( $format ); 83 84 if ( '' === $format ) { 56 85 return false; 57 86 } 58 } 59 60 /** 61 * Validate date format using flexible pattern-based approach 62 * 63 * @param string $format The format to validate 64 * @param string $type The type of format (year, month, day, date) 65 * @return string|false Valid format or false if invalid 66 */ 67 function cys_validate_date_format( $format, $type = 'year' ) { 68 // Remove any whitespace 69 $format = trim( $format ); 70 71 // Check if format is empty 72 if ( empty( $format ) ) { 87 88 /* 89 * The patterns below are anchored single character classes, so they cannot backtrack 90 * catastrophically; the cap is here for output size instead. Every accepted character 91 * expands to at least one character of date, so a format of a hundred thousand Y would 92 * render a hundred thousand digits into the page. No real date format is this long. 93 */ 94 if ( strlen( $format ) > 32 ) { 73 95 return false; 74 96 } 75 97 76 // Define allowed format characters based on type 98 // Define allowed format characters based on type. 77 99 $allowed_chars = array( 78 100 'year' => '/^[yY]+$/', … … 82 104 ); 83 105 84 // Check if format matches allowed pattern for the type 106 // Check if format matches allowed pattern for the type. 85 107 if ( isset( $allowed_chars[ $type ] ) && preg_match( $allowed_chars[ $type ], $format ) ) { 86 108 return $format; … … 91 113 92 114 /** 93 * Sanitize shortcode attributes for dates 94 * 95 * @param array $atts The attributes to sanitize 96 * @return array Sanitized attributes 97 */ 98 function cys_sanitize_date_atts( $atts ) { 99 $sanitized = array(); 100 101 foreach ( $atts as $key => $value ) { 102 // Sanitize text fields 103 if ( in_array( $key, array( 'format', 'offset' ), true ) ) { 104 $sanitized[ $key ] = sanitize_text_field( $value ); 105 } else { 106 $sanitized[ $key ] = $value; 107 } 108 } 109 110 return $sanitized; 111 } 112 113 /** 114 * Retrieve current year with optional format and offset 115 * 116 * @param array $atts Shortcode attributes 117 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format) 118 * - offset (string) Optional. Year offset (+1, -1, etc.). Default: 'none' 119 * @return string The formatted year or error message 120 */ 121 add_shortcode( 'y', 'cys_year' ); 122 function cys_year( $atts ) { 115 * Sanitize every attribute of a shortcode. 116 * 117 * Keys are not enumerated on purpose: a shortcode_atts_{$tag} filter can add keys this 118 * plugin was never written for, and sanitize_text_field() answers '' for arrays and 119 * objects by itself, so passing everything through it is both shorter and safer than 120 * listing the expected names. 121 * 122 * @param array $atts The attributes to sanitize. 123 * @return array Sanitized attributes. 124 */ 125 function cys_sanitize_shortcode_atts( $atts ) { 126 return array_map( 'sanitize_text_field', (array) $atts ); 127 } 128 129 /** 130 * Wrap an error message in the markup every shortcode uses to report one. 131 * 132 * @param string $message The message to display. 133 * @return string The message, escaped for safe output. 134 */ 135 function cys_error( $message ) { 136 return '<span role="alert">' . esc_html( $message ) . '</span>'; 137 } 138 139 /** 140 * Format a date placed at a relative offset from the site's own wall clock. 141 * 142 * Relative expressions are resolved by strtotime() against the UTC timezone WordPress forces 143 * in wp-settings.php, and date_i18n() then reinterprets that UTC wall clock as local time. 144 * The two do not cancel out: every site east or west of UTC shows the wrong day during 145 * part of each day, and the wrong year around New Year. Anchoring the expression to a 146 * local DateTimeImmutable and rendering the resulting instant with wp_date() keeps the 147 * day right in every timezone. 148 * 149 * @param string $format Date format, already validated by cys_validate_date_format(). 150 * @param string $expression Relative expression, already validated by cys_validate_offset(). 151 * @return string The formatted date. 152 */ 153 function cys_format_offset_date( $format, $expression ) { 154 /* 155 * current_datetime() and wp_date() both arrived in WordPress 5.3. On older versions 156 * keep the historical behaviour rather than fataling: wrong day near midnight, but 157 * exactly as wrong as every previous release of this plugin. 158 */ 159 if ( ! function_exists( 'current_datetime' ) || ! function_exists( 'wp_date' ) ) { 160 return date_i18n( $format, strtotime( $expression ) ); 161 } 162 163 $now = current_datetime(); 164 165 /* 166 * An offset carrying no time unit, such as "5" on [dmy], is not a relative expression 167 * PHP can parse. strtotime() used to answer false in silence, while modify() emits a 168 * warning below PHP 8.3 and throws from 8.3 on, so the unparsable case is recognized 169 * here and rendered as the current moment: what every previous release displayed. 170 */ 171 if ( false === strtotime( $expression, $now->getTimestamp() ) ) { 172 return wp_date( $format, $now->getTimestamp() ); 173 } 174 175 try { 176 $moment = $now->modify( $expression ); 177 } catch ( Exception $e ) { 178 // Backstop: reached only if modify() rejects an expression strtotime() accepted. 179 $moment = $now; 180 } 181 182 return wp_date( $format, $moment->getTimestamp() ); 183 } 184 185 /** 186 * Per-shortcode settings for the date shortcodes. 187 * 188 * The offset_type doubles as the time unit appended to a numeric offset, so the two can 189 * never drift apart; 'generic' means the offset is a whole strtotime expression and gets 190 * no unit. The format_type selects the allowed characters in cys_validate_date_format() 191 * and also names the shortcode in its error message. 192 * 193 * @return array Map of shortcode tag to its offset type, format type and default format. 194 */ 195 function cys_date_shortcode_config() { 196 return array( 197 'y' => array( 198 'offset_type' => 'years', 199 'format_type' => 'year', 200 'default_format' => 'Y', 201 ), 202 'm' => array( 203 'offset_type' => 'months', 204 'format_type' => 'month', 205 'default_format' => 'F', 206 ), 207 'd' => array( 208 'offset_type' => 'days', 209 'format_type' => 'day', 210 'default_format' => 'd', 211 ), 212 'dmy' => array( 213 'offset_type' => 'generic', 214 'format_type' => 'date', 215 'default_format' => 'd/m/Y', 216 ), 217 ); 218 } 219 220 /** 221 * Shared renderer behind every date shortcode. 222 * 223 * @param array $atts Shortcode attributes. 224 * @param string $tag Shortcode tag, used to look up the per-shortcode settings. 225 * @return string The formatted date or an error message. 226 */ 227 function cys_render_date_shortcode( $atts, $tag ) { 228 $config = cys_date_shortcode_config(); 229 $spec = $config[ $tag ]; 230 123 231 $atts = shortcode_atts( 124 232 array( … … 127 235 ), 128 236 $atts, 129 'y'237 $tag 130 238 ); 131 239 132 // Sanitize shortcode attributes 133 $atts = cys_sanitize_date_atts( $atts ); 134 135 // Validate offset parameter 136 $validated_offset = cys_validate_offset( $atts['offset'], 'years' ); 240 $atts = cys_sanitize_shortcode_atts( $atts ); 241 242 $validated_offset = cys_validate_offset( $atts['offset'], $spec['offset_type'] ); 137 243 if ( false === $validated_offset ) { 138 return '<span role="alert">' . esc_html( 'Invalid offset value!' ) . '</span>';244 return cys_error( 'Invalid offset value!' ); 139 245 } 140 246 141 247 if ( 'error' !== $atts['format'] ) { 142 $validated_format = cys_validate_date_format( $atts['format'], 'year' ); 143 if ( false !== $validated_format ) { 144 if ( 'none' !== $validated_offset ) { 145 return esc_html( date_i18n( $validated_format, strtotime( $validated_offset . ' years' ) ) ); 146 } else { 147 return esc_html( date_i18n( $validated_format ) ); 148 } 149 } else { 150 return '<span role="alert">' . esc_html( sanitize_text_field( $atts['format'] ) . ' is not a valid year format!' ) . '</span>'; 248 $format = cys_validate_date_format( $atts['format'], $spec['format_type'] ); 249 if ( false === $format ) { 250 return cys_error( $atts['format'] . ' is not a valid ' . $spec['format_type'] . ' format!' ); 151 251 } 152 252 } else { 153 if ( 'none' !== $validated_offset ) { 154 return esc_html( date_i18n( 'Y', strtotime( $validated_offset . ' years' ) ) ); 155 } else { 156 return esc_html( date_i18n( 'Y' ) ); 157 } 158 } 159 } 160 161 /** 162 * Retrieve current month with optional format and offset 163 * 164 * @param array $atts Shortcode attributes 165 * - format (string) Optional. Month format (F, m, M, n). Default: 'error' (uses default format) 166 * - offset (string) Optional. Month offset (+1, -1, etc.). Default: 'none' 167 * @return string The formatted month or error message 168 */ 253 $format = $spec['default_format']; 254 } 255 256 if ( 'none' === $validated_offset ) { 257 return esc_html( date_i18n( $format ) ); 258 } 259 260 // 'generic' offsets are whole expressions already; the others are bare numbers needing their unit. 261 $unit = ( 'generic' === $spec['offset_type'] ) ? '' : ' ' . $spec['offset_type']; 262 263 return esc_html( cys_format_offset_date( $format, $validated_offset . $unit ) ); 264 } 265 266 /** 267 * Retrieve current year with optional format and offset. 268 * 269 * @param array $atts Shortcode attributes. 270 * - format (string) Optional. Year format (y or Y). Default: 'error' (uses default format). 271 * - offset (string) Optional. Year offset (+1, -1, etc.). Default: 'none'. 272 * @return string The formatted year or error message. 273 */ 274 function cys_year( $atts ) { 275 return cys_render_date_shortcode( $atts, 'y' ); 276 } 277 add_shortcode( 'y', 'cys_year' ); 278 279 /** 280 * Retrieve current month with optional format and offset. 281 * 282 * @param array $atts Shortcode attributes. 283 * - format (string) Optional. Month format (F, m, M, n). Default: 'error' (uses default format). 284 * - offset (string) Optional. Month offset (+1, -1, etc.). Default: 'none'. 285 * @return string The formatted month or error message. 286 */ 287 function cys_month( $atts ) { 288 return cys_render_date_shortcode( $atts, 'm' ); 289 } 169 290 add_shortcode( 'm', 'cys_month' ); 170 function cys_month( $atts ) { 171 $atts = shortcode_atts( 172 array( 173 'format' => 'error', 174 'offset' => 'none', 175 ), 176 $atts, 177 'm' 178 ); 179 180 // Sanitize shortcode attributes 181 $atts = cys_sanitize_date_atts( $atts ); 182 183 // Validate offset parameter 184 $validated_offset = cys_validate_offset( $atts['offset'], 'months' ); 185 if ( false === $validated_offset ) { 186 return '<span role="alert">' . esc_html( 'Invalid offset value!' ) . '</span>'; 187 } 188 189 if ( 'error' !== $atts['format'] ) { 190 $validated_format = cys_validate_date_format( $atts['format'], 'month' ); 191 if ( false !== $validated_format ) { 192 if ( 'none' !== $validated_offset ) { 193 return esc_html( date_i18n( $validated_format, strtotime( $validated_offset . ' months' ) ) ); 194 } else { 195 return esc_html( date_i18n( $validated_format ) ); 196 } 197 } else { 198 return '<span role="alert">' . esc_html( sanitize_text_field( $atts['format'] ) . ' is not a valid month format!' ) . '</span>'; 199 } 200 } else { 201 if ( 'none' !== $validated_offset ) { 202 return esc_html( date_i18n( 'F', strtotime( $validated_offset . ' months' ) ) ); 203 } else { 204 return esc_html( date_i18n( 'F' ) ); 205 } 206 } 207 } 208 209 /** 210 * Retrieve current day with optional format and offset 211 * 212 * @param array $atts Shortcode attributes 213 * - format (string) Optional. Day format (d, D, j, N, S, w, z, t). Default: 'error' (uses default format) 214 * - offset (string) Optional. Day offset (+1, -1, etc.). Default: 'none' 215 * @return string The formatted day or error message 216 */ 291 292 /** 293 * Retrieve current day with optional format and offset. 294 * 295 * @param array $atts Shortcode attributes. 296 * - format (string) Optional. Day format (d, D, j, N, S, w, z, t). Default: 'error' (uses default format). 297 * - offset (string) Optional. Day offset (+1, -1, etc.). Default: 'none'. 298 * @return string The formatted day or error message. 299 */ 300 function cys_day( $atts ) { 301 return cys_render_date_shortcode( $atts, 'd' ); 302 } 217 303 add_shortcode( 'd', 'cys_day' ); 218 function cys_day( $atts ) { 219 $atts = shortcode_atts( 220 array( 221 'format' => 'error', 222 'offset' => 'none', 223 ), 224 $atts, 225 'd' 226 ); 227 228 // Sanitize shortcode attributes 229 $atts = cys_sanitize_date_atts( $atts ); 230 231 // Validate offset parameter 232 $validated_offset = cys_validate_offset( $atts['offset'], 'days' ); 233 if ( false === $validated_offset ) { 234 return '<span role="alert">' . esc_html( 'Invalid offset value!' ) . '</span>'; 235 } 236 237 if ( 'error' !== $atts['format'] ) { 238 $validated_format = cys_validate_date_format( $atts['format'], 'day' ); 239 if ( false !== $validated_format ) { 240 if ( 'none' !== $validated_offset ) { 241 return esc_html( date_i18n( $validated_format, strtotime( $validated_offset . ' days' ) ) ); 242 } else { 243 return esc_html( date_i18n( $validated_format ) ); 244 } 245 } else { 246 return '<span role="alert">' . esc_html( sanitize_text_field( $atts['format'] ) . ' is not a valid day format!' ) . '</span>'; 247 } 248 } else { 249 if ( 'none' !== $validated_offset ) { 250 return esc_html( date_i18n( 'd', strtotime( $validated_offset . ' days' ) ) ); 251 } else { 252 return esc_html( date_i18n( 'd' ) ); 253 } 254 } 255 } 256 257 /** 258 * Retrieve current date with optional format and offset 259 * 260 * @param array $atts Shortcode attributes 261 * - format (string) Optional. Date format (all PHP date format characters). Default: 'error' (uses d/m/Y) 262 * - offset (string) Optional. Date offset (+1 year, +5 months, today, yesterday, tomorrow, etc.). Default: 'none' 263 * @return string The formatted date or error message 264 */ 304 305 /** 306 * Retrieve current date with optional format and offset. 307 * 308 * @param array $atts Shortcode attributes. 309 * - format (string) Optional. Date format (all PHP date format characters). Default: 'error' (uses d/m/Y). 310 * - offset (string) Optional. Date offset (+1 year, +5 months, today, yesterday, tomorrow, etc.). Default: 'none'. 311 * @return string The formatted date or error message. 312 */ 313 function cys_current_date( $atts ) { 314 return cys_render_date_shortcode( $atts, 'dmy' ); 315 } 265 316 add_shortcode( 'dmy', 'cys_current_date' ); 266 function cys_current_date( $atts ) {267 $atts = shortcode_atts(268 array(269 'format' => 'error',270 'offset' => 'none',271 ),272 $atts,273 'dmy'274 );275 276 // Sanitize shortcode attributes277 $atts = cys_sanitize_date_atts( $atts );278 279 // Validate offset parameter for generic strtotime280 $validated_offset = cys_validate_offset( $atts['offset'], 'generic' );281 if ( false === $validated_offset ) {282 return '<span role="alert">' . esc_html( 'Invalid offset value!' ) . '</span>';283 }284 285 if ( 'error' !== $atts['format'] ) {286 $validated_format = cys_validate_date_format( $atts['format'], 'date' );287 if ( false !== $validated_format ) {288 if ( 'none' !== $validated_offset ) {289 return esc_html( date_i18n( $validated_format, strtotime( $validated_offset ) ) );290 } else {291 return esc_html( date_i18n( $validated_format ) );292 }293 } else {294 return '<span role="alert">' . esc_html( sanitize_text_field( $atts['format'] ) . ' is not a valid date format!' ) . '</span>';295 }296 } else {297 if ( 'none' !== $validated_offset ) {298 return esc_html( date_i18n( 'd/m/Y', strtotime( $validated_offset ) ) );299 } else {300 return esc_html( date_i18n( 'd/m/Y' ) );301 }302 }303 } -
current-year-shortcode/trunk/shortcodes/index.php
r2835678 r3646096 1 1 <?php 2 // Silence is golden. 2 /** 3 * Silence is golden. 4 * 5 * @package Current_Year_Shortcode 6 */ -
current-year-shortcode/trunk/shortcodes/ip.php
r3504475 r3646096 7 7 * License: GPLv2 or later 8 8 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 * 10 * @package Current_Year_Shortcode 9 11 */ 10 12 11 13 if ( ! defined( 'ABSPATH' ) ) { 12 exit; // Exit if accessed directly 13 } 14 15 /** 16 * Retrieve user IP address with security validation and sanitization 17 * 18 * Checks multiple proxy headers in priority order and validates IP addresses. 19 * Falls back to REMOTE_ADDR if no valid IP is found in proxy headers. 14 exit; // Exit if accessed directly. 15 } 16 17 /** 18 * The networks Cloudflare serves requests from. 19 * 20 * Published at https://www.cloudflare.com/ips/ and read on 2026-08-12. Kept apart from 21 * the other trusted networks because the CF-Connecting-IP header means nothing unless 22 * the request really arrived from Cloudflare. If this list ever falls behind, a site on 23 * a new range shows the address of the Cloudflare edge instead of the visitor: a wrong 24 * answer, never a forgeable one. 25 * 26 * @return array List of CIDR ranges. 27 */ 28 function cys_cloudflare_ranges() { 29 return array( 30 // IPv4. 31 '173.245.48.0/20', 32 '103.21.244.0/22', 33 '103.22.200.0/22', 34 '103.31.4.0/22', 35 '141.101.64.0/18', 36 '108.162.192.0/18', 37 '190.93.240.0/20', 38 '188.114.96.0/20', 39 '197.234.240.0/22', 40 '198.41.128.0/17', 41 '162.158.0.0/15', 42 '104.16.0.0/13', 43 '104.24.0.0/14', 44 '172.64.0.0/13', 45 '131.0.72.0/22', 46 // IPv6. 47 '2400:cb00::/32', 48 '2606:4700::/32', 49 '2803:f800::/32', 50 '2405:b500::/32', 51 '2405:8100::/32', 52 '2a06:98c0::/29', 53 '2c0f:f248::/32', 54 ); 55 } 56 57 /** 58 * Public networks whose forwarded headers can be believed. 59 * 60 * A forwarded header is only as trustworthy as whoever set it, and the one party a site 61 * can identify with certainty is the one that opened the TCP connection: REMOTE_ADDR. So 62 * the question is never "which header do I read" but "did this request reach me through 63 * a proxy of mine". It is the rule Apache's mod_remoteip applies with RemoteIPTrustedProxy. 64 * 65 * Private and reserved networks are recognized separately, in cys_is_trusted_proxy(). 66 * Any other CDN or load balancer with a public address is declared from the site: 67 * 68 * add_filter( 'cys_trusted_proxies', function ( $proxies ) { 69 * $proxies[] = '203.0.113.0/24'; 70 * return $proxies; 71 * } ); 72 * 73 * @return array List of CIDR ranges and single addresses. 74 */ 75 function cys_trusted_proxies() { 76 $proxies = apply_filters( 'cys_trusted_proxies', cys_cloudflare_ranges() ); 77 78 /* 79 * A callback that forgets its return statement hands back null, which would silently 80 * drop the shipped ranges along with whatever it meant to add. 81 */ 82 return is_array( $proxies ) ? $proxies : cys_cloudflare_ranges(); 83 } 84 85 /** 86 * Reduce an IPv4-mapped IPv6 address to its plain IPv4 form. 87 * 88 * A web server listening on a dual-stack socket reports an IPv4 peer as ::ffff:127.0.0.1, 89 * which matches no IPv4 range and would quietly switch the whole mechanism off. 90 * 91 * @param string $ip The address to normalize. 92 * @return string The IPv4 address when the input was a mapped one, the input otherwise. 93 */ 94 function cys_normalize_ip( $ip ) { 95 $packed = inet_pton( $ip ); 96 97 /* 98 * Recognising the mapping on the text would mean chasing every legal way of writing it: 99 * ::ffff:8.8.8.8 and ::ffff:0808:0808 are the same address, and a check that only knows 100 * the dotted form leaves the other one looking like a plain IPv6 address. All of them 101 * collapse to the same sixteen bytes, so the binary form is the one place to look. 102 */ 103 if ( false !== $packed && 16 === strlen( $packed ) && "\0\0\0\0\0\0\0\0\0\0\xff\xff" === substr( $packed, 0, 12 ) ) { 104 $unmapped = inet_ntop( substr( $packed, 12, 4 ) ); 105 106 if ( false !== $unmapped ) { 107 return $unmapped; 108 } 109 } 110 111 return $ip; 112 } 113 114 /** 115 * Tell whether an address falls inside a CIDR range. 116 * 117 * Works on both IPv4 and IPv6 by masking the packed binary forms, so an IPv4 address is 118 * never matched against an IPv6 range or the other way round. 119 * 120 * @param string $ip The address to test. 121 * @param string $range A CIDR range, or a single address when it carries no slash. 122 * @return bool True when the address is inside the range. 123 */ 124 function cys_ip_in_range( $ip, $range ) { 125 if ( false === strpos( $range, '/' ) ) { 126 return $ip === $range; 127 } 128 129 list( $subnet, $prefix ) = explode( '/', $range, 2 ); 130 131 $ip_packed = inet_pton( $ip ); 132 $subnet_packed = inet_pton( $subnet ); 133 134 if ( false === $ip_packed || false === $subnet_packed || strlen( $ip_packed ) !== strlen( $subnet_packed ) ) { 135 return false; 136 } 137 138 /* 139 * This list is extended from the site, so a typo has to fail closed: '10.0.0.0/' and 140 * '10.0.0.0/abc' both cast to a prefix of 0, and a prefix of 0 matches every address 141 * there is. Turning one mistyped range into "trust the whole internet" is exactly 142 * what this function exists to prevent. 143 */ 144 if ( ! ctype_digit( $prefix ) || (int) $prefix > strlen( $ip_packed ) * 8 ) { 145 return false; 146 } 147 148 $prefix = (int) $prefix; 149 $full_bytes = (int) ( $prefix / 8 ); 150 $spare_bits = $prefix % 8; 151 152 $mask = str_repeat( "\xFF", $full_bytes ); 153 if ( $spare_bits > 0 ) { 154 $mask .= chr( ( 0xFF << ( 8 - $spare_bits ) ) & 0xFF ); 155 } 156 $mask = str_pad( $mask, strlen( $ip_packed ), "\x00" ); 157 158 return ( $ip_packed & $mask ) === ( $subnet_packed & $mask ); 159 } 160 161 /** 162 * Tell whether an address falls inside any of the given ranges. 163 * 164 * @param string $ip The address to test. 165 * @param array $ranges List of CIDR ranges. 166 * @return bool True when the address is inside one of them. 167 */ 168 function cys_ip_in_any_range( $ip, $ranges ) { 169 foreach ( $ranges as $range ) { 170 if ( is_string( $range ) && cys_ip_in_range( $ip, $range ) ) { 171 return true; 172 } 173 } 174 175 return false; 176 } 177 178 /** 179 * Tell whether an address belongs to one of the site's own proxies. 180 * 181 * @param string $ip The address to test. 182 * @return bool True when the address is a trusted proxy. 183 */ 184 function cys_is_trusted_proxy( $ip ) { 185 // Normalized here too, so the answer does not depend on which form the caller passed in. 186 $ip = cys_normalize_ip( $ip ); 187 188 if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { 189 return false; 190 } 191 192 /* 193 * A peer on a private or reserved network did not reach the site across the public 194 * internet: on the overwhelming majority of installations it is infrastructure of the 195 * site itself, a reverse proxy, a load balancer or a container gateway. 196 * 197 * The trade-off is deliberate. mod_remoteip ships no defaults at all and makes the 198 * administrator name every proxy, which is stricter but leaves the common 199 * nginx-to-PHP-FPM setup reporting 127.0.0.1 for every visitor until someone 200 * configures it. The cost of trusting these networks is that on a site also reachable 201 * from an internal LAN, someone already inside that network can dictate the address 202 * shown back to them by sending their own X-Forwarded-For. 203 */ 204 if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) { 205 return true; 206 } 207 208 return cys_ip_in_any_range( $ip, cys_trusted_proxies() ); 209 } 210 211 /** 212 * Read the address a trusted proxy forwarded. 213 * 214 * Cloudflare states a single address in CF-Connecting-IP, so that one is read as is, but 215 * only from a request that really arrived from Cloudflare: any other proxy would simply 216 * be handing on a header the visitor wrote. 217 * 218 * X-Forwarded-For is instead a chain, "client, proxy1, proxy2", to which every hop 219 * APPENDS the address it received the connection from. Nothing stops a visitor from 220 * sending a chain of their own, which the first proxy then appends to, so the leftmost 221 * entry is whatever the visitor decided to put there: reading it hands the answer back 222 * to the party being identified. The chain is walked from the right instead, stepping 223 * over the site's own proxies, and the first address that is not one of them is the 224 * furthest point a proxy of ours actually observed. 225 * 226 * @param string $remote_addr The address that opened the connection. 227 * @return string The forwarded address, or an empty string when there is none to trust. 228 */ 229 function cys_forwarded_client_ip( $remote_addr ) { 230 if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) && cys_ip_in_any_range( $remote_addr, cys_cloudflare_ranges() ) ) { 231 $forwarded = cys_normalize_ip( sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) ); 232 233 if ( filter_var( $forwarded, FILTER_VALIDATE_IP ) ) { 234 return $forwarded; 235 } 236 } 237 238 if ( empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { 239 return ''; 240 } 241 242 $chain = explode( ',', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ); 243 244 foreach ( array_reverse( $chain ) as $hop ) { 245 $hop = cys_normalize_ip( trim( $hop ) ); 246 247 if ( ! filter_var( $hop, FILTER_VALIDATE_IP ) ) { 248 return ''; // A malformed hop breaks the chain of custody: stop believing the rest of it. 249 } 250 251 if ( ! cys_is_trusted_proxy( $hop ) ) { 252 return $hop; 253 } 254 } 255 256 return ''; 257 } 258 259 /** 260 * Resolve the address of whoever is asking for the page. 261 * 262 * @return string A validated IP address, or an empty string when none can be established. 263 */ 264 function cys_get_client_ip() { 265 if ( empty( $_SERVER['REMOTE_ADDR'] ) ) { 266 return ''; 267 } 268 269 // Unslash and sanitize the IP address from $_SERVER. 270 $remote_addr = cys_normalize_ip( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) ); 271 272 if ( ! filter_var( $remote_addr, FILTER_VALIDATE_IP ) ) { 273 return ''; 274 } 275 276 /* 277 * Forwarded headers are read only when the request actually arrived through one of 278 * the site's proxies. On a direct connection they are just text the visitor typed. 279 */ 280 if ( ! cys_is_trusted_proxy( $remote_addr ) ) { 281 return $remote_addr; 282 } 283 284 $forwarded = cys_forwarded_client_ip( $remote_addr ); 285 286 return ( '' !== $forwarded ) ? $forwarded : $remote_addr; 287 } 288 289 /** 290 * Retrieve user IP address with security validation and sanitization. 291 * 20 292 * Returns '0.0.0.0' as a safe default if no valid IP is found. 21 293 * … … 23 295 * Site administrators should ensure proper privacy policy disclosure. 24 296 * 25 * @return string The user's IP address, escaped for safe output 26 */ 297 * @return string The user's IP address, escaped for safe output. 298 */ 299 function cys_retrieve_ip() { 300 /* 301 * The output differs for every visitor, so the page must not be stored in a full page 302 * cache: one visitor's address would be served to everybody after them. DONOTCACHEPAGE 303 * is the constant the WordPress caching plugins look for and is what actually does the 304 * work here. The nocache_headers() call is a second line of defence for the rare case 305 * where output has not started yet, since it returns without doing anything once the 306 * headers have been sent, which is the normal state of affairs by the time a shortcode 307 * runs. Neither of the two reaches a cache sitting in front of the web server, so a 308 * site with "cache everything" enabled at the edge has to exclude the page there too. 309 */ 310 if ( ! defined( 'DONOTCACHEPAGE' ) ) { 311 define( 'DONOTCACHEPAGE', true ); 312 } 313 nocache_headers(); 314 315 $ip = cys_get_client_ip(); 316 317 // Default safe value if no valid IP found. 318 if ( '' === $ip ) { 319 $ip = '0.0.0.0'; 320 } 321 322 // Backward-compatible deprecated hook — fires only if someone is using it. 323 $ip = apply_filters_deprecated( 'wpb_get_ip', array( $ip ), '2.5', 'cys_get_ip' ); 324 325 // Escape output for security and apply filters. 326 return esc_html( apply_filters( 'cys_get_ip', $ip ) ); 327 } 27 328 add_shortcode( 'show_user_ip', 'cys_retrieve_ip' ); 28 function cys_retrieve_ip() {29 $ip = '';30 31 // List of proxy headers to check (in priority order)32 $proxy_headers = array(33 'HTTP_CF_CONNECTING_IP', // Cloudflare34 'HTTP_CLIENT_IP', // Proxy35 'HTTP_X_FORWARDED_FOR', // Load balancer/proxy36 'HTTP_X_FORWARDED', // Proxy37 'HTTP_X_CLUSTER_CLIENT_IP', // Cluster38 'HTTP_FORWARDED_FOR', // Proxy39 'HTTP_FORWARDED', // Proxy40 'REMOTE_ADDR' // Standard41 );42 43 // Find the first valid IP address44 foreach ( $proxy_headers as $header ) {45 if ( ! empty( $_SERVER[ $header ] ) ) {46 // Unslash and sanitize the IP address from $_SERVER47 $candidate_ip = sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) );48 49 // If multiple IPs (comma separated), take the first one50 if ( false !== strpos( $candidate_ip, ',' ) ) {51 $parts = explode( ',', $candidate_ip );52 $candidate_ip = trim( $parts[0] );53 }54 55 // Validate the IP address56 if ( filter_var( $candidate_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {57 $ip = $candidate_ip;58 break;59 }60 }61 }62 63 // Fallback to REMOTE_ADDR if no valid IP found64 if ( empty( $ip ) && ! empty( $_SERVER['REMOTE_ADDR'] ) ) {65 // Unslash and sanitize the IP address from $_SERVER66 $fallback_ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );67 if ( filter_var( $fallback_ip, FILTER_VALIDATE_IP ) ) {68 $ip = $fallback_ip;69 }70 }71 72 // Default safe value if no valid IP found73 if ( empty( $ip ) ) {74 $ip = '0.0.0.0';75 }76 77 // Backward-compatible deprecated hook — fires only if someone is using it78 $ip = apply_filters_deprecated( 'wpb_get_ip', array( $ip ), '2.5', 'cys_get_ip' );79 80 // Escape output for security and apply filters81 return esc_html( apply_filters( 'cys_get_ip', $ip ) );82 } -
current-year-shortcode/trunk/shortcodes/symbols.php
r3504475 r3646096 7 7 * License: GPLv2 or later 8 8 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 * 10 * @package Current_Year_Shortcode 9 11 */ 10 12 11 13 if ( ! defined( 'ABSPATH' ) ) { 12 exit; // Exit if accessed directly 14 exit; // Exit if accessed directly. 13 15 } 14 16 15 17 /** 16 * Retrieve registered trademark symbol (®)18 * Retrieve registered trademark symbol. 17 19 * 18 * @param array $atts Shortcode attributes (not used) 19 * @return string The registered trademark symbol, escaped for safe output 20 * @return string The registered trademark symbol, escaped for safe output. 20 21 */ 21 add_shortcode( 't', 'cys_registered_trademark' ); 22 function cys_registered_trademark( $atts ) { 22 function cys_registered_trademark() { 23 23 return '<abbr aria-label="' . esc_attr( 'Registered trademark' ) . '">®</abbr>'; 24 24 } 25 add_shortcode( 't', 'cys_registered_trademark' ); 25 26 26 27 /** 27 * Retrieve trademark symbol (™)28 * Retrieve trademark symbol. 28 29 * 29 * @param array $atts Shortcode attributes (not used) 30 * @return string The trademark symbol, escaped for safe output 30 * @return string The trademark symbol, escaped for safe output. 31 31 */ 32 add_shortcode( 'tm', 'cys_trademark' ); 33 function cys_trademark( $atts ) { 32 function cys_trademark() { 34 33 return '<abbr aria-label="' . esc_attr( 'Trademark' ) . '">™</abbr>'; 35 34 } 35 add_shortcode( 'tm', 'cys_trademark' ); 36 36 37 37 /** 38 * Retrieve service mark symbol (℠)38 * Retrieve service mark symbol. 39 39 * 40 * @param array $atts Shortcode attributes (not used) 41 * @return string The service mark symbol, escaped for safe output 40 * @return string The service mark symbol, escaped for safe output. 42 41 */ 43 add_shortcode( 'sm', 'cys_servicemark_trademark' ); 44 function cys_servicemark_trademark( $atts ) { 42 function cys_servicemark_trademark() { 45 43 return '<abbr aria-label="' . esc_attr( 'Service mark' ) . '">℠</abbr>'; 46 44 } 45 add_shortcode( 'sm', 'cys_servicemark_trademark' ); -
current-year-shortcode/trunk/year-kgm.php
r3504475 r3646096 2 2 /** 3 3 * Plugin Name: Current Year, Symbols and IP Shortcode 4 * Version: 2. 54 * Version: 2.6 5 5 * Description: Get current year, symbols and IP with shortcode. 6 6 * Author: KGM Servizi … … 8 8 * License: GPLv2 or later 9 9 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 * 11 * @package Current_Year_Shortcode 10 12 */ 11 13 12 14 if ( ! defined( 'ABSPATH' ) ) { 13 exit; // Exit if accessed directly 15 exit; // Exit if accessed directly. 14 16 } 15 17
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)