Completed
Pull Request — master (#111)
by Luca
02:07
created

ContentImporter::import_content()   C

Complexity

Conditions 8
Paths 17

Size

Total Lines 78
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 23
nc 17
nop 2
dl 0
loc 78
rs 6.102
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace lloc\Msls\ContentImport;
4
5
use lloc\Msls\ContentImport\Importers\Importer;
6
use lloc\Msls\ContentImport\Importers\Map;
7
use lloc\Msls\MslsBlogCollection;
8
use lloc\Msls\MslsMain;
9
use lloc\Msls\MslsOptionsPost;
10
use lloc\Msls\MslsRegistryInstance;
11
12
/**
13
 * Class ContentImporter
14
 *
15
 * Handles the request for a content import.
16
 *
17
 * @package lloc\Msls\ContentImport
18
 */
19
class ContentImporter extends MslsRegistryInstance {
20
21
	/**
22
	 * @var MslsMain
23
	 */
24
	protected $main;
25
26
	/**
27
	 * @var ImportLogger
28
	 */
29
	protected $logger;
30
	/**
31
	 * @var Relations
32
	 */
33
	protected $relations;
34
35
	/**
36
	 * @var bool Whether the class should handle requests or not.
37
	 */
38
	protected $handle = true;
39
40
	/**
41
	 * @var int The ID of the post the class created while handling the request, if any.
42
	 */
43
	protected $has_created_post = 0;
44
45
	/**
46
	 * ContentImporter constructor.
47
	 *
48
	 * @param \lloc\Msls\MslsMain|null $main
49
	 */
50
	public function __construct( MslsMain $main = null ) {
51
		$this->main = $main ?: MslsMain::init();
52
	}
53
54
	/**
55
	 * @return \lloc\Msls\ContentImport\ImportLogger
56
	 */
57
	public function get_logger() {
58
		return $this->logger;
59
	}
60
61
	/**
62
	 * @param \lloc\Msls\ContentImport\ImportLogger $logger
63
	 */
64
	public function set_logger( $logger ) {
65
		$this->logger = $logger;
66
	}
67
68
	/**
69
	 * @return \lloc\Msls\ContentImport\Relations
70
	 */
71
	public function get_relations() {
72
		return $this->relations;
73
	}
74
75
	/**
76
	 * @param \lloc\Msls\ContentImport\Relations $relations
77
	 */
78
	public function set_relations( $relations ) {
79
		$this->relations = $relations;
80
	}
81
82
	/**
83
	 * Handles an import request happening during a post save or a template redirect.
84
	 *
85
	 * @param array|null $data
86
	 *
87
	 * @return array The updated, if needed, data array.
88
	 */
89
	public function handle_import( array $data = [] ) {
90
		if ( ! $this->pre_flight_check() || false === $sources = $this->parse_sources() ) {
91
			return $data;
92
		}
93
94
		list( $source_blog_id, $source_post_id ) = $sources;
95
96
		if ( $source_blog_id === get_current_blog_id() ) {
97
			return $data;
98
		}
99
100
		$source_lang  = MslsBlogCollection::get_blog_language( $source_blog_id );
101
		$dest_blog_id = get_current_blog_id();
102
		$dest_lang    = MslsBlogCollection::get_blog_language( get_current_blog_id() );
103
104
		$dest_post_id = $this->get_the_blog_post_ID( $dest_blog_id );
105
106
		if ( empty( $dest_post_id ) ) {
107
			return $data;
108
		}
109
110
		switch_to_blog( $source_blog_id );
0 ignored issues
show
introduced by
switch_to_blog is not something you should ever need to do in a VIP theme context. Instead use an API (XML-RPC, REST) to interact with other sites if needed.
Loading history...
111
		$source_post = get_post( $source_post_id );
112
		restore_current_blog();
113
114
		if ( ! $source_post instanceof \WP_Post ) {
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
115
			return $data;
116
		}
117
118
		$import_coordinates = new ImportCoordinates();
119
120
		$import_coordinates->source_blog_id = $source_blog_id;
121
		$import_coordinates->source_post_id = $source_post_id;
122
		$import_coordinates->dest_blog_id   = $dest_blog_id;
123
		$import_coordinates->dest_post_id   = $dest_post_id;
124
		$import_coordinates->source_post    = $source_post;
125
		$import_coordinates->source_lang    = $source_lang;
126
		$import_coordinates->dest_lang      = $dest_lang;
127
128
		$import_coordinates->parse_importers_from_request();
129
130
		$data = $this->import_content( $import_coordinates, $data );
131
132
		if ( $this->has_created_post ) {
133
			$this->update_inserted_blog_post_data($dest_blog_id, $dest_post_id, $data );
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
134
			$this->redirect_to_blog_post( $dest_blog_id, $dest_post_id );
135
		}
136
137
		return $data;
138
	}
139
140
	/**
141
	 * Whether the importer should run or not.
142
	 *
143
	 * @return bool
144
	 */
145
	protected function pre_flight_check( array $data = [] ) {
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Coding Style introduced by
pre_flight_check uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
146
		if ( ! $this->handle ) {
147
			return false;
148
		}
149
150
		if ( ! $this->main->verify_nonce() ) {
151
			return false;
152
		}
153
154
		if ( ! isset( $_POST['msls_import'] ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
155
			return false;
156
		}
157
158
		return true;
159
	}
160
161
	/**
162
	 * Parses the source blog and post IDs from the $_POST array validating them.
163
	 *
164
	 * @return array|bool
165
	 */
166
	public function parse_sources() {
0 ignored issues
show
Coding Style introduced by
parse_sources uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
167
		if ( ! isset( $_POST['msls_import'] ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
168
			return false;
169
		}
170
171
		$import_data = array_filter( explode( '|', trim( $_POST['msls_import'] ) ), 'is_numeric' );
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_POST
Loading history...
172
173
		if ( count( $import_data ) !== 2 ) {
0 ignored issues
show
introduced by
Found "!== 2". Use Yoda Condition checks, you must
Loading history...
174
			return false;
175
		}
176
177
		return array_map( 'intval', $import_data );
178
	}
179
180
	protected function get_the_blog_post_ID( $blog_id ) {
0 ignored issues
show
Coding Style introduced by
The function name get_the_blog_post_ID is in camel caps, but expected get_the_blog_post_i_d instead as per the coding standard.
Loading history...
Coding Style introduced by
get_the_blog_post_ID uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
181
		switch_to_blog( $blog_id );
0 ignored issues
show
introduced by
switch_to_blog is not something you should ever need to do in a VIP theme context. Instead use an API (XML-RPC, REST) to interact with other sites if needed.
Loading history...
182
183
		$id = get_the_ID();
184
185
		if ( ! empty( $id ) ) {
186
			restore_current_blog();
187
188
			return $id;
189
		}
190
191
		if ( isset( $_REQUEST['post'] ) && filter_var( $_REQUEST['post'], FILTER_VALIDATE_INT ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
192
			return (int) $_REQUEST['post'];
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
193
		}
194
195
		return $this->insert_blog_post( $blog_id, [ 'post_title' => 'MSLS Content Import Draft - ' . date( 'Y-m-d H:i:s' ) ] );
196
	}
197
198
	protected function insert_blog_post( $blog_id, array $data = [] ) {
199
		if ( empty( $data ) ) {
200
			return false;
201
		}
202
203
		switch_to_blog( $blog_id );
0 ignored issues
show
introduced by
switch_to_blog is not something you should ever need to do in a VIP theme context. Instead use an API (XML-RPC, REST) to interact with other sites if needed.
Loading history...
204
205
		$this->handle( false );
206
		if ( isset( $data['ID'] ) ) {
207
			$post_id = wp_update_post( $data );
208
		} else {
209
			$post_id = wp_insert_post( $data );
210
		}
211
		$this->handle( true );
212
213
		$this->has_created_post = $post_id ?: false;
214
215
		restore_current_blog();
216
217
		return $this->has_created_post;
218
	}
219
220
	public function handle( $handle ) {
221
		$this->handle = $handle;
222
223
		// also prevent MSLS from saving
224
		if ( false === $handle ) {
225
			add_action( 'msls_main_save', '__return_false' );
226
		} else {
227
			remove_action( 'msls_main_save', '__return_false' );
228
		}
229
	}
230
231
	/**
232
	 * Imports content according to the provided coordinates.
233
	 *
234
	 * @param       ImportCoordinates $import_coordinates
235
	 * @param array $post_fields An optional array of post fields; this can be
236
	 *                                             left empty if the method is not called as a consequence
237
	 *                                             of filtering the `wp_insert_post_data` filter.
238
	 *
239
	 * @return array An array of modified post fields.
240
	 */
241
	public function import_content( ImportCoordinates $import_coordinates, array $post_fields = [] ) {
242
		if ( ! $import_coordinates->validate() ) {
243
			return $post_fields;
244
		}
245
246
		/**
247
		 * Fires before the import runs.
248
		 *
249
		 * @param ImportCoordinates $import_coordinates
250
		 */
251
		do_action( 'msls_content_import_before_import', $import_coordinates );
252
253
		/**
254
		 * Filters the data before the import runs.
255
		 *
256
		 * @since TBD
257
		 *
258
		 * @param array $post_fields
259
		 * @param ImportCoordinates $import_coordinates
260
		 */
261
		$post_fields = apply_filters( 'msls_content_import_data_before_import', $post_fields, $import_coordinates );
262
263
		/**
264
		 * Filters the importers map before it's populated.
265
		 *
266
		 * Returning a non `null` value here will override the creation of the importers map completely
267
		 * and use the one returned in the filter.
268
		 *
269
		 * @param null $importers
270
		 * @param ImportCoordinates $import_coordinates
271
		 */
272
		$importers = apply_filters( 'msls_content_import_importers', null, $import_coordinates );
273
274
		if ( null === $importers ) {
275
			$importers = Map::instance()->make( $import_coordinates );
276
		}
277
278
		$this->logger    = $this->logger ?: new ImportLogger( $import_coordinates );
279
		$this->relations = $this->relations ?: new Relations( $import_coordinates );
280
281
		if ( ! empty( $importers ) && is_array( $importers ) ) {
282
			$source_post_id = $import_coordinates->source_post_id;
283
			$dest_lang      = $import_coordinates->dest_lang;
284
			$dest_post_id   = $import_coordinates->dest_post_id;
285
			$this->relations->should_create( MslsOptionsPost::create( $source_post_id ), $dest_lang, $dest_post_id );
0 ignored issues
show
Bug introduced by
It seems like \lloc\Msls\MslsOptionsPo...create($source_post_id) can be null; however, should_create() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
286
287
			foreach ( $importers as $key => $importer ) {
288
				/** @var Importer $importer */
289
				$post_fields = $importer->import( $post_fields );
290
				$this->logger->merge( $importer->get_logger() );
291
				$this->relations->merge( $importer->get_relations() );
292
			}
293
294
			$this->relations->create();
295
			$this->logger->save();
296
		}
297
298
		/**
299
		 * Fires after the import ran.
300
		 *
301
		 * @since TBD
302
		 *
303
		 * @param ImportCoordinates $import_coordinates
304
		 * @param ImportLogger $logger
305
		 * @param Relations $relations
306
		 */
307
		do_action( 'msls_content_import_after_import', $import_coordinates, $this->logger, $this->relations );
308
309
		/**
310
		 * Filters the data after the import ran.
311
		 *
312
		 * @param array $post_fields
313
		 * @param ImportCoordinates $import_coordinates
314
		 * @param ImportLogger $logger
315
		 * @param Relations $relations
316
		 */
317
		return apply_filters( 'msls_content_import_data_after_import', $post_fields, $import_coordinates, $this->logger, $this->relations );
318
	}
319
320
	/**
321
	 * @param array $data
322
	 * @param int $post_id
323
	 *
324
	 * @return array
325
	 */
326
	protected function update_inserted_blog_post_data($blog_id, $post_id, array $data ) {
327
		$data['ID']          = $post_id;
328
		$data['post_status'] = empty( $data['post_status'] ) || $data['post_status'] === 'auto-draft'
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $data['post_status'] (integer) and 'auto-draft' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
329
			? 'draft'
330
			: $data['post_status'];
331
		$this->insert_blog_post( $blog_id, $data );
332
333
		return $data;
334
	}
335
336
	protected function redirect_to_blog_post( $dest_blog_id, $post_id ) {
337
		switch_to_blog( $dest_blog_id );
0 ignored issues
show
introduced by
switch_to_blog is not something you should ever need to do in a VIP theme context. Instead use an API (XML-RPC, REST) to interact with other sites if needed.
Loading history...
338
		$edit_post_link = html_entity_decode( get_edit_post_link( $post_id ) );
339
		wp_redirect( $edit_post_link );
340
		die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method redirect_to_blog_post() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
341
	}
342
343
	/**
344
	 * Filters whether the post should be considered empty or not.
345
	 *
346
	 * Empty posts would not be saved to database but it's fine if in
347
	 * the context of a content import as it will be populated.
348
	 *
349
	 * @param bool $empty
350
	 *
351
	 * @return bool
352
	 */
353
	public function filter_empty( $empty ) {
0 ignored issues
show
Coding Style introduced by
filter_empty uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
354
		if ( ! $this->main->verify_nonce() ) {
355
			return $empty;
356
		}
357
358
		if ( ! isset( $_POST['msls_import'] ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
359
			return $empty;
360
		}
361
362
		return false;
363
	}
364
}