Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
18.00% covered (danger)
18.00%
9 / 50
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Import
16.67% covered (danger)
16.67%
8 / 48
50.00% covered (danger)
50.00%
3 / 6
357.33
0.00% covered (danger)
0.00%
0 / 1
 name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 init_listeners
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 set_defaults
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sync_import_action
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 get_importer_name
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 get_calling_importer_class
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2/**
3 * Import sync module.
4 *
5 * @package automattic/jetpack-sync
6 */
7
8namespace Automattic\Jetpack\Sync\Modules;
9
10use Automattic\Jetpack\Sync\Settings;
11
12if ( ! defined( 'ABSPATH' ) ) {
13    exit( 0 );
14}
15
16/**
17 * Class to handle sync for imports.
18 */
19class Import extends Module {
20
21    /**
22     * Tracks which actions have already been synced for the import
23     * to prevent the same event from being triggered a second time.
24     *
25     * @var array
26     */
27    private $synced_actions = array();
28
29    /**
30     * A mapping of action types to sync action name.
31     * Keys are the name of the import action.
32     * Values are the resulting sync action.
33     *
34     * Note: import_done and import_end both intentionally map to
35     * jetpack_sync_import_end, as they both track the same type of action,
36     * the successful completion of an import. Different import plugins use
37     * differently named actions, and this is an attempt to consolidate.
38     *
39     * @var array
40     */
41    private static $import_sync_action_map = array(
42        'import_start' => 'jetpack_sync_import_start',
43        'import_done'  => 'jetpack_sync_import_end',
44        'import_end'   => 'jetpack_sync_import_end',
45    );
46
47    /**
48     * Sync module name.
49     *
50     * @access public
51     *
52     * @return string
53     */
54    public function name() {
55        return 'import';
56    }
57
58    /**
59     * Initialize imports action listeners.
60     *
61     * @access public
62     *
63     * @param callable $callable Action handler callable.
64     */
65    public function init_listeners( $callable ) {
66        add_action( 'export_wp', $callable );
67        add_action( 'jetpack_sync_import_start', $callable, 10, 2 );
68        add_action( 'jetpack_sync_import_end', $callable, 10, 2 );
69
70        // WordPress.
71        add_action( 'import_start', array( $this, 'sync_import_action' ) );
72
73        // Movable type, RSS, Livejournal.
74        add_action( 'import_done', array( $this, 'sync_import_action' ) );
75
76        // WordPress, Blogger, Livejournal, woo tax rate.
77        add_action( 'import_end', array( $this, 'sync_import_action' ) );
78    }
79
80    /**
81     * Set module defaults.
82     * Define an empty list of synced actions for us to fill later.
83     *
84     * @access public
85     */
86    public function set_defaults() {
87        $this->synced_actions = array();
88    }
89
90    /**
91     * Generic handler for import actions.
92     *
93     * @access public
94     *
95     * @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
96     */
97    public function sync_import_action( $importer ) {
98        $import_action = current_filter();
99        // Map action to event name.
100        $sync_action = self::$import_sync_action_map[ $import_action ];
101
102        // Only sync each action once per import.
103        if ( array_key_exists( $sync_action, $this->synced_actions ) && $this->synced_actions[ $sync_action ] ) {
104            return;
105        }
106
107        // Mark this action as synced.
108        $this->synced_actions[ $sync_action ] = true;
109
110        // Prefer self-reported $importer value.
111        if ( ! $importer ) {
112            // Fall back to inferring by calling class name.
113            $importer = self::get_calling_importer_class();
114        }
115
116        // Get $importer from known_importers.
117        $known_importers = Settings::get_setting( 'known_importers' );
118        if ( is_string( $importer ) && isset( $known_importers[ $importer ] ) ) {
119            $importer = $known_importers[ $importer ];
120        }
121
122        $importer_name = $this->get_importer_name( $importer );
123
124        switch ( $sync_action ) {
125            case 'jetpack_sync_import_start':
126                /**
127                 * Used for syncing the start of an import
128                 *
129                 * @since 1.6.3
130                 * @since-jetpack 7.3.0
131                 *
132                 * @module sync
133                 *
134                 * @param string $importer      Either a string reported by the importer, the class name of the importer, or 'unknown'.
135                 * @param string $importer_name The name reported by the importer, or 'Unknown Importer'.
136                 */
137                do_action( 'jetpack_sync_import_start', $importer, $importer_name );
138                break;
139
140            case 'jetpack_sync_import_end':
141                /**
142                 * Used for syncing the end of an import
143                 *
144                 * @since 1.6.3
145                 * @since-jetpack 7.3.0
146                 *
147                 * @module sync
148                 *
149                 * @param string $importer      Either a string reported by the importer, the class name of the importer, or 'unknown'.
150                 * @param string $importer_name The name reported by the importer, or 'Unknown Importer'.
151                 */
152                do_action( 'jetpack_sync_import_end', $importer, $importer_name );
153                break;
154        }
155    }
156
157    /**
158     * Retrieve the name of the importer.
159     *
160     * @access private
161     *
162     * @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
163     * @return string Name of the importer, or "Unknown Importer" if importer is unknown.
164     */
165    private function get_importer_name( $importer ) {
166        $importers = get_importers();
167        return isset( $importers[ $importer ] ) ? $importers[ $importer ][0] : 'Unknown Importer';
168    }
169
170    /**
171     * Determine the class that extends `WP_Importer` which is responsible for
172     * the current action. Designed to be used within an action handler.
173     *
174     * @access private
175     * @static
176     *
177     * @return string The name of the calling class, or 'unknown'.
178     */
179    private static function get_calling_importer_class() {
180        // If WP_Importer doesn't exist, neither will any importer that extends it.
181        if ( ! class_exists( 'WP_Importer', false ) ) {
182            return 'unknown';
183        }
184
185        $action    = current_filter();
186        $backtrace = debug_backtrace( 0 ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
187
188        $do_action_pos = -1;
189        $backtrace_len = count( $backtrace );
190        for ( $i = 0; $i < $backtrace_len; $i++ ) {
191            // Find the location in the stack of the calling action.
192            if ( 'do_action' === $backtrace[ $i ]['function'] && $action === $backtrace[ $i ]['args'][0] ) {
193                $do_action_pos = $i;
194                break;
195            }
196        }
197
198        // If the action wasn't called, the calling class is unknown.
199        if ( -1 === $do_action_pos ) {
200            return 'unknown';
201        }
202
203        // Continue iterating the stack looking for a caller that extends WP_Importer.
204        for ( $i = $do_action_pos + 1; $i < $backtrace_len; $i++ ) {
205            // If there is no class on the trace, continue.
206            if ( ! isset( $backtrace[ $i ]['class'] ) ) {
207                continue;
208            }
209
210            $class_name = $backtrace[ $i ]['class'];
211
212            // Check if the class extends WP_Importer.
213            if ( class_exists( $class_name, false ) ) {
214                $parents = class_parents( $class_name, false );
215                if ( $parents && in_array( 'WP_Importer', $parents, true ) ) {
216                    return $class_name;
217                }
218            }
219        }
220
221        // If we've exhausted the stack without a match, the calling class is unknown.
222        return 'unknown';
223    }
224}