Rara One Click Demo Import - Version 1.0.0

Version Description

Download this release

Release Info

Developer raratheme
Plugin Icon 128x128 Rara One Click Demo Import
Version 1.0.0
Comparing to
See all releases

Version 1.0.0

assets/css/style.css ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Overriding WordPress native styles */
2
+
3
+ .rrdi h2 {
4
+ text-align: inherit;
5
+ }
6
+
7
+ .rrdi h2:first-child,
8
+ .rrdi h3:first-child {
9
+ margin-top: 0;
10
+ }
11
+
12
+ .rrdi hr {
13
+ margin: 2.62em 0;
14
+ }
15
+
16
+ .feature-section + hr {
17
+ margin-top: 0;
18
+ }
19
+
20
+ #wpbody select {
21
+ height: auto;
22
+ padding: .62em;
23
+ line-height: inherit;
24
+ }
25
+
26
+ .rrdi .notice {
27
+ display: block !important;
28
+ margin-top: 1.4em;
29
+ margin-bottom: 0;
30
+ }
31
+
32
+ /* Plugin elements */
33
+
34
+ .RRDI__demo-import-files {
35
+ width: 100%;
36
+ }
37
+
38
+ .RRDI__demo-import-preview-image-message {
39
+ font-style: italic;
40
+ }
41
+
42
+ /* Plugin title */
43
+
44
+ .RRDI__title:before {
45
+ width: auto;
46
+ height: auto;
47
+ font-size: inherit;
48
+ }
49
+
50
+ /* Plugin intro text */
51
+
52
+ .RRDI__intro-text ul {
53
+ padding: 0 4%;
54
+ list-style-type: square;
55
+ }
56
+
57
+ /* Plugin multi select import and Plugin file upload containers */
58
+
59
+ .RRDI__file-upload,
60
+ .RRDI__multi-select-import,
61
+ .RRDI__demo-import-notice:not(:empty) {
62
+ padding: 4%;
63
+ margin: 1.62em 0;
64
+ background-color: #ffffff;
65
+ border: 1px solid #e5e5e5;
66
+ }
67
+
68
+ .RRDI__file-upload {
69
+ margin: 0;
70
+ margin-bottom: -1px;
71
+ }
72
+
73
+ .RRDI__file-upload span {
74
+ font-size: .81em;
75
+ font-weight: normal;
76
+ opacity: .66;
77
+ }
78
+
79
+ .RRDI__demo-import-notice:not(:empty) {
80
+ border: 0;
81
+ border-left: 4px solid #00a0d2;
82
+ -webkit-box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
83
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
84
+ }
85
+
86
+ [dir="rtl"] .RRDI__demo-import-notice:not(:empty) {
87
+ border: 0;
88
+ border-right: 4px solid #00a0d2;
89
+ }
90
+
91
+ /* Plugin button */
92
+
93
+ .RRDI__button-container {
94
+ margin-top: 1.62em;
95
+ }
96
+
97
+ /* AJAX loader */
98
+
99
+ .RRDI__ajax-loader {
100
+ font-size: 1.5em;
101
+ display: none;
102
+ }
103
+
104
+ .RRDI__ajax-loader .spinner {
105
+ display: inline-block;
106
+ float: none;
107
+ visibility: visible;
108
+ margin-bottom: 6px;
109
+ }
assets/js/script.js ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery( function ( $ ) {
2
+
3
+ $( '.js-rrdi-import-data' ).on( 'click', function () {
4
+ // Reset response div content.
5
+ $( '.js-rrdi-ajax-response' ).empty();
6
+
7
+ // Prepare data for the AJAX call
8
+ var data = new FormData();
9
+ data.append( 'action', 'rrdi_import_demo_data' );
10
+ data.append( 'security', rrdi.ajax_nonce );
11
+ data.append( 'selected', $( '#RRDI__demo-import-files' ).val() );
12
+ if ( $('#RRDI__content-file-upload').length ) {
13
+ data.append( 'content_file', $('#RRDI__content-file-upload')[0].files[0] );
14
+ }
15
+ if ( $('#RRDI__widget-file-upload').length ) {
16
+ data.append( 'widget_file', $('#RRDI__widget-file-upload')[0].files[0] );
17
+ }
18
+ if ( $('#RRDI__customizer-file-upload').length ) {
19
+ data.append( 'customizer_file', $('#RRDI__customizer-file-upload')[0].files[0] );
20
+ }
21
+
22
+ // AJAX call to import everything (content, widgets, before/after setup)
23
+ ajaxCall( data );
24
+
25
+ });
26
+
27
+ function ajaxCall( data ) {
28
+ $.ajax({
29
+ method: 'POST',
30
+ url: rrdi.ajax_url,
31
+ data: data,
32
+ contentType: false,
33
+ processData: false,
34
+ beforeSend: function() {
35
+ $( '.js-rrdi-ajax-loader' ).show();
36
+ }
37
+ })
38
+ .done( function( response ) {
39
+
40
+ if ( 'undefined' !== typeof response.status && 'newAJAX' === response.status ) {
41
+ ajaxCall( data );
42
+ }
43
+ else if ( 'undefined' !== typeof response.message ) {
44
+ $( '.js-rrdi-ajax-response' ).append( '<p>' + response.message + '</p>' );
45
+ $( '.js-rrdi-ajax-loader' ).hide();
46
+ }
47
+ else {
48
+ $( '.js-rrdi-ajax-response' ).append( '<div class="notice notice-error is-dismissible"><p>' + response + '</p></div>' );
49
+ $( '.js-rrdi-ajax-loader' ).hide();
50
+ }
51
+ })
52
+ .fail( function( error ) {
53
+ $( '.js-rrdi-ajax-response' ).append( '<div class="notice notice-error is-dismissible"><p>Error: ' + error.statusText + ' (' + error.status + ')' + '</p></div>' );
54
+ $( '.js-rrdi-ajax-loader' ).hide();
55
+ });
56
+ }
57
+
58
+ // Switch preview images on select change event, but only if the img element .js-rrdi-preview-image exists.
59
+ // Also switch the import notice (if it exists).
60
+ $( '#RRDI__demo-import-files' ).on( 'change', function(){
61
+ if ( $( '.js-rrdi-preview-image' ).length ) {
62
+
63
+ // Attempt to change the image, else display message for missing image.
64
+ var currentFilePreviewImage = rrdi.import_files[ this.value ]['import_preview_image_url'] || '';
65
+ $( '.js-rrdi-preview-image' ).prop( 'src', currentFilePreviewImage );
66
+ $( '.js-rrdi-preview-image-message' ).html( '' );
67
+
68
+ if ( '' === currentFilePreviewImage ) {
69
+ $( '.js-rrdi-preview-image-message' ).html( rrdi.texts.missing_preview_image );
70
+ }
71
+ }
72
+
73
+ // Update import notice.
74
+ var currentImportNotice = rrdi.import_files[ this.value ]['import_notice'] || '';
75
+ $( '.js-rrdi-demo-import-notice' ).html( currentImportNotice );
76
+ });
77
+
78
+ });
includes/class-rrdi-customizer-option.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * A class that extends WP_Customize_Setting so we can access
4
+ * the protected updated method when importing options.
5
+ *
6
+ * Used in the Customizer importer.
7
+ *
8
+ * @since 1.1.1
9
+ * @package rara-demo-import
10
+ */
11
+
12
+ final class RRDI_Customizer_Option extends WP_Customize_Setting {
13
+
14
+ /**
15
+ * Import an option value for this setting.
16
+ *
17
+ * @since 1.1.1
18
+ * @param mixed $value The option value.
19
+ * @return void
20
+ */
21
+ public function import( $value ) {
22
+ $this->update( $value );
23
+ }
24
+ }
includes/class-rrdi-helpers.php ADDED
@@ -0,0 +1,610 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Static functions used in the rrdi plugin.
4
+ *
5
+ * @package rara-demo-import
6
+ */
7
+
8
+ /**
9
+ * Class with static helper functions.
10
+ */
11
+ class RRDI_Helpers {
12
+
13
+ /**
14
+ * Filter through the array of import files and get rid of those who do not comply.
15
+ *
16
+ * @param array $import_files list of arrays with import file details.
17
+ * @return array list of filtered arrays.
18
+ */
19
+ public static function validate_import_file_info( $import_files ) {
20
+ $filtered_import_file_info = array();
21
+
22
+ foreach ( $import_files as $import_file ) {
23
+ if ( self::is_import_file_info_format_correct( $import_file ) ) {
24
+ $filtered_import_file_info[] = $import_file;
25
+ }
26
+ }
27
+
28
+ return $filtered_import_file_info;
29
+ }
30
+
31
+
32
+ /**
33
+ * Helper function: a simple check for valid import file format.
34
+ *
35
+ * @param array $import_file_info array with import file details.
36
+ * @return boolean
37
+ */
38
+ private static function is_import_file_info_format_correct( $import_file_info ) {
39
+ if ( ( empty( $import_file_info['import_file_url'] ) && empty( $import_file_info['local_import_file'] ) ) || empty( $import_file_info['import_file_name'] ) ) {
40
+ return false;
41
+ }
42
+
43
+ return true;
44
+ }
45
+
46
+
47
+ /**
48
+ * Download import files. Content .xml and widgets .wie|.json files.
49
+ *
50
+ * @param array $import_file_info array with import file details.
51
+ * @param string $start_date string of date and time.
52
+ * @return array|WP_Error array of paths to the downloaded files or WP_Error object with error message.
53
+ */
54
+ public static function download_import_files( $import_file_info, $start_date = '' ) {
55
+
56
+ $downloaded_files = array();
57
+ $upload_dir = wp_upload_dir();
58
+ $upload_path = apply_filters( 'rrdi/upload_file_path', trailingslashit( $upload_dir['path'] ) );
59
+
60
+ // ----- Set content file path -----
61
+ // Check if 'import_file_url' is not defined. That would mean a local file.
62
+ if ( empty( $import_file_info['import_file_url'] ) ) {
63
+ if ( file_exists( $import_file_info['local_import_file'] ) ) {
64
+ $downloaded_files['content'] = $import_file_info['local_import_file'];
65
+ }
66
+ else {
67
+ return new WP_Error(
68
+ 'url_or_local_file_not_defined',
69
+ sprintf(
70
+ __( '"import_file_url" or "local_import_file" for %s%s%s are not defined!', 'rara-demo-import' ),
71
+ '<strong>',
72
+ $$import_file_info['import_file_name'],
73
+ '</strong>'
74
+ )
75
+ );
76
+ }
77
+ }
78
+ else {
79
+
80
+ // Retrieve demo data content from the URL.
81
+ $demo_import_content = self::get_content_from_url( $import_file_info['import_file_url'], $import_file_info['import_file_name'] );
82
+
83
+ // Return from this function if there was an error.
84
+ if ( is_wp_error( $demo_import_content ) ) {
85
+ return $demo_import_content;
86
+ }
87
+
88
+ // Setup filename path to save the data content.
89
+ $demo_import_file_path = $upload_path . apply_filters( 'rrdi/downloaded_content_file_prefix', 'demo-content-import-file_' ) . $start_date . apply_filters( 'rrdi/downloaded_content_file_suffix_and_file_extension', '.xml' );
90
+
91
+ // Write data content to the file and return the file path on successful write.
92
+ $downloaded_files['content'] = self::write_to_file( $demo_import_content, $demo_import_file_path );
93
+
94
+ // Return from this function if there was an error.
95
+ if ( is_wp_error( $downloaded_files['content'] ) ) {
96
+ return $downloaded_files['content'];
97
+ }
98
+ }
99
+
100
+ // ----- Set widget file path -----
101
+ // Get widgets file as well. If defined!
102
+ if ( ! empty( $import_file_info['import_widget_file_url'] ) ) {
103
+
104
+ // Retrieve widget content from the URL.
105
+ $demo_import_widgets_content = self::get_content_from_url( $import_file_info['import_widget_file_url'], $import_file_info['import_file_name'] );
106
+
107
+ // Return from this function if there was an error.
108
+ if ( is_wp_error( $demo_import_widgets_content ) ) {
109
+ return $demo_import_widgets_content;
110
+ }
111
+
112
+ // Setup filename path to save the widget content.
113
+ $import_widgets_file_path = $upload_path . apply_filters( 'rrdi/downloaded_widgets_file_prefix', 'demo-widgets-import-file_' ) . $start_date . apply_filters( 'rrdi/downloaded_widgets_file_suffix_and_file_extension', '.json' );
114
+
115
+ // Write widget content to the file and return the file path on successful write.
116
+ $downloaded_files['widgets'] = self::write_to_file( $demo_import_widgets_content, $import_widgets_file_path );
117
+
118
+ // Return from this function if there was an error.
119
+ if ( is_wp_error( $downloaded_files['widgets'] ) ) {
120
+ return $downloaded_files['widgets'];
121
+ }
122
+ }
123
+ else if ( ! empty( $import_file_info['local_import_widget_file'] ) ) {
124
+ if ( file_exists( $import_file_info['local_import_widget_file'] ) ) {
125
+ $downloaded_files['widgets'] = $import_file_info['local_import_widget_file'];
126
+ }
127
+ }
128
+
129
+ // ----- Set customizer file path -----
130
+ // Get customizer import file as well. If defined!
131
+ if ( ! empty( $import_file_info['import_customizer_file_url'] ) ) {
132
+
133
+ // Retrieve customizer content from the URL.
134
+ $demo_import_customizer_content = self::get_content_from_url( $import_file_info['import_customizer_file_url'], $import_file_info['import_file_name'] );
135
+
136
+ // Return from this function if there was an error.
137
+ if ( is_wp_error( $demo_import_customizer_content ) ) {
138
+ return $demo_import_customizer_content;
139
+ }
140
+
141
+ // Setup filename path to save the customizer content.
142
+ $import_customizer_file_path = $upload_path . apply_filters( 'rrdi/downloaded_customizer_file_prefix', 'demo-customizer-import-file_' ) . $start_date . apply_filters( 'rrdi/downloaded_customizer_file_suffix_and_file_extension', '.dat' );
143
+
144
+ // Write customizer content to the file and return the file path on successful write.
145
+ $downloaded_files['customizer'] = self::write_to_file( $demo_import_customizer_content, $import_customizer_file_path );
146
+
147
+ // Return from this function if there was an error.
148
+ if ( is_wp_error( $downloaded_files['customizer'] ) ) {
149
+ return $downloaded_files['customizer'];
150
+ }
151
+ }
152
+ else if ( ! empty( $import_file_info['local_import_customizer_file'] ) ) {
153
+ if ( file_exists( $import_file_info['local_import_customizer_file'] ) ) {
154
+ $downloaded_files['customizer'] = $import_file_info['local_import_customizer_file'];
155
+ }
156
+ }
157
+
158
+ return $downloaded_files;
159
+ }
160
+
161
+
162
+ /**
163
+ * Helper function: get content from an url.
164
+ *
165
+ * @param string $url URL to the content file.
166
+ * @param string $file_name optional, name of the file (used in the error reports).
167
+ * @return string|WP_Error, content from the URL or WP_Error object with error message
168
+ */
169
+ private static function get_content_from_url( $url, $file_name = 'Import file' ) {
170
+
171
+ // Test if the URL to the file is defined.
172
+ if ( empty( $url ) ) {
173
+ return new WP_Error(
174
+ 'url_not_defined',
175
+ sprintf(
176
+ __( 'URL for %s%s%s file is not defined!', 'rara-demo-import' ),
177
+ '<strong>',
178
+ $file_name,
179
+ '</strong>'
180
+ )
181
+ );
182
+ }
183
+
184
+ // Get file content from the server.
185
+ $response = wp_remote_get(
186
+ $url,
187
+ array( 'timeout' => apply_filters( 'rrdi/timeout_for_downloading_import_file', 20 ) )
188
+ );
189
+
190
+ if ( is_wp_error( $response ) || 200 !== $response['response']['code'] ) {
191
+
192
+ // Collect the right format of error data (array or WP_Error).
193
+ $response_error = self::get_error_from_response( $response );
194
+
195
+ return new WP_Error(
196
+ 'file_fetching_error',
197
+ sprintf(
198
+ __( 'An error occurred while fetching %s%s%s file from the server!%sReason: %s - %s.', 'rara-demo-import' ),
199
+ '<strong>',
200
+ $file_name,
201
+ '</strong>',
202
+ '<br>',
203
+ $response_error['error_code'],
204
+ $response_error['error_message']
205
+ ) . '<br>' .
206
+ apply_filters( 'rrdi/message_after_file_fetching_error', '' )
207
+ );
208
+ }
209
+
210
+ // Return content retrieved from the URL.
211
+ return wp_remote_retrieve_body( $response );
212
+ }
213
+
214
+
215
+ /**
216
+ * Write content to a file.
217
+ *
218
+ * @param string $content content to be saved to the file.
219
+ * @param string $file_path file path where the content should be saved.
220
+ * @return string|WP_Error path to the saved file or WP_Error object with error message.
221
+ */
222
+ public static function write_to_file( $content, $file_path ) {
223
+
224
+ // Verify WP file-system credentials.
225
+ $verified_credentials = self::check_wp_filesystem_credentials();
226
+
227
+ if ( is_wp_error( $verified_credentials ) ) {
228
+ return $verified_credentials;
229
+ }
230
+
231
+ // By this point, the $wp_filesystem global should be working, so let's use it to create a file.
232
+ global $wp_filesystem;
233
+
234
+ if ( ! $wp_filesystem->put_contents( $file_path, $content ) ) {
235
+ return new WP_Error(
236
+ 'failed_writing_file_to_server',
237
+ sprintf(
238
+ __( 'An error occurred while writing file to your server! Tried to write a file to: %s%s.', 'rara-demo-import' ),
239
+ '<br>',
240
+ $file_path
241
+ )
242
+ );
243
+ }
244
+
245
+ // Return the file path on successful file write.
246
+ return $file_path;
247
+ }
248
+
249
+
250
+ /**
251
+ * Append content to the file.
252
+ *
253
+ * @param string $content content to be saved to the file.
254
+ * @param string $file_path file path where the content should be saved.
255
+ * @param string $separator_text separates the existing content of the file with the new content.
256
+ * @return boolean|WP_Error, path to the saved file or WP_Error object with error message.
257
+ */
258
+ public static function append_to_file( $content, $file_path, $separator_text = '' ) {
259
+
260
+ // Verify WP file-system credentials.
261
+ $verified_credentials = self::check_wp_filesystem_credentials();
262
+
263
+ if ( is_wp_error( $verified_credentials ) ) {
264
+ return $verified_credentials;
265
+ }
266
+
267
+ // By this point, the $wp_filesystem global should be working, so let's use it to create a file.
268
+ global $wp_filesystem;
269
+
270
+ $existing_data = '';
271
+ if ( file_exists( $file_path ) ) {
272
+ $existing_data = $wp_filesystem->get_contents( $file_path );
273
+ }
274
+
275
+ // Style separator.
276
+ $separator = PHP_EOL . '---' . $separator_text . '---' . PHP_EOL;
277
+
278
+ if ( ! $wp_filesystem->put_contents( $file_path, $existing_data . $separator . $content . PHP_EOL ) ) {
279
+ return new WP_Error(
280
+ 'failed_writing_file_to_server',
281
+ sprintf(
282
+ __( 'An error occurred while writing file to your server! Tried to write a file to: %s%s.', 'rara-demo-import' ),
283
+ '<br>',
284
+ $file_path
285
+ )
286
+ );
287
+ }
288
+
289
+ return true;
290
+ }
291
+
292
+
293
+ /**
294
+ * Get data from a file
295
+ *
296
+ * @param string $file_path file path where the content should be saved.
297
+ * @return string $data, content of the file or WP_Error object with error message.
298
+ */
299
+ public static function data_from_file( $file_path ) {
300
+
301
+ // Verify WP file-system credentials.
302
+ $verified_credentials = self::check_wp_filesystem_credentials();
303
+
304
+ if ( is_wp_error( $verified_credentials ) ) {
305
+ return $verified_credentials;
306
+ }
307
+
308
+ // By this point, the $wp_filesystem global should be working, so let's use it to read a file.
309
+ global $wp_filesystem;
310
+
311
+ $data = $wp_filesystem->get_contents( $file_path );
312
+
313
+ if ( ! $data ) {
314
+ return new WP_Error(
315
+ 'failed_reading_file_from_server',
316
+ sprintf(
317
+ __( 'An error occurred while reading a file from your server! Tried reading file from path: %s%s.', 'rara-demo-import' ),
318
+ '<br>',
319
+ $file_path
320
+ )
321
+ );
322
+ }
323
+
324
+ // Return the file data.
325
+ return $data;
326
+ }
327
+
328
+
329
+ /**
330
+ * Helper function: check for WP file-system credentials needed for reading and writing to a file.
331
+ *
332
+ * @return boolean|WP_Error
333
+ */
334
+ private static function check_wp_filesystem_credentials() {
335
+
336
+ // Check if the file-system method is 'direct', if not display an error.
337
+ if ( ! ( 'direct' === get_filesystem_method() ) ) {
338
+ return new WP_Error(
339
+ 'no_direct_file_access',
340
+ sprintf(
341
+ __( 'This WordPress page does not have %sdirect%s write file access. This plugin needs it in order to save the demo import xml file to the upload directory of your site. You can change this setting with these instructions: %s.', 'rara-demo-import' ),
342
+ '<strong>',
343
+ '</strong>',
344
+ '<a href="http://gregorcapuder.com/wordpress-how-to-set-direct-filesystem-method/" target="_blank">How to set <strong>direct</strong> filesystem method</a>'
345
+ )
346
+ );
347
+ }
348
+
349
+ // Get plugin page settings.
350
+ $plugin_page_setup = apply_filters( 'rrdi/plugin_page_setup', array(
351
+ 'parent_slug' => 'themes.php',
352
+ 'page_title' => esc_html__( 'Rara One Click Demo Import' , 'rara-demo-import' ),
353
+ 'menu_title' => esc_html__( 'Import Demo Data' , 'rara-demo-import' ),
354
+ 'capability' => 'import',
355
+ 'menu_slug' => 'rara-demo-import',
356
+ )
357
+ );
358
+
359
+ // Get user credentials for WP file-system API.
360
+ $demo_import_page_url = wp_nonce_url( $plugin_page_setup['parent_slug'] . '?page=' . $plugin_page_setup['menu_slug'], $plugin_page_setup['menu_slug'] );
361
+
362
+ if ( false === ( $creds = request_filesystem_credentials( $demo_import_page_url, '', false, false, null ) ) ) {
363
+ return new WP_error(
364
+ 'filesystem_credentials_could_not_be_retrieved',
365
+ __( 'An error occurred while retrieving reading/writing permissions to your server (could not retrieve WP filesystem credentials)!', 'rara-demo-import' )
366
+ );
367
+ }
368
+
369
+ // Now we have credentials, try to get the wp_filesystem running.
370
+ if ( ! WP_Filesystem( $creds ) ) {
371
+ return new WP_Error(
372
+ 'wrong_login_credentials',
373
+ __( 'Your WordPress login credentials don\'t allow to use WP_Filesystem!', 'rara-demo-import' )
374
+ );
375
+ }
376
+
377
+ return true;
378
+ }
379
+
380
+
381
+ /**
382
+ * Helper function: get the right format of response errors
383
+ *
384
+ * @param array|WP_Error $response array or WP_Error.
385
+ * @return array, with error code and error message.
386
+ */
387
+ private static function get_error_from_response( $response ) {
388
+ $response_error = array();
389
+
390
+ if ( is_array( $response ) ) {
391
+ $response_error['error_code'] = $response['response']['code'];
392
+ $response_error['error_message'] = $response['response']['message'];
393
+ }
394
+ else {
395
+ $response_error['error_code'] = $response->get_error_code();
396
+ $response_error['error_message'] = $response->get_error_message();
397
+ }
398
+
399
+ return $response_error;
400
+ }
401
+
402
+
403
+ /**
404
+ * Get log file path
405
+ *
406
+ * @param string $start_date date|time|timestamp to use in the log filename.
407
+ * @return string, path to the log file
408
+ */
409
+ public static function get_log_path( $start_date = '' ) {
410
+
411
+ $upload_dir = wp_upload_dir();
412
+ $upload_path = apply_filters( 'rrdi/upload_file_path', trailingslashit( $upload_dir['path'] ) );
413
+
414
+ $log_path = $upload_path . apply_filters( 'rrdi/log_file_prefix', 'log_file_' ) . $start_date . apply_filters( 'rrdi/log_file_suffix_and_file_extension', '.txt' );
415
+
416
+ self::register_file_as_media_attachment( $log_path );
417
+
418
+ return $log_path;
419
+ }
420
+
421
+
422
+ /**
423
+ * Register file as attachment to the Media page.
424
+ *
425
+ * @param string $log_path log file path.
426
+ * @return void
427
+ */
428
+ public static function register_file_as_media_attachment( $log_path ) {
429
+
430
+ // Check the type of file.
431
+ $log_mimes = array( 'txt' => 'text/plain' );
432
+ $filetype = wp_check_filetype( basename( $log_path ), apply_filters( 'rrdi/file_mimes', $log_mimes ) );
433
+
434
+ // Prepare an array of post data for the attachment.
435
+ $attachment = array(
436
+ 'guid' => self::get_log_url( $log_path ),
437
+ 'post_mime_type' => $filetype['type'],
438
+ 'post_title' => apply_filters( 'rrdi/attachment_prefix', esc_html__( 'Rara One Click Demo Import - ', 'rara-demo-import' ) ) . preg_replace( '/\.[^.]+$/', '', basename( $log_path ) ),
439
+ 'post_content' => '',
440
+ 'post_status' => 'inherit',
441
+ );
442
+
443
+ // Insert the file as attachment in Media page.
444
+ $attach_id = wp_insert_attachment( $attachment, $log_path );
445
+ }
446
+
447
+
448
+ /**
449
+ * Get log file url
450
+ *
451
+ * @param string $log_path log path to use for the log filename.
452
+ * @return string, url to the log file.
453
+ */
454
+ public static function get_log_url( $log_path ) {
455
+
456
+ $upload_dir = wp_upload_dir();
457
+ $upload_url = apply_filters( 'rrdi/upload_file_url', trailingslashit( $upload_dir['url'] ) );
458
+
459
+ return $upload_url . basename( $log_path );
460
+ }
461
+
462
+
463
+ /**
464
+ * Check if the AJAX call is valid.
465
+ */
466
+ public static function verify_ajax_call() {
467
+
468
+ check_ajax_referer( 'rrdi-ajax-verification', 'security' );
469
+
470
+ // Check if user has the WP capability to import data.
471
+ if ( ! current_user_can( 'import' ) ) {
472
+ wp_die(
473
+ sprintf(
474
+ __( '%sYour user role isn\'t high enough. You don\'t have permission to import demo data.%s', 'rara-demo-import' ),
475
+ '<div class="notice notice-error"><p>',
476
+ '</p></div>'
477
+ )
478
+ );
479
+ }
480
+ }
481
+
482
+
483
+ /**
484
+ * Process uploaded files and return the paths to these files.
485
+ *
486
+ * @param array $uploaded_files $_FILES array form an AJAX request.
487
+ * @param string $log_file_path path to the log file.
488
+ * @return array of paths to the content import and widget import files.
489
+ */
490
+ public static function process_uploaded_files( $uploaded_files, $log_file_path ) {
491
+
492
+ // Variable holding the paths to the uploaded files.
493
+ $selected_import_files = array();
494
+
495
+ // Upload settings to disable form and type testing for AJAX uploads.
496
+ $upload_overrides = array(
497
+ 'test_form' => false,
498
+ 'test_type' => false,
499
+ );
500
+
501
+ // Handle demo content and widgets file upload.
502
+ $content_file_info = wp_handle_upload( $_FILES['content_file'], $upload_overrides );
503
+ $widget_file_info = wp_handle_upload( $_FILES['widget_file'], $upload_overrides );
504
+ $customizer_file_info = wp_handle_upload( $_FILES['customizer_file'], $upload_overrides );
505
+
506
+ if ( empty( $content_file_info['file'] ) || isset( $content_file_info['error'] ) ) {
507
+
508
+ // Write error to log file and send an AJAX response with the error.
509
+ self::log_error_and_send_ajax_response(
510
+ __( 'Please upload XML file for content import. If you want to import widgets or customizer settings only, please use Widget Importer & Exporter or the Customizer Export/Import plugin.', 'rara-demo-import' ),
511
+ $log_file_path,
512
+ esc_html__( 'Upload files', 'rara-demo-import' )
513
+ );
514
+ }
515
+
516
+ // Set uploaded content file.
517
+ $selected_import_files['content'] = $content_file_info['file'];
518
+
519
+ // Process widget import file.
520
+ if ( $widget_file_info && ! isset( $widget_file_info['error'] ) ) {
521
+
522
+ // Set uploaded widget file.
523
+ $selected_import_files['widgets'] = $widget_file_info['file'];
524
+ }
525
+ else {
526
+
527
+ // Add this error to log file.
528
+ $log_added = self::append_to_file(
529
+ sprintf(
530
+ __( 'Widget file was not uploaded. Error: %s', 'rara-demo-import' ),
531
+ $widget_file_info['error']
532
+ ),
533
+ $log_file_path,
534
+ esc_html__( 'Upload files' , 'rara-demo-import' )
535
+ );
536
+ }
537
+
538
+ // Process Customizer import file.
539
+ if ( $customizer_file_info && ! isset( $customizer_file_info['error'] ) ) {
540
+
541
+ // Set uploaded widget file.
542
+ $selected_import_files['customizer'] = $customizer_file_info['file'];
543
+ }
544
+ else {
545
+
546
+ // Add this error to log file.
547
+ $log_added = self::append_to_file(
548
+ sprintf(
549
+ __( 'Customizer file was not uploaded. Error: %s', 'rara-demo-import' ),
550
+ $customizer_file_info['error']
551
+ ),
552
+ $log_file_path,
553
+ esc_html__( 'Upload files' , 'rara-demo-import' )
554
+ );
555
+ }
556
+
557
+ // Add this message to log file.
558
+ $log_added = self::append_to_file(
559
+ __( 'The import files were successfully uploaded!', 'rara-demo-import' ) . self::import_file_info( $selected_import_files ),
560
+ $log_file_path,
561
+ esc_html__( 'Upload files' , 'rara-demo-import' )
562
+ );
563
+
564
+ // Return array with paths of uploaded files.
565
+ return $selected_import_files;
566
+ }
567
+
568
+
569
+ /**
570
+ * Get import file information and max execution time.
571
+ *
572
+ * @param array $selected_import_files array of selected import files.
573
+ */
574
+ public static function import_file_info( $selected_import_files ) {
575
+ return PHP_EOL .
576
+ sprintf(
577
+ __( 'Initial max execution time = %s', 'rara-demo-import' ),
578
+ ini_get( 'max_execution_time' )
579
+ ) . PHP_EOL .
580
+ sprintf(
581
+ __( 'Files info:%1$sSite URL = %2$s%1$sData file = %3$s%1$sWidget file = %4$s%1$sCustomizer file = %5$s', 'rara-demo-import' ),
582
+ PHP_EOL,
583
+ get_site_url(),
584
+ $selected_import_files['content'],
585
+ empty( $selected_import_files['widgets'] ) ? esc_html__( 'not defined!', 'rara-demo-import' ) : $selected_import_files['widgets'],
586
+ empty( $selected_import_files['customizer'] ) ? esc_html__( 'not defined!', 'rara-demo-import' ) : $selected_import_files['customizer']
587
+ );
588
+ }
589
+
590
+
591
+ /**
592
+ * Write the error to the log file and send the AJAX response.
593
+ *
594
+ * @param string $error_text text to display in the log file and in the AJAX response.
595
+ * @param string $log_file_path path to the log file.
596
+ * @param string $separator title separating the old and new content.
597
+ */
598
+ public static function log_error_and_send_ajax_response( $error_text, $log_file_path, $separator = '' ) {
599
+
600
+ // Add this error to log file.
601
+ $log_added = self::append_to_file(
602
+ $error_text,
603
+ $log_file_path,
604
+ $separator
605
+ );
606
+
607
+ // Send JSON Error response to the AJAX call.
608
+ wp_send_json( $error_text );
609
+ }
610
+ }
includes/class-rrdi-importer.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class for declaring the importer used in the Rara One Click Demo Import plugin
4
+ *
5
+ * @package rara-demo-import
6
+ */
7
+
8
+ class RRDI_Importer {
9
+
10
+ private $importer;
11
+
12
+ public function __construct( $importer_options = array(), $logger = null ) {
13
+
14
+ // Include files that are needed for WordPress Importer v2.
15
+ $this->include_required_files();
16
+
17
+ // Set the WordPress Importer v2 as the importer used in this plugin.
18
+ // More: https://github.com/humanmade/WordPress-Importer.
19
+ $this->importer = new RRDI_WXR_Importer( $importer_options );
20
+
21
+ // Set logger to the importer.
22
+ if ( ! empty( $logger ) ) {
23
+ $this->set_logger( $logger );
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Include required files.
29
+ */
30
+ private function include_required_files() {
31
+ defined( 'WP_LOAD_IMPORTERS' ) || define( 'WP_LOAD_IMPORTERS', true );
32
+ require ABSPATH . '/wp-admin/includes/class-wp-importer.php';
33
+ require RRDI_PATH . 'includes/class-rrdi-wxr-importer.php';
34
+ }
35
+
36
+ /**
37
+ * Imports content from a WordPress export file.
38
+ *
39
+ * @param string $data_file path to xml file, file with WordPress export data.
40
+ */
41
+ public function import( $data_file ) {
42
+ $this->importer->import( $data_file );
43
+ }
44
+
45
+ /**
46
+ * Set the logger used in the import
47
+ *
48
+ * @param object $logger logger instance.
49
+ */
50
+ public function set_logger( $logger ) {
51
+ $this->importer->set_logger( $logger );
52
+ }
53
+
54
+ /**
55
+ * Get all protected variables from the HM_WXR_Importer needed for continuing the import.
56
+ */
57
+ public function get_importer_data() {
58
+ return $this->importer->get_importer_data();
59
+ }
60
+
61
+ /**
62
+ * Sets all protected variables from the HM_WXR_Importer needed for continuing the import.
63
+ *
64
+ * @param array $data with set variables.
65
+ */
66
+ public function set_importer_data( $data ) {
67
+ $this->importer->set_importer_data( $data );
68
+ }
69
+ }
includes/class-rrdi-include-files.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Include files.
4
+ *
5
+ * @package rara-demo-import
6
+ */
7
+
8
+ /**
9
+ * Rara One Click Demo Import class, so we don't have to worry about namespaces.
10
+ */
11
+ class RRDI_Theme_Demo_Include_Files {
12
+
13
+ function __construct()
14
+ {
15
+ // Include files.
16
+ require RRDI_PATH . 'includes/class-rrdi-helpers.php';
17
+ require RRDI_PATH . 'includes/class-rrdi-importer.php';
18
+ require RRDI_PATH . 'includes/vendor/class-rrdi-widget-importer.php';
19
+ require RRDI_PATH . 'includes/vendor/class-rrdi-customizer-importer.php';
20
+ require RRDI_PATH . 'includes/class-rrdi-logger.php';
21
+ }
22
+ }
23
+ new RRDI_Theme_Demo_Include_Files;
includes/class-rrdi-init.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Display admin error message if PHP version is older than 5.3.2.
4
+ * Otherwise execute the main plugin class.
5
+ */
6
+ class RDDI_init
7
+ {
8
+ function __construct()
9
+ {
10
+ $this->rddi_init_version_check();
11
+ }
12
+
13
+ function rddi_init_version_check()
14
+ {
15
+ if ( version_compare( phpversion(), '5.3.2', '<' ) ) {
16
+
17
+ /**
18
+ * Display an admin error notice when PHP is older the version 5.3.2.
19
+ * Hook it to the 'admin_notices' action.
20
+ */
21
+ function rrdi_old_php_admin_error_notice() {
22
+ $message = sprintf( esc_html__( 'The %2$sRara One Click Demo Import%3$s plugin requires %2$sPHP 5.3.2+%3$s to run properly. Please contact your hosting company and ask them to update the PHP version of your site to at least PHP 5.3.2.%4$s Your current version of PHP: %2$s%1$s%3$s', 'rara-demo-import' ), phpversion(), '<strong>', '</strong>', '<br>' );
23
+
24
+ printf( '<div class="notice notice-error"><p>%1$s</p></div>', wp_kses_post( $message ) );
25
+ }
26
+ add_action( 'admin_notices', 'rrdi_old_php_admin_error_notice' );
27
+ }
28
+ else {
29
+
30
+ // Require main plugin file.
31
+ require RRDI_PATH . 'includes/class-rrdi-main.php';
32
+
33
+ // Instantiate the main plugin class *Singleton*.
34
+ $Theme_Demo_Import = RRDI_Theme_Demo_Import::getInstance();
35
+ }
36
+ }
37
+ }
38
+ new RDDI_init;
includes/class-rrdi-logger.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Logger class used in the Rara One Click Demo Import plugin
4
+ *
5
+ * @package rara-demo-import
6
+ */
7
+
8
+ // Include files.
9
+ require RRDI_PATH . 'includes/extras/class-logger.php';
10
+ require RRDI_PATH . 'includes/extras/class-logger-cli.php';
11
+
12
+ class RRDI_Logger extends HM_WP_Importer_Logger_CLI {
13
+
14
+ /**
15
+ * Variable for front-end error display.
16
+ */
17
+ public $error_output = '';
18
+
19
+ /**
20
+ * Overwritten log function from HM_WP_Importer_Logger_CLI.
21
+ *
22
+ * Logs with an arbitrary level.
23
+ *
24
+ * @param mixed $level level of reporting.
25
+ * @param string $message log message.
26
+ * @param array $context context to the log message.
27
+ */
28
+ public function log( $level, $message, array $context = array() ) {
29
+
30
+ // Save error messages for front-end display.
31
+ $this->error_output( $level, $message, $context = array() );
32
+
33
+ if ( $this->level_to_numeric( $level ) < $this->level_to_numeric( $this->min_level ) ) {
34
+ return;
35
+ }
36
+
37
+ printf(
38
+ '[%s] %s' . PHP_EOL,
39
+ strtoupper( $level ),
40
+ $message
41
+ );
42
+ }
43
+
44
+
45
+ /**
46
+ * Save messages for error output.
47
+ * Only the messages greater then Error.
48
+ *
49
+ * @param mixed $level level of reporting.
50
+ * @param string $message log message.
51
+ * @param array $context context to the log message.
52
+ */
53
+ public function error_output( $level, $message, array $context = array() ) {
54
+ if ( $this->level_to_numeric( $level ) < $this->level_to_numeric( 'error' ) ) {
55
+ return;
56
+ }
57
+
58
+ $this->error_output .= sprintf(
59
+ '[%s] %s<br>',
60
+ strtoupper( $level ),
61
+ $message
62
+ );
63
+ }
64
+ }
includes/class-rrdi-main.php ADDED
@@ -0,0 +1,683 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Main Rara One Click Demo Import plugin class/file.
4
+ *
5
+ * @package rara-demo-import
6
+ */
7
+ /**
8
+ * Rara One Click Demo Import class, so we don't have to worry about namespaces.
9
+ */
10
+ class RRDI_Theme_Demo_Import {
11
+
12
+ /**
13
+ * @var $instance the reference to *Singleton* instance of this class
14
+ */
15
+ private static $instance;
16
+
17
+ /**
18
+ * Private variables used throughout the plugin.
19
+ */
20
+ private $importer, $plugin_page, $import_files, $logger, $log_file_path, $selected_index, $selected_import_files, $microtime, $frontend_error_messages, $ajax_call_number;
21
+
22
+
23
+ /**
24
+ * Returns the *Singleton* instance of this class.
25
+ *
26
+ * @return Theme_Demo_Import the *Singleton* instance.
27
+ */
28
+ public static function getInstance() {
29
+ if ( null === static::$instance ) {
30
+ static::$instance = new static();
31
+ }
32
+
33
+ return static::$instance;
34
+ }
35
+
36
+
37
+ /**
38
+ * Class construct function, to initiate the plugin.
39
+ * Protected constructor to prevent creating a new instance of the
40
+ * *Singleton* via the `new` operator from outside of this class.
41
+ */
42
+ protected function __construct() {
43
+ require RRDI_PATH . 'includes/class-rrdi-include-files.php';
44
+ // Actions.
45
+ add_action( 'admin_menu', array( $this, 'create_plugin_page' ) );
46
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
47
+ add_action( 'wp_ajax_rrdi_import_demo_data', array( $this, 'import_demo_data_ajax_callback' ) );
48
+ add_action( 'after_setup_theme', array( $this, 'setup_plugin_with_filter_data' ) );
49
+ add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
50
+ }
51
+
52
+
53
+ /**
54
+ * Private clone method to prevent cloning of the instance of the *Singleton* instance.
55
+ *
56
+ * @return void
57
+ */
58
+ private function __clone() {}
59
+
60
+
61
+ /**
62
+ * Private unserialize method to prevent unserializing of the *Singleton* instance.
63
+ *
64
+ * @return void
65
+ */
66
+ private function __wakeup() {}
67
+
68
+
69
+ /**
70
+ * Creates the plugin page and a submenu item in WP Appearance menu.
71
+ */
72
+ public function create_plugin_page() {
73
+ $plugin_page_setup = apply_filters( 'rrdi/plugin_page_setup', array(
74
+ 'parent_slug' => 'themes.php',
75
+ 'page_title' => esc_html__( 'Rara One Click Demo Import' , 'rara-demo-import' ),
76
+ 'menu_title' => esc_html__( 'Import Demo Content' , 'rara-demo-import' ),
77
+ 'capability' => 'import',
78
+ 'menu_slug' => 'rara-demo-import',
79
+ )
80
+ );
81
+
82
+ $this->plugin_page = add_submenu_page( $plugin_page_setup['parent_slug'], $plugin_page_setup['page_title'], $plugin_page_setup['menu_title'], $plugin_page_setup['capability'], $plugin_page_setup['menu_slug'], array( $this, 'display_plugin_page' ) );
83
+ }
84
+
85
+
86
+ /**
87
+ * Plugin page display.
88
+ */
89
+ public function display_plugin_page() {
90
+ ?>
91
+
92
+ <div class="RRDI__intro-notice notice notice-warning is-dismissible">
93
+ <p><?php $msg = __( 'Before you begin, make sure all the required plugins are activated.', 'rara-demo-import' ); echo apply_filters( 'rrdi_before_import_msg', $msg); ?>
94
+ </p>
95
+ </div>
96
+
97
+ <div class="rrdi wrap about-wrap">
98
+
99
+ <h1><?php esc_html_e( 'Rara One Click Demo Import', 'rara-demo-import' ); ?></h1>
100
+
101
+ <?php
102
+
103
+ // Display warrning if PHP safe mode is enabled, since we wont be able to change the max_execution_time.
104
+ if ( ini_get( 'safe_mode' ) ) {
105
+ printf(
106
+ esc_html__( '%sWarning: your server is using %sPHP safe mode%s. This means that you might experience server timeout errors.%s', 'rara-demo-import' ),
107
+ '<div class="notice notice-warning is-dismissible"><p>',
108
+ '<strong>',
109
+ '</strong>',
110
+ '</p></div>'
111
+ );
112
+ }
113
+
114
+ // Start output buffer for displaying the plugin intro text.
115
+ ob_start();
116
+ ?>
117
+
118
+ <div class="RRDI__intro-text">
119
+
120
+ <p class="about-description">
121
+ <?php esc_html_e( 'Upload your theme’s live demo content and settings with a click. If your theme comes with the demo content bundled with it, just click “Import Now” button. Otherwise, select files to import and click “Import Now”. As simple as that.', 'rara-demo-import' ); ?>
122
+ </p>
123
+
124
+ <h3><?php esc_html_e( 'The following data will be imported:', 'rara-demo-import' ); ?></h3>
125
+
126
+ <ul>
127
+ <li><?php esc_html_e( 'Posts', 'rara-demo-import' ); ?></li>
128
+ <li><?php esc_html_e( 'Pages', 'rara-demo-import' ); ?></li>
129
+ <li><?php esc_html_e( 'Images', 'rara-demo-import' ); ?></li>
130
+ <li><?php esc_html_e( 'Widgets', 'rara-demo-import' ); ?></li>
131
+ <li><?php esc_html_e( 'Menus', 'rara-demo-import' ); ?></li>
132
+ <li><?php esc_html_e( 'Settings', 'rara-demo-import' ); ?></li>
133
+ </ul>
134
+
135
+ <p><strong><?php esc_html_e( 'NOTE: Your existing content will NOT be deleted or modified.', 'rara-demo-import' ); ?></strong></p>
136
+
137
+ <hr>
138
+
139
+ </div>
140
+
141
+ <?php
142
+ $plugin_intro_text = ob_get_clean();
143
+
144
+ // Display the plugin intro text (can be replaced with custom text through the filter below).
145
+ echo wp_kses_post( apply_filters( 'rrdi/plugin_intro_text', $plugin_intro_text ) );
146
+ ?>
147
+
148
+
149
+ <?php if ( empty( $this->import_files ) ) : ?>
150
+
151
+ <div class="notice notice-info is-dismissible">
152
+ <p><?php esc_html_e( 'No predefined import files are available in this theme. Please upload the import files manually.', 'rara-demo-import' ); ?></p>
153
+ </div>
154
+
155
+ <div class="RRDI__file-upload-container">
156
+
157
+ <h2><?php esc_html_e( 'Please, select the following files.', 'rara-demo-import' ); ?></h2>
158
+
159
+ <div class="RRDI__file-upload">
160
+ <h3><label for="content-file-upload"><?php esc_html_e( 'Choose a XML file for content import:', 'rara-demo-import' ); ?></label></h3>
161
+ <input id="RRDI__content-file-upload" type="file" name="content-file-upload">
162
+ </div>
163
+
164
+ <div class="RRDI__file-upload">
165
+ <h3><label for="widget-file-upload"><?php esc_html_e( 'Choose a WIE or JSON file for widget import:', 'rara-demo-import' ); ?></label> <span><?php esc_html_e( '(*optional)', 'rara-demo-import' ); ?></span></h3>
166
+ <input id="RRDI__widget-file-upload" type="file" name="widget-file-upload">
167
+ </div>
168
+
169
+ <div class="RRDI__file-upload">
170
+ <h3><label for="customizer-file-upload"><?php esc_html_e( 'Choose a DAT file for customizer import:', 'rara-demo-import' ); ?></label> <span><?php esc_html_e( '(*optional)', 'rara-demo-import' ); ?></span></h3>
171
+ <input id="RRDI__customizer-file-upload" type="file" name="customizer-file-upload">
172
+ </div>
173
+
174
+ </div>
175
+
176
+ <?php elseif ( 1 < count( $this->import_files ) ) : ?>
177
+
178
+ <div class="RRDI__multi-select-import">
179
+
180
+ <h2><?php esc_html_e( 'Choose which demo you want to import:', 'rara-demo-import' ); ?></h2>
181
+
182
+ <select id="RRDI__demo-import-files" class="RRDI__demo-import-files">
183
+ <?php foreach ( $this->import_files as $index => $import_file ) : ?>
184
+ <option value="<?php echo esc_attr( $index ); ?>">
185
+ <?php echo esc_html( $import_file['import_file_name'] ); ?>
186
+ </option>
187
+ <?php endforeach; ?>
188
+ </select>
189
+
190
+ <?php
191
+ // Check if at least one preview image is defined, so we can prepare the structure for display.
192
+ $preview_image_is_defined = false;
193
+ foreach ( $this->import_files as $import_file ) {
194
+ if ( isset( $import_file['import_preview_image_url'] ) ) {
195
+ $preview_image_is_defined = true;
196
+ break;
197
+ }
198
+ }
199
+
200
+ if ( $preview_image_is_defined ) :
201
+ ?>
202
+
203
+ <div class="RRDI__demo-import-preview-container">
204
+
205
+ <p><?php esc_html_e( 'Import preview:', 'rara-demo-import' ); ?></p>
206
+
207
+ <p class="RRDI__demo-import-preview-image-message js-rrdi-preview-image-message"><?php
208
+ if ( ! isset( $this->import_files[0]['import_preview_image_url'] ) ) {
209
+ esc_html_e( 'No preview image defined for this import.', 'rara-demo-import' );
210
+ }
211
+ // Leave the img tag below and the p tag above available for later changes via JS.
212
+ ?></p>
213
+
214
+ <img id="RRDI__demo-import-preview-image" class="js-rrdi-preview-image" src="<?php echo ! empty( $this->import_files[0]['import_preview_image_url'] ) ? esc_url( $this->import_files[0]['import_preview_image_url'] ) : ''; ?>">
215
+
216
+ </div>
217
+
218
+ <?php endif; ?>
219
+
220
+ </div>
221
+
222
+ <?php endif; ?>
223
+
224
+ <div class="RRDI__demo-import-notice js-rrdi-demo-import-notice"><?php
225
+ if ( is_array( $this->import_files ) && ! empty( $this->import_files[0]['import_notice'] ) ) {
226
+ echo wp_kses_post( $this->import_files[0]['import_notice'] );
227
+ }
228
+ ?></div>
229
+
230
+ <p class="RRDI__button-container">
231
+ <button class="RRDI__button button button-hero button-primary js-rrdi-import-data"><?php esc_html_e( 'Import Now', 'rara-demo-import' ); ?></button>
232
+ <span><?php esc_html_e( 'Click the button to beign the importing process. Please be patient, the process might take a few minutes.', 'rara-demo-import' ); ?></span>
233
+ </p>
234
+
235
+ <p class="RRDI__ajax-loader js-rrdi-ajax-loader">
236
+ <span class="spinner"></span> <?php esc_html_e( 'Importing now, please wait!', 'rara-demo-import' ); ?>
237
+ </p>
238
+
239
+ <div class="RRDI__response js-rrdi-ajax-response"></div>
240
+
241
+ </div>
242
+
243
+ <?php
244
+ }
245
+
246
+
247
+ /**
248
+ * Enqueue admin scripts (JS and CSS)
249
+ *
250
+ * @param string $hook holds info on which admin page you are currently loading.
251
+ */
252
+ public function admin_enqueue_scripts( $hook ) {
253
+
254
+ // Enqueue the scripts only on the plugin page.
255
+ if ( $this->plugin_page === $hook ) {
256
+ wp_enqueue_script( 'rrdi-main-js', RRDI_URL . 'assets/js/script.js' , array( 'jquery', 'jquery-form' ), RRDI_VERSION );
257
+
258
+ wp_localize_script( 'rrdi-main-js', 'rrdi',
259
+ array(
260
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
261
+ 'ajax_nonce' => wp_create_nonce( 'rrdi-ajax-verification' ),
262
+ 'import_files' => $this->import_files,
263
+ 'texts' => array(
264
+ 'missing_preview_image' => esc_html__( 'No preview image defined for this import.', 'rara-demo-import' ),
265
+ ),
266
+ )
267
+ );
268
+
269
+ wp_enqueue_style( 'rrdi-main-css', RRDI_URL . 'assets/css/style.css', array() , RRDI_VERSION );
270
+ }
271
+ }
272
+
273
+
274
+ /**
275
+ * Main AJAX callback function for:
276
+ * 1. prepare import files (uploaded or predefined via filters)
277
+ * 2. import content
278
+ * 3. before widgets import setup (optional)
279
+ * 4. import widgets (optional)
280
+ * 5. import customizer options (optional)
281
+ * 6. after import setup (optional)
282
+ */
283
+ public function import_demo_data_ajax_callback() {
284
+
285
+ // Try to update PHP memory limit (so that it does not run out of it).
286
+ ini_set( 'memory_limit', apply_filters( 'rrdi/import_memory_limit', '350M' ) );
287
+
288
+ // Verify if the AJAX call is valid (checks nonce and current_user_can).
289
+ RRDI_Helpers::verify_ajax_call();
290
+
291
+ // Is this a new AJAX call to continue the previous import?
292
+ $use_existing_importer_data = $this->get_importer_data();
293
+
294
+ if ( ! $use_existing_importer_data ) {
295
+
296
+ // Set the AJAX call number.
297
+ $this->ajax_call_number = empty( $this->ajax_call_number ) ? 0 : $this->ajax_call_number;
298
+
299
+ // Error messages displayed on front page.
300
+ $this->frontend_error_messages = '';
301
+
302
+ // Create a date and time string to use for demo and log file names.
303
+ $demo_import_start_time = date( apply_filters( 'rrdi/date_format_for_file_names', 'Y-m-d__H-i-s' ) );
304
+
305
+ // Define log file path.
306
+ $this->log_file_path = RRDI_Helpers::get_log_path( $demo_import_start_time );
307
+
308
+ // Get selected file index or set it to 0.
309
+ $this->selected_index = empty( $_POST['selected'] ) ? 0 : absint( $_POST['selected'] );
310
+
311
+ /**
312
+ * 1. Prepare import files.
313
+ * Manually uploaded import files or predefined import files via filter: rrdi/import_files
314
+ */
315
+ if ( ! empty( $_FILES ) ) { // Using manual file uploads?
316
+
317
+ // Get paths for the uploaded files.
318
+ $this->selected_import_files = RRDI_Helpers::process_uploaded_files( $_FILES, $this->log_file_path );
319
+
320
+ // Set the name of the import files, because we used the uploaded files.
321
+ $this->import_files[ $this->selected_index ]['import_file_name'] = esc_html__( 'Manually uploaded files', 'rara-demo-import' );
322
+ }
323
+ elseif ( ! empty( $this->import_files[ $this->selected_index ] ) ) { // Use predefined import files from wp filter: rrdi/import_files.
324
+
325
+ // Download the import files (content and widgets files) and save it to variable for later use.
326
+ $this->selected_import_files = RRDI_Helpers::download_import_files(
327
+ $this->import_files[ $this->selected_index ],
328
+ $demo_import_start_time
329
+ );
330
+
331
+ // Check Errors.
332
+ if ( is_wp_error( $this->selected_import_files ) ) {
333
+
334
+ // Write error to log file and send an AJAX response with the error.
335
+ RRDI_Helpers::log_error_and_send_ajax_response(
336
+ $this->selected_import_files->get_error_message(),
337
+ $this->log_file_path,
338
+ esc_html__( 'Downloaded files', 'rara-demo-import' )
339
+ );
340
+ }
341
+
342
+ // Add this message to log file.
343
+ $log_added = RRDI_Helpers::append_to_file(
344
+ sprintf(
345
+ __( 'The import files for: %s were successfully downloaded!', 'rara-demo-import' ),
346
+ $this->import_files[ $this->selected_index ]['import_file_name']
347
+ ) . RRDI_Helpers::import_file_info( $this->selected_import_files ),
348
+ $this->log_file_path,
349
+ esc_html__( 'Downloaded files' , 'rara-demo-import' )
350
+ );
351
+ }
352
+ else {
353
+
354
+ // Send JSON Error response to the AJAX call.
355
+ wp_send_json( esc_html__( 'No import files specified!', 'rara-demo-import' ) );
356
+ }
357
+ }
358
+
359
+ /**
360
+ * 2. Import content.
361
+ * Returns any errors greater then the "error" logger level, that will be displayed on front page.
362
+ */
363
+ $this->frontend_error_messages .= $this->import_content( $this->selected_import_files['content'] );
364
+
365
+ /**
366
+ * 3. Before widgets import setup.
367
+ */
368
+ $action = 'rrdi/before_widgets_import';
369
+ if ( ( false !== has_action( $action ) ) && empty( $this->frontend_error_messages ) ) {
370
+
371
+ // Run the before_widgets_import action to setup other settings.
372
+ $this->do_import_action( $action, $this->import_files[ $this->selected_index ] );
373
+ }
374
+
375
+ /**
376
+ * 4. Import widgets.
377
+ */
378
+ if ( ! empty( $this->selected_import_files['widgets'] ) && empty( $this->frontend_error_messages ) ) {
379
+ $this->import_widgets( $this->selected_import_files['widgets'] );
380
+ }
381
+
382
+ /**
383
+ * 5. Import customize options.
384
+ */
385
+ if ( ! empty( $this->selected_import_files['customizer'] ) && empty( $this->frontend_error_messages ) ) {
386
+ $this->import_customizer( $this->selected_import_files['customizer'] );
387
+ }
388
+
389
+ /**
390
+ * 6. After import setup.
391
+ */
392
+ $action = 'rrdi/after_import';
393
+ if ( ( false !== has_action( $action ) ) && empty( $this->frontend_error_messages ) ) {
394
+
395
+ // Run the after_import action to setup other settings.
396
+ $this->do_import_action( $action, $this->import_files[ $this->selected_index ] );
397
+ }
398
+
399
+ // Display final messages (success or error messages).
400
+ if ( empty( $this->frontend_error_messages ) ) {
401
+ $response['message'] = sprintf(
402
+ __( '%1$s%3$sCompleted Successfully!%4$s%2$sThe process has finished. Please check your page and make sure that everything has been imported correctly. If you want, you can now deactivate the %3$sRara One Click Demo Import%4$s plugin.%5$s', 'rara-demo-import' ),
403
+ '<div class="notice notice-success"><p>',
404
+ '<br>',
405
+ '<strong>',
406
+ '</strong>',
407
+ '</p></div>'
408
+ );
409
+ }
410
+ else {
411
+ $response['message'] = $this->frontend_error_messages . '<br>';
412
+ $response['message'] .= sprintf(
413
+ __( '%1$sUnfortunately, demo import has finished with some errors.%2$sMore details about the errors can be found in this %3$s%5$slog file%6$s%4$s%7$s', 'rara-demo-import' ),
414
+ '<div class="notice notice-error"><p>',
415
+ '<br>',
416
+ '<strong>',
417
+ '</strong>',
418
+ '<a href="' . RRDI_Helpers::get_log_url( $this->log_file_path ) .'" target="_blank">',
419
+ '</a>',
420
+ '</p></div>'
421
+ );
422
+ }
423
+
424
+ wp_send_json( $response );
425
+ }
426
+
427
+
428
+ /**
429
+ * Import content from an WP XML file.
430
+ *
431
+ * @param string $import_file_path path to the import file.
432
+ */
433
+ private function import_content( $import_file_path ) {
434
+
435
+ $this->microtime = microtime( true );
436
+
437
+ // This should be replaced with multiple AJAX calls (import in smaller chunks)
438
+ // so that it would not come to the Internal Error, because of the PHP script timeout.
439
+ // Also this function has no effect when PHP is running in safe mode
440
+ // http://php.net/manual/en/function.set-time-limit.php.
441
+ // Increase PHP max execution time.
442
+ set_time_limit( apply_filters( 'rrdi/set_time_limit_for_demo_data_import', 300 ) );
443
+
444
+ // Disable import of authors.
445
+ add_filter( 'wxr_importer.pre_process.user', '__return_false' );
446
+
447
+ // Check, if we need to send another AJAX request and set the importing author to the current user.
448
+ add_filter( 'wxr_importer.pre_process.post', array( $this, 'new_ajax_request_maybe' ) );
449
+
450
+ // Disables generation of multiple image sizes (thumbnails) in the content import step.
451
+ if ( ! apply_filters( 'rrdi/regenerate_thumbnails_in_content_import', true ) ) {
452
+ add_filter( 'intermediate_image_sizes_advanced',
453
+ function() {
454
+ return null;
455
+ }
456
+ );
457
+ }
458
+
459
+ // Import content.
460
+ if ( ! empty( $import_file_path ) ) {
461
+ ob_start();
462
+ $this->importer->import( $import_file_path );
463
+ $message = ob_get_clean();
464
+
465
+ // Add this message to log file.
466
+ $log_added = RRDI_Helpers::append_to_file(
467
+ $message . PHP_EOL . esc_html__( 'Max execution time after content import = ' , 'rara-demo-import' ) . ini_get( 'max_execution_time' ),
468
+ $this->log_file_path,
469
+ esc_html__( 'Importing content' , 'rara-demo-import' )
470
+ );
471
+ }
472
+
473
+ // Delete content importer data for current import from DB.
474
+ delete_transient( 'RRDI_importer_data' );
475
+
476
+ // Return any error messages for the front page output (errors, critical, alert and emergency level messages only).
477
+ return $this->logger->error_output;
478
+ }
479
+
480
+
481
+ /**
482
+ * Import widgets from WIE or JSON file.
483
+ *
484
+ * @param string $widget_import_file_path path to the widget import file.
485
+ */
486
+ private function import_widgets( $widget_import_file_path ) {
487
+
488
+ // Widget import results.
489
+ $results = array();
490
+
491
+ // Create an instance of the Widget Importer.
492
+ $widget_importer = new RRDI_Widget_Importer();
493
+
494
+ // Import widgets.
495
+ if ( ! empty( $widget_import_file_path ) ) {
496
+
497
+ // Import widgets and return result.
498
+ $results = $widget_importer->import_widgets( $widget_import_file_path );
499
+ }
500
+
501
+ // Check for errors.
502
+ if ( is_wp_error( $results ) ) {
503
+
504
+ // Write error to log file and send an AJAX response with the error.
505
+ RRDI_Helpers::log_error_and_send_ajax_response(
506
+ $results->get_error_message(),
507
+ $this->log_file_path,
508
+ esc_html__( 'Importing widgets', 'rara-demo-import' )
509
+ );
510
+ }
511
+
512
+ ob_start();
513
+ $widget_importer->format_results_for_log( $results );
514
+ $message = ob_get_clean();
515
+
516
+ // Add this message to log file.
517
+ $log_added = RRDI_Helpers::append_to_file(
518
+ $message,
519
+ $this->log_file_path,
520
+ esc_html__( 'Importing widgets' , 'rara-demo-import' )
521
+ );
522
+ }
523
+
524
+
525
+ /**
526
+ * Import customizer from a DAT file, generated by the Customizer Export/Import plugin.
527
+ *
528
+ * @param string $customizer_import_file_path path to the customizer import file.
529
+ */
530
+ private function import_customizer( $customizer_import_file_path ) {
531
+
532
+ // Try to import the customizer settings.
533
+ $results = RRDI_Customizer_Importer::import_customizer_options( $customizer_import_file_path );
534
+
535
+ // Check for errors.
536
+ if ( is_wp_error( $results ) ) {
537
+
538
+ // Write error to log file and send an AJAX response with the error.
539
+ RRDI_Helpers::log_error_and_send_ajax_response(
540
+ $results->get_error_message(),
541
+ $this->log_file_path,
542
+ esc_html__( 'Importing customizer settings', 'rara-demo-import' )
543
+ );
544
+ }
545
+
546
+ // Add this message to log file.
547
+ $log_added = RRDI_Helpers::append_to_file(
548
+ esc_html__( 'Customizer settings import finished!', 'rara-demo-import' ),
549
+ $this->log_file_path,
550
+ esc_html__( 'Importing customizer settings' , 'rara-demo-import' )
551
+ );
552
+ }
553
+
554
+
555
+ /**
556
+ * Setup other things in the passed wp action.
557
+ *
558
+ * @param string $action the action name to be executed.
559
+ * @param array $selected_import with information about the selected import.
560
+ */
561
+ private function do_import_action( $action, $selected_import ) {
562
+
563
+ ob_start();
564
+ do_action( $action, $selected_import );
565
+ $message = ob_get_clean();
566
+
567
+ // Add this message to log file.
568
+ $log_added = RRDI_Helpers::append_to_file(
569
+ $message,
570
+ $this->log_file_path,
571
+ $action
572
+ );
573
+ }
574
+
575
+
576
+ /**
577
+ * Check if we need to create a new AJAX request, so that server does not timeout.
578
+ *
579
+ * @param array $data current post data.
580
+ * @return array
581
+ */
582
+ public function new_ajax_request_maybe( $data ) {
583
+ $time = microtime( true ) - $this->microtime;
584
+
585
+ // We should make a new ajax call, if the time is right.
586
+ if ( $time > apply_filters( 'rrdi/time_for_one_ajax_call', 25 ) ) {
587
+ $this->ajax_call_number++;
588
+ $this->set_importer_data();
589
+
590
+ $response = array(
591
+ 'status' => 'newAJAX',
592
+ 'message' => 'New AJAX request!: ' . $time,
593
+ );
594
+
595
+ // Add any output to the log file and clear the buffers.
596
+ $message = ob_get_clean();
597
+
598
+ // Add message to log file.
599
+ $log_added = RRDI_Helpers::append_to_file(
600
+ __( 'Completed AJAX call number: ' , 'rara-demo-import' ) . $this->ajax_call_number . PHP_EOL . $message,
601
+ $this->log_file_path,
602
+ ''
603
+ );
604
+
605
+ wp_send_json( $response );
606
+ }
607
+
608
+ // Set importing author to the current user.
609
+ // Fixes the [WARNING] Could not find the author for ... log warning messages.
610
+ $current_user_obj = wp_get_current_user();
611
+ $data['post_author'] = $current_user_obj->user_login;
612
+
613
+ return $data;
614
+ }
615
+
616
+ /**
617
+ * Set current state of the content importer, so we can continue the import with new AJAX request.
618
+ */
619
+ private function set_importer_data() {
620
+ $data = array(
621
+ 'frontend_error_messages' => $this->frontend_error_messages,
622
+ 'ajax_call_number' => $this->ajax_call_number,
623
+ 'log_file_path' => $this->log_file_path,
624
+ 'selected_index' => $this->selected_index,
625
+ 'selected_import_files' => $this->selected_import_files,
626
+ );
627
+
628
+ $data = array_merge( $data, $this->importer->get_importer_data() );
629
+
630
+ set_transient( 'RRDI_importer_data', $data, 0.5 * HOUR_IN_SECONDS );
631
+ }
632
+
633
+ /**
634
+ * Get content importer data, so we can continue the import with this new AJAX request.
635
+ */
636
+ private function get_importer_data() {
637
+ if ( $data = get_transient( 'RRDI_importer_data' ) ) {
638
+ $this->frontend_error_messages = empty( $data['frontend_error_messages'] ) ? '' : $data['frontend_error_messages'];
639
+ $this->ajax_call_number = empty( $data['ajax_call_number'] ) ? 1 : $data['ajax_call_number'];
640
+ $this->log_file_path = empty( $data['log_file_path'] ) ? '' : $data['log_file_path'];
641
+ $this->selected_index = empty( $data['selected_index'] ) ? 0 : $data['selected_index'];
642
+ $this->selected_import_files = empty( $data['selected_import_files'] ) ? array() : $data['selected_import_files'];
643
+ $this->importer->set_importer_data( $data );
644
+
645
+ return true;
646
+ }
647
+ return false;
648
+ }
649
+
650
+ /**
651
+ * Load the plugin textdomain, so that translations can be made.
652
+ */
653
+ public function load_textdomain() {
654
+ load_plugin_textdomain( 'rara-demo-import', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' );
655
+ }
656
+
657
+
658
+ /**
659
+ * Get data from filters, after the theme has loaded and instantiate the importer.
660
+ */
661
+ public function setup_plugin_with_filter_data() {
662
+
663
+ // Get info of import data files and filter it.
664
+ $this->import_files = RRDI_Helpers::validate_import_file_info( apply_filters( 'rrdi/import_files', array() ) );
665
+
666
+ // Importer options array.
667
+ $importer_options = apply_filters( 'rrdi/importer_options', array(
668
+ 'fetch_attachments' => true,
669
+ ) );
670
+
671
+ // Logger options for the logger used in the importer.
672
+ $logger_options = apply_filters( 'rrdi/logger_options', array(
673
+ 'logger_min_level' => 'warning',
674
+ ) );
675
+
676
+ // Configure logger instance and set it to the importer.
677
+ $this->logger = new RRDI_Logger();
678
+ $this->logger->min_level = $logger_options['logger_min_level'];
679
+
680
+ // Create importer instance with proper parameters.
681
+ $this->importer = new RRDI_Importer( $importer_options, $this->logger );
682
+ }
683
+ }
includes/class-rrdi-wxr-importer.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WXR importer class used in the Rara One Click Demo Import plugin.
4
+ * Needed to extend the HM_WXR_Importer class to get/set the importer protected variables,
5
+ * for use in the multiple AJAX calls.
6
+ *
7
+ * @package rara-demo-import
8
+ */
9
+
10
+ // Include files.
11
+ require RRDI_PATH . 'includes/extras/class-wxr-importer.php';
12
+
13
+ class RRDI_WXR_Importer extends HM_WXR_Importer {
14
+
15
+ public function __construct( $options = array() ) {
16
+ parent::__construct( $options );
17
+
18
+ // Set current user to $mapping variable.
19
+ // Fixes the [WARNING] Could not find the author for ... log warning messages.
20
+ $current_user_obj = wp_get_current_user();
21
+ $this->mapping['user_slug'][ $current_user_obj->user_login ] = $current_user_obj->ID;
22
+ }
23
+
24
+ /**
25
+ * Get all protected variables from the HM_WXR_Importer needed for continuing the import.
26
+ */
27
+ public function get_importer_data() {
28
+ return array(
29
+ 'mapping' => $this->mapping,
30
+ 'requires_remapping' => $this->requires_remapping,
31
+ 'exists' => $this->exists,
32
+ 'user_slug_override' => $this->user_slug_override,
33
+ 'url_remap' => $this->url_remap,
34
+ 'featured_images' => $this->featured_images,
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Sets all protected variables from the HM_WXR_Importer needed for continuing the import.
40
+ *
41
+ * @param array $data with set variables.
42
+ */
43
+ public function set_importer_data( $data ) {
44
+ $this->mapping = empty( $data['mapping'] ) ? array() : $data['mapping'];
45
+ $this->requires_remapping = empty( $data['requires_remapping'] ) ? array() : $data['requires_remapping'];
46
+ $this->exists = empty( $data['exists'] ) ? array() : $data['exists'];
47
+ $this->user_slug_override = empty( $data['user_slug_override'] ) ? array() : $data['user_slug_override'];
48
+ $this->url_remap = empty( $data['url_remap'] ) ? array() : $data['url_remap'];
49
+ $this->featured_images = empty( $data['featured_images'] ) ? array() : $data['featured_images'];
50
+ }
51
+ }
includes/extras/class-logger-cli.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class HM_WP_Importer_Logger_CLI extends HM_WP_Importer_Logger {
4
+ public $min_level = 'notice';
5
+
6
+ /**
7
+ * Logs with an arbitrary level.
8
+ *
9
+ * @param mixed $level
10
+ * @param string $message
11
+ * @param array $context
12
+ * @return null
13
+ */
14
+ public function log( $level, $message, array $context = array() ) {
15
+ if ( $this->level_to_numeric( $level ) < $this->level_to_numeric( $this->min_level ) ) {
16
+ return;
17
+ }
18
+
19
+ printf(
20
+ '[%s] %s' . PHP_EOL,
21
+ strtoupper( $level ),
22
+ $message
23
+ );
24
+ }
25
+
26
+ public static function level_to_numeric( $level ) {
27
+ $levels = array(
28
+ 'emergency' => 8,
29
+ 'alert' => 7,
30
+ 'critical' => 6,
31
+ 'error' => 5,
32
+ 'warning' => 4,
33
+ 'notice' => 3,
34
+ 'info' => 2,
35
+ 'debug' => 1,
36
+ );
37
+ if ( ! isset( $levels[ $level ] ) ) {
38
+ return 0;
39
+ }
40
+
41
+ return $levels[ $level ];
42
+ }
43
+ }
includes/extras/class-logger.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Describes a logger instance
5
+ *
6
+ * Based on PSR-3: http://www.php-fig.org/psr/psr-3/
7
+ *
8
+ * The message MUST be a string or object implementing __toString().
9
+ *
10
+ * The message MAY contain placeholders in the form: {foo} where foo
11
+ * will be replaced by the context data in key "foo".
12
+ *
13
+ * The context array can contain arbitrary data, the only assumption that
14
+ * can be made by implementors is that if an Exception instance is given
15
+ * to produce a stack trace, it MUST be in a key named "exception".
16
+ *
17
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
18
+ * for the full interface specification.
19
+ */
20
+ class HM_WP_Importer_Logger {
21
+ /**
22
+ * System is unusable.
23
+ *
24
+ * @param string $message
25
+ * @param array $context
26
+ * @return null
27
+ */
28
+ public function emergency( $message, array $context = array() ) {
29
+ return $this->log( 'emergency', $message, $context );
30
+ }
31
+
32
+ /**
33
+ * Action must be taken immediately.
34
+ *
35
+ * Example: Entire website down, database unavailable, etc. This should
36
+ * trigger the SMS alerts and wake you up.
37
+ *
38
+ * @param string $message
39
+ * @param array $context
40
+ * @return null
41
+ */
42
+ public function alert( $message, array $context = array() ) {
43
+ return $this->log( 'alert', $message, $context );
44
+ }
45
+
46
+ /**
47
+ * Critical conditions.
48
+ *
49
+ * Example: Application component unavailable, unexpected exception.
50
+ *
51
+ * @param string $message
52
+ * @param array $context
53
+ * @return null
54
+ */
55
+ public function critical( $message, array $context = array() ) {
56
+ return $this->log( 'critical', $message, $context );
57
+ }
58
+
59
+ /**
60
+ * Runtime errors that do not require immediate action but should typically
61
+ * be logged and monitored.
62
+ *
63
+ * @param string $message
64
+ * @param array $context
65
+ * @return null
66
+ */
67
+ public function error( $message, array $context = array()) {
68
+ return $this->log( 'error', $message, $context );
69
+ }
70
+
71
+ /**
72
+ * Exceptional occurrences that are not errors.
73
+ *
74
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
75
+ * that are not necessarily wrong.
76
+ *
77
+ * @param string $message
78
+ * @param array $context
79
+ * @return null
80
+ */
81
+ public function warning( $message, array $context = array() ) {
82
+ return $this->log( 'warning', $message, $context );
83
+ }
84
+
85
+ /**
86
+ * Normal but significant events.
87
+ *
88
+ * @param string $message
89
+ * @param array $context
90
+ * @return null
91
+ */
92
+ public function notice( $message, array $context = array() ) {
93
+ return $this->log( 'notice', $message, $context );
94
+ }
95
+
96
+ /**
97
+ * Interesting events.
98
+ *
99
+ * Example: User logs in, SQL logs.
100
+ *
101
+ * @param string $message
102
+ * @param array $context
103
+ * @return null
104
+ */
105
+ public function info( $message, array $context = array() ) {
106
+ return $this->log( 'info', $message, $context );
107
+ }
108
+
109
+ /**
110
+ * Detailed debug information.
111
+ *
112
+ * @param string $message
113
+ * @param array $context
114
+ * @return null
115
+ */
116
+ public function debug( $message, array $context = array() ) {
117
+ return $this->log( 'debug', $message, $context );
118
+ }
119
+
120
+ /**
121
+ * Logs with an arbitrary level.
122
+ *
123
+ * @param mixed $level
124
+ * @param string $message
125
+ * @param array $context
126
+ * @return null
127
+ */
128
+ public function log( $level, $message, array $context = array() ) {
129
+ $this->messages[] = array(
130
+ 'timestamp' => time(),
131
+ 'level' => $level,
132
+ 'message' => $message,
133
+ 'context' => $context,
134
+ );
135
+ }
136
+ }
includes/extras/class-wxr-importer.php ADDED
@@ -0,0 +1,2250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class HM_WXR_Importer {
4
+ /**
5
+ * Maximum supported WXR version
6
+ */
7
+ const MAX_WXR_VERSION = 1.2;
8
+
9
+ /**
10
+ * Regular expression for checking if a post references an attachment
11
+ *
12
+ * Note: This is a quick, weak check just to exclude text-only posts. More
13
+ * vigorous checking is done later to verify.
14
+ */
15
+ const REGEX_HAS_ATTACHMENT_REFS = '!
16
+ (
17
+ # Match anything with an image or attachment class
18
+ class=[\'"].*?\b(wp-image-\d+|attachment-[\w\-]+)\b
19
+ |
20
+ # Match anything that looks like an upload URL
21
+ src=[\'"][^\'"]*(
22
+ [0-9]{4}/[0-9]{2}/[^\'"]+\.(jpg|jpeg|png|gif)
23
+ |
24
+ content/uploads[^\'"]+
25
+ )[\'"]
26
+ )!ix';
27
+
28
+ /**
29
+ * Version of WXR we're importing.
30
+ *
31
+ * Defaults to 1.0 for compatibility. Typically overridden by a
32
+ * `<wp:wxr_version>` tag at the start of the file.
33
+ *
34
+ * @var string
35
+ */
36
+ protected $version = '1.0';
37
+
38
+ // information to import from WXR file
39
+ protected $categories = array();
40
+ protected $tags = array();
41
+ protected $base_url = '';
42
+
43
+ // TODO: REMOVE THESE
44
+ protected $processed_terms = array();
45
+ protected $processed_posts = array();
46
+ protected $processed_menu_items = array();
47
+ protected $menu_item_orphans = array();
48
+ protected $missing_menu_items = array();
49
+
50
+ // NEW STYLE
51
+ protected $mapping = array();
52
+ protected $requires_remapping = array();
53
+ protected $exists = array();
54
+ protected $user_slug_override = array();
55
+
56
+ protected $url_remap = array();
57
+ protected $featured_images = array();
58
+
59
+ /**
60
+ * Logger instance.
61
+ *
62
+ * @var HM_WP_Importer_Logger
63
+ */
64
+ protected $logger;
65
+
66
+ /**
67
+ * Constructor
68
+ *
69
+ * @param array $options {
70
+ * @var bool $prefill_existing_posts Should we prefill `post_exists` calls? (True prefills and uses more memory, false checks once per imported post and takes longer. Default is true.)
71
+ * @var bool $prefill_existing_comments Should we prefill `comment_exists` calls? (True prefills and uses more memory, false checks once per imported comment and takes longer. Default is true.)
72
+ * @var bool $prefill_existing_terms Should we prefill `term_exists` calls? (True prefills and uses more memory, false checks once per imported term and takes longer. Default is true.)
73
+ * @var bool $update_attachment_guids Should attachment GUIDs be updated to the new URL? (True updates the GUID, which keeps compatibility with v1, false doesn't update, and allows deduplication and reimporting. Default is false.)
74
+ * @var bool $fetch_attachments Fetch attachments from the remote server. (True fetches and creates attachment posts, false skips attachments. Default is false.)
75
+ * @var bool $aggressive_url_search Should we search/replace for URLs aggressively? (True searches all posts' content for old URLs and replaces, false checks for `<img class="wp-image-*">` only. Default is false.)
76
+ * @var int $default_author User ID to use if author is missing or invalid. (Default is null, which leaves posts unassigned.)
77
+ * }
78
+ */
79
+ public function __construct( $options = array() ) {
80
+ // Initialize some important variables
81
+ $empty_types = array(
82
+ 'post' => array(),
83
+ 'comment' => array(),
84
+ 'term' => array(),
85
+ 'user' => array(),
86
+ );
87
+
88
+ $this->mapping = $empty_types;
89
+ $this->mapping['user_slug'] = array();
90
+ $this->mapping['term_id'] = array();
91
+ $this->requires_remapping = $empty_types;
92
+ $this->exists = $empty_types;
93
+
94
+ $this->options = wp_parse_args( $options, array(
95
+ 'prefill_existing_posts' => true,
96
+ 'prefill_existing_comments' => true,
97
+ 'prefill_existing_terms' => true,
98
+ 'update_attachment_guids' => false,
99
+ 'fetch_attachments' => false,
100
+ 'aggressive_url_search' => false,
101
+ 'default_author' => null,
102
+ ) );
103
+ }
104
+
105
+ public function set_logger( $logger ) {
106
+ $this->logger = $logger;
107
+ }
108
+
109
+ /**
110
+ * Get a stream reader for the file.
111
+ *
112
+ * @param string $file Path to the XML file.
113
+ * @return XMLReader|WP_Error Reader instance on success, error otherwise.
114
+ */
115
+ protected function get_reader( $file ) {
116
+ // Avoid loading external entities for security
117
+ $old_value = null;
118
+ if ( function_exists( 'libxml_disable_entity_loader' ) ) {
119
+ // $old_value = libxml_disable_entity_loader( true );
120
+ }
121
+
122
+ $reader = new XMLReader();
123
+ $status = $reader->open( $file );
124
+
125
+ if ( ! is_null( $old_value ) ) {
126
+ // libxml_disable_entity_loader( $old_value );
127
+ }
128
+
129
+ if ( ! $status ) {
130
+ return new WP_Error( 'wxr_importer.cannot_parse', __( 'Could not open the file for parsing', 'rara-demo-import' ) );
131
+ }
132
+
133
+ return $reader;
134
+ }
135
+
136
+ /**
137
+ * The main controller for the actual import stage.
138
+ *
139
+ * @param string $file Path to the WXR file for importing
140
+ */
141
+ public function get_preliminary_information( $file ) {
142
+ // Let's run the actual importer now, woot
143
+ $reader = $this->get_reader( $file );
144
+ if ( is_wp_error( $reader ) ) {
145
+ return $reader;
146
+ }
147
+
148
+ // Set the version to compatibility mode first
149
+ $this->version = '1.0';
150
+
151
+ // Start parsing!
152
+ $data = new WXR_Import_Info();
153
+ while ( $reader->read() ) {
154
+ // Only deal with element opens
155
+ if ( $reader->nodeType !== XMLReader::ELEMENT ) {
156
+ continue;
157
+ }
158
+
159
+ switch ( $reader->name ) {
160
+ case 'wp:wxr_version':
161
+ // Upgrade to the correct version
162
+ $this->version = $reader->readString();
163
+
164
+ if ( version_compare( $this->version, self::MAX_WXR_VERSION, '>' ) ) {
165
+ $this->logger->warning( sprintf(
166
+ __( 'This WXR file (version %s) is newer than the importer (version %s) and may not be supported. Please consider updating.', 'rara-demo-import' ),
167
+ $this->version,
168
+ self::MAX_WXR_VERSION
169
+ ) );
170
+ }
171
+
172
+ // Handled everything in this node, move on to the next
173
+ $reader->next();
174
+ break;
175
+
176
+ case 'generator':
177
+ $data->generator = $reader->readString();
178
+ $reader->next();
179
+ break;
180
+
181
+ case 'title':
182
+ $data->title = $reader->readString();
183
+ $reader->next();
184
+ break;
185
+
186
+ case 'wp:base_site_url':
187
+ $data->siteurl = $reader->readString();
188
+ $reader->next();
189
+ break;
190
+
191
+ case 'wp:base_blog_url':
192
+ $data->home = $reader->readString();
193
+ $reader->next();
194
+ break;
195
+
196
+ case 'wp:author':
197
+ $node = $reader->expand();
198
+
199
+ $parsed = $this->parse_author_node( $node );
200
+ if ( is_wp_error( $parsed ) ) {
201
+ $this->log_error( $parsed );
202
+
203
+ // Skip the rest of this post
204
+ $reader->next();
205
+ break;
206
+ }
207
+
208
+ $data->users[] = $parsed;
209
+
210
+ // Handled everything in this node, move on to the next
211
+ $reader->next();
212
+ break;
213
+
214
+ case 'item':
215
+ $node = $reader->expand();
216
+ $parsed = $this->parse_post_node( $node );
217
+ if ( is_wp_error( $parsed ) ) {
218
+ $this->log_error( $parsed );
219
+
220
+ // Skip the rest of this post
221
+ $reader->next();
222
+ break;
223
+ }
224
+
225
+ if ( $parsed['data']['post_type'] === 'attachment' ) {
226
+ $data->media_count++;
227
+ } else {
228
+ $data->post_count++;
229
+ }
230
+ $data->comment_count += count( $parsed['comments'] );
231
+
232
+ // Handled everything in this node, move on to the next
233
+ $reader->next();
234
+ break;
235
+
236
+ case 'wp:category':
237
+ case 'wp:tag':
238
+ case 'wp:term':
239
+ $data->term_count++;
240
+
241
+ // Handled everything in this node, move on to the next
242
+ $reader->next();
243
+ break;
244
+ }
245
+ }
246
+
247
+ $data->version = $this->version;
248
+
249
+ return $data;
250
+ }
251
+
252
+ /**
253
+ * The main controller for the actual import stage.
254
+ *
255
+ * @param string $file Path to the WXR file for importing
256
+ */
257
+ public function parse_authors( $file ) {
258
+ // Let's run the actual importer now, woot
259
+ $reader = $this->get_reader( $file );
260
+ if ( is_wp_error( $reader ) ) {
261
+ return $reader;
262
+ }
263
+
264
+ // Set the version to compatibility mode first
265
+ $this->version = '1.0';
266
+
267
+ // Start parsing!
268
+ $authors = array();
269
+ while ( $reader->read() ) {
270
+ // Only deal with element opens
271
+ if ( $reader->nodeType !== XMLReader::ELEMENT ) {
272
+ continue;
273
+ }
274
+
275
+ switch ( $reader->name ) {
276
+ case 'wp:wxr_version':
277
+ // Upgrade to the correct version
278
+ $this->version = $reader->readString();
279
+
280
+ if ( version_compare( $this->version, self::MAX_WXR_VERSION, '>' ) ) {
281
+ $this->logger->warning( sprintf(
282
+ __( 'This WXR file (version %s) is newer than the importer (version %s) and may not be supported. Please consider updating.', 'rara-demo-import' ),
283
+ $this->version,
284
+ self::MAX_WXR_VERSION
285
+ ) );
286
+ }
287
+
288
+ // Handled everything in this node, move on to the next
289
+ $reader->next();
290
+ break;
291
+
292
+ case 'wp:author':
293
+ $node = $reader->expand();
294
+
295
+ $parsed = $this->parse_author_node( $node );
296
+ if ( is_wp_error( $parsed ) ) {
297
+ $this->log_error( $parsed );
298
+
299
+ // Skip the rest of this post
300
+ $reader->next();
301
+ break;
302
+ }
303
+
304
+ $authors[] = $parsed;
305
+
306
+ // Handled everything in this node, move on to the next
307
+ $reader->next();
308
+ break;
309
+ }
310
+ }
311
+
312
+ return $authors;
313
+ }
314
+
315
+ /**
316
+ * The main controller for the actual import stage.
317
+ *
318
+ * @param string $file Path to the WXR file for importing
319
+ */
320
+ public function import( $file ) {
321
+ add_filter( 'import_post_meta_key', array( $this, 'is_valid_meta_key' ) );
322
+ add_filter( 'http_request_timeout', array( &$this, 'bump_request_timeout' ) );
323
+
324
+ $result = $this->import_start( $file );
325
+ if ( is_wp_error( $result ) ) {
326
+ return $result;
327
+ }
328
+
329
+ // Let's run the actual importer now, woot
330
+ $reader = $this->get_reader( $file );
331
+ if ( is_wp_error( $reader ) ) {
332
+ return $reader;
333
+ }
334
+
335
+ // Set the version to compatibility mode first
336
+ $this->version = '1.0';
337
+
338
+ // Reset other variables
339
+ $this->base_url = '';
340
+
341
+ // Start parsing!
342
+ while ( $reader->read() ) {
343
+ // Only deal with element opens
344
+ if ( $reader->nodeType !== XMLReader::ELEMENT ) {
345
+ continue;
346
+ }
347
+
348
+ switch ( $reader->name ) {
349
+ case 'wp:wxr_version':
350
+ // Upgrade to the correct version
351
+ $this->version = $reader->readString();
352
+
353
+ if ( version_compare( $this->version, self::MAX_WXR_VERSION, '>' ) ) {
354
+ $this->logger->warning( sprintf(
355
+ __( 'This WXR file (version %s) is newer than the importer (version %s) and may not be supported. Please consider updating.', 'rara-demo-import' ),
356
+ $this->version,
357
+ self::MAX_WXR_VERSION
358
+ ) );
359
+ }
360
+
361
+ // Handled everything in this node, move on to the next
362
+ $reader->next();
363
+ break;
364
+
365
+ case 'wp:base_site_url':
366
+ $this->base_url = $reader->readString();
367
+
368
+ // Handled everything in this node, move on to the next
369
+ $reader->next();
370
+ break;
371
+
372
+ case 'item':
373
+ $node = $reader->expand();
374
+ $parsed = $this->parse_post_node( $node );
375
+ if ( is_wp_error( $parsed ) ) {
376
+ $this->log_error( $parsed );
377
+
378
+ // Skip the rest of this post
379
+ $reader->next();
380
+ break;
381
+ }
382
+
383
+ $this->process_post( $parsed['data'], $parsed['meta'], $parsed['comments'], $parsed['terms'] );
384
+
385
+ // Handled everything in this node, move on to the next
386
+ $reader->next();
387
+ break;
388
+
389
+ case 'wp:author':
390
+ $node = $reader->expand();
391
+
392
+ $parsed = $this->parse_author_node( $node );
393
+ if ( is_wp_error( $parsed ) ) {
394
+ $this->log_error( $parsed );
395
+
396
+ // Skip the rest of this post
397
+ $reader->next();
398
+ break;
399
+ }
400
+
401
+ $status = $this->process_author( $parsed['data'], $parsed['meta'] );
402
+ if ( is_wp_error( $status ) ) {
403
+ $this->log_error( $status );
404
+ }
405
+
406
+ // Handled everything in this node, move on to the next
407
+ $reader->next();
408
+ break;
409
+
410
+ case 'wp:category':
411
+ $node = $reader->expand();
412
+
413
+ $parsed = $this->parse_term_node( $node, 'category' );
414
+ if ( is_wp_error( $parsed ) ) {
415
+ $this->log_error( $parsed );
416
+
417
+ // Skip the rest of this post
418
+ $reader->next();
419
+ break;
420
+ }
421
+
422
+ $status = $this->process_term( $parsed['data'], $parsed['meta'] );
423
+
424
+ // Handled everything in this node, move on to the next
425
+ $reader->next();
426
+ break;
427
+
428
+ case 'wp:tag':
429
+ $node = $reader->expand();
430
+
431
+ $parsed = $this->parse_term_node( $node, 'tag' );
432
+ if ( is_wp_error( $parsed ) ) {
433
+ $this->log_error( $parsed );
434
+
435
+ // Skip the rest of this post
436
+ $reader->next();
437
+ break;
438
+ }
439
+
440
+ $status = $this->process_term( $parsed['data'], $parsed['meta'] );
441
+
442
+ // Handled everything in this node, move on to the next
443
+ $reader->next();
444
+ break;
445
+
446
+ case 'wp:term':
447
+ $node = $reader->expand();
448
+
449
+ $parsed = $this->parse_term_node( $node );
450
+ if ( is_wp_error( $parsed ) ) {
451
+ $this->log_error( $parsed );
452
+
453
+ // Skip the rest of this post
454
+ $reader->next();
455
+ break;
456
+ }
457
+
458
+ $status = $this->process_term( $parsed['data'], $parsed['meta'] );
459
+
460
+ // Handled everything in this node, move on to the next
461
+ $reader->next();
462
+ break;
463
+
464
+ default:
465
+ // Skip this node, probably handled by something already
466
+ break;
467
+ }
468
+ }
469
+
470
+ // Now that we've done the main processing, do any required
471
+ // post-processing and remapping.
472
+ $this->post_process();
473
+
474
+ if ( $this->options['aggressive_url_search'] ) {
475
+ $this->replace_attachment_urls_in_content();
476
+ }
477
+ // $this->remap_featured_images();
478
+
479
+ $this->import_end();
480
+ }
481
+
482
+ /**
483
+ * Log an error instance to the logger.
484
+ *
485
+ * @param WP_Error $error Error instance to log.
486
+ */
487
+ protected function log_error( WP_Error $error ) {
488
+ $this->logger->warning( $error->get_error_message() );
489
+
490
+ // Log the data as debug info too
491
+ $data = $error->get_error_data();
492
+ if ( ! empty( $data ) ) {
493
+ $this->logger->debug( var_export( $data, true ) );
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Parses the WXR file and prepares us for the task of processing parsed data
499
+ *
500
+ * @param string $file Path to the WXR file for importing
501
+ */
502
+ protected function import_start( $file ) {
503
+ if ( ! is_file( $file ) ) {
504
+ return new WP_Error( 'wxr_importer.file_missing', __( 'The file does not exist, please try again.', 'rara-demo-import' ) );
505
+ }
506
+
507
+ // Suspend bunches of stuff in WP core
508
+ wp_defer_term_counting( true );
509
+ wp_defer_comment_counting( true );
510
+ wp_suspend_cache_invalidation( true );
511
+
512
+ // Prefill exists calls if told to
513
+ if ( $this->options['prefill_existing_posts'] ) {
514
+ $this->prefill_existing_posts();
515
+ }
516
+ if ( $this->options['prefill_existing_comments'] ) {
517
+ $this->prefill_existing_comments();
518
+ }
519
+ if ( $this->options['prefill_existing_terms'] ) {
520
+ $this->prefill_existing_terms();
521
+ }
522
+
523
+ /**
524
+ * Begin the import.
525
+ *
526
+ * Fires before the import process has begun. If you need to suspend
527
+ * caching or heavy processing on hooks, do so here.
528
+ */
529
+ do_action( 'import_start' );
530
+ }
531
+
532
+ /**
533
+ * Performs post-import cleanup of files and the cache
534
+ */
535
+ protected function import_end() {
536
+ // Re-enable stuff in core
537
+ wp_suspend_cache_invalidation( false );
538
+ wp_cache_flush();
539
+ foreach ( get_taxonomies() as $tax ) {
540
+ delete_option( "{$tax}_children" );
541
+ _get_term_hierarchy( $tax );
542
+ }
543
+
544
+ wp_defer_term_counting( false );
545
+ wp_defer_comment_counting( false );
546
+
547
+ /**
548
+ * Complete the import.
549
+ *
550
+ * Fires after the import process has finished. If you need to update
551
+ * your cache or re-enable processing, do so here.
552
+ */
553
+ do_action( 'import_end' );
554
+ }
555
+
556
+ /**
557
+ * Set the user mapping.
558
+ *
559
+ * @param array $mapping List of map arrays (containing `old_slug`, `old_id`, `new_id`)
560
+ */
561
+ public function set_user_mapping( $mapping ) {
562
+ foreach ( $mapping as $map ) {
563
+ if ( empty( $map['old_slug'] ) || empty( $map['old_id'] ) || empty( $map['new_id'] ) ) {
564
+ $this->logger->warning( __( 'Invalid author mapping', 'rara-demo-import' ) );
565
+ $this->logger->debug( var_export( $map, true ) );
566
+ continue;
567
+ }
568
+
569
+ $old_slug = $map['old_slug'];
570
+ $old_id = $map['old_id'];
571
+ $new_id = $map['new_id'];
572
+
573
+ $this->mapping['user'][ $old_id ] = $new_id;
574
+ $this->mapping['user_slug'][ $old_slug ] = $new_id;
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Set the user slug overrides.
580
+ *
581
+ * Allows overriding the slug in the import with a custom/renamed version.
582
+ *
583
+ * @param string[] $overrides Map of old slug to new slug.
584
+ */
585
+ public function set_user_slug_overrides( $overrides ) {
586
+ foreach ( $overrides as $original => $renamed ) {
587
+ $this->user_slug_override[ $original ] = $renamed;
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Parse a post node into post data.
593
+ *
594
+ * @param DOMElement $node Parent node of post data (typically `item`).
595
+ * @return array|WP_Error Post data array on success, error otherwise.
596
+ */
597
+ protected function parse_post_node( $node ) {
598
+ $data = array();
599
+ $meta = array();
600
+ $comments = array();
601
+ $terms = array();
602
+
603
+ foreach ( $node->childNodes as $child ) {
604
+ // We only care about child elements
605
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
606
+ continue;
607
+ }
608
+
609
+ switch ( $child->tagName ) {
610
+ case 'wp:post_type':
611
+ $data['post_type'] = $child->textContent;
612
+ break;
613
+
614
+ case 'title':
615
+ $data['post_title'] = $child->textContent;
616
+ break;
617
+
618
+ case 'guid':
619
+ $data['guid'] = $child->textContent;
620
+ break;
621
+
622
+ case 'dc:creator':
623
+ $data['post_author'] = $child->textContent;
624
+ break;
625
+
626
+ case 'content:encoded':
627
+ $data['post_content'] = $child->textContent;
628
+ break;
629
+
630
+ case 'excerpt:encoded':
631
+ $data['post_excerpt'] = $child->textContent;
632
+ break;
633
+
634
+ case 'wp:post_id':
635
+ $data['post_id'] = $child->textContent;
636
+ break;
637
+
638
+ case 'wp:post_date':
639
+ $data['post_date'] = $child->textContent;
640
+ break;
641
+
642
+ case 'wp:post_date_gmt':
643
+ $data['post_date_gmt'] = $child->textContent;
644
+ break;
645
+
646
+ case 'wp:comment_status':
647
+ $data['comment_status'] = $child->textContent;
648
+ break;
649
+
650
+ case 'wp:ping_status':
651
+ $data['ping_status'] = $child->textContent;
652
+ break;
653
+
654
+ case 'wp:post_name':
655
+ $data['post_name'] = $child->textContent;
656
+ break;
657
+
658
+ case 'wp:status':
659
+ $data['post_status'] = $child->textContent;
660
+
661
+ if ( $data['post_status'] === 'auto-draft' ) {
662
+ // Bail now
663
+ return new WP_Error(
664
+ 'wxr_importer.post.cannot_import_draft',
665
+ __( 'Cannot import auto-draft posts' ),
666
+ $data
667
+ );
668
+ }
669
+ break;
670
+
671
+ case 'wp:post_parent':
672
+ $data['post_parent'] = $child->textContent;
673
+ break;
674
+
675
+ case 'wp:menu_order':
676
+ $data['menu_order'] = $child->textContent;
677
+ break;
678
+
679
+ case 'wp:post_password':
680
+ $data['post_password'] = $child->textContent;
681
+ break;
682
+
683
+ case 'wp:is_sticky':
684
+ $data['is_sticky'] = $child->textContent;
685
+ break;
686
+
687
+ case 'wp:attachment_url':
688
+ $data['attachment_url'] = $child->textContent;
689
+ break;
690
+
691
+ case 'wp:postmeta':
692
+ $meta_item = $this->parse_meta_node( $child );
693
+ if ( ! empty( $meta_item ) ) {
694
+ $meta[] = $meta_item;
695
+ }
696
+ break;
697
+
698
+ case 'wp:comment':
699
+ $comment_item = $this->parse_comment_node( $child );
700
+ if ( ! empty( $comment_item ) ) {
701
+ $comments[] = $comment_item;
702
+ }
703
+ break;
704
+
705
+ case 'category':
706
+ $term_item = $this->parse_category_node( $child );
707
+ if ( ! empty( $term_item ) ) {
708
+ $terms[] = $term_item;
709
+ }
710
+ break;
711
+ }
712
+ }
713
+
714
+ return compact( 'data', 'meta', 'comments', 'terms' );
715
+ }
716
+
717
+ /**
718
+ * Create new posts based on import information
719
+ *
720
+ * Posts marked as having a parent which doesn't exist will become top level items.
721
+ * Doesn't create a new post if: the post type doesn't exist, the given post ID
722
+ * is already noted as imported or a post with the same title and date already exists.
723
+ * Note that new/updated terms, comments and meta are imported for the last of the above.
724
+ */
725
+ protected function process_post( $data, $meta, $comments, $terms ) {
726
+ /**
727
+ * Pre-process post data.
728
+ *
729
+ * @param array $data Post data. (Return empty to skip.)
730
+ * @param array $meta Meta data.
731
+ * @param array $comments Comments on the post.
732
+ * @param array $terms Terms on the post.
733
+ */
734
+ $data = apply_filters( 'wxr_importer.pre_process.post', $data, $meta, $comments, $terms );
735
+ if ( empty( $data ) ) {
736
+ return false;
737
+ }
738
+
739
+ $original_id = isset( $data['post_id'] ) ? (int) $data['post_id'] : 0;
740
+ $parent_id = isset( $data['post_parent'] ) ? (int) $data['post_parent'] : 0;
741
+ $author_id = isset( $data['post_author'] ) ? (int) $data['post_author'] : 0;
742
+
743
+ // Have we already processed this?
744
+ if ( isset( $this->mapping['post'][ $original_id ] ) ) {
745
+ return;
746
+ }
747
+
748
+ $post_type_object = get_post_type_object( $data['post_type'] );
749
+
750
+ // Is this type even valid?
751
+ if ( ! $post_type_object ) {
752
+ $this->logger->warning( sprintf(
753
+ __( 'Failed to import "%s": Invalid post type %s', 'rara-demo-import' ),
754
+ $data['post_title'],
755
+ $data['post_type']
756
+ ) );
757
+ return false;
758
+ }
759
+
760
+ $post_exists = $this->post_exists( $data );
761
+ if ( $post_exists ) {
762
+ $this->logger->info( sprintf(
763
+ __( '%s "%s" already exists.', 'rara-demo-import' ),
764
+ $post_type_object->labels->singular_name,
765
+ $data['post_title']
766
+ ) );
767
+
768
+ // Even though this post already exists, new comments might need importing
769
+ $this->process_comments( $comments, $original_id, $data, $post_exists );
770
+
771
+ return false;
772
+ }
773
+
774
+ // Map the parent post, or mark it as one we need to fix
775
+ $requires_remapping = false;
776
+ if ( $parent_id ) {
777
+ if ( isset( $this->mapping['post'][ $parent_id ] ) ) {
778
+ $data['post_parent'] = $this->mapping['post'][ $parent_id ];
779
+ } else {
780
+ $meta[] = array( 'key' => '_wxr_import_parent', 'value' => $parent_id );
781
+ $requires_remapping = true;
782
+
783
+ $data['post_parent'] = 0;
784
+ }
785
+ }
786
+
787
+ // Map the author, or mark it as one we need to fix
788
+ $author = sanitize_user( $data['post_author'], true );
789
+ if ( empty( $author ) ) {
790
+ // Missing or invalid author, use default if available.
791
+ $data['post_author'] = $this->options['default_author'];
792
+ } elseif ( isset( $this->mapping['user_slug'][ $author ] ) ) {
793
+ $data['post_author'] = $this->mapping['user_slug'][ $author ];
794
+ } else {
795
+ $meta[] = array( 'key' => '_wxr_import_user_slug', 'value' => $author );
796
+ $requires_remapping = true;
797
+
798
+ $data['post_author'] = (int) get_current_user_id();
799
+ }
800
+
801
+ // Does the post look like it contains attachment images?
802
+ if ( preg_match( self::REGEX_HAS_ATTACHMENT_REFS, $data['post_content'] ) ) {
803
+ $meta[] = array( 'key' => '_wxr_import_has_attachment_refs', 'value' => true );
804
+ $requires_remapping = true;
805
+ }
806
+
807
+ // Whitelist to just the keys we allow
808
+ $postdata = array(
809
+ 'import_id' => $data['post_id'],
810
+ );
811
+ $allowed = array(
812
+ 'post_author' => true,
813
+ 'post_date' => true,
814
+ 'post_date_gmt' => true,
815
+ 'post_content' => true,
816
+ 'post_excerpt' => true,
817
+ 'post_title' => true,
818
+ 'post_status' => true,
819
+ 'post_name' => true,
820
+ 'comment_status' => true,
821
+ 'ping_status' => true,
822
+ 'guid' => true,
823
+ 'post_parent' => true,
824
+ 'menu_order' => true,
825
+ 'post_type' => true,
826
+ 'post_password' => true,
827
+ );
828
+ foreach ( $data as $key => $value ) {
829
+ if ( ! isset( $allowed[ $key ] ) ) {
830
+ continue;
831
+ }
832
+
833
+ $postdata[ $key ] = $data[ $key ];
834
+ }
835
+
836
+ $postdata = apply_filters( 'wp_import_post_data_processed', $postdata, $data );
837
+
838
+ if ( 'attachment' === $postdata['post_type'] ) {
839
+ if ( ! $this->options['fetch_attachments'] ) {
840
+ $this->logger->notice( sprintf(
841
+ __( 'Skipping attachment "%s", fetching attachments disabled' ),
842
+ $data['post_title']
843
+ ) );
844
+ return false;
845
+ }
846
+ $remote_url = ! empty( $data['attachment_url'] ) ? $data['attachment_url'] : $data['guid'];
847
+ $post_id = $this->process_attachment( $postdata, $meta, $remote_url );
848
+ } else {
849
+ $post_id = wp_insert_post( $postdata, true );
850
+ do_action( 'wp_import_insert_post', $post_id, $original_id, $postdata, $data );
851
+ }
852
+
853
+ if ( is_wp_error( $post_id ) ) {
854
+ $this->logger->error( sprintf(
855
+ __( 'Failed to import "%s" (%s)', 'rara-demo-import' ),
856
+ $data['post_title'],
857
+ $post_type_object->labels->singular_name
858
+ ) );
859
+ $this->logger->debug( $post_id->get_error_message() );
860
+
861
+ /**
862
+ * Post processing failed.
863
+ *
864
+ * @param WP_Error $post_id Error object.
865
+ * @param array $data Raw data imported for the post.
866
+ * @param array $meta Raw meta data, already processed by {@see process_post_meta}.
867
+ * @param array $comments Raw comment data, already processed by {@see process_comments}.
868
+ * @param array $terms Raw term data, already processed.
869
+ */
870
+ do_action( 'wxr_importer.process_failed.post', $post_id, $data, $meta, $comments, $terms );
871
+ return false;
872
+ }
873
+
874
+ // Ensure stickiness is handled correctly too
875
+ if ( $data['is_sticky'] === '1' ) {
876
+ stick_post( $post_id );
877
+ }
878
+
879
+ // map pre-import ID to local ID
880
+ $this->mapping['post'][ $original_id ] = (int) $post_id;
881
+ if ( $requires_remapping ) {
882
+ $this->requires_remapping['post'][ $post_id ] = true;
883
+ }
884
+ $this->mark_post_exists( $data, $post_id );
885
+
886
+ $this->logger->info( sprintf(
887
+ __( 'Imported "%s" (%s)', 'rara-demo-import' ),
888
+ $data['post_title'],
889
+ $post_type_object->labels->singular_name
890
+ ) );
891
+ $this->logger->debug( sprintf(
892
+ __( 'Post %d remapped to %d', 'rara-demo-import' ),
893
+ $original_id,
894
+ $post_id
895
+ ) );
896
+
897
+ // Handle the terms too
898
+ $terms = apply_filters( 'wp_import_post_terms', $terms, $post_id, $data );
899
+
900
+ if ( ! empty( $terms ) ) {
901
+ $term_ids = array();
902
+ foreach ( $terms as $term ) {
903
+ $taxonomy = $term['taxonomy'];
904
+ $key = sha1( $taxonomy . ':' . $term['slug'] );
905
+
906
+ if ( isset( $this->mapping['term'][ $key ] ) ) {
907
+ $term_ids[ $taxonomy ][] = (int) $this->mapping['term'][ $key ];
908
+ } else {
909
+ $meta[] = array( 'key' => '_wxr_import_term', 'value' => $term );
910
+ $requires_remapping = true;
911
+ }
912
+ }
913
+
914
+ foreach ( $term_ids as $tax => $ids ) {
915
+ $tt_ids = wp_set_post_terms( $post_id, $ids, $tax );
916
+ do_action( 'wp_import_set_post_terms', $tt_ids, $ids, $tax, $post_id, $data );
917
+ }
918
+ }
919
+
920
+ $this->process_comments( $comments, $post_id, $data );
921
+ $this->process_post_meta( $meta, $post_id, $data );
922
+
923
+ if ( 'nav_menu_item' === $data['post_type'] ) {
924
+ $this->process_menu_item_meta( $post_id, $data, $meta );
925
+ }
926
+
927
+ /**
928
+ * Post processing completed.
929
+ *
930
+ * @param int $post_id New post ID.
931
+ * @param array $data Raw data imported for the post.
932
+ * @param array $meta Raw meta data, already processed by {@see process_post_meta}.
933
+ * @param array $comments Raw comment data, already processed by {@see process_comments}.
934
+ * @param array $terms Raw term data, already processed.
935
+ */
936
+ do_action( 'wxr_importer.processed.post', $post_id, $data, $meta, $comments, $terms );
937
+ }
938
+
939
+ /**
940
+ * Attempt to create a new menu item from import data
941
+ *
942
+ * Fails for draft, orphaned menu items and those without an associated nav_menu
943
+ * or an invalid nav_menu term. If the post type or term object which the menu item
944
+ * represents doesn't exist then the menu item will not be imported (waits until the
945
+ * end of the import to retry again before discarding).
946
+ *
947
+ * @param array $item Menu item details from WXR file
948
+ */
949
+ protected function process_menu_item_meta( $post_id, $data, $meta ) {
950
+
951
+ $item_type = get_post_meta( $post_id, '_menu_item_type', true );
952
+ $original_object_id = get_post_meta( $post_id, '_menu_item_object_id', true );
953
+ $object_id = null;
954
+
955
+ $this->logger->debug( sprintf( 'Processing menu item %s', $item_type ) );
956
+
957
+ $requires_remapping = false;
958
+ switch ( $item_type ) {
959
+ case 'taxonomy':
960
+ if ( isset( $this->mapping['term_id'][ $original_object_id ] ) ) {
961
+ $object_id = $this->mapping['term_id'][ $original_object_id ];
962
+ } else {
963
+ add_post_meta( $post_id, '_wxr_import_menu_item', wp_slash( $original_object_id ) );
964
+ $requires_remapping = true;
965
+ }
966
+ break;
967
+
968
+ case 'post_type':
969
+ if ( isset( $this->mapping['post'][ $original_object_id ] ) ) {
970
+ $object_id = $this->mapping['post'][ $original_object_id ];
971
+ } else {
972
+ add_post_meta( $post_id, '_wxr_import_menu_item', wp_slash( $original_object_id ) );
973
+ $requires_remapping = true;
974
+ }
975
+ break;
976
+
977
+ case 'custom':
978
+ // Custom refers to itself, wonderfully easy.
979
+ $object_id = $post_id;
980
+ break;
981
+
982
+ default:
983
+ // associated object is missing or not imported yet, we'll retry later
984
+ $this->missing_menu_items[] = $item;
985
+ $this->logger->debug( 'Unknown menu item type' );
986
+ break;
987
+ }
988
+
989
+ if ( $requires_remapping ) {
990
+ $this->requires_remapping['post'][ $post_id ] = true;
991
+ }
992
+
993
+ if ( empty( $object_id ) ) {
994
+ // Nothing needed here.
995
+ return;
996
+ }
997
+
998
+ $this->logger->debug( sprintf( 'Menu item %d mapped to %d', $original_object_id, $object_id ) );
999
+ update_post_meta( $post_id, '_menu_item_object_id', wp_slash( $object_id ) );
1000
+ }
1001
+
1002
+ /**
1003
+ * If fetching attachments is enabled then attempt to create a new attachment
1004
+ *
1005
+ * @param array $post Attachment post details from WXR
1006
+ * @param string $url URL to fetch attachment from
1007
+ * @return int|WP_Error Post ID on success, WP_Error otherwise
1008
+ */
1009
+ protected function process_attachment( $post, $meta, $remote_url ) {
1010
+ // try to use _wp_attached file for upload folder placement to ensure the same location as the export site
1011
+ // e.g. location is 2003/05/image.jpg but the attachment post_date is 2010/09, see media_handle_upload()
1012
+ $post['upload_date'] = $post['post_date'];
1013
+ foreach ( $meta as $meta_item ) {
1014
+ if ( $meta_item['key'] !== '_wp_attached_file' ) {
1015
+ continue;
1016
+ }
1017
+
1018
+ if ( preg_match( '%^[0-9]{4}/[0-9]{2}%', $meta_item['value'], $matches ) ) {
1019
+ $post['upload_date'] = $matches[0];
1020
+ }
1021
+ break;
1022
+ }
1023
+
1024
+ // if the URL is absolute, but does not contain address, then upload it assuming base_site_url
1025
+ if ( preg_match( '|^/[\w\W]+$|', $remote_url ) ) {
1026
+ $remote_url = rtrim( $this->base_url, '/' ) . $remote_url;
1027
+ }
1028
+
1029
+ $upload = $this->fetch_remote_file( $remote_url, $post );
1030
+ if ( is_wp_error( $upload ) ) {
1031
+ return $upload;
1032
+ }
1033
+
1034
+ $info = wp_check_filetype( $upload['file'] );
1035
+ if ( ! $info ) {
1036
+ return new WP_Error( 'attachment_processing_error', __( 'Invalid file type', 'rara-demo-import' ) );
1037
+ }
1038
+
1039
+ $post['post_mime_type'] = $info['type'];
1040
+
1041
+ // WP really likes using the GUID for display. Allow updating it.
1042
+ // See https://core.trac.wordpress.org/ticket/33386
1043
+ if ( $this->options['update_attachment_guids'] ) {
1044
+ $post['guid'] = $upload['url'];
1045
+ }
1046
+
1047
+ // as per wp-admin/includes/upload.php
1048
+ $post_id = wp_insert_attachment( $post, $upload['file'] );
1049
+ if ( is_wp_error( $post_id ) ) {
1050
+ return $post_id;
1051
+ }
1052
+
1053
+ $attachment_metadata = wp_generate_attachment_metadata( $post_id, $upload['file'] );
1054
+ wp_update_attachment_metadata( $post_id, $attachment_metadata );
1055
+
1056
+ // Map this image URL later if we need to
1057
+ $this->url_remap[ $remote_url ] = $upload['url'];
1058
+
1059
+ // If we have a HTTPS URL, ensure the HTTP URL gets replaced too
1060
+ if ( substr( $remote_url, 0, 8 ) === 'https://' ) {
1061
+ $insecure_url = 'http' . substr( $remote_url, 5 );
1062
+ $this->url_remap[ $insecure_url ] = $upload['url'];
1063
+ }
1064
+
1065
+ if ( $this->options['aggressive_url_search'] ) {
1066
+ // remap resized image URLs, works by stripping the extension and remapping the URL stub.
1067
+ /*if ( preg_match( '!^image/!', $info['type'] ) ) {
1068
+ $parts = pathinfo( $remote_url );
1069
+ $name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2
1070
+
1071
+ $parts_new = pathinfo( $upload['url'] );
1072
+ $name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" );
1073
+
1074
+ $this->url_remap[$parts['dirname'] . '/' . $name] = $parts_new['dirname'] . '/' . $name_new;
1075
+ }*/
1076
+ }
1077
+
1078
+ return $post_id;
1079
+ }
1080
+
1081
+ /**
1082
+ * Parse a meta node into meta data.
1083
+ *
1084
+ * @param DOMElement $node Parent node of meta data (typically `wp:postmeta` or `wp:commentmeta`).
1085
+ * @return array|null Meta data array on success, or null on error.
1086
+ */
1087
+ protected function parse_meta_node( $node ) {
1088
+ foreach ( $node->childNodes as $child ) {
1089
+ // We only care about child elements
1090
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
1091
+ continue;
1092
+ }
1093
+
1094
+ switch ( $child->tagName ) {
1095
+ case 'wp:meta_key':
1096
+ $key = $child->textContent;
1097
+ break;
1098
+
1099
+ case 'wp:meta_value':
1100
+ $value = $child->textContent;
1101
+ break;
1102
+ }
1103
+ }
1104
+
1105
+ if ( empty( $key ) || empty( $value ) ) {
1106
+ return null;
1107
+ }
1108
+
1109
+ return compact( 'key', 'value' );
1110
+ }
1111
+
1112
+ /**
1113
+ * Process and import post meta items.
1114
+ *
1115
+ * @param array $meta List of meta data arrays
1116
+ * @param int $post_id Post to associate with
1117
+ * @param array $post Post data
1118
+ * @return int|WP_Error Number of meta items imported on success, error otherwise.
1119
+ */
1120
+ protected function process_post_meta( $meta, $post_id, $post ) {
1121
+ if ( empty( $meta ) ) {
1122
+ return true;
1123
+ }
1124
+
1125
+ foreach ( $meta as $meta_item ) {
1126
+ /**
1127
+ * Pre-process post meta data.
1128
+ *
1129
+ * @param array $meta_item Meta data. (Return empty to skip.)
1130
+ * @param int $post_id Post the meta is attached to.
1131
+ */
1132
+ $meta_item = apply_filters( 'wxr_importer.pre_process.post_meta', $meta_item, $post_id );
1133
+ if ( empty( $meta_item ) ) {
1134
+ return false;
1135
+ }
1136
+
1137
+ $key = apply_filters( 'import_post_meta_key', $meta_item['key'], $post_id, $post );
1138
+ $value = false;
1139
+
1140
+ if ( '_edit_last' === $key ) {
1141
+ $value = intval( $meta_item['value'] );
1142
+ if ( ! isset( $this->mapping['user'][ $value ] ) ) {
1143
+ // Skip!
1144
+ continue;
1145
+ }
1146
+
1147
+ $value = $this->mapping['user'][ $value ];
1148
+ }
1149
+
1150
+ if ( $key ) {
1151
+ // export gets meta straight from the DB so could have a serialized string
1152
+ if ( ! $value ) {
1153
+ $value = maybe_unserialize( $meta_item['value'] );
1154
+ }
1155
+
1156
+ add_post_meta( $post_id, $key, $value );
1157
+ do_action( 'import_post_meta', $post_id, $key, $value );
1158
+
1159
+ // if the post has a featured image, take note of this in case of remap
1160
+ if ( '_thumbnail_id' === $key ) {
1161
+ $this->featured_images[ $post_id ] = (int) $value;
1162
+ }
1163
+ }
1164
+ }
1165
+
1166
+ return true;
1167
+ }
1168
+
1169
+ /**
1170
+ * Parse a comment node into comment data.
1171
+ *
1172
+ * @param DOMElement $node Parent node of comment data (typically `wp:comment`).
1173
+ * @return array Comment data array.
1174
+ */
1175
+ protected function parse_comment_node( $node ) {
1176
+ $data = array(
1177
+ 'commentmeta' => array(),
1178
+ );
1179
+
1180
+ foreach ( $node->childNodes as $child ) {
1181
+ // We only care about child elements
1182
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
1183
+ continue;
1184
+ }
1185
+
1186
+ switch ( $child->tagName ) {
1187
+ case 'wp:comment_id':
1188
+ $data['comment_id'] = $child->textContent;
1189
+ break;
1190
+ case 'wp:comment_author':
1191
+ $data['comment_author'] = $child->textContent;
1192
+ break;
1193
+
1194
+ case 'wp:comment_author_email':
1195
+ $data['comment_author_email'] = $child->textContent;
1196
+ break;
1197
+
1198
+ case 'wp:comment_author_IP':
1199
+ $data['comment_author_IP'] = $child->textContent;
1200
+ break;
1201
+
1202
+ case 'wp:comment_author_url':
1203
+ $data['comment_author_url'] = $child->textContent;
1204
+ break;
1205
+
1206
+ case 'wp:comment_user_id':
1207
+ $data['comment_user_id'] = $child->textContent;
1208
+ break;
1209
+
1210
+ case 'wp:comment_date':
1211
+ $data['comment_date'] = $child->textContent;
1212
+ break;
1213
+
1214
+ case 'wp:comment_date_gmt':
1215
+ $data['comment_date_gmt'] = $child->textContent;
1216
+ break;
1217
+
1218
+ case 'wp:comment_content':
1219
+ $data['comment_content'] = $child->textContent;
1220
+ break;
1221
+
1222
+ case 'wp:comment_approved':
1223
+ $data['comment_approved'] = $child->textContent;
1224
+ break;
1225
+
1226
+ case 'wp:comment_type':
1227
+ $data['comment_type'] = $child->textContent;
1228
+ break;
1229
+
1230
+ case 'wp:comment_parent':
1231
+ $data['comment_parent'] = $child->textContent;
1232
+ break;
1233
+
1234
+ case 'wp:commentmeta':
1235
+ $meta_item = $this->parse_meta_node( $child );
1236
+ if ( ! empty( $meta_item ) ) {
1237
+ $data['commentmeta'][] = $meta_item;
1238
+ }
1239
+ break;
1240
+ }
1241
+ }
1242
+
1243
+ return $data;
1244
+ }
1245
+
1246
+ /**
1247
+ * Process and import comment data.
1248
+ *
1249
+ * @param array $comments List of comment data arrays.
1250
+ * @param int $post_id Post to associate with.
1251
+ * @param array $post Post data.
1252
+ * @return int|WP_Error Number of comments imported on success, error otherwise.
1253
+ */
1254
+ protected function process_comments( $comments, $post_id, $post, $post_exists = false ) {
1255
+
1256
+ $comments = apply_filters( 'wp_import_post_comments', $comments, $post_id, $post );
1257
+ if ( empty( $comments ) ) {
1258
+ return 0;
1259
+ }
1260
+
1261
+ $num_comments = 0;
1262
+
1263
+ // Sort by ID to avoid excessive remapping later
1264
+ usort( $comments, array( $this, 'sort_comments_by_id' ) );
1265
+
1266
+ foreach ( $comments as $key => $comment ) {
1267
+ /**
1268
+ * Pre-process comment data
1269
+ *
1270
+ * @param array $comment Comment data. (Return empty to skip.)
1271
+ * @param int $post_id Post the comment is attached to.
1272
+ */
1273
+ $comment = apply_filters( 'wxr_importer.pre_process.comment', $comment, $post_id );
1274
+ if ( empty( $comment ) ) {
1275
+ return false;
1276
+ }
1277
+
1278
+ $original_id = isset( $comment['comment_id'] ) ? (int) $comment['comment_id'] : 0;
1279
+ $parent_id = isset( $comment['comment_parent'] ) ? (int) $comment['comment_parent'] : 0;
1280
+ $author_id = isset( $comment['comment_user_id'] ) ? (int) $comment['comment_user_id'] : 0;
1281
+
1282
+ // if this is a new post we can skip the comment_exists() check
1283
+ // TODO: Check comment_exists for performance
1284
+ if ( $post_exists ) {
1285
+ $existing = $this->comment_exists( $comment );
1286
+ if ( $existing ) {
1287
+ $this->mapping['comment'][ $original_id ] = $exists;
1288
+ continue;
1289
+ }
1290
+ }
1291
+
1292
+ // Remove meta from the main array
1293
+ $meta = isset( $comment['commentmeta'] ) ? $comment['commentmeta'] : array();
1294
+ unset( $comment['commentmeta'] );
1295
+
1296
+ // Map the parent comment, or mark it as one we need to fix
1297
+ $requires_remapping = false;
1298
+ if ( $parent_id ) {
1299
+ if ( isset( $this->mapping['comment'][ $parent_id ] ) ) {
1300
+ $comment['comment_parent'] = $this->mapping['comment'][ $parent_id ];
1301
+ } else {
1302
+ // Prepare for remapping later
1303
+ $meta[] = array( 'key' => '_wxr_import_parent', 'value' => $parent_id );
1304
+ $requires_remapping = true;
1305
+
1306
+ // Wipe the parent for now
1307
+ $comment['comment_parent'] = 0;
1308
+ }
1309
+ }
1310
+
1311
+ // Map the author, or mark it as one we need to fix
1312
+ if ( $author_id ) {
1313
+ if ( isset( $this->mapping['user'][ $author_id ] ) ) {
1314
+ $comment['user_id'] = $this->mapping['user'][ $author_id ];
1315
+ } else {
1316
+ // Prepare for remapping later
1317
+ $meta[] = array( 'key' => '_wxr_import_user', 'value' => $author_id );
1318
+ $requires_remapping = true;
1319
+
1320
+ // Wipe the user for now
1321
+ $comment['user_id'] = 0;
1322
+ }
1323
+ }
1324
+
1325
+ // Run standard core filters
1326
+ $comment['comment_post_ID'] = $post_id;
1327
+ $comment = wp_filter_comment( $comment );
1328
+
1329
+ // wp_insert_comment expects slashed data
1330
+ $comment_id = wp_insert_comment( wp_slash( $comment ) );
1331
+ $this->mapping['comment'][ $original_id ] = $comment_id;
1332
+ if ( $requires_remapping ) {
1333
+ $this->requires_remapping['comment'][ $comment_id ] = true;
1334
+ }
1335
+ $this->mark_comment_exists( $comment, $comment_id );
1336
+
1337
+ /**
1338
+ * Comment has been imported.
1339
+ *
1340
+ * @param int $comment_id New comment ID
1341
+ * @param array $comment Comment inserted (`comment_id` item refers to the original ID)
1342
+ * @param int $post_id Post parent of the comment
1343
+ * @param array $post Post data
1344
+ */
1345
+ do_action( 'wp_import_insert_comment', $comment_id, $comment, $post_id, $post );
1346
+
1347
+ // Process the meta items
1348
+ foreach ( $meta as $meta_item ) {
1349
+ $value = maybe_unserialize( $meta_item['value'] );
1350
+ add_comment_meta( $comment_id, wp_slash( $meta_item['key'] ), wp_slash( $value ) );
1351
+ }
1352
+
1353
+ /**
1354
+ * Post processing completed.
1355
+ *
1356
+ * @param int $post_id New post ID.
1357
+ * @param array $comment Raw data imported for the comment.
1358
+ * @param array $meta Raw meta data, already processed by {@see process_post_meta}.
1359
+ * @param array $post_id Parent post ID.
1360
+ */
1361
+ do_action( 'wxr_importer.processed.comment', $comment_id, $comment, $meta, $post_id );
1362
+
1363
+ $num_comments++;
1364
+ }
1365
+
1366
+ return $num_comments;
1367
+ }
1368
+
1369
+ protected function parse_category_node( $node ) {
1370
+ $data = array(
1371
+ // Default taxonomy to "category", since this is a `<category>` tag
1372
+ 'taxonomy' => 'category',
1373
+ );
1374
+ $meta = array();
1375
+
1376
+ if ( $node->hasAttribute( 'domain' ) ) {
1377
+ $data['taxonomy'] = $node->getAttribute( 'domain' );
1378
+ }
1379
+ if ( $node->hasAttribute( 'nicename' ) ) {
1380
+ $data['slug'] = $node->getAttribute( 'nicename' );
1381
+ }
1382
+
1383
+ $data['name'] = $node->textContent;
1384
+
1385
+ if ( empty( $data['slug'] ) ) {
1386
+ return null;
1387
+ }
1388
+
1389
+ // Just for extra compatibility
1390
+ if ( $data['taxonomy'] === 'tag' ) {
1391
+ $data['taxonomy'] = 'post_tag';
1392
+ }
1393
+
1394
+ return $data;
1395
+ }
1396
+
1397
+ /**
1398
+ * Callback for `usort` to sort comments by ID
1399
+ *
1400
+ * @param array $a Comment data for the first comment
1401
+ * @param array $b Comment data for the second comment
1402
+ * @return int
1403
+ */
1404
+ public static function sort_comments_by_id( $a, $b ) {
1405
+ if ( empty( $a['comment_id'] ) ) {
1406
+ return 1;
1407
+ }
1408
+
1409
+ if ( empty( $b['comment_id'] ) ) {
1410
+ return -1;
1411
+ }
1412
+
1413
+ return $a['comment_id'] - $b['comment_id'];
1414
+ }
1415
+
1416
+ protected function parse_author_node( $node ) {
1417
+ $data = array();
1418
+ $meta = array();
1419
+ foreach ( $node->childNodes as $child ) {
1420
+ // We only care about child elements
1421
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
1422
+ continue;
1423
+ }
1424
+
1425
+ switch ( $child->tagName ) {
1426
+ case 'wp:author_login':
1427
+ $data['user_login'] = $child->textContent;
1428
+ break;
1429
+
1430
+ case 'wp:author_id':
1431
+ $data['ID'] = $child->textContent;
1432
+ break;
1433
+
1434
+ case 'wp:author_email':
1435
+ $data['user_email'] = $child->textContent;
1436
+ break;
1437
+
1438
+ case 'wp:author_display_name':
1439
+ $data['display_name'] = $child->textContent;
1440
+ break;
1441
+
1442
+ case 'wp:author_first_name':
1443
+ $data['first_name'] = $child->textContent;
1444
+ break;
1445
+
1446
+ case 'wp:author_last_name':
1447
+ $data['last_name'] = $child->textContent;
1448
+ break;
1449
+ }
1450
+ }
1451
+
1452
+ return compact( 'data', 'meta' );
1453
+ }
1454
+
1455
+ protected function process_author( $data, $meta ) {
1456
+ /**
1457
+ * Pre-process user data.
1458
+ *
1459
+ * @param array $data User data. (Return empty to skip.)
1460
+ * @param array $meta Meta data.
1461
+ */
1462
+ $data = apply_filters( 'wxr_importer.pre_process.user', $data, $meta );
1463
+ if ( empty( $data ) ) {
1464
+ return false;
1465
+ }
1466
+
1467
+ // Have we already handled this user?
1468
+ $original_id = isset( $data['ID'] ) ? $data['ID'] : 0;
1469
+ $original_slug = $data['user_login'];
1470
+
1471
+ if ( isset( $this->mapping['user'][ $original_id ] ) ) {
1472
+ $existing = $this->mapping['user'][ $original_id ];
1473
+
1474
+ // Note the slug mapping if we need to too
1475
+ if ( ! isset( $this->mapping['user_slug'][ $original_slug ] ) ) {
1476
+ $this->mapping['user_slug'][ $original_slug ] = $existing;
1477
+ }
1478
+
1479
+ return false;
1480
+ }
1481
+
1482
+ if ( isset( $this->mapping['user_slug'][ $original_slug ] ) ) {
1483
+ $existing = $this->mapping['user_slug'][ $original_slug ];
1484
+
1485
+ // Ensure we note the mapping too
1486
+ $this->mapping['user'][ $original_id ] = $existing;
1487
+
1488
+ return false;
1489
+ }
1490
+
1491
+ // Allow overriding the user's slug
1492
+ $login = $original_slug;
1493
+ if ( isset( $this->user_slug_override[ $login ] ) ) {
1494
+ $login = $this->user_slug_override[ $login ];
1495
+ }
1496
+
1497
+ $userdata = array(
1498
+ 'user_login' => sanitize_user( $login, true ),
1499
+ 'user_pass' => wp_generate_password(),
1500
+ );
1501
+
1502
+ $allowed = array(
1503
+ 'user_email' => true,
1504
+ 'display_name' => true,
1505
+ 'first_name' => true,
1506
+ 'last_name' => true,
1507
+ );
1508
+ foreach ( $data as $key => $value ) {
1509
+ if ( ! isset( $allowed[ $key ] ) ) {
1510
+ continue;
1511
+ }
1512
+
1513
+ $userdata[ $key ] = $data[ $key ];
1514
+ }
1515
+
1516
+ $user_id = wp_insert_user( wp_slash( $userdata ) );
1517
+ if ( is_wp_error( $user_id ) ) {
1518
+ $this->logger->error( sprintf(
1519
+ __( 'Failed to import user "%s"', 'rara-demo-import' ),
1520
+ $userdata['user_login']
1521
+ ) );
1522
+ $this->logger->debug( $user_id->get_error_message() );
1523
+
1524
+ /**
1525
+ * User processing failed.
1526
+ *
1527
+ * @param WP_Error $user_id Error object.
1528
+ * @param array $userdata Raw data imported for the user.
1529
+ */
1530
+ do_action( 'wxr_importer.process_failed.user', $user_id, $userdata );
1531
+ return false;
1532
+ }
1533
+
1534
+ if ( $original_id ) {
1535
+ $this->mapping['user'][ $original_id ] = $user_id;
1536
+ }
1537
+ $this->mapping['user_slug'][ $original_slug ] = $user_id;
1538
+
1539
+ $this->logger->info( sprintf(
1540
+ __( 'Imported user "%s"', 'rara-demo-import' ),
1541
+ $userdata['user_login']
1542
+ ) );
1543
+ $this->logger->debug( sprintf(
1544
+ __( 'User %d remapped to %d', 'rara-demo-import' ),
1545
+ $original_id,
1546
+ $user_id
1547
+ ) );
1548
+
1549
+ // TODO: Implement meta handling once WXR includes it
1550
+ /**
1551
+ * User processing completed.
1552
+ *
1553
+ * @param int $user_id New user ID.
1554
+ * @param array $userdata Raw data imported for the user.
1555
+ */
1556
+ do_action( 'wxr_importer.processed.user', $user_id, $userdata );
1557
+ }
1558
+
1559
+ protected function parse_term_node( $node, $type = 'term' ) {
1560
+ $data = array();
1561
+ $meta = array();
1562
+
1563
+ $tag_name = array(
1564
+ 'id' => 'wp:term_id',
1565
+ 'taxonomy' => 'wp:term_taxonomy',
1566
+ 'slug' => 'wp:term_slug',
1567
+ 'parent' => 'wp:term_parent',
1568
+ 'name' => 'wp:term_name',
1569
+ 'description' => 'wp:term_description',
1570
+ );
1571
+ $taxonomy = null;
1572
+
1573
+ // Special casing!
1574
+ switch ( $type ) {
1575
+ case 'category':
1576
+ $tag_name['slug'] = 'wp:category_nicename';
1577
+ $tag_name['parent'] = 'wp:category_parent';
1578
+ $tag_name['name'] = 'wp:cat_name';
1579
+ $tag_name['description'] = 'wp:category_description';
1580
+ $tag_name['taxonomy'] = null;
1581
+
1582
+ $data['taxonomy'] = 'category';
1583
+ break;
1584
+
1585
+ case 'tag':
1586
+ $tag_name['slug'] = 'wp:tag_slug';
1587
+ $tag_name['parent'] = null;
1588
+ $tag_name['name'] = 'wp:tag_name';
1589
+ $tag_name['description'] = 'wp:tag_description';
1590
+ $tag_name['taxonomy'] = null;
1591
+
1592
+ $data['taxonomy'] = 'post_tag';
1593
+ break;
1594
+ }
1595
+
1596
+ foreach ( $node->childNodes as $child ) {
1597
+ // We only care about child elements
1598
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
1599
+ continue;
1600
+ }
1601
+
1602
+ $key = array_search( $child->tagName, $tag_name );
1603
+ if ( $key ) {
1604
+ $data[ $key ] = $child->textContent;
1605
+ }
1606
+ }
1607
+
1608
+ if ( empty( $data['taxonomy'] ) ) {
1609
+ return null;
1610
+ }
1611
+
1612
+ // Compatibility with WXR 1.0
1613
+ if ( $data['taxonomy'] === 'tag' ) {
1614
+ $data['taxonomy'] = 'post_tag';
1615
+ }
1616
+
1617
+ return compact( 'data', 'meta' );
1618
+ }
1619
+
1620
+ protected function process_term( $data, $meta ) {
1621
+ /**
1622
+ * Pre-process term data.
1623
+ *
1624
+ * @param array $data Term data. (Return empty to skip.)
1625
+ * @param array $meta Meta data.
1626
+ */
1627
+ $data = apply_filters( 'wxr_importer.pre_process.term', $data, $meta );
1628
+ if ( empty( $data ) ) {
1629
+ return false;
1630
+ }
1631
+
1632
+ $original_id = isset( $data['id'] ) ? (int) $data['id'] : 0;
1633
+ $parent_id = isset( $data['parent'] ) ? (int) $data['parent'] : 0;
1634
+
1635
+ $mapping_key = sha1( $data['taxonomy'] . ':' . $data['slug'] );
1636
+ $existing = $this->term_exists( $data );
1637
+ if ( $existing ) {
1638
+ $this->mapping['term'][ $mapping_key ] = $existing;
1639
+ $this->mapping['term_id'][ $original_id ] = $existing;
1640
+ return false;
1641
+ }
1642
+
1643
+ // WP really likes to repeat itself in export files
1644
+ if ( isset( $this->mapping['term'][ $mapping_key ] ) ) {
1645
+ return false;
1646
+ }
1647
+
1648
+ $termdata = array();
1649
+ $allowed = array(
1650
+ 'slug' => true,
1651
+ 'description' => true,
1652
+ );
1653
+
1654
+ // Map the parent comment, or mark it as one we need to fix
1655
+ // TODO: add parent mapping and remapping
1656
+ /*$requires_remapping = false;
1657
+ if ( $parent_id ) {
1658
+ if ( isset( $this->mapping['term'][ $parent_id ] ) ) {
1659
+ $data['parent'] = $this->mapping['term'][ $parent_id ];
1660
+ } else {
1661
+ // Prepare for remapping later
1662
+ $meta[] = array( 'key' => '_wxr_import_parent', 'value' => $parent_id );
1663
+ $requires_remapping = true;
1664
+
1665
+ // Wipe the parent for now
1666
+ $data['parent'] = 0;
1667
+ }
1668
+ }*/
1669
+
1670
+ foreach ( $data as $key => $value ) {
1671
+ if ( ! isset( $allowed[ $key ] ) ) {
1672
+ continue;
1673
+ }
1674
+
1675
+ $termdata[ $key ] = $data[ $key ];
1676
+ }
1677
+
1678
+ $result = wp_insert_term( $data['name'], $data['taxonomy'], $termdata );
1679
+ if ( is_wp_error( $result ) ) {
1680
+ $this->logger->warning( sprintf(
1681
+ __( 'Failed to import %s %s', 'rara-demo-import' ),
1682
+ $data['taxonomy'],
1683
+ $data['name']
1684
+ ) );
1685
+ $this->logger->debug( $result->get_error_message() );
1686
+ do_action( 'wp_import_insert_term_failed', $result, $data );
1687
+
1688
+ /**
1689
+ * Term processing failed.
1690
+ *
1691
+ * @param WP_Error $result Error object.
1692
+ * @param array $data Raw data imported for the term.
1693
+ * @param array $meta Meta data supplied for the term.
1694
+ */
1695
+ do_action( 'wxr_importer.process_failed.term', $result, $data, $meta );
1696
+ return false;
1697
+ }
1698
+
1699
+ $term_id = $result['term_id'];
1700
+
1701
+ $this->mapping['term'][ $mapping_key ] = $term_id;
1702
+ $this->mapping['term_id'][ $original_id ] = $term_id;
1703
+
1704
+ $this->logger->info( sprintf(
1705
+ __( 'Imported "%s" (%s)', 'rara-demo-import' ),
1706
+ $data['name'],
1707
+ $data['taxonomy']
1708
+ ) );
1709
+ $this->logger->debug( sprintf(
1710
+ __( 'Term %d remapped to %d', 'rara-demo-import' ),
1711
+ $original_id,
1712
+ $term_id
1713
+ ) );
1714
+
1715
+ do_action( 'wp_import_insert_term', $term_id, $data );
1716
+
1717
+ /**
1718
+ * Term processing completed.
1719
+ *
1720
+ * @param int $term_id New term ID.
1721
+ * @param array $data Raw data imported for the term.
1722
+ */
1723
+ do_action( 'wxr_importer.processed.term', $term_id, $data );
1724
+ }
1725
+
1726
+ /**
1727
+ * Attempt to download a remote file attachment
1728
+ *
1729
+ * @param string $url URL of item to fetch
1730
+ * @param array $post Attachment details
1731
+ * @return array|WP_Error Local file location details on success, WP_Error otherwise
1732
+ */
1733
+ protected function fetch_remote_file( $url, $post ) {
1734
+ // extract the file name and extension from the url
1735
+ $file_name = basename( $url );
1736
+
1737
+ // get placeholder file in the upload dir with a unique, sanitized filename
1738
+ $upload = wp_upload_bits( $file_name, 0, '', $post['upload_date'] );
1739
+ if ( $upload['error'] ) {
1740
+ return new WP_Error( 'upload_dir_error', $upload['error'] );
1741
+ }
1742
+
1743
+ // fetch the remote url and write it to the placeholder file
1744
+ $response = wp_remote_get( $url, array(
1745
+ 'stream' => true,
1746
+ 'filename' => $upload['file'],
1747
+ ) );
1748
+
1749
+ // request failed
1750
+ if ( is_wp_error( $response ) ) {
1751
+ unlink( $upload['file'] );
1752
+ return $response;
1753
+ }
1754
+
1755
+ $code = (int) wp_remote_retrieve_response_code( $response );
1756
+
1757
+ // make sure the fetch was successful
1758
+ if ( $code !== 200 ) {
1759
+ unlink( $upload['file'] );
1760
+ return new WP_Error(
1761
+ 'import_file_error',
1762
+ sprintf(
1763
+ __( 'Remote server returned %1$d %2$s for %3$s', 'rara-demo-import' ),
1764
+ $code,
1765
+ get_status_header_desc( $code ),
1766
+ $url
1767
+ )
1768
+ );
1769
+ }
1770
+
1771
+ $filesize = filesize( $upload['file'] );
1772
+ $headers = wp_remote_retrieve_headers( $response );
1773
+
1774
+ if ( isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) {
1775
+ unlink( $upload['file'] );
1776
+ return new WP_Error( 'import_file_error', __( 'Remote file is incorrect size', 'rara-demo-import' ) );
1777
+ }
1778
+
1779
+ if ( 0 === $filesize ) {
1780
+ unlink( $upload['file'] );
1781
+ return new WP_Error( 'import_file_error', __( 'Zero size file downloaded', 'rara-demo-import' ) );
1782
+ }
1783
+
1784
+ $max_size = (int) $this->max_attachment_size();
1785
+ if ( ! empty( $max_size ) && $filesize > $max_size ) {
1786
+ unlink( $upload['file'] );
1787
+ $message = sprintf( __( 'Remote file is too large, limit is %s', 'rara-demo-import' ), size_format( $max_size ) );
1788
+ return new WP_Error( 'import_file_error', $message );
1789
+ }
1790
+
1791
+ return $upload;
1792
+ }
1793
+
1794
+ protected function post_process() {
1795
+ // Time to tackle any left-over bits
1796
+ if ( ! empty( $this->requires_remapping['post'] ) ) {
1797
+ $this->post_process_posts( $this->requires_remapping['post'] );
1798
+ }
1799
+ if ( ! empty( $this->requires_remapping['comment'] ) ) {
1800
+ $this->post_process_comments( $this->requires_remapping['comment'] );
1801
+ }
1802
+ }
1803
+
1804
+ protected function post_process_posts( $todo ) {
1805
+ foreach ( $todo as $post_id => $_ ) {
1806
+ $this->logger->debug( sprintf(
1807
+ // Note: title intentionally not used to skip extra processing
1808
+ // for when debug logging is off
1809
+ __( 'Running post-processing for post %d', 'rara-demo-import' ),
1810
+ $post_id
1811
+ ) );
1812
+
1813
+ $data = array();
1814
+
1815
+ $parent_id = get_post_meta( $post_id, '_wxr_import_parent', true );
1816
+ if ( ! empty( $parent_id ) ) {
1817
+ // Have we imported the parent now?
1818
+ if ( isset( $this->mapping['post'][ $parent_id ] ) ) {
1819
+ $data['post_parent'] = $this->mapping['post'][ $parent_id ];
1820
+ } else {
1821
+ $this->logger->warning( sprintf(
1822
+ __( 'Could not find the post parent for "%s" (post #%d)', 'rara-demo-import' ),
1823
+ get_the_title( $post_id ),
1824
+ $post_id
1825
+ ) );
1826
+ $this->logger->debug( sprintf(
1827
+ __( 'Post %d was imported with parent %d, but could not be found', 'rara-demo-import' ),
1828
+ $post_id,
1829
+ $parent_id
1830
+ ) );
1831
+ }
1832
+ }
1833
+
1834
+ $author_slug = get_post_meta( $post_id, '_wxr_import_user_slug', true );
1835
+ if ( ! empty( $author_slug ) ) {
1836
+ // Have we imported the user now?
1837
+ if ( isset( $this->mapping['user_slug'][ $author_slug ] ) ) {
1838
+ $data['post_author'] = $this->mapping['user_slug'][ $author_slug ];
1839
+ } else {
1840
+ $this->logger->warning( sprintf(
1841
+ __( 'Could not find the author for "%s" (post #%d)', 'rara-demo-import' ),
1842
+ get_the_title( $post_id ),
1843
+ $post_id
1844
+ ) );
1845
+ $this->logger->debug( sprintf(
1846
+ __( 'Post %d was imported with author "%s", but could not be found', 'rara-demo-import' ),
1847
+ $post_id,
1848
+ $author_slug
1849
+ ) );
1850
+ }
1851
+ }
1852
+
1853
+ $has_attachments = get_post_meta( $post_id, '_wxr_import_has_attachment_refs', true );
1854
+ if ( ! empty( $has_attachments ) ) {
1855
+ $post = get_post( $post_id );
1856
+ $content = $post->post_content;
1857
+
1858
+ // Replace all the URLs we've got
1859
+ $new_content = str_replace( array_keys( $this->url_remap ), $this->url_remap, $content );
1860
+ if ( $new_content !== $content ) {
1861
+ $data['post_content'] = $new_content;
1862
+ }
1863
+ }
1864
+
1865
+ if ( get_post_type( $post_id ) === 'nav_menu_item' ) {
1866
+ $this->post_process_menu_item( $post_id );
1867
+ }
1868
+
1869
+ // Do we have updates to make?
1870
+ if ( empty( $data ) ) {
1871
+ $this->logger->debug( sprintf(
1872
+ __( 'Post %d was marked for post-processing, but none was required.', 'rara-demo-import' ),
1873
+ $post_id
1874
+ ) );
1875
+ continue;
1876
+ }
1877
+
1878
+ // Run the update
1879
+ $data['ID'] = $post_id;
1880
+ $result = wp_update_post( $data, true );
1881
+ if ( is_wp_error( $result ) ) {
1882
+ $this->logger->warning( sprintf(
1883
+ __( 'Could not update "%s" (post #%d) with mapped data', 'rara-demo-import' ),
1884
+ get_the_title( $post_id ),
1885
+ $post_id
1886
+ ) );
1887
+ $this->logger->debug( $result->get_error_message() );
1888
+ continue;
1889
+ }
1890
+
1891
+ // Clear out our temporary meta keys
1892
+ delete_post_meta( $post_id, '_wxr_import_parent' );
1893
+ delete_post_meta( $post_id, '_wxr_import_user_slug' );
1894
+ delete_post_meta( $post_id, '_wxr_import_has_attachment_refs' );
1895
+ }
1896
+ }
1897
+
1898
+ protected function post_process_menu_item( $post_id ) {
1899
+ $menu_object_id = get_post_meta( $post_id, '_wxr_import_menu_item', true );
1900
+ if ( empty( $menu_object_id ) ) {
1901
+ // No processing needed!
1902
+ return;
1903
+ }
1904
+
1905
+ $menu_item_type = get_post_meta( $post_id, '_menu_item_type', true );
1906
+ switch ( $menu_item_type ) {
1907
+ case 'taxonomy':
1908
+ if ( isset( $this->mapping['term_id'][ $menu_object_id ] ) ) {
1909
+ $menu_object = $this->mapping['term_id'][ $menu_object_id ];
1910
+ }
1911
+ break;
1912
+
1913
+ case 'post_type':
1914
+ if ( isset( $this->mapping['post'][ $menu_object_id ] ) ) {
1915
+ $menu_object = $this->mapping['post'][ $menu_object_id ];
1916
+ }
1917
+ break;
1918
+
1919
+ default:
1920
+ // Cannot handle this.
1921
+ return;
1922
+ }
1923
+
1924
+ if ( ! empty( $menu_object ) ) {
1925
+ update_post_meta( $post_id, '_menu_item_object_id', wp_slash( $menu_object ) );
1926
+ } else {
1927
+ $this->logger->warning( sprintf(
1928
+ __( 'Could not find the menu object for "%s" (post #%d)', 'rara-demo-import' ),
1929
+ get_the_title( $post_id ),
1930
+ $post_id
1931
+ ) );
1932
+ $this->logger->debug( sprintf(
1933
+ __( 'Post %d was imported with object "%d" of type "%s", but could not be found', 'rara-demo-import' ),
1934
+ $post_id,
1935
+ $menu_object_id,
1936
+ $menu_item_type
1937
+ ) );
1938
+ }
1939
+
1940
+ delete_post_meta( $post_id, '_wxr_import_menu_item' );
1941
+ }
1942
+
1943
+
1944
+ protected function post_process_comments( $todo ) {
1945
+ foreach ( $todo as $comment_id => $_ ) {
1946
+ $data = array();
1947
+
1948
+ $parent_id = get_comment_meta( $comment_id, '_wxr_import_parent', true );
1949
+ if ( ! empty( $parent_id ) ) {
1950
+ // Have we imported the parent now?
1951
+ if ( isset( $this->mapping['comment'][ $parent_id ] ) ) {
1952
+ $data['comment_parent'] = $this->mapping['comment'][ $parent_id ];
1953
+ } else {
1954
+ $this->logger->warning( sprintf(
1955
+ __( 'Could not find the comment parent for comment #%d', 'rara-demo-import' ),
1956
+ $comment_id
1957
+ ) );
1958
+ $this->logger->debug( sprintf(
1959
+ __( 'Comment %d was imported with parent %d, but could not be found', 'rara-demo-import' ),
1960
+ $comment_id,
1961
+ $parent_id
1962
+ ) );
1963
+ }
1964
+ }
1965
+
1966
+ $author_id = get_comment_meta( $comment_id, '_wxr_import_user', true );
1967
+ if ( ! empty( $author_id ) ) {
1968
+ // Have we imported the user now?
1969
+ if ( isset( $this->mapping['user'][ $author_id ] ) ) {
1970
+ $data['user_id'] = $this->mapping['user'][ $author_id ];
1971
+ } else {
1972
+ $this->logger->warning( sprintf(
1973
+ __( 'Could not find the author for comment #%d', 'rara-demo-import' ),
1974
+ $comment_id
1975
+ ) );
1976
+ $this->logger->debug( sprintf(
1977
+ __( 'Comment %d was imported with author %d, but could not be found', 'rara-demo-import' ),
1978
+ $comment_id,
1979
+ $author_id
1980
+ ) );
1981
+ }
1982
+ }
1983
+
1984
+ // Do we have updates to make?
1985
+ if ( empty( $data ) ) {
1986
+ continue;
1987
+ }
1988
+
1989
+ // Run the update
1990
+ $data['comment_ID'] = $comment_ID;
1991
+ $result = wp_update_comment( wp_slash( $data ) );
1992
+ if ( empty( $result ) ) {
1993
+ $this->logger->warning( sprintf(
1994
+ __( 'Could not update comment #%d with mapped data', 'rara-demo-import' ),
1995
+ $comment_id
1996
+ ) );
1997
+ continue;
1998
+ }
1999
+
2000
+ // Clear out our temporary meta keys
2001
+ delete_comment_meta( $comment_id, '_wxr_import_parent' );
2002
+ delete_comment_meta( $comment_id, '_wxr_import_user' );
2003
+ }
2004
+ }
2005
+
2006
+ /**
2007
+ * Use stored mapping information to update old attachment URLs
2008
+ */
2009
+ protected function replace_attachment_urls_in_content() {
2010
+ global $wpdb;
2011
+ // make sure we do the longest urls first, in case one is a substring of another
2012
+ uksort( $this->url_remap, array( $this, 'cmpr_strlen' ) );
2013
+
2014
+ foreach ( $this->url_remap as $from_url => $to_url ) {
2015
+ // remap urls in post_content
2016
+ $query = $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url );
2017
+ $wpdb->query( $query );
2018
+
2019
+ // remap enclosure urls
2020
+ $query = $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url );
2021
+ $result = $wpdb->query( $query );
2022
+ }
2023
+ }
2024
+
2025
+ /**
2026
+ * Update _thumbnail_id meta to new, imported attachment IDs
2027
+ */
2028
+ function remap_featured_images() {
2029
+ // cycle through posts that have a featured image
2030
+ foreach ( $this->featured_images as $post_id => $value ) {
2031
+ if ( isset( $this->processed_posts[ $value ] ) ) {
2032
+ $new_id = $this->processed_posts[ $value ];
2033
+
2034
+ // only update if there's a difference
2035
+ if ( $new_id !== $value ) {
2036
+ update_post_meta( $post_id, '_thumbnail_id', $new_id );
2037
+ }
2038
+ }
2039
+ }
2040
+ }
2041
+
2042
+ /**
2043
+ * Decide if the given meta key maps to information we will want to import
2044
+ *
2045
+ * @param string $key The meta key to check
2046
+ * @return string|bool The key if we do want to import, false if not
2047
+ */
2048
+ public function is_valid_meta_key( $key ) {
2049
+ // skip attachment metadata since we'll regenerate it from scratch
2050
+ // skip _edit_lock as not relevant for import
2051
+ if ( in_array( $key, array( '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ) ) ) {
2052
+ return false;
2053
+ }
2054
+
2055
+ return $key;
2056
+ }
2057
+
2058
+ /**
2059
+ * Decide what the maximum file size for downloaded attachments is.
2060
+ * Default is 0 (unlimited), can be filtered via import_attachment_size_limit
2061
+ *
2062
+ * @return int Maximum attachment file size to import
2063
+ */
2064
+ protected function max_attachment_size() {
2065
+ return apply_filters( 'import_attachment_size_limit', 0 );
2066
+ }
2067
+
2068
+ /**
2069
+ * Added to http_request_timeout filter to force timeout at 60 seconds during import
2070
+ *
2071
+ * @access protected
2072
+ * @return int 60
2073
+ */
2074
+ function bump_request_timeout($val) {
2075
+ return 60;
2076
+ }
2077
+
2078
+ // return the difference in length between two strings
2079
+ function cmpr_strlen( $a, $b ) {
2080
+ return strlen( $b ) - strlen( $a );
2081
+ }
2082
+
2083
+ /**
2084
+ * Prefill existing post data.
2085
+ *
2086
+ * This preloads all GUIDs into memory, allowing us to avoid hitting the
2087
+ * database when we need to check for existence. With larger imports, this
2088
+ * becomes prohibitively slow to perform SELECT queries on each.
2089
+ *
2090
+ * By preloading all this data into memory, it's a constant-time lookup in
2091
+ * PHP instead. However, this does use a lot more memory, so for sites doing
2092
+ * small imports onto a large site, it may be a better tradeoff to use
2093
+ * on-the-fly checking instead.
2094
+ */
2095
+ protected function prefill_existing_posts() {
2096
+ global $wpdb;
2097
+ $posts = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts}" );
2098
+
2099
+ foreach ( $posts as $item ) {
2100
+ $this->exists['post'][ $item->guid ] = $item->ID;
2101
+ }
2102
+ }
2103
+
2104
+ /**
2105
+ * Does the post exist?
2106
+ *
2107
+ * @param array $data Post data to check against.
2108
+ * @return int|bool Existing post ID if it exists, false otherwise.
2109
+ */
2110
+ protected function post_exists( $data ) {
2111
+ // Constant-time lookup if we prefilled
2112
+ $exists_key = $data['guid'];
2113
+
2114
+ if ( $this->options['prefill_existing_posts'] ) {
2115
+ return isset( $this->exists['post'][ $exists_key ] ) ? $this->exists['post'][ $exists_key ] : false;
2116
+ }
2117
+
2118
+ // No prefilling, but might have already handled it
2119
+ if ( isset( $this->exists['post'][ $exists_key ] ) ) {
2120
+ return $this->exists['post'][ $exists_key ];
2121
+ }
2122
+
2123
+ // Still nothing, try post_exists, and cache it
2124
+ $exists = post_exists( $data['post_title'], $data['post_content'], $data['post_date'] );
2125
+ $this->exists['post'][ $exists_key ] = $exists;
2126
+
2127
+ return $exists;
2128
+ }
2129
+
2130
+ /**
2131
+ * Mark the post as existing.
2132
+ *
2133
+ * @param array $data Post data to mark as existing.
2134
+ * @param int $post_id Post ID.
2135
+ */
2136
+ protected function mark_post_exists( $data, $post_id ) {
2137
+ $exists_key = $data['guid'];
2138
+ $this->exists['post'][ $exists_key ] = $post_id;
2139
+ }
2140
+
2141
+ /**
2142
+ * Prefill existing comment data.
2143
+ *
2144
+ * @see self::prefill_existing_posts() for justification of why this exists.
2145
+ */
2146
+ protected function prefill_existing_comments() {
2147
+ global $wpdb;
2148
+ $posts = $wpdb->get_results( "SELECT comment_ID, comment_author, comment_date FROM {$wpdb->comments}" );
2149
+
2150
+ foreach ( $posts as $item ) {
2151
+ $exists_key = sha1( $item->comment_author . ':' . $item->comment_date );
2152
+ $this->exists['comment'][ $exists_key ] = $item->comment_ID;
2153
+ }
2154
+ }
2155
+
2156
+ /**
2157
+ * Does the comment exist?
2158
+ *
2159
+ * @param array $data Comment data to check against.
2160
+ * @return int|bool Existing comment ID if it exists, false otherwise.
2161
+ */
2162
+ protected function comment_exists( $data ) {
2163
+ $exists_key = sha1( $data['comment_author'] . ':' . $data['comment_date'] );
2164
+
2165
+ // Constant-time lookup if we prefilled
2166
+ if ( $this->options['prefill_existing_comments'] ) {
2167
+ return isset( $this->exists['comment'][ $exists_key ] ) ? $this->exists['comment'][ $exists_key ] : false;
2168
+ }
2169
+
2170
+ // No prefilling, but might have already handled it
2171
+ if ( isset( $this->exists['comment'][ $exists_key ] ) ) {
2172
+ return $this->exists['comment'][ $exists_key ];
2173
+ }
2174
+
2175
+ // Still nothing, try comment_exists, and cache it
2176
+ $exists = comment_exists( $data['comment_author'], $data['comment_date'] );
2177
+ $this->exists['comment'][ $exists_key ] = $exists;
2178
+
2179
+ return $exists;
2180
+ }
2181
+
2182
+ /**
2183
+ * Mark the comment as existing.
2184
+ *
2185
+ * @param array $data Comment data to mark as existing.
2186
+ * @param int $comment_id Comment ID.
2187
+ */
2188
+ protected function mark_comment_exists( $data, $comment_id ) {
2189
+ $exists_key = sha1( $data['comment_author'] . ':' . $data['comment_date'] );
2190
+ $this->exists['comment'][ $exists_key ] = $comment_id;
2191
+ }
2192
+
2193
+ /**
2194
+ * Prefill existing term data.
2195
+ *
2196
+ * @see self::prefill_existing_posts() for justification of why this exists.
2197
+ */
2198
+ protected function prefill_existing_terms() {
2199
+ global $wpdb;
2200
+ $query = "SELECT t.term_id, tt.taxonomy, t.slug FROM {$wpdb->terms} AS t";
2201
+ $query .= " JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id";
2202
+ $terms = $wpdb->get_results( $query );
2203
+
2204
+ foreach ( $terms as $item ) {
2205
+ $exists_key = sha1( $item->taxonomy . ':' . $item->slug );
2206
+ $this->exists['term'][ $exists_key ] = $item->term_id;
2207
+ }
2208
+ }
2209
+
2210
+ /**
2211
+ * Does the term exist?
2212
+ *
2213
+ * @param array $data Term data to check against.
2214
+ * @return int|bool Existing term ID if it exists, false otherwise.
2215
+ */
2216
+ protected function term_exists( $data ) {
2217
+ $exists_key = sha1( $data['taxonomy'] . ':' . $data['slug'] );
2218
+
2219
+ // Constant-time lookup if we prefilled
2220
+ if ( $this->options['prefill_existing_terms'] ) {
2221
+ return isset( $this->exists['term'][ $exists_key ] ) ? $this->exists['term'][ $exists_key ] : false;
2222
+ }
2223
+
2224
+ // No prefilling, but might have already handled it
2225
+ if ( isset( $this->exists['term'][ $exists_key ] ) ) {
2226
+ return $this->exists['term'][ $exists_key ];
2227
+ }
2228
+
2229
+ // Still nothing, try comment_exists, and cache it
2230
+ $exists = term_exists( $data['slug'], $data['taxonomy'] );
2231
+ if ( is_array( $exists ) ) {
2232
+ $exists = $exists['term_id'];
2233
+ }
2234
+
2235
+ $this->exists['term'][ $exists_key ] = $exists;
2236
+
2237
+ return $exists;
2238
+ }
2239
+
2240
+ /**
2241
+ * Mark the term as existing.
2242
+ *
2243
+ * @param array $data Term data to mark as existing.
2244
+ * @param int $term_id Term ID.
2245
+ */
2246
+ protected function mark_term_exists( $data, $term_id ) {
2247
+ $exists_key = sha1( $data['taxonomy'] . ':' . $data['slug'] );
2248
+ $this->exists['term'][ $exists_key ] = $term_id;
2249
+ }
2250
+ }
includes/vendor/class-rrdi-customizer-importer.php ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class for the customizer importer used in the Rara One Click Demo Import plugin.
4
+ *
5
+ * Code is mostly from the Customizer Export/Import plugin.
6
+ *
7
+ * @see https://wordpress.org/plugins/customizer-export-import/
8
+ * @package rara-demo-import
9
+ */
10
+
11
+ class RRDI_Customizer_Importer {
12
+
13
+ /**
14
+ * Imports uploaded mods and calls WordPress core customize_save actions so
15
+ * themes that hook into them can act before mods are saved to the database.
16
+ *
17
+ * Update: WP core customize_save actions were removed, because of some errors.
18
+ *
19
+ * @since 1.1.1
20
+ * @param string $import_file_path Path to the import file.
21
+ * @return void|WP_Error
22
+ */
23
+ public static function import_customizer_options( $import_file_path ) {
24
+
25
+ // Setup global vars.
26
+ global $wp_customize;
27
+
28
+ // Setup internal vars.
29
+ $template = get_template();
30
+
31
+ // Make sure we have an import file.
32
+ if ( ! file_exists( $import_file_path ) ) {
33
+ return new WP_Error(
34
+ 'missing_cutomizer_import_file',
35
+ sprintf(
36
+ esc_html__( 'The customizer import file is missing! File path: %s', 'rara-demo-import' ),
37
+ $import_file_path
38
+ )
39
+ );
40
+ }
41
+
42
+ // Get the upload data.
43
+ $raw = RRDI_Helpers::data_from_file( $import_file_path );
44
+
45
+ // Make sure we got the data.
46
+ if ( is_wp_error( $raw ) ) {
47
+ return $raw;
48
+ }
49
+
50
+ $data = unserialize( $raw );
51
+
52
+ // Data checks.
53
+ if ( ! is_array( $data ) && ( ! isset( $data['template'] ) || ! isset( $data['mods'] ) ) ) {
54
+ return new WP_Error(
55
+ 'customizer_import_data_error',
56
+ esc_html__( 'No correct file format. Please make sure to use the correct customizer import file.', 'rara-demo-import' )
57
+ );
58
+ }
59
+ if ( $data['template'] !== $template ) {
60
+ return new WP_Error(
61
+ 'customizer_import_wrong_theme',
62
+ esc_html__( 'The customizer import file is not suitable for current theme. You can only import customizer settings for the same theme or a child theme.', 'rara-demo-import' )
63
+ );
64
+ }
65
+
66
+ // Import images.
67
+ if ( apply_filters( 'rrdi/customizer_import_images', true ) ) {
68
+ $data['mods'] = self::import_customizer_images( $data['mods'] );
69
+ }
70
+
71
+ // Import custom options.
72
+ if ( isset( $data['options'] ) ) {
73
+
74
+ // Require modified customizer options class.
75
+ if ( ! class_exists( 'WP_Customize_Setting' ) ) {
76
+ require_once ABSPATH . 'wp-includes/class-wp-customize-setting.php';
77
+ }
78
+ require RRDI_PATH . 'includes/class-rrdi-customizer-option.php';
79
+
80
+ foreach ( $data['options'] as $option_key => $option_value ) {
81
+ $option = new RRDI_Customizer_Option( $wp_customize, $option_key, array(
82
+ 'default' => '',
83
+ 'type' => 'option',
84
+ 'capability' => 'edit_theme_options',
85
+ ) );
86
+
87
+ $option->import( $option_value );
88
+ }
89
+ }
90
+
91
+ // Loop through the mods.
92
+ foreach ( $data['mods'] as $key => $val ) {
93
+
94
+ // Save the mod.
95
+ set_theme_mod( $key, $val );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Helper function: Customizer import - imports images for settings saved as mods.
101
+ *
102
+ * @since 1.1.1
103
+ * @param array $mods An array of customizer mods.
104
+ * @return array The mods array with any new import data.
105
+ */
106
+ private static function import_customizer_images( $mods ) {
107
+ foreach ( $mods as $key => $val ) {
108
+ if ( self::customizer_is_image_url( $val ) ) {
109
+ $data = self::customizer_sideload_image( $val );
110
+ if ( ! is_wp_error( $data ) ) {
111
+ $mods[ $key ] = $data->url;
112
+
113
+ // Handle header image controls.
114
+ if ( isset( $mods[ $key . '_data' ] ) ) {
115
+ $mods[ $key . '_data' ] = $data;
116
+ update_post_meta( $data->attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ return $mods;
123
+ }
124
+
125
+ /**
126
+ * Helper function: Customizer import
127
+ * Taken from the core media_sideload_image function and
128
+ * modified to return an array of data instead of html.
129
+ *
130
+ * @since 1.1.1.
131
+ * @param string $file The image file path.
132
+ * @return array An array of image data.
133
+ */
134
+ private static function customizer_sideload_image( $file ) {
135
+ $data = new stdClass();
136
+
137
+ if ( ! function_exists( 'media_handle_sideload' ) ) {
138
+ require_once( ABSPATH . 'wp-admin/includes/media.php' );
139
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
140
+ require_once( ABSPATH . 'wp-admin/includes/image.php' );
141
+ }
142
+ if ( ! empty( $file ) ) {
143
+
144
+ // Set variables for storage, fix file filename for query strings.
145
+ preg_match( '/[^\?]+\.(jpe?g|jpe|gif|png)\b/i', $file, $matches );
146
+ $file_array = array();
147
+ $file_array['name'] = basename( $matches[0] );
148
+
149
+ // Download file to temp location.
150
+ $file_array['tmp_name'] = download_url( $file );
151
+
152
+ // If error storing temporarily, return the error.
153
+ if ( is_wp_error( $file_array['tmp_name'] ) ) {
154
+ return $file_array['tmp_name'];
155
+ }
156
+
157
+ // Do the validation and storage stuff.
158
+ $id = media_handle_sideload( $file_array, 0 );
159
+
160
+ // If error storing permanently, unlink.
161
+ if ( is_wp_error( $id ) ) {
162
+ unlink( $file_array['tmp_name'] );
163
+ return $id;
164
+ }
165
+
166
+ // Build the object to return.
167
+ $meta = wp_get_attachment_metadata( $id );
168
+ $data->attachment_id = $id;
169
+ $data->url = wp_get_attachment_url( $id );
170
+ $data->thumbnail_url = wp_get_attachment_thumb_url( $id );
171
+ $data->height = $meta['height'];
172
+ $data->width = $meta['width'];
173
+ }
174
+
175
+ return $data;
176
+ }
177
+
178
+ /**
179
+ * Checks to see whether a string is an image url or not.
180
+ *
181
+ * @since 1.1.1
182
+ * @param string $string The string to check.
183
+ * @return bool Whether the string is an image url or not.
184
+ */
185
+ private static function customizer_is_image_url( $string = '' ) {
186
+ if ( is_string( $string ) ) {
187
+ if ( preg_match( '/\.(jpg|jpeg|png|gif)/i', $string ) ) {
188
+ return true;
189
+ }
190
+ }
191
+
192
+ return false;
193
+ }
194
+ }
includes/vendor/class-rrdi-widget-importer.php ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class for the widget importer used in the Rara One Click Demo Import plugin.
4
+ *
5
+ * Code is mostly from the Widget Importer & Exporter plugin.
6
+ *
7
+ * @see https://wordpress.org/plugins/widget-importer-exporter/
8
+ * @package rara-demo-import
9
+ */
10
+
11
+ class RRDI_Widget_Importer {
12
+
13
+ /**
14
+ * Imports widgets from a json file.
15
+ *
16
+ * @param string $data_file path to json file with WordPress widget export data.
17
+ */
18
+ public function import_widgets( $data_file ) {
19
+
20
+ // Get widgets data from file.
21
+ $data = $this->process_import_file( $data_file );
22
+
23
+ // Return from this function if there was an error.
24
+ if ( is_wp_error( $data ) ) {
25
+ return $data;
26
+ }
27
+
28
+ // Import the widget data and save the results.
29
+ return $this->import_data( $data );
30
+ }
31
+
32
+ /**
33
+ * Process import file - this parses the widget data and returns it.
34
+ *
35
+ * @param string $file path to json file.
36
+ * @return object $data decoded JSON string
37
+ */
38
+ private function process_import_file( $file ) {
39
+
40
+ // File exists?
41
+ if ( ! file_exists( $file ) ) {
42
+ return new WP_Error(
43
+ 'widget_import_file_not_found',
44
+ __( 'Widget import file could not be found.', 'rara-demo-import' )
45
+ );
46
+ }
47
+
48
+ // Get file contents and decode.
49
+ $data = RRDI_Helpers::data_from_file( $file );
50
+
51
+ // Return from this function if there was an error.
52
+ if ( is_wp_error( $data ) ) {
53
+ return $data;
54
+ }
55
+
56
+ return json_decode( $data );
57
+ }
58
+
59
+
60
+
61
+ /**
62
+ * Import widget JSON data
63
+ *
64
+ * @global array $wp_registered_sidebars
65
+ * @param object $data JSON widget data.
66
+ * @return array $results
67
+ */
68
+ private function import_data( $data ) {
69
+
70
+ global $wp_registered_sidebars;
71
+
72
+ // Have valid data? If no data or could not decode.
73
+ if ( empty( $data ) || ! is_object( $data ) ) {
74
+ return new WP_Error(
75
+ 'corrupted_widget_import_data',
76
+ __( 'Widget import data could not be read. Please try a different file.', 'rara-demo-import' )
77
+ );
78
+ }
79
+
80
+ // Hook before import.
81
+ do_action( 'rrdi/widget_importer_before_widgets_import' );
82
+ $data = apply_filters( 'rrdi/before_widgets_import_data', $data );
83
+
84
+ // Get all available widgets site supports.
85
+ $available_widgets = $this->available_widgets();
86
+
87
+ // Get all existing widget instances.
88
+ $widget_instances = array();
89
+
90
+ foreach ( $available_widgets as $widget_data ) {
91
+ $widget_instances[ $widget_data['id_base'] ] = get_option( 'widget_' . $widget_data['id_base'] );
92
+ }
93
+
94
+ // Begin results.
95
+ $results = array();
96
+
97
+ // Loop import data's sidebars.
98
+ foreach ( $data as $sidebar_id => $widgets ) {
99
+
100
+ // Skip inactive widgets (should not be in export file).
101
+ if ( 'wp_inactive_widgets' == $sidebar_id ) {
102
+ continue;
103
+ }
104
+
105
+ // Check if sidebar is available on this site. Otherwise add widgets to inactive, and say so.
106
+ if ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ) {
107
+ $sidebar_available = true;
108
+ $use_sidebar_id = $sidebar_id;
109
+ $sidebar_message_type = 'success';
110
+ $sidebar_message = '';
111
+ }
112
+ else {
113
+ $sidebar_available = false;
114
+ $use_sidebar_id = 'wp_inactive_widgets'; // Add to inactive if sidebar does not exist in theme.
115
+ $sidebar_message_type = 'error';
116
+ $sidebar_message = __( 'Sidebar does not exist in theme (moving widget to Inactive)', 'rara-demo-import' );
117
+ }
118
+
119
+ // Result for sidebar.
120
+ $results[ $sidebar_id ]['name'] = ! empty( $wp_registered_sidebars[ $sidebar_id ]['name'] ) ? $wp_registered_sidebars[ $sidebar_id ]['name'] : $sidebar_id; // Sidebar name if theme supports it; otherwise ID.
121
+ $results[ $sidebar_id ]['message_type'] = $sidebar_message_type;
122
+ $results[ $sidebar_id ]['message'] = $sidebar_message;
123
+ $results[ $sidebar_id ]['widgets'] = array();
124
+
125
+ // Loop widgets.
126
+ foreach ( $widgets as $widget_instance_id => $widget ) {
127
+
128
+ $fail = false;
129
+
130
+ // Get id_base (remove -# from end) and instance ID number.
131
+ $id_base = preg_replace( '/-[0-9]+$/', '', $widget_instance_id );
132
+ $instance_id_number = str_replace( $id_base . '-', '', $widget_instance_id );
133
+
134
+ // Does site support this widget?
135
+ if ( ! $fail && ! isset( $available_widgets[ $id_base ] ) ) {
136
+ $fail = true;
137
+ $widget_message_type = 'error';
138
+ $widget_message = __( 'Site does not support widget', 'rara-demo-import' ); // Explain why widget not imported.
139
+ }
140
+
141
+ // Filter to modify settings object before conversion to array and import.
142
+ // Leave this filter here for backwards compatibility with manipulating objects (before conversion to array below).
143
+ // Ideally the newer wie_widget_settings_array below will be used instead of this.
144
+ $widget = apply_filters( 'rrdi/widget_settings', $widget ); // Object.
145
+
146
+ // Convert multidimensional objects to multidimensional arrays.
147
+ // Some plugins like Jetpack Widget Visibility store settings as multidimensional arrays.
148
+ // Without this, they are imported as objects and cause fatal error on Widgets page.
149
+ // If this creates problems for plugins that do actually intend settings in objects then may need to consider other approach: https://wordpress.org/support/topic/problem-with-array-of-arrays.
150
+ // It is probably much more likely that arrays are used than objects, however.
151
+ $widget = json_decode( json_encode( $widget ), true );
152
+
153
+ // Filter to modify settings array.
154
+ // This is preferred over the older wie_widget_settings filter above.
155
+ // Do before identical check because changes may make it identical to end result (such as URL replacements).
156
+ $widget = apply_filters( 'rrdi/widget_settings_array', $widget );
157
+
158
+ // Does widget with identical settings already exist in same sidebar?
159
+ if ( ! $fail && isset( $widget_instances[ $id_base ] ) ) {
160
+
161
+ // Get existing widgets in this sidebar.
162
+ $sidebars_widgets = get_option( 'sidebars_widgets' );
163
+ $sidebar_widgets = isset( $sidebars_widgets[ $use_sidebar_id ] ) ? $sidebars_widgets[ $use_sidebar_id ] : array(); // Check Inactive if that's where will go.
164
+
165
+ // Loop widgets with ID base.
166
+ $single_widget_instances = ! empty( $widget_instances[ $id_base ] ) ? $widget_instances[ $id_base ] : array();
167
+ foreach ( $single_widget_instances as $check_id => $check_widget ) {
168
+
169
+ // Is widget in same sidebar and has identical settings?
170
+ if ( in_array( "$id_base-$check_id", $sidebar_widgets ) && (array) $widget == $check_widget ) {
171
+ $fail = true;
172
+ $widget_message_type = 'warning';
173
+ $widget_message = __( 'Widget already exists', 'rara-demo-import' ); // Explain why widget not imported.
174
+
175
+ break;
176
+ }
177
+ }
178
+ }
179
+
180
+ // No failure.
181
+ if ( ! $fail ) {
182
+
183
+ // Add widget instance.
184
+ $single_widget_instances = get_option( 'widget_' . $id_base ); // All instances for that widget ID base, get fresh every time.
185
+ $single_widget_instances = ! empty( $single_widget_instances ) ? $single_widget_instances : array( '_multiwidget' => 1 ); // Start fresh if have to.
186
+ $single_widget_instances[] = $widget; // Add it.
187
+
188
+ // Get the key it was given.
189
+ end( $single_widget_instances );
190
+ $new_instance_id_number = key( $single_widget_instances );
191
+
192
+ // If key is 0, make it 1.
193
+ // When 0, an issue can occur where adding a widget causes data from other widget to load, and the widget doesn't stick (reload wipes it).
194
+ if ( '0' === strval( $new_instance_id_number ) ) {
195
+ $new_instance_id_number = 1;
196
+ $single_widget_instances[ $new_instance_id_number ] = $single_widget_instances[0];
197
+ unset( $single_widget_instances[0] );
198
+ }
199
+
200
+ // Move _multiwidget to end of array for uniformity.
201
+ if ( isset( $single_widget_instances['_multiwidget'] ) ) {
202
+ $multiwidget = $single_widget_instances['_multiwidget'];
203
+ unset( $single_widget_instances['_multiwidget'] );
204
+ $single_widget_instances['_multiwidget'] = $multiwidget;
205
+ }
206
+
207
+ // Update option with new widget.
208
+ update_option( 'widget_' . $id_base, $single_widget_instances );
209
+
210
+ // Assign widget instance to sidebar.
211
+ $sidebars_widgets = get_option( 'sidebars_widgets' ); // Which sidebars have which widgets, get fresh every time.
212
+ $new_instance_id = $id_base . '-' . $new_instance_id_number; // Use ID number from new widget instance.
213
+ $sidebars_widgets[ $use_sidebar_id ][] = $new_instance_id; // Add new instance to sidebar.
214
+ update_option( 'sidebars_widgets', $sidebars_widgets ); // Save the amended data.
215
+
216
+ // After widget import action.
217
+ $after_widget_import = array(
218
+ 'sidebar' => $use_sidebar_id,
219
+ 'sidebar_old' => $sidebar_id,
220
+ 'widget' => $widget,
221
+ 'widget_type' => $id_base,
222
+ 'widget_id' => $new_instance_id,
223
+ 'widget_id_old' => $widget_instance_id,
224
+ 'widget_id_num' => $new_instance_id_number,
225
+ 'widget_id_num_old' => $instance_id_number,
226
+ );
227
+ do_action( 'rrdi/widget_importer_after_single_widget_import', $after_widget_import );
228
+
229
+ // Success message.
230
+ if ( $sidebar_available ) {
231
+ $widget_message_type = 'success';
232
+ $widget_message = __( 'Imported', 'rara-demo-import' );
233
+ }
234
+ else {
235
+ $widget_message_type = 'warning';
236
+ $widget_message = __( 'Imported to Inactive', 'rara-demo-import' );
237
+ }
238
+ }
239
+
240
+ // Result for widget instance.
241
+ $results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['name'] = isset( $available_widgets[ $id_base ]['name'] ) ? $available_widgets[ $id_base ]['name'] : $id_base; // Widget name or ID if name not available (not supported by site).
242
+ $results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['title'] = ! empty( $widget['title'] ) ? $widget['title'] : __( 'No Title', 'rara-demo-import' ); // Show "No Title" if widget instance is untitled.
243
+ $results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['message_type'] = $widget_message_type;
244
+ $results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['message'] = $widget_message;
245
+
246
+ }
247
+ }
248
+
249
+ // Hook after import.
250
+ do_action( 'rrdi/widget_importer_after_widgets_import' );
251
+
252
+ // Return results.
253
+ return apply_filters( 'rrdi/widget_import_results', $results );
254
+ }
255
+
256
+
257
+ /**
258
+ * Available widgets.
259
+ *
260
+ * Gather site's widgets into array with ID base, name, etc.
261
+ *
262
+ * @global array $wp_registered_widget_controls
263
+ * @return array $available_widgets, Widget information
264
+ */
265
+ private function available_widgets() {
266
+
267
+ global $wp_registered_widget_controls;
268
+
269
+ $widget_controls = $wp_registered_widget_controls;
270
+ $available_widgets = array();
271
+
272
+ foreach ( $widget_controls as $widget ) {
273
+ if ( ! empty( $widget['id_base'] ) && ! isset( $available_widgets[ $widget['id_base'] ] ) ) {
274
+ $available_widgets[ $widget['id_base'] ]['id_base'] = $widget['id_base'];
275
+ $available_widgets[ $widget['id_base'] ]['name'] = $widget['name'];
276
+ }
277
+ }
278
+
279
+ return apply_filters( 'rrdi/available_widgets', $available_widgets );
280
+ }
281
+
282
+
283
+ /**
284
+ * Format results for log file
285
+ *
286
+ * @param array $results widget import results.
287
+ */
288
+ public function format_results_for_log( $results ) {
289
+
290
+ if ( empty( $results ) ) {
291
+ esc_html_e( 'No results for widget import!', 'rara-demo-import' );
292
+ }
293
+
294
+ // Loop sidebars.
295
+ foreach ( $results as $sidebar ) {
296
+ echo esc_html( $sidebar['name'] ) . ' : ' . esc_html( $sidebar['message'] ) . PHP_EOL . PHP_EOL;
297
+ // Loop widgets.
298
+ foreach ( $sidebar['widgets'] as $widget ) {
299
+ echo esc_html( $widget['name'] ) . ' - ' . esc_html( $widget['title'] ) . ' - ' . esc_html( $widget['message'] ) . PHP_EOL;
300
+ }
301
+ echo PHP_EOL;
302
+ }
303
+ }
304
+ }
languages/rara-demo-import.pot ADDED
@@ -0,0 +1,528 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2016 ProteusThemes
2
+ # This file is distributed under the GPL 2.0.
3
+ #, fuzzy
4
+ msgid ""
5
+ msgstr ""
6
+ "Project-Id-Version: Rara One Click Demo Import 1.2.0\n"
7
+ "Report-Msgid-Bugs-To: http://support.proteusthemes.com/\n"
8
+ "POT-Creation-Date: 2016-10-25 13:05-0400\n"
9
+ "MIME-Version: 1.0\n"
10
+ "Content-Type: text/plain; charset=utf-8\n"
11
+ "Content-Transfer-Encoding: 8bit\n"
12
+ "PO-Revision-Date: 2016-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "X-Generator: Poedit 1.8.9\n"
16
+ "X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;"
17
+ "_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;"
18
+ "esc_html_x:1,2c\n"
19
+ "Language: en\n"
20
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
21
+ "X-Poedit-SourceCharset: UTF-8\n"
22
+ "X-Poedit-Basepath: ..\n"
23
+ "X-Textdomain-Support: yes\n"
24
+ "X-Poedit-SearchPath-0: .\n"
25
+
26
+ #: includes/vendor/class-rrdi-customizer-importer.php:36
27
+ #, php-format
28
+ msgid "The customizer import file is missing! File path: %s"
29
+ msgstr ""
30
+
31
+ #: includes/vendor/class-rrdi-customizer-importer.php:56
32
+ msgid ""
33
+ "The customizer import file is not in a correct format. Please make sure to "
34
+ "use the correct customizer import file."
35
+ msgstr ""
36
+
37
+ #: includes/vendor/class-rrdi-customizer-importer.php:62
38
+ msgid ""
39
+ "The customizer import file is not suitable for current theme. You can only "
40
+ "import customizer settings for the same theme or a child theme."
41
+ msgstr ""
42
+
43
+ #: includes/class-rrdi-helpers.php:70
44
+ #, php-format
45
+ msgid "\"import_file_url\" or \"local_import_file\" for %s%s%s are not defined!"
46
+ msgstr ""
47
+
48
+ #: includes/class-rrdi-helpers.php:176
49
+ #, php-format
50
+ msgid "URL for %s%s%s file is not defined!"
51
+ msgstr ""
52
+
53
+ #: includes/class-rrdi-helpers.php:198
54
+ #, php-format
55
+ msgid ""
56
+ "An error occurred while fetching %s%s%s file from the server!%sReason: %s - "
57
+ "%s."
58
+ msgstr ""
59
+
60
+ #: includes/class-rrdi-helpers.php:238 includes/class-rrdi-helpers.php:282
61
+ #, php-format
62
+ msgid ""
63
+ "An error occurred while writing file to your server! Tried to write a file "
64
+ "to: %s%s."
65
+ msgstr ""
66
+
67
+ #: includes/class-rrdi-helpers.php:317
68
+ #, php-format
69
+ msgid ""
70
+ "An error occurred while reading a file from your server! Tried reading file "
71
+ "from path: %s%s."
72
+ msgstr ""
73
+
74
+ #: includes/class-rrdi-helpers.php:341
75
+ #, php-format
76
+ msgid ""
77
+ "This WordPress page does not have %sdirect%s write file access. This plugin "
78
+ "needs it in order to save the demo import xml file to the upload directory of "
79
+ "your site. You can change this setting with these instructions: %s."
80
+ msgstr ""
81
+
82
+ #: includes/class-rrdi-helpers.php:352 includes/class-rrdi-main.php:83
83
+ #: includes/class-rrdi-main.php:106
84
+ msgid "Rara One Click Demo Import"
85
+ msgstr ""
86
+
87
+ #: includes/class-rrdi-helpers.php:353
88
+ msgid "Import Demo Data"
89
+ msgstr ""
90
+
91
+ #: includes/class-rrdi-helpers.php:365
92
+ msgid ""
93
+ "An error occurred while retrieving reading/writing permissions to your server "
94
+ "(could not retrieve WP filesystem credentials)!"
95
+ msgstr ""
96
+
97
+ #: includes/class-rrdi-helpers.php:373
98
+ msgid "Your WordPress login credentials don't allow to use WP_Filesystem!"
99
+ msgstr ""
100
+
101
+ #: includes/class-rrdi-helpers.php:438
102
+ msgid "Rara One Click Demo Import - "
103
+ msgstr ""
104
+
105
+ #: includes/class-rrdi-helpers.php:474
106
+ #, php-format
107
+ msgid ""
108
+ "%sYour user role isn't high enough. You don't have permission to import demo "
109
+ "data.%s"
110
+ msgstr ""
111
+
112
+ #: includes/class-rrdi-helpers.php:510
113
+ msgid ""
114
+ "Please upload XML file for content import. If you want to import widgets or "
115
+ "customizer settings only, please use Widget Importer & Exporter or the "
116
+ "Customizer Export/Import plugin."
117
+ msgstr ""
118
+
119
+ #: includes/class-rrdi-helpers.php:512 includes/class-rrdi-helpers.php:534
120
+ #: includes/class-rrdi-helpers.php:553 includes/class-rrdi-helpers.php:561
121
+ msgid "Upload files"
122
+ msgstr ""
123
+
124
+ #: includes/class-rrdi-helpers.php:530
125
+ #, php-format
126
+ msgid "Widget file was not uploaded. Error: %s"
127
+ msgstr ""
128
+
129
+ #: includes/class-rrdi-helpers.php:549
130
+ #, php-format
131
+ msgid "Customizer file was not uploaded. Error: %s"
132
+ msgstr ""
133
+
134
+ #: includes/class-rrdi-helpers.php:559
135
+ msgid "The import files were successfully uploaded!"
136
+ msgstr ""
137
+
138
+ #: includes/class-rrdi-helpers.php:577
139
+ #, php-format
140
+ msgid "Initial max execution time = %s"
141
+ msgstr ""
142
+
143
+ #: includes/class-rrdi-helpers.php:581
144
+ #, php-format
145
+ msgid ""
146
+ "Files info:%1$sSite URL = %2$s%1$sData file = %3$s%1$sWidget file = %4$s"
147
+ "%1$sCustomizer file = %5$s"
148
+ msgstr ""
149
+
150
+ #: includes/class-rrdi-helpers.php:585 includes/class-rrdi-helpers.php:586
151
+ msgid "not defined!"
152
+ msgstr ""
153
+
154
+ #: includes/class-rrdi-main.php:84 includes/class-rrdi-main.php:238
155
+ msgid "Import Demo Content"
156
+ msgstr ""
157
+
158
+ #: includes/class-rrdi-main.php:101
159
+ msgid "Before you begin, make sure all the required plugins are activated."
160
+ msgstr ""
161
+
162
+ #: includes/class-rrdi-main.php:113
163
+ #, php-format
164
+ msgid ""
165
+ "%sWarning: your server is using %sPHP safe mode%s. This means that you might "
166
+ "experience server timeout errors.%s"
167
+ msgstr ""
168
+
169
+ #: includes/class-rrdi-main.php:128
170
+ msgid ""
171
+ "Quickly import your theme's live demo content, widgets and settings. This "
172
+ "will provide you a base to build your website and speed up the development "
173
+ "process."
174
+ msgstr ""
175
+
176
+ #: includes/class-rrdi-main.php:131
177
+ msgid "The following data will be imported:"
178
+ msgstr ""
179
+
180
+ #: includes/class-rrdi-main.php:134
181
+ msgid "Posts"
182
+ msgstr ""
183
+
184
+ #: includes/class-rrdi-main.php:135
185
+ msgid "Pages"
186
+ msgstr ""
187
+
188
+ #: includes/class-rrdi-main.php:136
189
+ msgid "Images"
190
+ msgstr ""
191
+
192
+ #: includes/class-rrdi-main.php:137
193
+ msgid "Widgets"
194
+ msgstr ""
195
+
196
+ #: includes/class-rrdi-main.php:138
197
+ msgid "Menus"
198
+ msgstr ""
199
+
200
+ #: includes/class-rrdi-main.php:139
201
+ msgid "Settings"
202
+ msgstr ""
203
+
204
+ #: includes/class-rrdi-main.php:142
205
+ msgid "NOTE: Your existing content will NOT be deleted or modified."
206
+ msgstr ""
207
+
208
+ #: includes/class-rrdi-main.php:159
209
+ msgid ""
210
+ "There are no predefined import files available in this theme. Please upload "
211
+ "the import files manually!"
212
+ msgstr ""
213
+
214
+ #: includes/class-rrdi-main.php:164
215
+ msgid "Manually upload the demo files"
216
+ msgstr ""
217
+
218
+ #: includes/class-rrdi-main.php:167
219
+ msgid "Choose a XML file for content import:"
220
+ msgstr ""
221
+
222
+ #: includes/class-rrdi-main.php:172
223
+ msgid "Choose a WIE or JSON file for widget import:"
224
+ msgstr ""
225
+
226
+ #: includes/class-rrdi-main.php:172 includes/class-rrdi-main.php:177
227
+ msgid "(*optional)"
228
+ msgstr ""
229
+
230
+ #: includes/class-rrdi-main.php:177
231
+ msgid "Choose a DAT file for customizer import:"
232
+ msgstr ""
233
+
234
+ #: includes/class-rrdi-main.php:187
235
+ msgid "Choose which demo you want to import:"
236
+ msgstr ""
237
+
238
+ #: includes/class-rrdi-main.php:212
239
+ msgid "Import preview:"
240
+ msgstr ""
241
+
242
+ #: includes/class-rrdi-main.php:216 includes/class-rrdi-main.php:271
243
+ msgid "No preview image defined for this import."
244
+ msgstr ""
245
+
246
+ #: includes/class-rrdi-main.php:239
247
+ msgid ""
248
+ "Click the button once and wait. The import process may take several minutes."
249
+ msgstr ""
250
+
251
+ #: includes/class-rrdi-main.php:243
252
+ msgid "Importing, please wait!"
253
+ msgstr ""
254
+
255
+ #: includes/class-rrdi-main.php:328
256
+ msgid "Manually uploaded files"
257
+ msgstr ""
258
+
259
+ #: includes/class-rrdi-main.php:345 includes/class-rrdi-main.php:356
260
+ msgid "Downloaded files"
261
+ msgstr ""
262
+
263
+ #: includes/class-rrdi-main.php:352
264
+ #, php-format
265
+ msgid "The import files for: %s were successfully downloaded!"
266
+ msgstr ""
267
+
268
+ #: includes/class-rrdi-main.php:362
269
+ msgid "No import files specified!"
270
+ msgstr ""
271
+
272
+ #: includes/class-rrdi-main.php:409
273
+ #, php-format
274
+ msgid ""
275
+ "%1$s%3$sThat's it, all done!%4$s%2$sThe demo import has finished. Please "
276
+ "check your page and make sure that everything has imported correctly. If it "
277
+ "did, you can deactivate the %3$sRara One Click Demo Import%4$s plugin.%5$s"
278
+ msgstr ""
279
+
280
+ #: includes/class-rrdi-main.php:420
281
+ #, php-format
282
+ msgid ""
283
+ "%1$sThe demo import has finished, but there were some import errors.%2$sMore "
284
+ "details about the errors can be found in this %3$s%5$slog file%6$s%4$s%7$s"
285
+ msgstr ""
286
+
287
+ #: includes/class-rrdi-main.php:474
288
+ msgid "Max execution time after content import = "
289
+ msgstr ""
290
+
291
+ #: includes/class-rrdi-main.php:476
292
+ msgid "Importing content"
293
+ msgstr ""
294
+
295
+ #: includes/class-rrdi-main.php:515 includes/class-rrdi-main.php:527
296
+ msgid "Importing widgets"
297
+ msgstr ""
298
+
299
+ #: includes/class-rrdi-main.php:549 includes/class-rrdi-main.php:557
300
+ msgid "Importing customizer settings"
301
+ msgstr ""
302
+
303
+ #: includes/class-rrdi-main.php:555
304
+ msgid "Customizer settings import finished!"
305
+ msgstr ""
306
+
307
+ #: includes/class-rrdi-main.php:607
308
+ msgid "Completed AJAX call number: "
309
+ msgstr ""
310
+
311
+ #: includes/vendor/class-rrdi-widget-importer.php:44
312
+ msgid "Widget import file could not be found."
313
+ msgstr ""
314
+
315
+ #: includes/vendor/class-rrdi-widget-importer.php:76
316
+ msgid "Widget import data could not be read. Please try a different file."
317
+ msgstr ""
318
+
319
+ #: includes/vendor/class-rrdi-widget-importer.php:116
320
+ msgid "Sidebar does not exist in theme (moving widget to Inactive)"
321
+ msgstr ""
322
+
323
+ #: includes/vendor/class-rrdi-widget-importer.php:138
324
+ msgid "Site does not support widget"
325
+ msgstr ""
326
+
327
+ #: includes/vendor/class-rrdi-widget-importer.php:173
328
+ msgid "Widget already exists"
329
+ msgstr ""
330
+
331
+ #: includes/vendor/class-rrdi-widget-importer.php:232
332
+ msgid "Imported"
333
+ msgstr ""
334
+
335
+ #: includes/vendor/class-rrdi-widget-importer.php:236
336
+ msgid "Imported to Inactive"
337
+ msgstr ""
338
+
339
+ #: includes/vendor/class-rrdi-widget-importer.php:242
340
+ msgid "No Title"
341
+ msgstr ""
342
+
343
+ #: includes/vendor/class-rrdi-widget-importer.php:291
344
+ msgid "No results for widget import!"
345
+ msgstr ""
346
+
347
+ #: includes/extras/class-wxr-importer.php:130
348
+ msgid "Could not open the file for parsing"
349
+ msgstr ""
350
+
351
+ #: includes/extras/class-wxr-importer.php:166
352
+ #: includes/extras/class-wxr-importer.php:282
353
+ #: includes/extras/class-wxr-importer.php:355
354
+ #, php-format
355
+ msgid ""
356
+ "This WXR file (version %s) is newer than the importer (version %s) and may "
357
+ "not be supported. Please consider updating."
358
+ msgstr ""
359
+
360
+ #: includes/extras/class-wxr-importer.php:504
361
+ msgid "The file does not exist, please try again."
362
+ msgstr ""
363
+
364
+ #: includes/extras/class-wxr-importer.php:564
365
+ msgid "Invalid author mapping"
366
+ msgstr ""
367
+
368
+ #: includes/extras/class-wxr-importer.php:665
369
+ msgid "Cannot import auto-draft posts"
370
+ msgstr ""
371
+
372
+ #: includes/extras/class-wxr-importer.php:753
373
+ #, php-format
374
+ msgid "Failed to import \"%s\": Invalid post type %s"
375
+ msgstr ""
376
+
377
+ #: includes/extras/class-wxr-importer.php:763
378
+ #, php-format
379
+ msgid "%s \"%s\" already exists."
380
+ msgstr ""
381
+
382
+ #: includes/extras/class-wxr-importer.php:841
383
+ #, php-format
384
+ msgid "Skipping attachment \"%s\", fetching attachments disabled"
385
+ msgstr ""
386
+
387
+ #: includes/extras/class-wxr-importer.php:855
388
+ #, php-format
389
+ msgid "Failed to import \"%s\" (%s)"
390
+ msgstr ""
391
+
392
+ #: includes/extras/class-wxr-importer.php:887
393
+ #: includes/extras/class-wxr-importer.php:1705
394
+ #, php-format
395
+ msgid "Imported \"%s\" (%s)"
396
+ msgstr ""
397
+
398
+ #: includes/extras/class-wxr-importer.php:892
399
+ #, php-format
400
+ msgid "Post %d remapped to %d"
401
+ msgstr ""
402
+
403
+ #: includes/extras/class-wxr-importer.php:1036
404
+ msgid "Invalid file type"
405
+ msgstr ""
406
+
407
+ #: includes/extras/class-wxr-importer.php:1519
408
+ #, php-format
409
+ msgid "Failed to import user \"%s\""
410
+ msgstr ""
411
+
412
+ #: includes/extras/class-wxr-importer.php:1540
413
+ #, php-format
414
+ msgid "Imported user \"%s\""
415
+ msgstr ""
416
+
417
+ #: includes/extras/class-wxr-importer.php:1544
418
+ #, php-format
419
+ msgid "User %d remapped to %d"
420
+ msgstr ""
421
+
422
+ #: includes/extras/class-wxr-importer.php:1681
423
+ #, php-format
424
+ msgid "Failed to import %s %s"
425
+ msgstr ""
426
+
427
+ #: includes/extras/class-wxr-importer.php:1710
428
+ #, php-format
429
+ msgid "Term %d remapped to %d"
430
+ msgstr ""
431
+
432
+ #: includes/extras/class-wxr-importer.php:1763
433
+ #, php-format
434
+ msgid "Remote server returned %1$d %2$s for %3$s"
435
+ msgstr ""
436
+
437
+ #: includes/extras/class-wxr-importer.php:1776
438
+ msgid "Remote file is incorrect size"
439
+ msgstr ""
440
+
441
+ #: includes/extras/class-wxr-importer.php:1781
442
+ msgid "Zero size file downloaded"
443
+ msgstr ""
444
+
445
+ #: includes/extras/class-wxr-importer.php:1787
446
+ #, php-format
447
+ msgid "Remote file is too large, limit is %s"
448
+ msgstr ""
449
+
450
+ #: includes/extras/class-wxr-importer.php:1809
451
+ #, php-format
452
+ msgid "Running post-processing for post %d"
453
+ msgstr ""
454
+
455
+ #: includes/extras/class-wxr-importer.php:1822
456
+ #, php-format
457
+ msgid "Could not find the post parent for \"%s\" (post #%d)"
458
+ msgstr ""
459
+
460
+ #: includes/extras/class-wxr-importer.php:1827
461
+ #, php-format
462
+ msgid "Post %d was imported with parent %d, but could not be found"
463
+ msgstr ""
464
+
465
+ #: includes/extras/class-wxr-importer.php:1841
466
+ #, php-format
467
+ msgid "Could not find the author for \"%s\" (post #%d)"
468
+ msgstr ""
469
+
470
+ #: includes/extras/class-wxr-importer.php:1846
471
+ #, php-format
472
+ msgid "Post %d was imported with author \"%s\", but could not be found"
473
+ msgstr ""
474
+
475
+ #: includes/extras/class-wxr-importer.php:1872
476
+ #, php-format
477
+ msgid "Post %d was marked for post-processing, but none was required."
478
+ msgstr ""
479
+
480
+ #: includes/extras/class-wxr-importer.php:1883
481
+ #, php-format
482
+ msgid "Could not update \"%s\" (post #%d) with mapped data"
483
+ msgstr ""
484
+
485
+ #: includes/extras/class-wxr-importer.php:1928
486
+ #, php-format
487
+ msgid "Could not find the menu object for \"%s\" (post #%d)"
488
+ msgstr ""
489
+
490
+ #: includes/extras/class-wxr-importer.php:1933
491
+ #, php-format
492
+ msgid ""
493
+ "Post %d was imported with object \"%d\" of type \"%s\", but could not be found"
494
+ msgstr ""
495
+
496
+ #: includes/extras/class-wxr-importer.php:1955
497
+ #, php-format
498
+ msgid "Could not find the comment parent for comment #%d"
499
+ msgstr ""
500
+
501
+ #: includes/extras/class-wxr-importer.php:1959
502
+ #, php-format
503
+ msgid "Comment %d was imported with parent %d, but could not be found"
504
+ msgstr ""
505
+
506
+ #: includes/extras/class-wxr-importer.php:1973
507
+ #, php-format
508
+ msgid "Could not find the author for comment #%d"
509
+ msgstr ""
510
+
511
+ #: includes/extras/class-wxr-importer.php:1977
512
+ #, php-format
513
+ msgid "Comment %d was imported with author %d, but could not be found"
514
+ msgstr ""
515
+
516
+ #: includes/extras/class-wxr-importer.php:1994
517
+ #, php-format
518
+ msgid "Could not update comment #%d with mapped data"
519
+ msgstr ""
520
+
521
+ #: rara-demo-import.php:29
522
+ #, php-format
523
+ msgid ""
524
+ "The %2$sRara One Click Demo Import%3$s plugin requires %2$sPHP 5.3.2+%3$s to run "
525
+ "properly. Please contact your hosting company and ask them to update the PHP "
526
+ "version of your site to at least PHP 5.3.2.%4$s Your current version of PHP: "
527
+ "%2$s%1$s%3$s"
528
+ msgstr ""
rara-demo-import.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: Rara One Click Demo Import
4
+ Plugin URI: https://wordpress.org/plugins/rrdi/
5
+ Description: Importing your theme's live demo content, widgets and settings is just one click away.
6
+ Version: 1.0.0
7
+ Author: raratheme
8
+ Author URI: https://www.raratheme.com
9
+ License: GPL3
10
+ License URI: http://www.gnu.org/licenses/gpl.html
11
+ Text Domain: rara-demo-import
12
+ */
13
+
14
+ // Block direct access to the main plugin file.
15
+ defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
16
+
17
+ define( 'RRDI_PATH', plugin_dir_path( __FILE__ ) );
18
+
19
+ // Current version of the plugin.
20
+ define( 'RRDI_VERSION', '1.0.0' );
21
+
22
+ // Path/URL to root of this plugin, with trailing slash.
23
+ define( 'RRDI_URL', plugin_dir_url( __FILE__ ) );
24
+
25
+ require RRDI_PATH . 'includes/class-rrdi-init.php';
readme.txt ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Rara One Click Demo Import ===
2
+ Contributors: raratheme
3
+ Tags: import, content, demo, data, widgets, settings
4
+ Requires at least: 4.0.0
5
+ Tested up to: 4.7
6
+ Stable tag: 1.0.0
7
+ License: GPLv3 or later
8
+
9
+ Importing your theme's live demo content, widgets and settings is just one click away.
10
+
11
+ == Description ==
12
+
13
+ Importing your theme's live demo content, widgets and settings is just one click away. This will provide something to start with as a basic layout is provided to build your website.
14
+
15
+ This plugin will create a page in **APPEARANCE > Import Demo Content**.
16
+
17
+ In case of no predefined import files, you will be presented with three file upload inputs:
18
+
19
+ i. First one is required and you will have to upload a demo content XML file, for the actual demo import.
20
+
21
+ ii. The second one is optional and will ask you for a WIE or JSON file for widgets import. You create that file using the [Widget Importer & Exporter](https://wordpress.org/plugins/widget-importer-exporter/) plugin.
22
+
23
+ iii. The third one is also optional and will import the customizer settings, select the DAT file which you can generate from [Customizer Export/Import](https://wordpress.org/plugins/customizer-export-import/) plugin (the customizer settings will be imported only if the export file was created from the same theme).
24
+
25
+ This plugin is based off the 'Rara One Click Demo Import' plugin by Themely, https://wordpress.org/plugins/rara-demo-import/
26
+
27
+ As well as the improved WP Import 2.0 plugin by @humanmade, https://github.com/humanmade/WordPress-Importer.
28
+
29
+ == Installation ==
30
+
31
+ **From your WordPress dashboard**
32
+
33
+ 1. Visit 'Plugins > Add New',
34
+ 2. Search for 'Rara One Click Demo Import' and install the plugin.
35
+ 3. Activate 'Rara One Click Demo Import' from your Plugins page.
36
+
37
+ Once the plugin is activated you will find the actual import page in **Appearance -> Import Demo Content.**
38
+
39
+ == Frequently Asked Questions ==
40
+
41
+ = I have activated the plugin. Where is the "Import Demo Data" page? =
42
+
43
+ You will find the import page in *wp-admin -> Appearance -> Import Demo Data*.
44
+
45
+ = Where are the demo import files and the log files saved? =
46
+
47
+ The files used in the demo import will be saved to the default WordPress uploads directory. An example of that directory would be: `../wp-content/uploads/2017/03/`.
48
+
49
+ The log file will also be registered in the *wp-admin -> Media* section, so you can access it easily.
50
+
51
+ = How to customize the message that appears before importing the data?
52
+
53
+ Using the filter: `apply_filters( 'rrdi_before_import_msg', $msg);`.
54
+
55
+ = How to predefine demo imports? =
56
+
57
+ This question is for theme authors. To predefine demo imports, you just have to add the following code structure, with your own values to your theme (using the `rrdi/import_files` filter):
58
+
59
+ `
60
+ function rrdi_import_files() {
61
+ return array(
62
+ array(
63
+ 'import_file_name' => 'Demo Import 1',
64
+ 'import_file_url' => 'http://www.your_domain.com/rrdi/demo-content.xml',
65
+ 'import_widget_file_url' => 'http://www.your_domain.com/rrdi/widgets.json',
66
+ 'import_customizer_file_url' => 'http://www.your_domain.com/rrdi/customizer.dat',
67
+ 'import_preview_image_url' => 'http://www.your_domain.com/rrdi/preview_import_image1.jpg',
68
+ 'import_notice' => __( 'After you import this demo, you will have to setup the slider separately.', 'your-textdomain' ),
69
+ ),
70
+ array(
71
+ 'import_file_name' => 'Demo Import 2',
72
+ 'import_file_url' => 'http://www.your_domain.com/rrdi/demo-content2.xml',
73
+ 'import_widget_file_url' => 'http://www.your_domain.com/rrdi/widgets2.json',
74
+ 'import_customizer_file_url' => 'http://www.your_domain.com/rrdi/customizer2.dat',
75
+ 'import_preview_image_url' => 'http://www.your_domain.com/rrdi/preview_import_image2.jpg',
76
+ 'import_notice' => __( 'A special note for this import.', 'your-textdomain' ),
77
+ ),
78
+ );
79
+ }
80
+ add_filter( 'rrdi/import_files', 'rrdi_import_files' );
81
+ `
82
+
83
+ You can set content import, widgets, and customizer import files. You can also define a preview image, which will be used only when multiple demo imports are defined, so that the user will see the difference between imports.
84
+
85
+ = How to automatically assign "Front page", "Posts page" and menu locations after the importer is done? =
86
+
87
+ You can do that, with the `rrdi/after_import` action hook. The code would look something like this:
88
+
89
+ `
90
+ function rrdi_after_import_setup() {
91
+ // Assign menus to their locations.
92
+ $main_menu = get_term_by( 'name', 'Top Menu', 'nav_menu' );
93
+
94
+ set_theme_mod( 'nav_menu_locations', array(
95
+ 'primary-menu' => $main_menu->term_id,
96
+ )
97
+ );
98
+
99
+ // Assign front page and posts page (blog page).
100
+ $front_page_id = get_page_by_title( 'Home' );
101
+ $blog_page_id = get_page_by_title( 'Blog' );
102
+
103
+ update_option( 'show_on_front', 'page' );
104
+ update_option( 'page_on_front', $front_page_id->ID );
105
+ update_option( 'page_for_posts', $blog_page_id->ID );
106
+
107
+ }
108
+ add_action( 'rrdi/after_import', 'rrdi_after_import_setup' );
109
+ `
110
+
111
+ = What about using local import files (from theme folder)? =
112
+
113
+ You have to use the same filter as in above example, but with a slightly different array keys: `local_*`. The values have to be absolute paths (not URLs) to your import files. To use local import files, that reside in your theme folder, please use the below code. Note: make sure your import files are readable!
114
+
115
+ `
116
+ function rrdi_import_files() {
117
+ return array(
118
+ array(
119
+ 'import_file_name' => 'Demo Import 1',
120
+ 'local_import_file' => trailingslashit( get_template_directory() ) . 'rrdi/demo-content.xml',
121
+ 'local_import_widget_file' => trailingslashit( get_template_directory() ) . 'rrdi/widgets.json',
122
+ 'local_import_customizer_file' => trailingslashit( get_template_directory() ) . 'rrdi/customizer.dat',
123
+ 'import_preview_image_url' => 'http://www.your_domain.com/rrdi/preview_import_image1.jpg',
124
+ 'import_notice' => __( 'After you import this demo, you will have to setup the slider separately.', 'your-textdomain' ),
125
+ ),
126
+ array(
127
+ 'import_file_name' => 'Demo Import 2',
128
+ 'local_import_file' => trailingslashit( get_template_directory() ) . 'rrdi/demo-content2.xml',
129
+ 'local_import_widget_file' => trailingslashit( get_template_directory() ) . 'rrdi/widgets2.json',
130
+ 'local_import_customizer_file' => trailingslashit( get_template_directory() ) . 'rrdi/customizer2.dat',
131
+ 'import_preview_image_url' => 'http://www.your_domain.com/rrdi/preview_import_image2.jpg',
132
+ 'import_notice' => __( 'A special note for this import.', 'your-textdomain' ),
133
+ ),
134
+ );
135
+ }
136
+ add_filter( 'rrdi/import_files', 'rrdi_import_files' );
137
+ `
138
+
139
+ = How to handle different "after import setups" depending on which predefined import was selected? =
140
+
141
+ This question might be asked by a theme author wanting to implement different after import setups for multiple predefined demo imports. Lets say we have predefined two demo imports with the following names: 'Demo Import 1' and 'Demo Import 2', the code for after import setup would be (using the `rrdi/after_import` filter):
142
+
143
+ `
144
+ function rrdi_after_import( $selected_import ) {
145
+ echo "This will be displayed on all after imports!";
146
+
147
+ if ( 'Demo Import 1' === $selected_import['import_file_name'] ) {
148
+ echo "This will be displayed only on after import if user selects Demo Import 1";
149
+
150
+ // Set logo in customizer
151
+ set_theme_mod( 'logo_img', get_template_directory_uri() . '/assets/images/logo1.png' );
152
+ }
153
+ elseif ( 'Demo Import 2' === $selected_import['import_file_name'] ) {
154
+ echo "This will be displayed only on after import if user selects Demo Import 2";
155
+
156
+ // Set logo in customizer
157
+ set_theme_mod( 'logo_img', get_template_directory_uri() . '/assets/images/logo2.png' );
158
+ }
159
+ }
160
+ add_action( 'rrdi/after_import', 'rrdi_after_import' );
161
+ `
162
+
163
+ = Can I add some code before the widgets get imported? =
164
+
165
+ Of course you can, use the `rrdi/before_widgets_import` action. You can also target different predefined demo imports like in the example above. Here is a simple example code of the `rrdi/before_widgets_import` action:
166
+
167
+ `
168
+ function rrdi_before_widgets_import( $selected_import ) {
169
+ echo "Add your code here that will be executed before the widgets get imported!";
170
+ }
171
+ add_action( 'rrdi/before_widgets_import', 'rrdi_before_widgets_import' );
172
+ `
173
+
174
+ = I'm a theme author and I want to change the plugin intro text, how can I do that? =
175
+
176
+ You can change the plugin intro text by using the `rrdi/plugin_intro_text` filter:
177
+
178
+ `
179
+ function rrdi_plugin_intro_text( $default_text ) {
180
+ $default_text .= '<div class="RRDI__intro-text">This is a custom text added to this plugin intro text.</div>';
181
+
182
+ return $default_text;
183
+ }
184
+ add_filter( 'rrdi/plugin_intro_text', 'rrdi_plugin_intro_text' );
185
+ `
186
+
187
+ To add some text in a separate "box", you should wrap your text in a div with a class of 'RRDI__intro-text', like in the code example above.
188
+
189
+ = How to disable generation of smaller images (thumbnails) during the content import =
190
+
191
+ This will greatly improve the time needed to import the content (images), but only the original sized images will be imported. You can disable it with a filter, so just add this code to your theme function.php file:
192
+
193
+ `add_filter( 'rrdi/regenerate_thumbnails_in_content_import', '__return_false' );`
194
+
195
+ = How to change the location, title and other parameters of the plugin page? =
196
+
197
+ As a theme author you do not like the location of the "Import Demo Data" plugin page in *Appearance -> Import Demo Data*? You can change that with the filter below. Apart from the location, you can also change the title or the page/menu and some other parameters as well.
198
+
199
+ `
200
+ function rrdi_plugin_page_setup( $default_settings ) {
201
+ $default_settings['parent_slug'] = 'themes.php';
202
+ $default_settings['page_title'] = esc_html__( 'Rara One Click Demo Import' , 'rara-demo-import' );
203
+ $default_settings['menu_title'] = esc_html__( 'Import Demo Content' , 'rara-demo-import' );
204
+ $default_settings['capability'] = 'import';
205
+ $default_settings['menu_slug'] = 'rara-demo-import';
206
+
207
+ return $default_settings;
208
+ }
209
+ add_filter( 'rrdi/plugin_page_setup', 'rrdi_plugin_page_setup' );
210
+ `
211
+
212
+ = I can't activate the plugin, because of a fatal error, what can I do? =
213
+
214
+ *Update: There is now a admin error notice, stating that the minimal PHP version required for this plugin is 5.3.2.*
215
+
216
+ You want to activate the plugin, but this error shows up:
217
+
218
+ *Plugin could not be activated because it triggered a fatal error*
219
+
220
+ This happens, because your hosting server is using a very old version of PHP. This plugin requires PHP version of at least **5.3.x**, but we recommend version *5.6.x*. Please contact your hosting company and ask them to update the PHP version for your site.
221
+
222
+
223
+ == License ==
224
+
225
+ Rara One Click Demo Import uses the script of
226
+ 'Theme Demo Import' plugin by Themely,
227
+ https://wordpress.org/plugins/theme-demo-import/
228
+ Licensed under the GNU General Public License v2.0,
229
+ http://www.gnu.org/licenses/gpl-2.0.html
230
+
231
+ Rara One Click Demo Import uses 'Wordpress Importer' plugin script
232
+ https://github.com/humanmade/WordPress-Importer
233
+ (C) 2016 @humanmade
234
+ Licensed under the GNU General Public License v2.0,
235
+ http://www.gnu.org/licenses/gpl-2.0.html
236
+
237
+
238
+ == Copyright ==
239
+
240
+ Rara One Click Demo Import is distributed under the terms of the GNU GPL
241
+
242
+ This program is free software; you can redistribute it and/or modify
243
+ it under the terms of the GNU General Public License as published by
244
+ the Free Software Foundation; either version 2 of the License, or
245
+ (at your option) any later version.
246
+
247
+ This program is distributed in the hope that it will be useful,
248
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
249
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
250
+ GNU General Public License for more details.
251
+
252
+ You should have received a copy of the GNU General Public License along
253
+ with this program; if not, write to the Free Software Foundation, Inc.,
254
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
255
+
256
+
257
+ == Changelog ==
258
+
259
+ **1.0.0 - 10/10/2016**
260
+
261
+ - INITIAL RELEASE