<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[2137] 2013/frederickding/importer/trunk: Add author mapping process step.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://gsoc.trac.wordpress.org/changeset/2137">2137</a></dd>
<dt>Author</dt> <dd>frederick.ding</dd>
<dt>Date</dt> <dd>2013-07-23 00:16:52 +0000 (Tue, 23 Jul 2013)</dd>
</dl>
<h3>Log Message</h3>
<pre>Add author mapping process step.
- Modified to use internal class variable $params (with getters/setters)
to eliminate dependence on $_POST, which wouldn't be usable from a cron job
- See <a href="http://gsoc.trac.wordpress.org/ticket/327">#327</a></pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#2013frederickdingimportertrunkclasswordpressimporterphp">2013/frederickding/importer/trunk/class-wordpress-importer.php</a></li>
<li><a href="#2013frederickdingimportertrunkteststestclasswordpressimporterphp">2013/frederickding/importer/trunk/tests/test-class-wordpress-importer.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="2013frederickdingimportertrunkclasswordpressimporterphp"></a>
<div class="modfile"><h4>Modified: 2013/frederickding/importer/trunk/class-wordpress-importer.php (2136 => 2137)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/frederickding/importer/trunk/class-wordpress-importer.php 2013-07-18 21:52:42 UTC (rev 2136)
+++ 2013/frederickding/importer/trunk/class-wordpress-importer.php 2013-07-23 00:16:52 UTC (rev 2137)
</span><span class="lines">@@ -33,12 +33,25 @@
</span><span class="cx">
</span><span class="cx"> protected $authors = array();
</span><span class="cx">
</span><ins>+ protected $processed_authors = array();
+
+ protected $author_mappings = array();
+
</ins><span class="cx"> protected $_data = array();
</span><ins>+
+ /**
+ * Stores user-submitted choices on how to proceed with the process.
+ *
+ * @var array
+ */
+ public $params = array();
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- * Parses the WXR file and prepares us for the task of processing parsed
- * data
</del><ins>+ * Parses the WXR file and prepares us to process parsed data.
</ins><span class="cx"> *
</span><ins>+ * This method is synchronous and is NOT for use in a cron-scheduled job; it
+ * should be invoked by a controller UI before do_import() is scheduled.
+ *
</ins><span class="cx"> * @param string $file
</span><span class="cx"> * Path to the WXR file for parsing
</span><span class="cx"> * @return array WP_Error data if successful, or an instance of WP_Error if
</span><span class="lines">@@ -70,6 +83,9 @@
</span><span class="cx"> $this->version = $version;
</span><span class="cx"> $this->wxr_file = $file;
</span><span class="cx"> $this->_data = $data;
</span><ins>+ if ( $user = get_current_user_id() > 0 ) {
+ $this->set_param( 'admin_user_id', $user );
+ }
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> *
</span><span class="lines">@@ -99,6 +115,9 @@
</span><span class="cx"> $done = false;
</span><span class="cx">
</span><span class="cx"> do {
</span><ins>+ // we could probably be doing call_user_func() or using some
+ // Reflection, but this makes more sense for readability and
+ // process-specific modifications
</ins><span class="cx"> switch ( $this->progress[$file] ) {
</span><span class="cx"> case 'start':
</span><span class="cx"> case 'authors':
</span><span class="lines">@@ -243,13 +262,123 @@
</span><span class="cx"> $this->advance();
</span><span class="cx"> return true;
</span><span class="cx"> }
</span><del>-
</del><ins>+
</ins><span class="cx"> /**
</span><ins>+ * Map old author logins to local user IDs based on decisions.
</ins><span class="cx"> *
</span><del>- * @todo
</del><ins>+ * Can map to an existing user, create a new user, or falls back to the
+ * current user in case of an error with the other cases.
</ins><span class="cx"> */
</span><del>- protected function process_author_mappings() {}
</del><ins>+ protected function process_author_mappings () {
+ $create_users = $this->is_allowed_user_creation();
+
+ // adapted from WP_Import; however, we don't have the luxury of
+ // directly accessing $_POST, so we rely instead on DB-stored params
+ $imported_authors = $this->get_param( 'imported_authors', array() );
+
+ if ( is_wp_error( $imported_authors ) ) {
+ $this->error = $imported_authors;
+ return false;
+ }
+
+ foreach ( (array) $imported_authors as $i => $old_login ) {
+ // Multisite adds strtolower to sanitize_user. Need to sanitize here
+ // to stop breakage in process_posts.
+ $sanitized_old_login = sanitize_user( $old_login, true );
+
+ /*
+ * $this->authors should have already been set by process_authors(),
+ * although author_id will be missing if author information was
+ * pulled out of post meta
+ */
+ $old_id = isset( $this->authors[$old_login]['author_id'] ) ? intval(
+ $this->authors[$old_login]['author_id'] ) : false;
+
+ $user_map = $this->get_param( 'user_map', array() );
+ if ( ! empty( $user_map[$i] ) ) {
+ // the mapping old->new was defined from the form
+ $user = get_userdata( intval( $user_map[$i] ) );
+ if ( isset( $user->ID ) ) {
+ if ( $old_id )
+ $this->processed_authors[$old_id] = $user->ID;
+ $this->author_mappings[$sanitized_old_login] = $user->ID;
+ }
+ } elseif ( $create_users ) {
+ // make a user for an unmapped author only if allowed
+ $user_new = $this->get_param( 'user_new', array() );
+ if ( ! empty( $user_new[$i] ) ) {
+ $user_id = wp_create_user( $user_new[$i],
+ wp_generate_password() );
+ } elseif ( $this->version != '1.0' ) {
+ $user_data = array(
+ 'user_login' => $old_login,
+ 'user_pass' => wp_generate_password(),
+ 'user_email' => isset(
+ $this->authors[$old_login]['author_email'] ) ? $this->authors[$old_login]['author_email'] : '',
+ 'display_name' => $this->authors[$old_login]['author_display_name'],
+ 'first_name' => isset(
+ $this->authors[$old_login]['author_first_name'] ) ? $this->authors[$old_login]['author_first_name'] : '',
+ 'last_name' => isset(
+ $this->authors[$old_login]['author_last_name'] ) ? $this->authors[$old_login]['author_last_name'] : ''
+ );
+ $user_id = wp_insert_user( $user_data );
+ }
+
+ if ( ! is_wp_error( $user_id ) ) {
+ // successful user creation; add to mappings
+ if ( $old_id )
+ $this->processed_authors[$old_id] = $user_id;
+ $this->author_mappings[$sanitized_old_login] = $user_id;
+ } else {
+ // set a warning in the importer class, and include the
+ // WP_Error object emitted by wp_insert_user() as data.
+ $this->warnings[] = new WP_Error(
+ 'import_process_author_mappings_warning',
+ __(
+ 'Failed to create new user for %s. Their posts will be attributed to the current user.',
+ 'wordpress-importer' ),
+ esc_html( $this->authors[$old_login]['author_display_name'] ),
+ $user_id );
+ // really wish WP_Error were like an exception and that we
+ // could use try...catch...finally.
+ }
+ }
+
+ /*
+ * Failsafe: if the user_id was invalid, default to the current user.
+ *
+ * Cron note: can't use logged in user; instead default to the user
+ * ID stored in params, which should be the admin user who triggered
+ * the import. It is possible to override this from an external
+ * scope by calling $importer->set_param('admin_user_id', $a_value)
+ * after do_parse().
+ */
+ if ( ! isset( $this->author_mappings[$sanitized_old_login] ) ) {
+ if ( $old_id )
+ $this->processed_authors[$old_id] = (int) $this->get_param(
+ 'admin_user_id', 0 );
+ $this->author_mappings[$sanitized_old_login] = (int) $this->get_param(
+ 'admin_user_id', 0 );
+ }
+ }
</ins><span class="cx">
</span><ins>+ /*
+ * At this point, theoretically all authors should be mapped. However,
+ * emit a warning if the number of mapped authors is fewer than the
+ * number of authors identified from the import data.
+ */
+ if ( count( $this->processed_authors ) < count( $this->authors ) ) {
+ $this->warnings[] = new WP_Error(
+ 'import_process_author_mappings_warning',
+ __(
+ 'Some authors may not have been attributed correctly.',
+ 'wordpress-importer' ) );
+ }
+
+ $this->advance();
+ return true;
+ }
+
</ins><span class="cx"> /**
</span><span class="cx"> *
</span><span class="cx"> * @todo
</span><span class="lines">@@ -297,7 +426,7 @@
</span><span class="cx"> *
</span><span class="cx"> * @param string $key
</span><span class="cx"> * The meta key to check
</span><del>- * @return string bool key if we do want to import, false if not
</del><ins>+ * @return string|bool key if we do want to import, false if not
</ins><span class="cx"> */
</span><span class="cx"> public function is_valid_meta_key( $key ) {
</span><span class="cx"> // skip attachment metadata since we'll regenerate it from scratch
</span><span class="lines">@@ -351,4 +480,50 @@
</span><span class="cx"> public function get_warnings() {
</span><span class="cx"> return $this->warnings;
</span><span class="cx"> }
</span><ins>+
+ /**
+ * Stores parameters typically used for user input.
+ *
+ * Previously, some of these params would have been directly accessed with
+ * $_POST; now they are stored.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return boolean WP_Error if unsuccessful, an error object
+ */
+ public function set_param ( $name, $value ) {
+ if ( ! isset( $this->wxr_file ) ) {
+ // cannot set user options before WXR data is parsed
+ return new WP_Error( 'import_param_set_error',
+ __( 'Cannot set param without parsed data',
+ 'wordpress-importer' ) );
+ }
+ if ( ! is_array( $this->params[$this->wxr_file] ) )
+ $this->params[$this->wxr_file] = array();
+ $this->params[$this->wxr_file][$name] = $value;
+
+ return true;
+ }
+
+ /**
+ * Retrieves parameters typically used for user input.
+ *
+ * @param string $name
+ * @param mixed $default
+ * @return mixed WP_Error requested parameter, or if unsuccessful, an error
+ * object
+ */
+ public function get_param ( $name, $default = null ) {
+ if ( ! isset( $this->wxr_file ) ) {
+ // cannot get user options before WXR data is parsed
+ return new WP_Error( 'import_param_get_error',
+ __( 'Cannot get param without parsed data',
+ 'wordpress-importer' ) );
+ }
+ if ( isset( $this->params[$this->wxr_file] ) &&
+ isset( $this->params[$this->wxr_file][$name] ) )
+ return $this->params[$this->wxr_file][$name];
+
+ return $default;
+ }
</ins><span class="cx"> }
</span><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="2013frederickdingimportertrunkteststestclasswordpressimporterphp"></a>
<div class="modfile"><h4>Modified: 2013/frederickding/importer/trunk/tests/test-class-wordpress-importer.php (2136 => 2137)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/frederickding/importer/trunk/tests/test-class-wordpress-importer.php 2013-07-18 21:52:42 UTC (rev 2136)
+++ 2013/frederickding/importer/trunk/tests/test-class-wordpress-importer.php 2013-07-23 00:16:52 UTC (rev 2137)
</span><span class="lines">@@ -93,25 +93,42 @@
</span><span class="cx"> $this->assertEquals( 'finish', $instance->advance( 'finish' ) );
</span><span class="cx"> }
</span><span class="cx">
</span><del>- public function test_process_authors() {
- $instance = new WordPress_Importer();
-
- $this->assertFalse(self::call($instance, 'process_authors'));
</del><ins>+ public function test_get_error() {
+ // clean instance, no errors
+ $instance = new WordPress_Importer();
+
+ $this->assertNull($instance->get_error());
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- public function test_get_error() {
- // clean instance, no errors
</del><ins>+ public function test_get_warnings() {
+ // clean instance, no warnings
+ $instance = new WordPress_Importer();
+
+ $this->assertInternalType('array', $instance->get_warnings());
+ $this->assertEmpty($instance->get_warnings());
+ }
+
+ /**
+ * @depends test_get_error
+ */
+ public function test_process_authors_standalone() {
</ins><span class="cx"> $instance = new WordPress_Importer();
</span><span class="cx">
</span><del>- $this->assertNull($instance->get_error());
</del><ins>+ $this->assertFalse(self::call($instance, 'process_authors'));
+ $this->assertWPError($instance->get_error());
+
+ return $instance;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- public function test_get_warnings() {
- // clean instance, no warnings
- $instance = new WordPress_Importer();
</del><ins>+ /**
+ * @depends test_process_authors_standalone
+ * @depends test_get_error
+ */
+ public function test_process_author_mappings_standalone(WordPress_Importer $instance) {
</ins><span class="cx">
</span><del>- $this->assertInternalType('array', $instance->get_warnings());
- $this->assertEmpty($instance->get_warnings());
</del><ins>+ $this->assertFalse(self::call($instance, 'process_author_mappings'));
+ $this->assertWPError($instance->get_error());
+
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> /**
</span></span></pre>
</div>
</div>
</body>
</html>