Ticket #14525: 14525.combined.patch
File 14525.combined.patch, 96.9 KB (added by , 13 years ago) |
---|
-
blogger-importer-blogitem.php
1 <?php 2 3 /** 4 * Based on WP_SimplePieAtomPub_Item 5 * Expect this to become part of core wordpress at some point. 6 * See http://core.trac.wordpress.org/ticket/7652 7 * 8 * Todo GeoTag parsing 9 * http://codex.wordpress.org/Geodata 10 * 11 */ 12 13 define('SIMPLEPIE_NAMESPACE_ATOMPUB', 'http://www.w3.org/2007/app'); 14 15 /** 16 * SimplePie Helper for AtomPub 17 * 18 * @package WordPress 19 * @subpackage Publishing 20 * @since 3.1 21 */ 22 if ( !class_exists( 'WP_SimplePie_Blog_Item' ) ) { 23 class WP_SimplePie_Blog_Item extends SimplePie_Item { 24 /** 25 * Constructor 26 */ 27 function WP_SimplePieAtomPub_Item($feed, $data) { 28 parent::SimplePie_Item($feed, $data); 29 } 30 31 /** 32 * Get the status of the entry 33 * 34 * @return bool True if the item is a draft, false otherwise 35 */ 36 function get_draft_status() { 37 $draft = false; 38 if (($control = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOMPUB, 'control')) && !empty($control[0]['child'][SIMPLEPIE_NAMESPACE_ATOMPUB]['draft'][0]['data'])) { 39 $draft = ('yes' == $control[0]['child'][SIMPLEPIE_NAMESPACE_ATOMPUB]['draft'][0]['data']); 40 } 41 return $draft; 42 } 43 44 //Tried using date functions from http://core.trac.wordpress.org/attachment/ticket/7652/7652-separate.diff 45 //but ended up with 1970s dates so returned to Otto's version which is much simplified 46 function get_updated() { 47 $temparray = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'); 48 if ( isset( $temparray[0]['data'] ) ) return $this->convert_date($temparray[0]['data']); 49 else return NULL; 50 } 51 52 function get_published() { 53 $temparray = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'); 54 if ( isset( $temparray[0]['data'] ) ) return $this->convert_date($temparray[0]['data']); 55 else return NULL; 56 } 57 58 function convert_date( $date ) { 59 preg_match('#([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.[0-9]+)?(Z|[\+|\-][0-9]{2,4}){0,1}#', $date, $date_bits); 60 $offset = iso8601_timezone_to_offset( $date_bits[7] ); 61 $timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]); 62 $timestamp -= $offset; // Convert from Blogger local time to GMT 63 $timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time 64 return gmdate('Y-m-d H:i:s', $timestamp); 65 } 66 67 //Don't Sanitize the ID, the default get_id was cleaning our IDs and that meant that nested comments did not work 68 function get_id() 69 { 70 if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) 71 { 72 return $return[0]['data']; 73 } 74 } 75 76 } 77 78 } 79 80 ?> 81 No newline at end of file -
blogger-importer-sanitize.php
1 <?php 2 /** 3 * New class to sanitize trusted content from blogger import 4 * Based on the SimplePie_Sanitize class by Ryan Parman and Geoffrey Sneddon 5 * 6 */ 7 8 class Blogger_Importer_Sanitize extends Simplepie_Sanitize 9 { 10 // Private vars 11 var $base; 12 13 // Options 14 var $image_handler = ''; 15 var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'font', 'form', 16 'frame', 'frameset', 'html', 'input', 'marquee', 'meta', 'script', 'style'); 17 //Allow iframe (new style) and embed, param and object(old style) so that we get youtube videos transferred 18 //Allow object and noscript for Amazon widgets 19 var $encode_instead_of_strip = false; 20 var $strip_attributes = array('bgsound','class', 'expr', 'id', 'imageanchor', 'onclick', 'onerror', 21 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); 22 //Allow styles so we don't have to redo in Wordpress 23 //Brett Morgan from Google has confirmed that imageanchor is a made up attribute that is just used in the blogger editor so we can remove that 24 var $output_encoding = 'UTF-8'; 25 var $enable_cache = true; 26 var $cache_location = './cache'; 27 var $cache_name_function = 'md5'; 28 var $cache_class = 'SimplePie_Cache'; 29 var $file_class = 'SimplePie_File'; 30 var $timeout = 10; 31 var $useragent = ''; 32 var $force_fsockopen = false; 33 34 var $replace_url_attributes = array('a' => 'href', 'area' => 'href', 35 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 36 'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite'); 37 38 function _normalize_tag( $matches ) { 39 return '<' . strtolower( $matches[1] ); 40 } 41 42 function sanitize($data, $type, $base = '') 43 { 44 //Simplified function 45 $data = trim($data); 46 47 // Remappings 48 $data = str_replace('<br>', '<br />', $data); 49 $data = str_replace('<hr>', '<hr />', $data); 50 //<span style="font-weight:bold;">Workshopshed:</span> > <b>Workshopshed:</b> 51 $data = preg_replace('|(<span style="font-weight:bold;">)(?<!<span style="font-weight:bold;">).*(.*)(</span>)|', '<strong>$2</strong>', $data); 52 53 //N.B. Don't strip comments as blogger uses <!--more--> which is the same as Wordpress 54 55 //Now clean up 56 foreach ($this->strip_htmltags as $tag) { 57 $pcre = "/<($tag)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$tag" . 58 SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>|(\/)?>)/siU'; 59 while (preg_match($pcre, $data)) { 60 $data = preg_replace_callback($pcre, array(&$this, 'do_strip_htmltags'), $data); 61 } 62 } 63 64 foreach ($this->strip_attributes as $attrib) { 65 $data = preg_replace('/(<[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*)' . 66 SIMPLEPIE_PCRE_HTML_ATTRIBUTE . trim($attrib) . '(?:\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?' . 67 SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>/', '\1\2\3>', $data); 68 } 69 70 // Replace relative URLs 71 $this->base = $base; 72 foreach ($this->replace_url_attributes as $element => $attributes) { 73 $data = $this->replace_urls($data, $element, $attributes); 74 } 75 76 // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags. 77 if (isset($this->image_handler) && ((string )$this->image_handler) !== '' && $this-> 78 enable_cache) { 79 $images = SimplePie_Misc::get_element('img', $data); 80 foreach ($images as $img) { 81 if (isset($img['attribs']['src']['data'])) { 82 $image_url = call_user_func($this->cache_name_function, $img['attribs']['src']['data']); 83 $cache = call_user_func(array($this->cache_class, 'create'), $this-> 84 cache_location, $image_url, 'spi'); 85 if ($cache->load()) { 86 $img['attribs']['src']['data'] = $this->image_handler . $image_url; 87 $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data); 88 } else { 89 $file = &new $this->file_class($img['attribs']['src']['data'], $this->timeout, 5, 90 array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this-> 91 force_fsockopen); 92 $headers = $file->headers; 93 if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file-> 94 status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) { 95 if ($cache->save(array('headers' => $file->headers, 'body' => $file->body))) { 96 $img['attribs']['src']['data'] = $this->image_handler . $image_url; 97 $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data); 98 } else { 99 trigger_error("$this->cache_location is not writeable", E_USER_WARNING); 100 } 101 } 102 } 103 } 104 } 105 } 106 107 // Having (possibly) taken stuff out, there may now be whitespace at the beginning/end of the data 108 $data = trim($data); 109 110 // Normalise tags 111 $data = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $data); 112 113 return $data; 114 } 115 116 function replace_urls($data, $tag, $attributes) 117 { 118 if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) { 119 $elements = SimplePie_Misc::get_element($tag, $data); 120 foreach ($elements as $element) { 121 if (is_array($attributes)) { 122 foreach ($attributes as $attribute) { 123 if (isset($element['attribs'][$attribute]['data'])) { 124 $element['attribs'][$attribute]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attribute]['data'], 125 $this->base); 126 $new_element = SimplePie_Misc::element_implode($element); 127 $data = str_replace($element['full'], $new_element, $data); 128 $element['full'] = $new_element; 129 } 130 } 131 } elseif (isset($element['attribs'][$attributes]['data'])) { 132 $element['attribs'][$attributes]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attributes]['data'], 133 $this->base); 134 $data = str_replace($element['full'], SimplePie_Misc::element_implode($element), 135 $data); 136 } 137 } 138 } 139 return $data; 140 } 141 142 143 } 144 ?> -
blogger-importer.css
1 2 td { text-align: center; line-height: 2em;} 3 thead td { font-weight: bold; } 4 .bar { 5 width: 200px; 6 text-align: left; 7 line-height: 2em; 8 padding: 0px; 9 } 10 .ind { 11 position: absolute; 12 background-color: #83B4D8; 13 width: 1px; 14 z-index: 9; 15 } 16 .stat { 17 z-index: 10; 18 position: relative; 19 text-align: center; 20 } 21 td.submit { 22 margin:0; 23 padding:0; 24 } 25 26 td { 27 padding-left:10px; 28 padding-right:10px; 29 } -
blogger-importer.php
2 2 /* 3 3 Plugin Name: Blogger Importer 4 4 Plugin URI: http://wordpress.org/extend/plugins/blogger-importer/ 5 Description: Import posts, comments, tags, and attachments from a Blogger blog.5 Description: Imports posts, comments and tags from a Blogger blog then migrates authors to Wordpress users. 6 6 Author: wordpressdotorg 7 7 Author URI: http://wordpress.org/ 8 Version: 0.4 9 Stable tag: 0.4 10 License: GPL version 2 or later - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 8 Version: 0.6 9 License: GPL v2 - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 11 10 */ 12 11 13 12 if ( !defined('WP_LOAD_IMPORTERS') ) … … 16 15 // Load Importer API 17 16 require_once ABSPATH . 'wp-admin/includes/import.php'; 18 17 18 // Load Simple Pie 19 require_once ABSPATH . WPINC . '/class-feed.php'; 20 require_once 'blogger-importer-sanitize.php'; 21 require_once 'blogger-importer-blogitem.php'; 22 23 // Load OAuth library 24 require_once 'oauth.php'; 25 19 26 if ( !class_exists( 'WP_Importer' ) ) { 20 27 $class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php'; 21 28 if ( file_exists( $class_wp_importer ) ) … … 30 37 * @var int 31 38 * @since unknown 32 39 */ 33 define( 'MAX_RESULTS', 50);40 define( 'MAX_RESULTS', 25 ); 34 41 35 42 /** 36 43 * How many seconds to let the script run … … 61 68 if ( class_exists( 'WP_Importer' ) ) { 62 69 class Blogger_Import extends WP_Importer { 63 70 71 function Blogger_Import() { 72 global $importer_started; 73 $importer_started = time(); 74 if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) { 75 add_action('admin_print_scripts', array(&$this, 'queue_scripts')); 76 add_action('admin_print_styles', array(&$this, 'queue_style')); 77 } 78 } 79 80 function queue_scripts($hook) { 81 wp_enqueue_script('jquery'); 82 } 83 84 function queue_style() { 85 wp_enqueue_style('BloggerImporter',plugins_url('/blogger-importer.css', __FILE__)); 86 } 87 64 88 // Shows the welcome screen and the magic auth link. 65 89 function greet() { 66 90 $next_url = get_option('siteurl') . '/wp-admin/index.php?import=blogger&noheader=true'; 67 $auth_url = "https://www.google.com/accounts/AuthSubRequest";91 $auth_url = $this->get_oauth_link(); 68 92 $title = __('Import Blogger', 'blogger-importer'); 69 93 $welcome = __('Howdy! This importer allows you to import posts and comments from your Blogger account into your WordPress site.', 'blogger-importer'); 70 94 $prereqs = __('To use this importer, you must have a Google account and an upgraded (New, was Beta) blog hosted on blogspot.com or a custom domain (not FTP).', 'blogger-importer'); … … 76 100 ".screen_icon()." 77 101 <h2>$title</h2> 78 102 <p>$welcome</p><p>$prereqs</p><p>$stepone</p> 79 <form action=' $auth_url' method='get'>103 <form action='{$auth_url['url']}' method='get'> 80 104 <p class='submit' style='text-align:left;'> 81 105 <input type='submit' class='button' value='$auth' /> 82 <input type='hidden' name='scope' value='http://www.blogger.com/feeds/' /> 83 <input type='hidden' name='session' value='1' /> 84 <input type='hidden' name='secure' value='0' /> 85 <input type='hidden' name='next' value='$next_url' /> 106 <input type='hidden' name='oauth_token' value='{$auth_url['oauth_token']}' /> 107 <input type='hidden' name='oauth_callback' value='{$auth_url['oauth_callback']}' /> 86 108 </p> 87 109 </form> 88 110 </div>\n"; 89 111 } 112 113 function get_oauth_link() { 114 // Establish an Blogger_OAuth consumer 115 $base_url = get_option('siteurl') . '/wp-admin'; 116 $request_token_endpoint = 'https://www.google.com/accounts/OAuthGetRequestToken'; 117 $authorize_endpoint = 'https://www.google.com/accounts/OAuthAuthorizeToken'; 90 118 119 $test_consumer = new Blogger_OAuthConsumer('anonymous', 'anonymous', NULL); // anonymous is a google thing to allow non-registered apps to work 120 121 //prepare to get request token 122 $sig_method = new Blogger_OAuthSignatureMethod_HMAC_SHA1(); 123 $parsed = parse_url($request_token_endpoint); 124 $params = array('callback' => $base_url, 'scope'=>'http://www.blogger.com/feeds/', 'xoauth_displayname'=>'WordPress'); 125 126 $req_req = Blogger_OAuthRequest::from_consumer_and_token($test_consumer, NULL, "GET", $request_token_endpoint, $params); 127 $req_req->sign_request($sig_method, $test_consumer, NULL); 128 129 // go get the request tokens from Google 130 $req_token = wp_remote_retrieve_body(wp_remote_get($req_req->to_url(), array('sslverify'=>false) ) ); 131 132 // parse the tokens 133 parse_str ($req_token,$tokens); 134 135 $oauth_token = $tokens['oauth_token']; 136 $oauth_token_secret = $tokens['oauth_token_secret']; 137 138 $callback_url = "$base_url/index.php?import=blogger&noheader=true&token=$oauth_token&token_secret=$oauth_token_secret"; 139 140 return array('url'=>$authorize_endpoint, 'oauth_token'=>$oauth_token, 'oauth_callback'=>$callback_url ); 141 } 142 91 143 function uh_oh($title, $message, $info) { 92 144 echo "<div class='wrap'>"; 93 145 screen_icon(); … … 95 147 } 96 148 97 149 function auth() { 98 // We have a single-use token that must be upgraded to a session token.99 $token = urldecode( preg_replace( '/[^%-_0-9a-zA-Z]/', '', $_GET['token'] ) );100 $ headers = array(101 "GET /accounts/AuthSubSessionToken HTTP/1.0",102 "Authorization: AuthSub token=\"$token\""103 );104 $request = join( "\r\n", $headers ) . "\r\n\r\n";105 $ sock = $this->_get_auth_sock();106 if ( ! $sock ) return false;107 $ response = $this->_txrx( $sock, $request);108 preg_match( '/token=([%-_0-9a-z]+)/i', $response, $matches);109 if ( empty( $matches[1] ) ) {110 $this->uh_oh(111 __( 'Authorization failed' , 'blogger-importer'),112 __( 'Something went wrong. If the problem persists, send this info to support:' , 'blogger-importer'),113 htmlspecialchars($response)114 115 return false;116 }117 $this->token = urldecode( $matches[1] );118 150 // we have a authorized request token now, so upgrade it to an access token 151 $token = $_GET['token']; 152 $token_secret = $_GET['token_secret']; 153 154 $oauth_access_token_endpoint = 'https://www.google.com/accounts/OAuthGetAccessToken'; 155 156 // auth the token 157 $test_consumer = new Blogger_OAuthConsumer('anonymous', 'anonymous', NULL); 158 $auth_token = new Blogger_OAuthConsumer($token, $token_secret); 159 $access_token_req = new Blogger_OAuthRequest("GET", $oauth_access_token_endpoint); 160 $access_token_req = $access_token_req->from_consumer_and_token($test_consumer, $auth_token, "GET", $oauth_access_token_endpoint); 161 162 $access_token_req->sign_request(new Blogger_OAuthSignatureMethod_HMAC_SHA1(),$test_consumer, $auth_token); 163 164 $after_access_request = wp_remote_retrieve_body(wp_remote_get($access_token_req->to_url(), array('sslverify'=>false) ) ); 165 166 parse_str($after_access_request,$access_tokens); 167 168 $this->token = $access_tokens['oauth_token']; 169 $this->token_secret = $access_tokens['oauth_token_secret']; 170 119 171 wp_redirect( remove_query_arg( array( 'token', 'noheader' ) ) ); 120 172 } 173 174 // get a URL using the oauth token for authentication (returns false on failure) 175 function oauth_get($url, $params=NULL) { 176 $test_consumer = new Blogger_OAuthConsumer('anonymous', 'anonymous', NULL); 177 $goog = new Blogger_OAuthConsumer($this->token, $this->token_secret, NULL); 178 $request = new Blogger_OAuthRequest("GET", $url, $params); 121 179 122 function get_token_info() { 123 $headers = array( 124 "GET /accounts/AuthSubTokenInfo HTTP/1.0", 125 "Authorization: AuthSub token=\"$this->token\"" 126 ); 127 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 128 $sock = $this->_get_auth_sock( ); 129 if ( ! $sock ) return; 130 $response = $this->_txrx( $sock, $request ); 131 return $this->parse_response($response); 132 } 180 //Ref: Not importing properly http://core.trac.wordpress.org/ticket/19096 181 $blog_req = $request->from_consumer_and_token($test_consumer, $goog, 'GET', $url,$params); 133 182 134 function token_is_valid() { 135 $info = $this->get_token_info(); 183 $blog_req->sign_request(new Blogger_OAuthSignatureMethod_HMAC_SHA1(),$test_consumer,$goog); 136 184 137 if ( $info['code'] == 200 ) 138 return true; 139 140 return false; 185 $data = wp_remote_get($blog_req->to_url(), array('sslverify'=>false) ); 186 187 if ( wp_remote_retrieve_response_code( $data ) == 200 ) { 188 $response = wp_remote_retrieve_body( $data ); 189 } else { 190 $response = false; 191 } 192 193 return $response; 141 194 } 142 195 143 196 function show_blogs($iter = 0) { 144 197 if ( empty($this->blogs) ) { 145 $headers = array( 146 "GET /feeds/default/blogs HTTP/1.0", 147 "Host: www.blogger.com", 148 "Authorization: AuthSub token=\"$this->token\"" 149 ); 150 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 151 $sock = $this->_get_blogger_sock( ); 152 if ( ! $sock ) return; 153 $response = $this->_txrx( $sock, $request ); 198 $xml = $this->oauth_get('https://www.blogger.com/feeds/default/blogs'); 154 199 155 // Quick and dirty XML mining.156 list( $headers, $xml ) = explode( "\r\n\r\n", $response );157 $p = xml_parser_create();158 xml_parse_into_struct($p, $xml, $vals, $index);159 xml_parser_free($p);160 161 $this->title = $vals[$index['TITLE'][0]]['value'];162 163 200 // Give it a few retries... this step often flakes out the first time. 164 if ( empty( $ index['ENTRY']) ) {201 if ( empty( $xml ) ) { 165 202 if ( $iter < 3 ) { 166 203 return $this->show_blogs($iter + 1); 167 204 } else { … … 173 210 return false; 174 211 } 175 212 } 176 177 foreach ( $index['ENTRY'] as $i ) { 178 $blog = array(); 179 while ( ( $tag = $vals[$i] ) && ! ( $tag['tag'] == 'ENTRY' && $tag['type'] == 'close' ) ) { 180 if ( $tag['tag'] == 'TITLE' ) { 181 $blog['title'] = $tag['value']; 182 } elseif ( $tag['tag'] == 'SUMMARY' ) { 183 $blog['summary'] = $tag['value']; 184 } elseif ( $tag['tag'] == 'LINK' ) { 185 if ( $tag['attributes']['REL'] == 'alternate' && $tag['attributes']['TYPE'] == 'text/html' ) { 186 $parts = parse_url( $tag['attributes']['HREF'] ); 187 $blog['host'] = $parts['host']; 188 } elseif ( $tag['attributes']['REL'] == 'edit' ) { 189 $blog['gateway'] = $tag['attributes']['HREF']; 190 } elseif ( $tag['attributes']['REL'] == 'http://schemas.google.com/g/2005#post' ) { 191 $parts = parse_url( $tag['attributes']['HREF'] ); 192 $blog['posts_host'] = $parts['host']; 193 $blog['posts_path'] = $parts['path']; 194 } 195 } 196 ++$i; 197 } 213 214 $feed = new SimplePie(); 215 $feed->set_raw_data($xml); 216 $feed->init(); 217 218 foreach ($feed->get_items() as $item) { 219 $blog = array(); //reset 220 $blog['title'] = $item->get_title(); 221 $blog['summary'] = $item->get_description(); 222 223 //ID is of the form tag:blogger.com,1999:blog-417730729915399755 224 //We need that number from the end 225 $rawid = explode('-',$item->get_id()); 226 $blog['id'] = $rawid[count($rawid)-1]; 227 228 $parts = parse_url( $item->get_link( 0, 'alternate' ) ); 229 $blog['host'] = $parts['host']; 230 $blog['gateway'] = $item->get_link( 0, 'edit' ); 231 $blog['posts_url'] = $item->get_link( 0, 'http://schemas.google.com/g/2005#post' ); 232 233 //AGC:20/4/2012 Developers guide suggests that the correct feed is located as follows 234 //See https://developers.google.com/blogger/docs/1.0/developers_guide_php 235 $blog['comments_url'] = "http://www.blogger.com/feeds/{$blog['id']}/comments/default"; 236 198 237 if ( ! empty ( $blog ) ) { 199 $blog['total_posts'] = $this->get_total_results('posts', $blog['host']); 200 $blog['total_comments'] = $this->get_total_results('comments', $blog['host']); 238 $blog['total_posts'] = $this->get_total_results( $blog['posts_url'] ); 239 $blog['total_comments'] = $this->get_total_results($blog['comments_url']); 240 201 241 $blog['mode'] = 'init'; 202 242 $this->blogs[] = $blog; 203 243 } 244 204 245 } 205 246 206 247 if ( empty( $this->blogs ) ) { 207 248 $this->uh_oh( 208 249 __('No blogs found', 'blogger-importer'), … … 210 251 '' 211 252 ); 212 253 return false; 213 } 254 } 214 255 } 256 257 //Should probably be using WP_LIST_TABLE here rather than manually rendering a table in html 258 //http://wpengineer.com/2426/wp_list_table-a-step-by-step-guide/ 215 259 //echo '<pre>'.print_r($this,1).'</pre>'; 216 260 $start = esc_js( __('Import', 'blogger-importer') ); 217 261 $continue = esc_js( __('Continue', 'blogger-importer') ); … … 230 274 $noscript = __('This feature requires Javascript but it seems to be disabled. Please enable Javascript and then reload this page. Don’t worry, you can turn it back off when you’re done.', 'blogger-importer'); 231 275 232 276 $interval = STATUS_INTERVAL * 1000; 277 $init = ''; 278 $rows = ''; 233 279 234 280 foreach ( $this->blogs as $i => $blog ) { 235 281 if ( $blog['mode'] == 'init' ) … … 242 288 $blogtitle = esc_js( $blog['title'] ); 243 289 $pdone = isset($blog['posts_done']) ? (int) $blog['posts_done'] : 0; 244 290 $cdone = isset($blog['comments_done']) ? (int) $blog['comments_done'] : 0; 245 $init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}', " . $this->get_js_status($i) .');';291 $init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}','" . $this->get_js_status($i) . '\');'; 246 292 $pstat = "<div class='ind' id='pind$i'> </div><div id='pstat$i' class='stat'>$pdone/{$blog['total_posts']}</div>"; 247 293 $cstat = "<div class='ind' id='cind$i'> </div><div id='cstat$i' class='stat'>$cdone/{$blog['total_comments']}</div>"; 248 294 $rows .= "<tr id='blog$i'><td class='blogtitle'>$blogtitle</td><td class='bloghost'>{$blog['host']}</td><td class='bar'>$pstat</td><td class='bar'>$cstat</td><td class='submit'><input type='submit' class='button' id='submit$i' value='$value' /><input type='hidden' name='blog' value='$i' /></td></tr>\n"; … … 258 304 this.blog = i; 259 305 this.mode = mode; 260 306 this.title = title; 261 this.status = status;307 eval('this.status='+status); 262 308 this.button = document.getElementById('submit'+this.blog); 263 309 }; 264 310 blog.prototype = { … … 311 357 }, 312 358 update: function() { 313 359 jQuery('#pind'+this.blog).width(((this.status.p1>0&&this.status.p2>0)?(this.status.p1/this.status.p2*jQuery('#pind'+this.blog).parent().width()):1)+'px'); 360 jQuery('#pstat'+this.blog).attr('title', 'Posts skipped '+this.status.p3); 314 361 jQuery('#cind'+this.blog).width(((this.status.c1>0&&this.status.c2>0)?(this.status.c1/this.status.c2*jQuery('#cind'+this.blog).parent().width()):1)+'px'); 362 jQuery('#cstat'+this.blog).attr('title', 'Comments skipped '+this.status.c3); 363 315 364 }, 316 365 stop: function() { 317 366 this.cont = false; … … 372 421 function have_time() { 373 422 global $importer_started; 374 423 if ( time() - $importer_started > MAX_EXECUTION_TIME ) 375 die('continue');424 self::ajax_die('continue'); 376 425 return true; 377 426 } 378 379 function get_total_results($type, $host) { 380 $headers = array( 381 "GET /feeds/$type/default?max-results=1&start-index=2 HTTP/1.0", 382 "Host: $host", 383 "Authorization: AuthSub token=\"$this->token\"" 384 ); 385 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 386 $sock = $this->_get_blogger_sock( $host ); 387 if ( ! $sock ) return; 388 $response = $this->_txrx( $sock, $request ); 389 $response = $this->parse_response( $response ); 390 $parser = xml_parser_create(); 391 xml_parse_into_struct($parser, $response['body'], $struct, $index); 392 xml_parser_free($parser); 393 $total_results = $struct[$index['OPENSEARCH:TOTALRESULTS'][0]]['value']; 394 return (int) $total_results; 427 428 function get_total_results($url) { 429 $response = $this->oauth_get( $url, array('max-results'=>1, 'start-index'=>2) ); 430 431 $feed = new SimplePie(); 432 $feed->set_raw_data($response); 433 $feed->init(); 434 $results = $feed->get_channel_tags('http://a9.com/-/spec/opensearchrss/1.0/', 'totalResults'); 435 436 $total_results = $results[0]['data']; 437 unset($feed); 438 return (int) $total_results; 395 439 } 396 440 397 441 function import_blog($blogID) { 398 global $importing_blog;442 global $importing_blog; 399 443 $importing_blog = $blogID; 400 444 401 445 if ( isset($_GET['authors']) ) 402 446 return print($this->get_author_form()); 403 447 404 header('Content-Type: text/plain');405 406 448 if ( isset($_GET['status']) ) 407 die($this->get_js_status());449 self::ajax_die($this->get_js_status()); 408 450 409 451 if ( isset($_GET['saveauthors']) ) 410 die($this->save_authors());452 self::ajax_die($this->save_authors()); 411 453 412 $blog = $this->blogs[$blogID]; 413 $total_results = $this->get_total_results('posts', $blog['host']); 414 $this->blogs[$importing_blog]['total_posts'] = $total_results; 415 416 $start_index = $total_results - MAX_RESULTS + 1; 417 454 //Simpler counting for posts as we load them forwards 418 455 if ( isset( $this->blogs[$importing_blog]['posts_start_index'] ) ) 419 456 $start_index = (int) $this->blogs[$importing_blog]['posts_start_index']; 420 elseif ( $total_results > MAX_RESULTS )421 $start_index = $total_results - MAX_RESULTS + 1;422 457 else 423 458 $start_index = 1; 424 459 … … 426 461 if ( $start_index > 0 ) { 427 462 // Grab all the posts 428 463 $this->blogs[$importing_blog]['mode'] = 'posts'; 429 $query = "start-index=$start_index&max-results=" . MAX_RESULTS;430 464 do { 465 431 466 $index = $struct = $entries = array(); 432 $headers = array(433 "GET {$blog['posts_path']}?$query HTTP/1.0",434 "Host: {$blog['posts_host']}",435 "Authorization: AuthSub token=\"$this->token\""436 );437 $request = join( "\r\n", $headers ) . "\r\n\r\n";438 $sock = $this->_get_blogger_sock( $blog['posts_host'] );439 if ( ! $sock ) return; // TODO: Error handling440 $response = $this->_txrx( $sock, $request );441 467 442 $ response = $this->parse_response( $response );468 $url = $this->blogs[$importing_blog]['posts_url']; 443 469 444 // Extract the entries and send for insertion 445 preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches ); 446 if ( count( $matches[0] ) ) { 447 $entries = array_reverse($matches[0]); 448 foreach ( $entries as $entry ) { 449 $entry = "<feed>$entry</feed>"; 450 $AtomParser = new AtomParser(); 451 $AtomParser->parse( $entry ); 452 $result = $this->import_post($AtomParser->entry); 453 if ( is_wp_error( $result ) ) 454 return $result; 455 unset($AtomParser); 470 $response = $this->oauth_get( $url, array('max-results'=>MAX_RESULTS, 'start-index'=>$start_index) ); 471 472 if ($response == false) break; 473 474 // parse the feed 475 $feed = new SimplePie(); 476 $feed->set_item_class('WP_SimplePie_Blog_Item'); 477 $feed->set_sanitize_class('Blogger_Importer_Sanitize'); 478 $feed->set_raw_data($response); 479 $feed->init(); 480 481 foreach ( $feed->get_items() as $item ) { 482 483 $blogentry = new BloggerEntry(); 484 485 $blogentry->id = $item->get_id(); 486 $blogentry->published = $item->get_published(); 487 $blogentry->updated = $item->get_updated(); 488 $blogentry->isDraft = $item->get_draft_status($item); 489 $blogentry->title = $item->get_title(); 490 $blogentry->content = $item->get_content(); 491 $blogentry->author = $item->get_author()->get_name(); 492 493 $linktypes = array('replies','edit','self','alternate'); 494 foreach ($linktypes as $type) { 495 $links = $item->get_links($type); 496 497 if (!is_null($links)) { 498 foreach ($links as $link) { 499 $blogentry->links[] = array( 'rel' => $type, 'href' => $link ); 500 } 501 } 456 502 } 457 } else break; 458 459 // Get the 'previous' query string which we'll use on the next iteration 460 $query = ''; 461 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches); 462 if ( count( $matches[1] ) ) 463 foreach ( $matches[1] as $match ) 464 if ( preg_match('/rel=.previous./', $match) ) 465 $query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') ); 466 467 if ( $query ) { 468 parse_str($query, $q); 469 $this->blogs[$importing_blog]['posts_start_index'] = (int) $q['start-index']; 470 } else 471 $this->blogs[$importing_blog]['posts_start_index'] = 0; 503 504 $cats = $item->get_categories(); 505 506 if (!is_null($cats)) { 507 foreach ($cats as $cat) { 508 $blogentry->categories[] = $cat->term; 509 } 510 } 511 512 $result = $this->import_post($blogentry); 513 514 //Ref: Not importing properly http://core.trac.wordpress.org/ticket/19096 515 //Simplified this section to count what is loaded rather than parsing the results again 516 $start_index++; 517 } 518 519 $this->blogs[$importing_blog]['posts_start_index'] = $start_index; 520 472 521 $this->save_vars(); 473 } while ( !empty( $query ) && $this->have_time() ); 522 523 } while ( $this->blogs[$importing_blog]['total_posts'] >$start_index && $this->have_time() ); //have time function will "die" if it's out of time 474 524 } 475 525 476 $total_results = $this->get_total_results( 'comments', $blog['host'] ); 477 $this->blogs[$importing_blog]['total_comments'] = $total_results; 478 479 if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) ) 526 527 if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) ) 480 528 $start_index = (int) $this->blogs[$importing_blog]['comments_start_index']; 481 elseif ( $total_results > MAX_RESULTS ) 482 $start_index = $total_results - MAX_RESULTS + 1; 483 else 484 $start_index = 1;529 else 530 $start_index = 1; 531 532 if ( $start_index > 0 && $this->blogs[$importing_blog]['total_comments'] > 0) { 485 533 486 if ( $start_index > 0 ) {487 // Grab all the comments488 534 $this->blogs[$importing_blog]['mode'] = 'comments'; 489 $query = "start-index=$start_index&max-results=" . MAX_RESULTS;490 535 do { 491 536 $index = $struct = $entries = array(); 492 $headers = array(493 "GET /feeds/comments/default?$query HTTP/1.0",494 "Host: {$blog['host']}",495 "Authorization: AuthSub token=\"$this->token\""496 );497 $request = join( "\r\n", $headers ) . "\r\n\r\n";498 $sock = $this->_get_blogger_sock( $blog['host'] );499 if ( ! $sock ) return; // TODO: Error handling500 $response = $this->_txrx( $sock, $request );501 537 502 $response = $this->parse_response( $response ); 503 504 // Extract the comments and send for insertion 505 preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches ); 506 if ( count( $matches[0] ) ) { 507 $entries = array_reverse( $matches[0] ); 508 foreach ( $entries as $entry ) { 509 $entry = "<feed>$entry</feed>"; 510 $AtomParser = new AtomParser(); 511 $AtomParser->parse( $entry ); 512 $this->import_comment($AtomParser->entry); 513 unset($AtomParser); 514 } 515 } 516 517 // Get the 'previous' query string which we'll use on the next iteration 518 $query = ''; 519 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches); 520 if ( count( $matches[1] ) ) 521 foreach ( $matches[1] as $match ) 522 if ( preg_match('/rel=.previous./', $match) ) 523 $query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') ); 524 525 parse_str($query, $q); 526 527 $this->blogs[$importing_blog]['comments_start_index'] = (int) $q['start-index']; 538 //So we can link up the comments as we go we need to load them in reverse order 539 //Reverse the start index as the GData Blogger feed can't be sorted 540 $batch = ((floor(($this->blogs[$importing_blog]['total_comments'] - $start_index)/MAX_RESULTS)*MAX_RESULTS)+1); 541 542 $response = $this->oauth_get( $this->blogs[$importing_blog]['comments_url'], array('max-results'=>MAX_RESULTS, 'start-index'=>$batch) ); 543 544 // parse the feed 545 $feed = new SimplePie(); 546 $feed->set_item_class('WP_SimplePie_Blog_Item'); 547 // Use the standard "stricter" sanitize class for comments 548 $feed->set_raw_data($response); 549 $feed->init(); 550 551 //Reverse the batch so we load the oldest comments first and hence can link up nested comments 552 $comments = array_reverse($feed->get_items()); 553 554 if (!is_null($comments)) { 555 foreach ($comments as $item ) { 556 557 $blogentry = new BloggerEntry(); 558 $blogentry->id = $item->get_id(); 559 $blogentry->updated = $item->get_updated(); 560 $blogentry->content = $item->get_content(); 561 $blogentry->author = $item->get_author()->get_name(); 562 $blogentry->authoruri = $item->get_author()->get_link(); 563 $blogentry->authoremail = $item->get_author()->get_email(); 564 565 $temp = $item->get_item_tags('http://purl.org/syndication/thread/1.0','in-reply-to'); 566 567 foreach ($temp as $t) { 568 if ( isset( $t['attribs']['']['source'] ) ) { 569 $blogentry->source = $t['attribs']['']['source']; 570 } 571 } 572 573 //Get the links 574 $linktypes = array('edit','self','alternate','related'); 575 foreach ($linktypes as $type) { 576 $links = $item->get_links($type); 577 if (!is_null($links)) { 578 foreach ($links as $link) { 579 $blogentry->links[] = array( 'rel' => $type, 'href' => $link ); 580 } 581 } 582 } 583 584 $this->import_comment($blogentry); 585 $start_index++; 586 } 587 } 588 589 $this->blogs[$importing_blog]['comments_start_index'] = $start_index; 528 590 $this->save_vars(); 529 } while ( !empty( $query )&& $this->have_time() );591 } while ( $this->blogs[$importing_blog]['total_comments'] > $start_index && $this->have_time() ); 530 592 } 593 531 594 $this->blogs[$importing_blog]['mode'] = 'authors'; 532 595 $this->save_vars(); 596 533 597 if ( !$this->blogs[$importing_blog]['posts_done'] && !$this->blogs[$importing_blog]['comments_done'] ) 534 die('nothing'); 598 self::ajax_die('nothing'); 599 535 600 do_action('import_done', 'blogger'); 536 die('done');601 self::ajax_die('done'); 537 602 } 538 603 539 function convert_date( $date ) {540 preg_match('#([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.[0-9]+)?(Z|[\+|\-][0-9]{2,4}){0,1}#', $date, $date_bits);541 $offset = iso8601_timezone_to_offset( $date_bits[7] );542 $timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]);543 $timestamp -= $offset; // Convert from Blogger local time to GMT544 $timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time545 return gmdate('Y-m-d H:i:s', $timestamp);546 }547 548 604 function no_apos( $string ) { 549 605 return str_replace( ''', "'", $string); 550 606 } … … 559 615 560 616 function import_post( $entry ) { 561 617 global $importing_blog; 562 563 // The old permalink is all Blogger gives us to link comments to their posts. 564 if ( isset( $entry->draft ) ) 565 $rel = 'self'; 566 else 567 $rel = 'alternate'; 568 foreach ( $entry->links as $link ) { 569 if ( $link['rel'] == $rel ) { 618 619 foreach ( $entry->links as $link ) { 620 // save the self link as meta 621 if ( $link['rel'] == 'self' ) { 622 $postself = $link['href']; 570 623 $parts = parse_url( $link['href'] ); 571 624 $entry->old_permalink = $parts['path']; 572 break;573 625 } 626 627 // get the old URI for the page when available 628 if ( $link['rel'] == 'alternate' ) { 629 $parts = parse_url( $link['href'] ); 630 $entry->bookmark = $parts['path']; 631 } 632 633 // save the replies feed link as meta (ignore the comment form one) 634 if ( $link['rel'] == 'replies' && false === strpos($link['href'], '#comment-form') ) { 635 $postreplies = $link['href']; 636 } 574 637 } 575 576 $post_date = $this->convert_date( $entry->published ); 638 639 //Check if we are double cleaning here? Does the Simplepie already do all this? 640 $post_date = $entry->published; 577 641 $post_content = trim( addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) ) ); 578 642 $post_title = trim( addslashes( $this->no_apos( $this->min_whitespace( $entry->title ) ) ) ); 579 $post_status = isset( $entry->draft ) ? 'draft' : 'publish'; 643 644 $post_status = $entry->isDraft ? 'draft' : 'publish'; 580 645 581 // Clean up content 582 $post_content = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_content); 583 $post_content = str_replace('<br>', '<br />', $post_content); 584 $post_content = str_replace('<hr>', '<hr />', $post_content); 646 // N.B. Clean up of $post_content is now part of the sanitize class 585 647 586 648 // Checks for duplicates 587 649 if ( isset( $this->blogs[$importing_blog]['posts'][$entry->old_permalink] ) ) { 588 ++$this->blogs[$importing_blog]['posts_skipped'];650 $this->blogs[$importing_blog]['posts_skipped']++; 589 651 } elseif ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) { 590 652 $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id; 591 ++$this->blogs[$importing_blog]['posts_skipped'];653 $this->blogs[$importing_blog]['posts_skipped']++; 592 654 } else { 593 655 $post = compact('post_date', 'post_content', 'post_title', 'post_status'); 594 656 … … 602 664 603 665 add_post_meta( $post_id, 'blogger_blog', $this->blogs[$importing_blog]['host'], true ); 604 666 add_post_meta( $post_id, 'blogger_author', $author, true ); 605 add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true ); 667 668 //Use the page id if available or the blogger internal id if it's a draft 669 if ( $entry->isDraft | !isset($entry->bookmark) ) 670 add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true ); 671 else 672 add_post_meta( $post_id, 'blogger_permalink', $entry->bookmark, true ); 673 674 add_post_meta( $post_id, '_blogger_self', $postself, true ); 675 //Undefined variable: postreplies 676 //add_post_meta( $post_id, '_blogger_replies', $postreplies, true ); 606 677 607 678 $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id; 608 ++$this->blogs[$importing_blog]['posts_done']; 679 680 $this->blogs[$importing_blog]['posts_done']++; 609 681 } 610 682 $this->save_vars(); 611 683 return; … … 613 685 614 686 function import_comment( $entry ) { 615 687 global $importing_blog; 616 688 689 $parts = parse_url( $entry->source ); 690 $entry->old_post_permalink = $parts['path']; //Will be something like this '/feeds/417730729915399755/posts/default/8397846992898424746' 691 617 692 // Drop the #fragment and we have the comment's old post permalink. 618 693 foreach ( $entry->links as $link ) { 619 694 if ( $link['rel'] == 'alternate' ) { 620 695 $parts = parse_url( $link['href'] ); 621 696 $entry->old_permalink = $parts['fragment']; 622 $entry->old_post_permalink = $parts['path'];623 break;624 697 } 698 //Parent post for nested links 699 if ( $link['rel'] == 'related' ) { 700 $parts = parse_url( $link['href'] ); 701 $entry->related = $parts['path']; 702 } 703 if ( $link['rel'] == 'self' ) { 704 $parts = parse_url( $link['href'] ); 705 $entry->self = $parts['path']; 706 } 625 707 } 708 709 //Check for duplicated cleanup here 710 $comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink]; 711 $comment_author = addslashes( $this->no_apos( strip_tags( $entry->author ) ) ); 712 $comment_author_url = addslashes( $this->no_apos( strip_tags( $entry->authoruri ) ) ); 713 $comment_author_email = addslashes( $this->no_apos( strip_tags( $entry->authoremail ) ) ); 714 $comment_date = $entry->updated; 626 715 627 $comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink]; 628 preg_match('#<name>(.+?)</name>.*(?:\<uri>(.+?)</uri>)?#', $entry->author, $matches); 629 $comment_author = addslashes( $this->no_apos( strip_tags( (string) $matches[1] ) ) ); 630 $comment_author_url = addslashes( $this->no_apos( strip_tags( (string) $matches[2] ) ) ); 631 $comment_date = $this->convert_date( $entry->updated ); 716 // Clean up content 717 // Again, check if the Simplepie is already handling all this stuff 632 718 $comment_content = addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) ); 633 634 // Clean up content635 719 $comment_content = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $comment_content); 636 720 $comment_content = str_replace('<br>', '<br />', $comment_content); 637 721 $comment_content = str_replace('<hr>', '<hr />', $comment_content); 638 639 // Checks for duplicates 640 if ( 641 isset( $this->blogs[$importing_blog]['comments'][$entry->old_permalink] ) || 642 comment_exists( $comment_author, $comment_date ) 643 ) { 644 ++$this->blogs[$importing_blog]['comments_skipped']; 645 } else { 646 $comment = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_date', 'comment_content'); 647 648 $comment = wp_filter_comment($comment); 649 $comment_id = wp_insert_comment($comment); 650 651 $this->blogs[$importing_blog]['comments'][$entry->old_permalink] = $comment_id; 652 653 ++$this->blogs[$importing_blog]['comments_done']; 654 } 722 723 // Nested comment? 724 if (!is_null($entry->related)) { 725 $comment_parent = $this->blogs[$importing_blog]['comments'][$entry->related]; 726 } 727 728 // if the post does not exist then we need stop and not add the comment 729 if ($comment_post_ID != 0) { 730 // Checks for duplicates 731 if ( 732 isset( $this->blogs[$importing_blog][$entry->id] ) || 733 $this->comment_exists($comment_post_ID, $comment_author, $comment_date ) 734 ) { 735 $this->blogs[$importing_blog]['comments_skipped']++; 736 } else { 737 $comment = compact('comment_post_ID', 'comment_author', 'comment_author_url','comment_author_email', 'comment_date', 'comment_content','comment_parent'); 738 739 $comment = wp_filter_comment($comment); 740 $comment_id = wp_insert_comment($comment); 741 742 $this->blogs[$importing_blog]['comments'][$entry->id] = $comment_id; 743 $this->blogs[$importing_blog]['comments'][$entry->self] = $comment_id; //For nested comments 744 745 $this->blogs[$importing_blog]['comments_done']++; 746 } 747 } 748 else { 749 $this->blogs[$importing_blog]['comments_skipped']++; 750 } 655 751 $this->save_vars(); 656 752 } 753 754 function ajax_die($data){ 755 ob_clean(); //Discard any debug messages or other fluff already sent 756 header('Content-Type: text/plain'); 757 die($data); 758 } 657 759 760 658 761 function get_js_status($blog = false) { 659 762 global $importing_blog; 660 763 if ( $blog === false ) 661 764 $blog = $this->blogs[$importing_blog]; 662 765 else 663 766 $blog = $this->blogs[$blog]; 767 664 768 $p1 = isset( $blog['posts_done'] ) ? (int) $blog['posts_done'] : 0; 665 769 $p2 = isset( $blog['total_posts'] ) ? (int) $blog['total_posts'] : 0; 770 $p3 = isset( $blog['posts_skipped'] ) ? (int) $blog['posts_skipped'] : 0; 666 771 $c1 = isset( $blog['comments_done'] ) ? (int) $blog['comments_done'] : 0; 667 772 $c2 = isset( $blog['total_comments'] ) ? (int) $blog['total_comments'] : 0; 668 return "{p1:$p1,p2:$p2,c1:$c1,c2:$c2}"; 773 $c3 = isset( $blog['comments_skipped'] ) ? (int) $blog['comments_skipped'] : 0; 774 return "{p1:$p1,p2:$p2,p3:$p3,c1:$c1,c2:$c2,c3:$c3}"; 669 775 } 670 776 671 777 function get_author_form($blog = false) { … … 688 794 $mapthis = __('Blogger username', 'blogger-importer'); 689 795 $tothis = __('WordPress login', 'blogger-importer'); 690 796 $submit = esc_js( __('Save Changes', 'blogger-importer') ); 797 $rows = ''; 691 798 692 799 foreach ( $blog['authors'] as $i => $author ) 693 800 $rows .= "<tr><td><label for='authors[$i]'>{$author[0]}</label></td><td><select name='authors[$i]' id='authors[$i]'>" . $this->get_user_options($author[1]) . "</select></td></tr>"; … … 698 805 function get_user_options($current) { 699 806 global $importer_users; 700 807 if ( ! isset( $importer_users ) ) 701 $importer_users = (array) get_users_of_blog(); 808 $importer_users = (array) get_users(); //Function: get_users_of_blog() Deprecated in version 3.1. Use get_users() instead. 702 809 810 $options = ''; 811 703 812 foreach ( $importer_users as $user ) { 704 $sel = ( $user-> user_id== $current ) ? " selected='selected'" : '';705 $options .= "<option value='$user-> user_id'$sel>$user->display_name</option>";813 $sel = ( $user->ID == $current ) ? " selected='selected'" : ''; 814 $options .= "<option value='$user->ID'$sel>$user->display_name</option>"; 706 815 } 707 816 708 817 return $options; … … 710 819 711 820 function save_authors() { 712 821 global $importing_blog, $wpdb; 822 $blog =& $this->blogs[$importing_blog]; //Get a reference to blogs so we don't have to write it longhand 823 713 824 $authors = (array) $_POST['authors']; 714 825 715 $host = $ this->blogs[$importing_blog]['host'];826 $host = $blog['host']; 716 827 717 828 // Get an array of posts => authors 718 829 $post_ids = (array) $wpdb->get_col( $wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'blogger_blog' AND meta_value = %s", $host) ); … … 725 836 $user_id = (int) $user_id; 726 837 727 838 // Skip authors that haven't been changed 728 if ( $user_id == $ this->blogs[$importing_blog]['authors'][$author][1] )839 if ( $user_id == $blog['authors'][$author][1] ) 729 840 continue; 730 841 731 842 // Get a list of the selected author's posts 732 $post_ids = (array) array_keys( $authors_posts, $ this->blogs[$importing_blog]['authors'][$author][0] );843 $post_ids = (array) array_keys( $authors_posts, $blog['authors'][$author][0] ); 733 844 $post_ids = join( ',', $post_ids); 734 845 735 846 $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET post_author = %d WHERE id IN ($post_ids)", $user_id) ); 736 $ this->blogs[$importing_blog]['authors'][$author][1] = $user_id;847 $blog['authors'][$author][1] = $user_id; 737 848 } 738 849 $this->save_vars(); 739 850 740 851 wp_redirect('edit.php'); 741 852 } 742 853 743 function _get_auth_sock() {744 // Connect to https://www.google.com745 if ( !$sock = @ fsockopen('ssl://www.google.com', 443, $errno, $errstr) ) {746 $this->uh_oh(747 __('Could not connect to https://www.google.com', 'blogger-importer'),748 __('There was a problem opening a secure connection to Google. This is what went wrong:', 'blogger-importer'),749 "$errstr ($errno)"750 );751 return false;752 }753 return $sock;754 }755 756 function _get_blogger_sock($host = 'www2.blogger.com') {757 if ( !$sock = @ fsockopen($host, 80, $errno, $errstr) ) {758 $this->uh_oh(759 sprintf( __('Could not connect to %s', 'blogger-importer'), $host ),760 __('There was a problem opening a connection to Blogger. This is what went wrong:', 'blogger-importer'),761 "$errstr ($errno)"762 );763 return false;764 }765 return $sock;766 }767 768 function _txrx( $sock, $request ) {769 fwrite( $sock, $request );770 while ( ! feof( $sock ) )771 $response .= @ fread ( $sock, 8192 );772 fclose( $sock );773 return $response;774 }775 776 function revoke($token) {777 $headers = array(778 "GET /accounts/AuthSubRevokeToken HTTP/1.0",779 "Authorization: AuthSub token=\"$token\""780 );781 $request = join( "\r\n", $headers ) . "\r\n\r\n";782 $sock = $this->_get_auth_sock( );783 if ( ! $sock ) return false;784 $this->_txrx( $sock, $request );785 }786 787 854 function restart() { 788 855 global $wpdb; 789 856 $options = get_option( 'blogger_importer' ); 790 857 791 if ( isset( $options['token'] ) )792 $this->revoke( $options['token'] );793 794 858 delete_option('blogger_importer'); 795 859 $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'blogger_author'"); 796 860 wp_redirect('?import=blogger'); 797 861 } 798 862 799 // Returns associative array of code, header, cookies, body. Based on code from php.net.800 function parse_response($this_response) {801 // Split response into header and body sections802 list($response_headers, $response_body) = explode("\r\n\r\n", $this_response, 2);803 $response_header_lines = explode("\r\n", $response_headers);804 805 // First line of headers is the HTTP response code806 $http_response_line = array_shift($response_header_lines);807 if (preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@',$http_response_line, $matches)) { $response_code = $matches[1]; }808 809 // put the rest of the headers in an array810 $response_header_array = array();811 foreach($response_header_lines as $header_line) {812 list($header,$value) = explode(': ', $header_line, 2);813 $response_header_array[$header] .= $value."\n";814 }815 816 $cookie_array = array();817 $cookies = explode("\n", $response_header_array["Set-Cookie"]);818 foreach($cookies as $this_cookie) { array_push($cookie_array, "Cookie: ".$this_cookie); }819 820 return array("code" => $response_code, "header" => $response_header_array, "cookies" => $cookie_array, "body" => $response_body);821 }822 823 863 // Step 9: Congratulate the user 824 864 function congrats() { 825 865 $blog = (int) $_GET['blog']; … … 836 876 function start() { 837 877 if ( isset($_POST['restart']) ) 838 878 $this->restart(); 839 879 840 880 $options = get_option('blogger_importer'); 841 881 842 882 if ( is_array($options) ) … … 849 889 $result = $this->import_blog( $blog ); 850 890 if ( is_wp_error( $result ) ) 851 891 echo $result->get_error_message(); 852 } elseif ( isset($_GET['token']) )892 } elseif ( isset($_GET['token']) && isset($_GET['token_secret']) ) 853 893 $this->auth(); 854 elseif ( isset($this->token) && $this->token_is_valid() )894 elseif ( isset($this->token) && isset($this->token_secret) ) 855 895 $this->show_blogs(); 856 896 else 857 897 $this->greet(); … … 873 913 return !empty($vars); 874 914 } 875 915 876 function admin_head() { 877 ?> 878 <style type="text/css"> 879 td { text-align: center; line-height: 2em;} 880 thead td { font-weight: bold; } 881 .bar { 882 width: 200px; 883 text-align: left; 884 line-height: 2em; 885 padding: 0px; 886 } 887 .ind { 888 position: absolute; 889 background-color: #83B4D8; 890 width: 1px; 891 z-index: 9; 892 } 893 .stat { 894 z-index: 10; 895 position: relative; 896 text-align: center; 897 } 898 </style> 899 <?php 900 } 916 function comment_exists($post_id, $comment_author, $comment_date) { 917 //Do we have 2 comments for the same author at the same time, on the same post? 918 //returns comment id 919 global $wpdb; 901 920 902 function Blogger_Import() { 903 global $importer_started; 904 $importer_started = time(); 905 if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) { 906 wp_enqueue_script('jquery'); 907 add_action('admin_head', array(&$this, 'admin_head')); 908 } 909 } 910 } 921 $comment_author = stripslashes($comment_author); 922 $comment_date = stripslashes($comment_date); 911 923 912 $blogger_import = new Blogger_Import(); 924 return $wpdb->get_var( $wpdb->prepare("SELECT comment_ID FROM $wpdb->comments 925 WHERE comment_post_ID = %s and comment_author = %s AND comment_date = %s", $post_id, $comment_author, $comment_date) ); 926 } 913 927 914 register_importer('blogger', __('Blogger', 'blogger-importer'), __('Import posts, comments, and users from a Blogger blog.', 'blogger-importer'), array ($blogger_import, 'start')); 928 } 915 929 916 class AtomEntry {930 class BloggerEntry { 917 931 var $links = array(); 918 932 var $categories = array(); 919 933 } 920 934 921 class AtomParser {922 923 var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights');924 var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft','author');925 926 var $depth = 0;927 var $indent = 2;928 var $in_content;929 var $ns_contexts = array();930 var $ns_decls = array();931 var $is_xhtml = false;932 var $skipped_div = false;933 934 var $entry;935 936 function AtomParser() {937 $this->entry = new AtomEntry();938 }939 940 function _map_attrs_func( $k, $v ) {941 return "$k=\"$v\"";942 }943 944 function _map_xmlns_func( $p, $n ) {945 $xd = "xmlns";946 if ( strlen( $n[0] ) > 0 )947 $xd .= ":{$n[0]}";948 949 return "{$xd}=\"{$n[1]}\"";950 }951 952 function parse($xml) {953 954 global $app_logging;955 array_unshift($this->ns_contexts, array());956 957 $parser = xml_parser_create_ns();958 xml_set_object($parser, $this);959 xml_set_element_handler($parser, "start_element", "end_element");960 xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);961 xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0);962 xml_set_character_data_handler($parser, "cdata");963 xml_set_default_handler($parser, "_default");964 xml_set_start_namespace_decl_handler($parser, "start_ns");965 xml_set_end_namespace_decl_handler($parser, "end_ns");966 967 $contents = "";968 969 xml_parse($parser, $xml);970 971 xml_parser_free($parser);972 973 return true;974 }975 976 function start_element($parser, $name, $attrs) {977 978 $tag = array_pop(split(":", $name));979 980 array_unshift($this->ns_contexts, $this->ns_decls);981 982 $this->depth++;983 984 if (!empty($this->in_content)) {985 $attrs_prefix = array();986 987 // resolve prefixes for attributes988 foreach($attrs as $key => $value) {989 $attrs_prefix[$this->ns_to_prefix($key)] = $this->xml_escape($value);990 }991 $attrs_str = join(' ', array_map( array( &$this, '_map_attrs_func' ), array_keys($attrs_prefix), array_values($attrs_prefix)));992 if (strlen($attrs_str) > 0) {993 $attrs_str = " " . $attrs_str;994 }995 996 $xmlns_str = join(' ', array_map( array( &$this, '_map_xmlns_func' ), array_keys($this->ns_contexts[0]), array_values($this->ns_contexts[0])));997 if (strlen($xmlns_str) > 0) {998 $xmlns_str = " " . $xmlns_str;999 }1000 1001 // handle self-closing tags (case: a new child found right-away, no text node)1002 if (count($this->in_content) == 2) {1003 array_push($this->in_content, ">");1004 }1005 1006 array_push($this->in_content, "<". $this->ns_to_prefix($name) ."{$xmlns_str}{$attrs_str}");1007 } else if (in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) {1008 $this->in_content = array();1009 $this->is_xhtml = $attrs['type'] == 'xhtml';1010 array_push($this->in_content, array($tag,$this->depth));1011 } else if ($tag == 'link') {1012 array_push($this->entry->links, $attrs);1013 } else if ($tag == 'category') {1014 array_push($this->entry->categories, $attrs['term']);1015 }1016 1017 $this->ns_decls = array();1018 }1019 1020 function end_element($parser, $name) {1021 1022 $tag = array_pop(split(":", $name));1023 1024 if (!empty($this->in_content)) {1025 if ($this->in_content[0][0] == $tag &&1026 $this->in_content[0][1] == $this->depth) {1027 array_shift($this->in_content);1028 if ($this->is_xhtml) {1029 $this->in_content = array_slice($this->in_content, 2, count($this->in_content)-3);1030 }1031 $this->entry->$tag = join('',$this->in_content);1032 $this->in_content = array();1033 } else {1034 $endtag = $this->ns_to_prefix($name);1035 if (strpos($this->in_content[count($this->in_content)-1], '<' . $endtag) !== false) {1036 array_push($this->in_content, "/>");1037 } else {1038 array_push($this->in_content, "</$endtag>");1039 }1040 }1041 }1042 1043 array_shift($this->ns_contexts);1044 1045 #print str_repeat(" ", $this->depth * $this->indent) . "end_element('$name')" ."\n";1046 1047 $this->depth--;1048 }1049 1050 function start_ns($parser, $prefix, $uri) {1051 #print str_repeat(" ", $this->depth * $this->indent) . "starting: " . $prefix . ":" . $uri . "\n";1052 array_push($this->ns_decls, array($prefix,$uri));1053 }1054 1055 function end_ns($parser, $prefix) {1056 #print str_repeat(" ", $this->depth * $this->indent) . "ending: #" . $prefix . "#\n";1057 }1058 1059 function cdata($parser, $data) {1060 #print str_repeat(" ", $this->depth * $this->indent) . "data: #" . $data . "#\n";1061 if (!empty($this->in_content)) {1062 // handle self-closing tags (case: text node found, need to close element started)1063 if (strpos($this->in_content[count($this->in_content)-1], '<') !== false) {1064 array_push($this->in_content, ">");1065 }1066 array_push($this->in_content, $this->xml_escape($data));1067 }1068 }1069 1070 function _default($parser, $data) {1071 # when does this gets called?1072 }1073 1074 1075 function ns_to_prefix($qname) {1076 $components = split(":", $qname);1077 $name = array_pop($components);1078 1079 if (!empty($components)) {1080 $ns = join(":",$components);1081 foreach ($this->ns_contexts as $context) {1082 foreach ($context as $mapping) {1083 if ($mapping[1] == $ns && strlen($mapping[0]) > 0) {1084 return "$mapping[0]:$name";1085 }1086 }1087 }1088 }1089 return $name;1090 }1091 1092 function xml_escape($string)1093 {1094 return str_replace(array('&','"',"'",'<','>'),1095 array('&','"',''','<','>'),1096 $string );1097 }1098 }1099 935 } // class_exists( 'WP_Importer' ) 1100 936 1101 937 function blogger_importer_init() { 1102 938 load_plugin_textdomain( 'blogger-importer', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); 939 940 $blogger_import = new Blogger_Import(); 941 register_importer('blogger', __('Blogger', 'blogger-importer'), __('Import categories, posts and comments then maps users from a Blogger blog.', 'blogger-importer'), array ($blogger_import, 'start')); 942 1103 943 } 1104 add_action( ' init', 'blogger_importer_init' );944 add_action( 'admin_init', 'blogger_importer_init' ); -
oauth.php
1 <?php 2 /* 3 Original File from: http://oauth.googlecode.com/svn/code/php/ 4 License: MIT License: 5 6 The MIT License 7 8 Copyright (c) 2007 Andy Smith 9 10 Permission is hereby granted, free of charge, to any person obtaining a copy 11 of this software and associated documentation files (the "Software"), to deal 12 in the Software without restriction, including without limitation the rights 13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 copies of the Software, and to permit persons to whom the Software is 15 furnished to do so, subject to the following conditions: 16 17 The above copyright notice and this permission notice shall be included in 18 all copies or substantial portions of the Software. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 THE SOFTWARE. 27 28 29 Modified for use in WordPress by Otto 30 31 Changes: All classes renamed to Blogger_* to prevent conflicts with other plugins/code using the same OAuth library. 32 33 */ 34 // vim: foldmethod=marker 35 36 /* Generic exception class 37 */ 38 class Blogger_OAuthException extends Exception { 39 // pass 40 } 41 42 class Blogger_OAuthConsumer { 43 public $key; 44 public $secret; 45 46 function __construct($key, $secret, $callback_url=NULL) { 47 $this->key = $key; 48 $this->secret = $secret; 49 $this->callback_url = $callback_url; 50 } 51 52 function __toString() { 53 return "Blogger_OAuthConsumer[key=$this->key,secret=$this->secret]"; 54 } 55 } 56 57 class Blogger_OAuthToken { 58 // access tokens and request tokens 59 public $key; 60 public $secret; 61 62 /** 63 * key = the token 64 * secret = the token secret 65 */ 66 function __construct($key, $secret) { 67 $this->key = $key; 68 $this->secret = $secret; 69 } 70 71 /** 72 * generates the basic string serialization of a token that a server 73 * would respond to request_token and access_token calls with 74 */ 75 function to_string() { 76 return "oauth_token=" . 77 Blogger_OAuthUtil::urlencode_rfc3986($this->key) . 78 "&oauth_token_secret=" . 79 Blogger_OAuthUtil::urlencode_rfc3986($this->secret); 80 } 81 82 function __toString() { 83 return $this->to_string(); 84 } 85 } 86 87 /** 88 * A class for implementing a Signature Method 89 * See section 9 ("Signing Requests") in the spec 90 */ 91 abstract class Blogger_OAuthSignatureMethod { 92 /** 93 * Needs to return the name of the Signature Method (ie HMAC-SHA1) 94 * @return string 95 */ 96 abstract public function get_name(); 97 98 /** 99 * Build up the signature 100 * NOTE: The output of this function MUST NOT be urlencoded. 101 * the encoding is handled in Blogger_OAuthRequest when the final 102 * request is serialized 103 * @param Blogger_OAuthRequest $request 104 * @param Blogger_OAuthConsumer $consumer 105 * @param Blogger_OAuthToken $token 106 * @return string 107 */ 108 abstract public function build_signature($request, $consumer, $token); 109 110 /** 111 * Verifies that a given signature is correct 112 * @param Blogger_OAuthRequest $request 113 * @param Blogger_OAuthConsumer $consumer 114 * @param Blogger_OAuthToken $token 115 * @param string $signature 116 * @return bool 117 */ 118 public function check_signature($request, $consumer, $token, $signature) { 119 $built = $this->build_signature($request, $consumer, $token); 120 121 // Check for zero length, although unlikely here 122 if (strlen($built) == 0 || strlen($signature) == 0) { 123 return false; 124 } 125 126 if (strlen($built) != strlen($signature)) { 127 return false; 128 } 129 130 // Avoid a timing leak with a (hopefully) time insensitive compare 131 $result = 0; 132 for ($i = 0; $i < strlen($signature); $i++) { 133 $result |= ord($built{$i}) ^ ord($signature{$i}); 134 } 135 136 return $result == 0; 137 } 138 } 139 140 /** 141 * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] 142 * where the Signature Base String is the text and the key is the concatenated values (each first 143 * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' 144 * character (ASCII code 38) even if empty. 145 * - Chapter 9.2 ("HMAC-SHA1") 146 */ 147 class Blogger_OAuthSignatureMethod_HMAC_SHA1 extends Blogger_OAuthSignatureMethod { 148 function get_name() { 149 return "HMAC-SHA1"; 150 } 151 152 public function build_signature($request, $consumer, $token) { 153 $base_string = $request->get_signature_base_string(); 154 $request->base_string = $base_string; 155 156 $key_parts = array( 157 $consumer->secret, 158 ($token) ? $token->secret : "" 159 ); 160 161 $key_parts = Blogger_OAuthUtil::urlencode_rfc3986($key_parts); 162 $key = implode('&', $key_parts); 163 164 return base64_encode(hash_hmac('sha1', $base_string, $key, true)); 165 } 166 } 167 168 /** 169 * The PLAINTEXT method does not provide any security protection and SHOULD only be used 170 * over a secure channel such as HTTPS. It does not use the Signature Base String. 171 * - Chapter 9.4 ("PLAINTEXT") 172 */ 173 class Blogger_OAuthSignatureMethod_PLAINTEXT extends Blogger_OAuthSignatureMethod { 174 public function get_name() { 175 return "PLAINTEXT"; 176 } 177 178 /** 179 * oauth_signature is set to the concatenated encoded values of the Consumer Secret and 180 * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is 181 * empty. The result MUST be encoded again. 182 * - Chapter 9.4.1 ("Generating Signatures") 183 * 184 * Please note that the second encoding MUST NOT happen in the SignatureMethod, as 185 * Blogger_OAuthRequest handles this! 186 */ 187 public function build_signature($request, $consumer, $token) { 188 $key_parts = array( 189 $consumer->secret, 190 ($token) ? $token->secret : "" 191 ); 192 193 $key_parts = Blogger_OAuthUtil::urlencode_rfc3986($key_parts); 194 $key = implode('&', $key_parts); 195 $request->base_string = $key; 196 197 return $key; 198 } 199 } 200 201 /** 202 * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in 203 * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for 204 * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a 205 * verified way to the Service Provider, in a manner which is beyond the scope of this 206 * specification. 207 * - Chapter 9.3 ("RSA-SHA1") 208 */ 209 abstract class Blogger_OAuthSignatureMethod_RSA_SHA1 extends Blogger_OAuthSignatureMethod { 210 public function get_name() { 211 return "RSA-SHA1"; 212 } 213 214 // Up to the SP to implement this lookup of keys. Possible ideas are: 215 // (1) do a lookup in a table of trusted certs keyed off of consumer 216 // (2) fetch via http using a url provided by the requester 217 // (3) some sort of specific discovery code based on request 218 // 219 // Either way should return a string representation of the certificate 220 protected abstract function fetch_public_cert(&$request); 221 222 // Up to the SP to implement this lookup of keys. Possible ideas are: 223 // (1) do a lookup in a table of trusted certs keyed off of consumer 224 // 225 // Either way should return a string representation of the certificate 226 protected abstract function fetch_private_cert(&$request); 227 228 public function build_signature($request, $consumer, $token) { 229 $base_string = $request->get_signature_base_string(); 230 $request->base_string = $base_string; 231 232 // Fetch the private key cert based on the request 233 $cert = $this->fetch_private_cert($request); 234 235 // Pull the private key ID from the certificate 236 $privatekeyid = openssl_get_privatekey($cert); 237 238 // Sign using the key 239 $ok = openssl_sign($base_string, $signature, $privatekeyid); 240 241 // Release the key resource 242 openssl_free_key($privatekeyid); 243 244 return base64_encode($signature); 245 } 246 247 public function check_signature($request, $consumer, $token, $signature) { 248 $decoded_sig = base64_decode($signature); 249 250 $base_string = $request->get_signature_base_string(); 251 252 // Fetch the public key cert based on the request 253 $cert = $this->fetch_public_cert($request); 254 255 // Pull the public key ID from the certificate 256 $publickeyid = openssl_get_publickey($cert); 257 258 // Check the computed signature against the one passed in the query 259 $ok = openssl_verify($base_string, $decoded_sig, $publickeyid); 260 261 // Release the key resource 262 openssl_free_key($publickeyid); 263 264 return $ok == 1; 265 } 266 } 267 268 class Blogger_OAuthRequest { 269 protected $parameters; 270 protected $http_method; 271 protected $http_url; 272 // for debug purposes 273 public $base_string; 274 public static $version = '1.0'; 275 public static $POST_INPUT = 'php://input'; 276 277 function __construct($http_method, $http_url, $parameters=NULL) { 278 $parameters = ($parameters) ? $parameters : array(); 279 $parameters = array_merge( Blogger_OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); 280 $this->parameters = $parameters; 281 $this->http_method = $http_method; 282 $this->http_url = $http_url; 283 } 284 285 286 /** 287 * attempt to build up a request from what was passed to the server 288 */ 289 public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) { 290 $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") 291 ? 'http' 292 : 'https'; 293 $http_url = ($http_url) ? $http_url : $scheme . 294 '://' . $_SERVER['SERVER_NAME'] . 295 ':' . 296 $_SERVER['SERVER_PORT'] . 297 $_SERVER['REQUEST_URI']; 298 $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD']; 299 300 // We weren't handed any parameters, so let's find the ones relevant to 301 // this request. 302 // If you run XML-RPC or similar you should use this to provide your own 303 // parsed parameter-list 304 if (!$parameters) { 305 // Find request headers 306 $request_headers = Blogger_OAuthUtil::get_headers(); 307 308 // Parse the query-string to find GET parameters 309 $parameters = Blogger_OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); 310 311 // It's a POST request of the proper content-type, so parse POST 312 // parameters and add those overriding any duplicates from GET 313 if ($http_method == "POST" 314 && isset($request_headers['Content-Type']) 315 && strstr($request_headers['Content-Type'], 316 'application/x-www-form-urlencoded') 317 ) { 318 $post_data = Blogger_OAuthUtil::parse_parameters( 319 file_get_contents(self::$POST_INPUT) 320 ); 321 $parameters = array_merge($parameters, $post_data); 322 } 323 324 // We have a Authorization-header with Blogger_OAuth data. Parse the header 325 // and add those overriding any duplicates from GET or POST 326 if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'Blogger_OAuth ') { 327 $header_parameters = Blogger_OAuthUtil::split_header( 328 $request_headers['Authorization'] 329 ); 330 $parameters = array_merge($parameters, $header_parameters); 331 } 332 333 } 334 335 return new Blogger_OAuthRequest($http_method, $http_url, $parameters); 336 } 337 338 /** 339 * pretty much a helper function to set up the request 340 */ 341 public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) { 342 $parameters = ($parameters) ? $parameters : array(); 343 $defaults = array("oauth_version" => Blogger_OAuthRequest::$version, 344 "oauth_nonce" => Blogger_OAuthRequest::generate_nonce(), 345 "oauth_timestamp" => Blogger_OAuthRequest::generate_timestamp(), 346 "oauth_consumer_key" => $consumer->key); 347 if ($token) 348 $defaults['oauth_token'] = $token->key; 349 350 $parameters = array_merge($defaults, $parameters); 351 352 return new Blogger_OAuthRequest($http_method, $http_url, $parameters); 353 } 354 355 public function set_parameter($name, $value, $allow_duplicates = true) { 356 if ($allow_duplicates && isset($this->parameters[$name])) { 357 // We have already added parameter(s) with this name, so add to the list 358 if (is_scalar($this->parameters[$name])) { 359 // This is the first duplicate, so transform scalar (string) 360 // into an array so we can add the duplicates 361 $this->parameters[$name] = array($this->parameters[$name]); 362 } 363 364 $this->parameters[$name][] = $value; 365 } else { 366 $this->parameters[$name] = $value; 367 } 368 } 369 370 public function get_parameter($name) { 371 return isset($this->parameters[$name]) ? $this->parameters[$name] : null; 372 } 373 374 public function get_parameters() { 375 return $this->parameters; 376 } 377 378 public function unset_parameter($name) { 379 unset($this->parameters[$name]); 380 } 381 382 /** 383 * The request parameters, sorted and concatenated into a normalized string. 384 * @return string 385 */ 386 public function get_signable_parameters() { 387 // Grab all parameters 388 $params = $this->parameters; 389 390 // Remove oauth_signature if present 391 // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") 392 if (isset($params['oauth_signature'])) { 393 unset($params['oauth_signature']); 394 } 395 396 return Blogger_OAuthUtil::build_http_query($params); 397 } 398 399 /** 400 * Returns the base string of this request 401 * 402 * The base string defined as the method, the url 403 * and the parameters (normalized), each urlencoded 404 * and the concated with &. 405 */ 406 public function get_signature_base_string() { 407 $parts = array( 408 $this->get_normalized_http_method(), 409 $this->get_normalized_http_url(), 410 $this->get_signable_parameters() 411 ); 412 413 $parts = Blogger_OAuthUtil::urlencode_rfc3986($parts); 414 415 return implode('&', $parts); 416 } 417 418 /** 419 * just uppercases the http method 420 */ 421 public function get_normalized_http_method() { 422 return strtoupper($this->http_method); 423 } 424 425 /** 426 * parses the url and rebuilds it to be 427 * scheme://host/path 428 */ 429 public function get_normalized_http_url() { 430 $parts = parse_url($this->http_url); 431 432 $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; 433 $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80'); 434 $host = (isset($parts['host'])) ? strtolower($parts['host']) : ''; 435 $path = (isset($parts['path'])) ? $parts['path'] : ''; 436 437 if (($scheme == 'https' && $port != '443') 438 || ($scheme == 'http' && $port != '80')) { 439 $host = "$host:$port"; 440 } 441 return "$scheme://$host$path"; 442 } 443 444 /** 445 * builds a url usable for a GET request 446 */ 447 public function to_url() { 448 $post_data = $this->to_postdata(); 449 $out = $this->get_normalized_http_url(); 450 if ($post_data) { 451 $out .= '?'.$post_data; 452 } 453 return $out; 454 } 455 456 /** 457 * builds the data one would send in a POST request 458 */ 459 public function to_postdata() { 460 return Blogger_OAuthUtil::build_http_query($this->parameters); 461 } 462 463 /** 464 * builds the Authorization: header 465 */ 466 public function to_header($realm=null) { 467 $first = true; 468 if($realm) { 469 $out = 'Authorization: Blogger_OAuth realm="' . Blogger_OAuthUtil::urlencode_rfc3986($realm) . '"'; 470 $first = false; 471 } else 472 $out = 'Authorization: Blogger_OAuth'; 473 474 $total = array(); 475 foreach ($this->parameters as $k => $v) { 476 if (substr($k, 0, 5) != "oauth") continue; 477 if (is_array($v)) { 478 throw new Blogger_OAuthException('Arrays not supported in headers'); 479 } 480 $out .= ($first) ? ' ' : ','; 481 $out .= Blogger_OAuthUtil::urlencode_rfc3986($k) . 482 '="' . 483 Blogger_OAuthUtil::urlencode_rfc3986($v) . 484 '"'; 485 $first = false; 486 } 487 return $out; 488 } 489 490 public function __toString() { 491 return $this->to_url(); 492 } 493 494 495 public function sign_request($signature_method, $consumer, $token) { 496 $this->set_parameter( 497 "oauth_signature_method", 498 $signature_method->get_name(), 499 false 500 ); 501 $signature = $this->build_signature($signature_method, $consumer, $token); 502 $this->set_parameter("oauth_signature", $signature, false); 503 } 504 505 public function build_signature($signature_method, $consumer, $token) { 506 $signature = $signature_method->build_signature($this, $consumer, $token); 507 return $signature; 508 } 509 510 /** 511 * util function: current timestamp 512 */ 513 private static function generate_timestamp() { 514 return time(); 515 } 516 517 /** 518 * util function: current nonce 519 */ 520 private static function generate_nonce() { 521 $mt = microtime(); 522 $rand = mt_rand(); 523 524 return md5($mt . $rand); // md5s look nicer than numbers 525 } 526 } 527 528 class Blogger_OAuthServer { 529 protected $timestamp_threshold = 300; // in seconds, five minutes 530 protected $version = '1.0'; // hi blaine 531 protected $signature_methods = array(); 532 533 protected $data_store; 534 535 function __construct($data_store) { 536 $this->data_store = $data_store; 537 } 538 539 public function add_signature_method($signature_method) { 540 $this->signature_methods[$signature_method->get_name()] = 541 $signature_method; 542 } 543 544 // high level functions 545 546 /** 547 * process a request_token request 548 * returns the request token on success 549 */ 550 public function fetch_request_token(&$request) { 551 $this->get_version($request); 552 553 $consumer = $this->get_consumer($request); 554 555 // no token required for the initial token request 556 $token = NULL; 557 558 $this->check_signature($request, $consumer, $token); 559 560 // Rev A change 561 $callback = $request->get_parameter('oauth_callback'); 562 $new_token = $this->data_store->new_request_token($consumer, $callback); 563 564 return $new_token; 565 } 566 567 /** 568 * process an access_token request 569 * returns the access token on success 570 */ 571 public function fetch_access_token(&$request) { 572 $this->get_version($request); 573 574 $consumer = $this->get_consumer($request); 575 576 // requires authorized request token 577 $token = $this->get_token($request, $consumer, "request"); 578 579 $this->check_signature($request, $consumer, $token); 580 581 // Rev A change 582 $verifier = $request->get_parameter('oauth_verifier'); 583 $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); 584 585 return $new_token; 586 } 587 588 /** 589 * verify an api call, checks all the parameters 590 */ 591 public function verify_request(&$request) { 592 $this->get_version($request); 593 $consumer = $this->get_consumer($request); 594 $token = $this->get_token($request, $consumer, "access"); 595 $this->check_signature($request, $consumer, $token); 596 return array($consumer, $token); 597 } 598 599 // Internals from here 600 /** 601 * version 1 602 */ 603 private function get_version(&$request) { 604 $version = $request->get_parameter("oauth_version"); 605 if (!$version) { 606 // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. 607 // Chapter 7.0 ("Accessing Protected Ressources") 608 $version = '1.0'; 609 } 610 if ($version !== $this->version) { 611 throw new Blogger_OAuthException("Blogger_OAuth version '$version' not supported"); 612 } 613 return $version; 614 } 615 616 /** 617 * figure out the signature with some defaults 618 */ 619 private function get_signature_method($request) { 620 $signature_method = $request instanceof Blogger_OAuthRequest 621 ? $request->get_parameter("oauth_signature_method") 622 : NULL; 623 624 if (!$signature_method) { 625 // According to chapter 7 ("Accessing Protected Ressources") the signature-method 626 // parameter is required, and we can't just fallback to PLAINTEXT 627 throw new Blogger_OAuthException('No signature method parameter. This parameter is required'); 628 } 629 630 if (!in_array($signature_method, 631 array_keys($this->signature_methods))) { 632 throw new Blogger_OAuthException( 633 "Signature method '$signature_method' not supported " . 634 "try one of the following: " . 635 implode(", ", array_keys($this->signature_methods)) 636 ); 637 } 638 return $this->signature_methods[$signature_method]; 639 } 640 641 /** 642 * try to find the consumer for the provided request's consumer key 643 */ 644 private function get_consumer($request) { 645 $consumer_key = $request instanceof Blogger_OAuthRequest 646 ? $request->get_parameter("oauth_consumer_key") 647 : NULL; 648 649 if (!$consumer_key) { 650 throw new Blogger_OAuthException("Invalid consumer key"); 651 } 652 653 $consumer = $this->data_store->lookup_consumer($consumer_key); 654 if (!$consumer) { 655 throw new Blogger_OAuthException("Invalid consumer"); 656 } 657 658 return $consumer; 659 } 660 661 /** 662 * try to find the token for the provided request's token key 663 */ 664 private function get_token($request, $consumer, $token_type="access") { 665 $token_field = $request instanceof Blogger_OAuthRequest 666 ? $request->get_parameter('oauth_token') 667 : NULL; 668 669 $token = $this->data_store->lookup_token( 670 $consumer, $token_type, $token_field 671 ); 672 if (!$token) { 673 throw new Blogger_OAuthException("Invalid $token_type token: $token_field"); 674 } 675 return $token; 676 } 677 678 /** 679 * all-in-one function to check the signature on a request 680 * should guess the signature method appropriately 681 */ 682 private function check_signature($request, $consumer, $token) { 683 // this should probably be in a different method 684 $timestamp = $request instanceof Blogger_OAuthRequest 685 ? $request->get_parameter('oauth_timestamp') 686 : NULL; 687 $nonce = $request instanceof Blogger_OAuthRequest 688 ? $request->get_parameter('oauth_nonce') 689 : NULL; 690 691 $this->check_timestamp($timestamp); 692 $this->check_nonce($consumer, $token, $nonce, $timestamp); 693 694 $signature_method = $this->get_signature_method($request); 695 696 $signature = $request->get_parameter('oauth_signature'); 697 $valid_sig = $signature_method->check_signature( 698 $request, 699 $consumer, 700 $token, 701 $signature 702 ); 703 704 if (!$valid_sig) { 705 throw new Blogger_OAuthException("Invalid signature"); 706 } 707 } 708 709 /** 710 * check that the timestamp is new enough 711 */ 712 private function check_timestamp($timestamp) { 713 if( ! $timestamp ) 714 throw new Blogger_OAuthException( 715 'Missing timestamp parameter. The parameter is required' 716 ); 717 718 // verify that timestamp is recentish 719 $now = time(); 720 if (abs($now - $timestamp) > $this->timestamp_threshold) { 721 throw new Blogger_OAuthException( 722 "Expired timestamp, yours $timestamp, ours $now" 723 ); 724 } 725 } 726 727 /** 728 * check that the nonce is not repeated 729 */ 730 private function check_nonce($consumer, $token, $nonce, $timestamp) { 731 if( ! $nonce ) 732 throw new Blogger_OAuthException( 733 'Missing nonce parameter. The parameter is required' 734 ); 735 736 // verify that the nonce is uniqueish 737 $found = $this->data_store->lookup_nonce( 738 $consumer, 739 $token, 740 $nonce, 741 $timestamp 742 ); 743 if ($found) { 744 throw new Blogger_OAuthException("Nonce already used: $nonce"); 745 } 746 } 747 748 } 749 750 class Blogger_OAuthDataStore { 751 function lookup_consumer($consumer_key) { 752 // implement me 753 } 754 755 function lookup_token($consumer, $token_type, $token) { 756 // implement me 757 } 758 759 function lookup_nonce($consumer, $token, $nonce, $timestamp) { 760 // implement me 761 } 762 763 function new_request_token($consumer, $callback = null) { 764 // return a new token attached to this consumer 765 } 766 767 function new_access_token($token, $consumer, $verifier = null) { 768 // return a new access token attached to this consumer 769 // for the user associated with this token if the request token 770 // is authorized 771 // should also invalidate the request token 772 } 773 774 } 775 776 class Blogger_OAuthUtil { 777 public static function urlencode_rfc3986($input) { 778 if (is_array($input)) { 779 return array_map(array('Blogger_OAuthUtil', 'urlencode_rfc3986'), $input); 780 } else if (is_scalar($input)) { 781 return str_replace( 782 '+', 783 ' ', 784 str_replace('%7E', '~', rawurlencode($input)) 785 ); 786 } else { 787 return ''; 788 } 789 } 790 791 792 // This decode function isn't taking into consideration the above 793 // modifications to the encoding process. However, this method doesn't 794 // seem to be used anywhere so leaving it as is. 795 public static function urldecode_rfc3986($string) { 796 return urldecode($string); 797 } 798 799 // Utility function for turning the Authorization: header into 800 // parameters, has to do some unescaping 801 // Can filter out any non-oauth parameters if needed (default behaviour) 802 // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement. 803 // see http://code.google.com/p/oauth/issues/detail?id=163 804 public static function split_header($header, $only_allow_oauth_parameters = true) { 805 $params = array(); 806 if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) { 807 foreach ($matches[1] as $i => $h) { 808 $params[$h] = Blogger_OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]); 809 } 810 if (isset($params['realm'])) { 811 unset($params['realm']); 812 } 813 } 814 return $params; 815 } 816 817 // helper to try to sort out headers for people who aren't running apache 818 public static function get_headers() { 819 if (function_exists('apache_request_headers')) { 820 // we need this to get the actual Authorization: header 821 // because apache tends to tell us it doesn't exist 822 $headers = apache_request_headers(); 823 824 // sanitize the output of apache_request_headers because 825 // we always want the keys to be Cased-Like-This and arh() 826 // returns the headers in the same case as they are in the 827 // request 828 $out = array(); 829 foreach ($headers AS $key => $value) { 830 $key = str_replace( 831 " ", 832 "-", 833 ucwords(strtolower(str_replace("-", " ", $key))) 834 ); 835 $out[$key] = $value; 836 } 837 } else { 838 // otherwise we don't have apache and are just going to have to hope 839 // that $_SERVER actually contains what we need 840 $out = array(); 841 if( isset($_SERVER['CONTENT_TYPE']) ) 842 $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; 843 if( isset($_ENV['CONTENT_TYPE']) ) 844 $out['Content-Type'] = $_ENV['CONTENT_TYPE']; 845 846 foreach ($_SERVER as $key => $value) { 847 if (substr($key, 0, 5) == "HTTP_") { 848 // this is chaos, basically it is just there to capitalize the first 849 // letter of every word that is not an initial HTTP and strip HTTP 850 // code from przemek 851 $key = str_replace( 852 " ", 853 "-", 854 ucwords(strtolower(str_replace("_", " ", substr($key, 5)))) 855 ); 856 $out[$key] = $value; 857 } 858 } 859 } 860 return $out; 861 } 862 863 // This function takes a input like a=b&a=c&d=e and returns the parsed 864 // parameters like this 865 // array('a' => array('b','c'), 'd' => 'e') 866 public static function parse_parameters( $input ) { 867 if (!isset($input) || !$input) return array(); 868 869 $pairs = explode('&', $input); 870 871 $parsed_parameters = array(); 872 foreach ($pairs as $pair) { 873 $split = explode('=', $pair, 2); 874 $parameter = Blogger_OAuthUtil::urldecode_rfc3986($split[0]); 875 $value = isset($split[1]) ? Blogger_OAuthUtil::urldecode_rfc3986($split[1]) : ''; 876 877 if (isset($parsed_parameters[$parameter])) { 878 // We have already recieved parameter(s) with this name, so add to the list 879 // of parameters with this name 880 881 if (is_scalar($parsed_parameters[$parameter])) { 882 // This is the first duplicate, so transform scalar (string) into an array 883 // so we can add the duplicates 884 $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]); 885 } 886 887 $parsed_parameters[$parameter][] = $value; 888 } else { 889 $parsed_parameters[$parameter] = $value; 890 } 891 } 892 return $parsed_parameters; 893 } 894 895 public static function build_http_query($params) { 896 if (!$params) return ''; 897 898 // Urlencode both keys and values 899 $keys = Blogger_OAuthUtil::urlencode_rfc3986(array_keys($params)); 900 $values = Blogger_OAuthUtil::urlencode_rfc3986(array_values($params)); 901 $params = array_combine($keys, $values); 902 903 // Parameters are sorted by name, using lexicographical byte value ordering. 904 // Ref: Spec: 9.1.1 (1) 905 uksort($params, 'strcmp'); 906 907 $pairs = array(); 908 foreach ($params as $parameter => $value) { 909 if (is_array($value)) { 910 // If two or more parameters share the same name, they are sorted by their value 911 // Ref: Spec: 9.1.1 (1) 912 // June 12th, 2010 - changed to sort because of issue 164 by hidetaka 913 sort($value, SORT_STRING); 914 foreach ($value as $duplicate_value) { 915 $pairs[] = $parameter . '=' . $duplicate_value; 916 } 917 } else { 918 $pairs[] = $parameter . '=' . $value; 919 } 920 } 921 // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) 922 // Each name-value pair is separated by an '&' character (ASCII code 38) 923 return implode('&', $pairs); 924 } 925 } 926 -
readme.txt
1 === Blogger Importer===2 Contributors: wordpressdotorg 1 === Plugin Name === 2 Contributors: wordpressdotorg, otto42, workshopshed, SergeyBiryukov, rmccue 3 3 Donate link: 4 4 Tags: importer, blogger 5 5 Requires at least: 3.0 6 6 Tested up to: 3.2 7 Stable tag: 0.4 7 Stable tag: 0.6 8 License: GPLv2 or later 8 9 9 Import posts, comments, and users from a Blogger blog.10 Imports posts, comments and categories (blogger tags) from a Blogger blog then migrates authors to Wordpress users. 10 11 11 12 == Description == 12 13 13 Th is is the Blogger Importer available from the WordPress Tools->Import screen. It imports posts, comments, and users from a Blogger site into a WordPressinstallation.14 The Blogger Importer imports your blog data from a Blogger site into a WordPress.org installation. 14 15 16 = Items imported = 17 18 * Categories 19 * Posts (published, scheduled and draft) 20 * Comments (not spam) 21 22 = Items not imported = 23 24 * Pages 25 * Images (the images will appear in your new blog but will link to the old blogspot or picassa web locations) 26 15 27 == Installation == 16 28 17 29 1. Upload the `blogger-importer` folder to the `/wp-content/plugins/` directory 18 30 1. Activate the plugin through the 'Plugins' menu in WordPress 19 1. Go to the Tools -> Import screen, Click on Blogger20 31 32 = How to use = 33 34 1. Blogger Importer is available from the WordPress Tools->Import screen. 35 1. Press Authorise 36 1. If you are not already logged into Google you will be asked to login 37 1. You will be asked to grant Wordpress access to your Blogger information, to continue press Grant Access 38 1. You will be presented with a list of all your blogs 39 1. Select the appropriate blog and press the import button 40 1. Wait whilst the posts and comments are imported 41 1. Press the Set Authors button 42 1. Select the appropriate mapping for the authors 43 1. Review categories, posts and comments 44 45 You can now remove the importer plugin if you no longer need to use it. 46 21 47 == Frequently Asked Questions == 22 48 49 = How do I re-import? = 50 51 Press the clear account information button, then re-connect to blogger and re-import, the importer is designed not to re-import the same posts. If you need to do a full re-import then delete the posts and then empty the trash before re-importing. 52 53 = How do I know which posts were imported? = 54 55 Each of the posts loaded is tagged with a meta tags indicating where the posts were loaded from. The permalink will be set to the visible URL if the post was published or the internal ID if it was still a draft or scheduled post 56 57 * blogger_author 58 * blogger_blog 59 * blogger_permalink 60 61 = Why does it keep stopping? = 62 63 The importer is designed not to overload blogger or your site so only imports in batches and will run for a fixed number of seconds before pausing, the admin screen will refresh every few seconds to show how many it has done so far. Press continue to continue importing posts and comments. 64 65 = After importing there are a lot of categories = 66 67 Blogger does not distinguish between tags and categories so you will likely want to review what was imported and then use the categories to tags converter 68 69 = What about pages? = 70 71 This importer does not handle blogger pages, you will need to manually transfer them. 72 73 = What about images? = 74 75 The importer will simply load the tags for the images as they appear in your source data, so you will have references to blogspot and picassa based images. If you do not migrate these with a separate tool then these will be lost when you delete your old blogger blog. 76 77 = Are the permalinks the same? = 78 79 No, Wordpress and Blogger handle the permalinks differently. However, it is possible to use the redirection plugin to map the old URLs across to the new URLs. 80 81 = What about future posts? = 82 83 The scheduled posts will be transferred and will be published as specified. However, Blogger and Wordpress handle drafts differently, Wordpress does not support dates on draft posts so you will need to use a plugin if you wish to plan your writing schedule. 84 85 = My posts and comments moved across but some things are stripped out = 86 87 The importer uses the SimplePie classes to process the data, these in turn use a Simplepie_Sanitize class to remove potentially malicious code from the source data. 88 23 89 == Screenshots == 24 90 91 == Reference == 92 93 * https://developers.google.com/blogger/docs/1.0/developers_guide_php 94 * https://developers.google.com/gdata/articles/oauth 95 96 == Other changes to review == 97 98 Review the santisation processes, are these being done twice? 99 100 http://core.trac.wordpress.org/attachment/ticket/15737/blogger-importer.patch 101 http://core.trac.wordpress.org/ticket/15737 102 103 104 105 http://core.trac.wordpress.org/ticket/7652 106 http://core.trac.wordpress.org/attachment/ticket/7652/7652-blogger.diff 107 http://core.trac.wordpress.org/attachment/ticket/7652/7652-separate.diff 108 109 Move the Javascript into a separate file, pass the parameters and localised strings using wp_localize_script 110 111 Inconsistent UI (sometimes you get a set of tools graphic, sometimes not) 112 113 Handle geotags See http://codex.wordpress.org/Geodata 114 115 116 25 117 == Changelog == 26 118 119 = 0.6 = 120 * Merged in fix by SergeyBiryukov http://core.trac.wordpress.org/ticket/16012 121 * Merged in rmccue change to get_total_results to also use SimplePie from http://core.trac.wordpress.org/attachment/ticket/7652/7652-blogger.diff 122 * Reviewed in rmccue's changes in http://core.trac.wordpress.org/attachment/ticket/7652/7652-separate.diff issues with date handling functions so skipped those 123 * Moved SimplePie functions in new class WP_SimplePie_Blog_Item incorporating get_draft_status and get_updated and convert date 124 * Andy from Workshopshed tested comments from source blog GMT-8, destination London (currently GMT-1), comment dates transferred correctly. 125 * Andy from Workshopshed Fixed typo in oauth_get 126 127 = 0.5 = 128 * Change by Otto42, rmccue to use Simplepie XML processing rather than Atomparser, http://core.trac.wordpress.org/ticket/14525 ref: http://core.trac.wordpress.org/attachment/ticket/7652/7652-blogger.diff 129 this also fixes http://core.trac.wordpress.org/ticket/15560 130 * Change by Otto42 to use OAuth rather than AuthSub authentication, should make authentication more reliable 131 * Fix by Andy from Workshopshed to load comments correctly 132 * Fix by Andy from Workshopshed to correctly pass the blogger start-index and max-results parameters to oAuth functions and to process more than one batch http://core.trac.wordpress.org/ticket/19096 133 * Fix by Andy from Workshopshed error about incorrect enqueuing of scripts also changed styles to work the same 134 * Change by Andy from Workshopshed testing in debug mode and wrapped ajax return into a function to suppress debug messages 135 * Fix by Andy from Workshopshed notices for undefined variables. 136 * Change by Andy from Workshopshed Added tooltip to results table to show numbers of posts and comments skipped (duplicates / missing key) 137 * Fix by Andy from Workshopshed incorrectly checking for duplicates based on only the date and username, this gave false positives when large numbers of comments, particularly anonymous ones. 138 27 139 = 0.4 = 28 140 * Fix for tracking images being added by Blogger to non-authenticated feeds http://core.trac.wordpress.org/ticket/17623 29 141 … … 35 147 36 148 == Upgrade Notice == 37 149 38 = 0.4 = 39 * Fix for tracking images being added by Blogger to non-authenticated feeds http://core.trac.wordpress.org/ticket/17623 150 = 0.6 = 40 151 41 = 0.3 = 42 * Bugfix for 403 Invalid AuthSub Token 152 Merged in fixes found in Trac 43 153 154 = 0.5 = 155 156 This version is a significant re-write based on previous versions. 157