+ $subject = str_replace('_', ' ', $matches[2]);
+ $subject = preg_replace_callback('#\=([0-9a-f]{2})#i', '_wp_iso_convert', $subject);
+ return $subject;
+ }
+}
+
+/**
+ * Helper function to convert hex encoded chars to ASCII
+ *
+ * @since 3.1.0
+ * @access private
+ * @param array $match The preg_replace_callback matches array
+ * @return array Converted chars
+ */
+function _wp_iso_convert( $match ) {
+ return chr( hexdec( strtolower( $match[1] ) ) );
+}
+
+/**
+ * Returns a date in the GMT equivalent.
+ *
+ * Requires and returns a date in the Y-m-d H:i:s format. Simply subtracts the
+ * value of the 'gmt_offset' option. Return format can be overridden using the
+ * $format parameter. The DateTime and DateTimeZone classes are used to respect
+ * time zone differences in DST.
+ *
+ * @since 1.2.0
+ *
+ * @uses get_option() to retrieve the the value of 'gmt_offset'.
+ * @param string $string The date to be converted.
+ * @param string $format The format string for the returned date (default is Y-m-d H:i:s)
+ * @return string GMT version of the date provided.
+ */
+function get_gmt_from_date($string, $format = 'Y-m-d H:i:s') {
+ preg_match('#([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})#', $string, $matches);
+ if ( ! $matches )
+ return date( $format, 0 );
+
+ $tz = get_option('timezone_string');
+ if ( $tz ) {
+ date_default_timezone_set( $tz );
+ $datetime = date_create( $string );
+ if ( ! $datetime )
+ return date( $format, 0 );
+
+ $datetime->setTimezone( new DateTimeZone('UTC') );
+ $offset = $datetime->getOffset();
+ $datetime->modify( '+' . $offset / HOUR_IN_SECONDS . ' hours');
+ $string_gmt = gmdate($format, $datetime->format('U'));
+
+ date_default_timezone_set('UTC');
+ } else {
+ $string_time = gmmktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
+ $string_gmt = gmdate($format, $string_time - get_option('gmt_offset') * HOUR_IN_SECONDS);
+ }
+ return $string_gmt;
+}
+
+/**
+ * Converts a GMT date into the correct format for the blog.
+ *
+ * Requires and returns in the Y-m-d H:i:s format. Simply adds the value of
+ * gmt_offset.Return format can be overridden using the $format parameter
+ *
+ * @since 1.2.0
+ *
+ * @param string $string The date to be converted.
+ * @param string $format The format string for the returned date (default is Y-m-d H:i:s)
+ * @return string Formatted date relative to the GMT offset.
+ */
+function get_date_from_gmt($string, $format = 'Y-m-d H:i:s') {
+ preg_match('#([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})#', $string, $matches);
+ $string_time = gmmktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
+ $string_localtime = gmdate($format, $string_time + get_option('gmt_offset') * HOUR_IN_SECONDS);
+ return $string_localtime;
+}
+
+/**
+ * Computes an offset in seconds from an iso8601 timezone.
+ *
+ * @since 1.5.0
+ *
+ * @param string $timezone Either 'Z' for 0 offset or '±hhmm'.
+ * @return int|float The offset in seconds.
+ */
+function iso8601_timezone_to_offset($timezone) {
+ // $timezone is either 'Z' or '[+|-]hhmm'
+ if ($timezone == 'Z') {
+ $offset = 0;
+ } else {
+ $sign = (substr($timezone, 0, 1) == '+') ? 1 : -1;
+ $hours = intval(substr($timezone, 1, 2));
+ $minutes = intval(substr($timezone, 3, 4)) / 60;
+ $offset = $sign * HOUR_IN_SECONDS * ($hours + $minutes);
+ }
+ return $offset;
+}
+
+/**
+ * Converts an iso8601 date to MySQL DateTime format used by post_date[_gmt].
+ *
+ * @since 1.5.0
+ *
+ * @param string $date_string Date and time in ISO 8601 format {@link http://en.wikipedia.org/wiki/ISO_8601}.
+ * @param string $timezone Optional. If set to GMT returns the time minus gmt_offset. Default is 'user'.
+ * @return string The date and time in MySQL DateTime format - Y-m-d H:i:s.
+ */
+function iso8601_to_datetime($date_string, $timezone = 'user') {
+ $timezone = strtolower($timezone);
+
+ if ($timezone == 'gmt') {
+
+ preg_match('#([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(Z|[\+|\-][0-9]{2,4}){0,1}#', $date_string, $date_bits);
+
+ if (!empty($date_bits[7])) { // we have a timezone, so let's compute an offset
+ $offset = iso8601_timezone_to_offset($date_bits[7]);
+ } else { // we don't have a timezone, so we assume user local timezone (not server's!)
+ $offset = HOUR_IN_SECONDS * get_option('gmt_offset');
+ }
+
+ $timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]);
+ $timestamp -= $offset;
+
+ return gmdate('Y-m-d H:i:s', $timestamp);
+
+ } else if ($timezone == 'user') {
+ return preg_replace('#([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(Z|[\+|\-][0-9]{2,4}){0,1}#', '$1-$2-$3 $4:$5:$6', $date_string);
+ }
+}
+
+/**
+ * Adds a element attributes to open links in new windows.
+ *
+ * Comment text in popup windows should be filtered through this. Right now it's
+ * a moderately dumb function, ideally it would detect whether a target or rel
+ * attribute was already there and adjust its actions accordingly.
+ *
+ * @since 0.71
+ *
+ * @param string $text Content to replace links to open in a new window.
+ * @return string Content that has filtered links.
+ */
+function popuplinks($text) {
+ $text = preg_replace('/<a (.+?)>/i', "<a $1 target='_blank' rel='external'>", $text);
+ return $text;
+}
+
+/**
+ * Strips out all characters that are not allowable in an email.
+ *
+ * @since 1.5.0
+ *
+ * @param string $email Email address to filter.
+ * @return string Filtered email address.
+ */
+function sanitize_email( $email ) {
+ // Test for the minimum length the email can be
+ if ( strlen( $email ) < 3 ) {
+ return apply_filters( 'sanitize_email', '', $email, 'email_too_short' );
+ }
+
+ // Test for an @ character after the first position
+ if ( strpos( $email, '@', 1 ) === false ) {
+ return apply_filters( 'sanitize_email', '', $email, 'email_no_at' );
+ }
+
+ // Split out the local and domain parts
+ list( $local, $domain ) = explode( '@', $email, 2 );
+
+ // LOCAL PART
+ // Test for invalid characters
+ $local = preg_replace( '/[^a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]/', '', $local );
+ if ( '' === $local ) {
+ return apply_filters( 'sanitize_email', '', $email, 'local_invalid_chars' );
+ }
+
+ // DOMAIN PART
+ // Test for sequences of periods
+ $domain = preg_replace( '/\.{2,}/', '', $domain );
+ if ( '' === $domain ) {
+ return apply_filters( 'sanitize_email', '', $email, 'domain_period_sequence' );
+ }
+
+ // Test for leading and trailing periods and whitespace
+ $domain = trim( $domain, " \t\n\r\0\x0B." );
+ if ( '' === $domain ) {
+ return apply_filters( 'sanitize_email', '', $email, 'domain_period_limits' );
+ }
+
+ // Split the domain into subs
+ $subs = explode( '.', $domain );
+
+ // Assume the domain will have at least two subs
+ if ( 2 > count( $subs ) ) {
+ return apply_filters( 'sanitize_email', '', $email, 'domain_no_periods' );