Astra Starter Sites - Version 1.0.0

Version Description

Download this release

Release Info

Developer Nikschavan
Plugin Icon Astra Starter Sites
Version 1.0.0
Comparing to
See all releases

Version 1.0.0

admin/class-astra-sites-admin.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Astra Demo Importer Admin
4
+ *
5
+ * @package Astra Addon
6
+ */
7
+
8
+ defined( 'ABSPATH' ) or exit;
9
+
10
+ /**
11
+ * Astra Demo Importer Admin
12
+ *
13
+ * @since 1.0.0
14
+ */
15
+ class Astra_Sites_Admin {
16
+
17
+ /**
18
+ * Instance of Astra_Sites_Admin
19
+ *
20
+ * @since 1.0.0
21
+ * @var Astra_Sites_Admin
22
+ */
23
+ private static $_instance = null;
24
+
25
+ /**
26
+ * Instanciate Astra_Sites_Admin
27
+ *
28
+ * @since 1.0.0
29
+ * @return (Object) Astra_Sites_Admin
30
+ */
31
+ public static function instance() {
32
+ if ( ! isset( self::$_instance ) ) {
33
+ self::$_instance = new self;
34
+ }
35
+
36
+ return self::$_instance;
37
+ }
38
+
39
+ /**
40
+ * Constructor
41
+ *
42
+ * @since 1.0.0
43
+ */
44
+ private function __construct() {
45
+
46
+ add_filter( 'astra_menu_options', array( $this, 'menu' ) );
47
+ add_action( 'astra_menu_astra_sites_action', array( $this, 'view_demos' ) );
48
+
49
+ }
50
+
51
+ /**
52
+ * Register admin menu for demo importer
53
+ *
54
+ * @since 1.0.0
55
+ *
56
+ * @param (Array) $actions Previously registered tabs menus.
57
+ *
58
+ * @return (Array) registered tabs menus in Astra menu.
59
+ */
60
+ public function menu( $actions ) {
61
+
62
+ $actions['astra-sites'] = array(
63
+ 'label' => __( 'Astra Sites', 'astra-sites' ),
64
+ 'show' => ! is_network_admin(),
65
+ );
66
+
67
+ return $actions;
68
+ }
69
+
70
+ /**
71
+ * View Astra Sites
72
+ *
73
+ * @since 1.0.0
74
+ */
75
+ public function view_demos() {
76
+
77
+ include ASTRA_SITES_DIR . 'admin/view-astra-sites.php';
78
+ }
79
+
80
+ }
81
+
82
+ Astra_Sites_Admin::instance();
admin/view-astra-sites.php ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Astra Demo View.
4
+ *
5
+ * @package Astra Addon
6
+ */
7
+
8
+ defined( 'ABSPATH' ) or exit;
9
+
10
+ // Enqueue scripts.
11
+ wp_enqueue_script( 'astra-sites-admin' );
12
+ wp_enqueue_style( 'astra-sites-admin' );
13
+
14
+ // Load demo importer markup.
15
+ $all_demos = Astra_Sites::get_astra_all_demos();
16
+
17
+ do_action( 'astra_sites_before_site_grid', $all_demos );
18
+
19
+ if ( count( $all_demos ) > 0 ) {
20
+
21
+ /**
22
+ * Initial Demo List
23
+ *
24
+ * Generated though PHP
25
+ */
26
+ ?>
27
+ <div class="wrap">
28
+
29
+ <div class="wp-filter hide-if-no-js">
30
+
31
+ <ul class="filter-links">
32
+
33
+ <li><a href="#" data-sort="all" class="current" data-id="all"><?php esc_html_e( 'All', 'astra-sites' ); ?></a></li>
34
+
35
+ <?php foreach ( Astra_Sites::get_demo_categories() as $key => $category ) { ?>
36
+ <li>
37
+ <a href="#"
38
+ data-sort="<?php echo esc_attr( $category['slug'] ); ?>"
39
+ data-id="<?php echo esc_attr( $category['id'] ); ?>">
40
+ <?php echo esc_attr( $category['name'] ); ?>
41
+ </a>
42
+ </li>
43
+ <?php } ?>
44
+ </ul>
45
+
46
+ <div class="search-form">
47
+ <label class="screen-reader-text" for="wp-filter-search-input"><?php esc_html_e( 'Search Sites', 'astra-sites' ); ?></label>
48
+ <input placeholder="<?php esc_attr_e( 'Search Sites...', 'astra-sites' ); ?>" type="search" aria-describedby="live-search-desc" id="wp-filter-search-input" class="wp-filter-search">
49
+ </div>
50
+
51
+ </div>
52
+
53
+ <span class="spinner"></span>
54
+
55
+ <div class="theme-browser rendered">
56
+ <div class="themes wp-clearfix">
57
+
58
+ <?php foreach ( $all_demos as $key => $demo ) { ?>
59
+
60
+ <div class="theme astra-theme" tabindex="0" aria-describedby="astra-theme-action astra-theme-name"
61
+ data-demo-id="<?php echo esc_attr( $demo['id'] ); ?>"
62
+ data-demo-type="<?php echo esc_attr( $demo['astra_demo_type'] ); ?>"
63
+ data-demo-url="<?php echo esc_url( $demo['astra_demo_url'] ); ?>"
64
+ data-demo-api="<?php echo esc_url( $demo['demo_api'] ); ?>"
65
+ data-screenshot="<?php echo esc_url( $demo['featured_image_url'] ); ?>"
66
+ data-demo-name="<?php echo esc_attr( $demo['title'] ); ?>"
67
+ data-demo-slug="<?php echo esc_attr( $demo['slug'] ); ?>"
68
+ data-content="<?php echo esc_attr( $demo['content'] ); ?>"
69
+ data-required-plugins="<?php echo esc_attr( $demo['required_plugins'] ); ?>">
70
+
71
+ <?php if ( 'premium' === $demo['astra_demo_type'] ) { ?>
72
+ <span class="demo-type <?php echo esc_attr( $demo['astra_demo_type'] ); ?>"><?php echo esc_attr( $demo['astra_demo_type'] ); ?></span>
73
+ <?php } ?>
74
+
75
+ <div class="theme-screenshot">
76
+ <?php if ( ! empty( $demo['featured_image_url'] ) ) { ?>
77
+ <img src="<?php echo esc_attr( $demo['featured_image_url'] ); ?>" alt="">
78
+ <?php } ?>
79
+ </div>
80
+
81
+ <a href="<?php echo esc_url( $demo['astra_demo_url'] ); ?>" target="_blank">
82
+ <span class="more-details" id="astra-theme-action"><?php esc_html_e( 'Details &amp; Preview', 'astra-sites' ); ?></span>
83
+ </a>
84
+
85
+ <h3 class="theme-name" id="astra-theme-name"><?php echo esc_attr( $demo['title'] ); ?></h3>
86
+ <div class="theme-actions">
87
+ <button class="button preview install-theme-preview"><?php esc_html_e( 'Preview', 'astra-sites' ); ?></button>
88
+ </div>
89
+ </div>
90
+
91
+ <?php } ?>
92
+
93
+ </div>
94
+ </div>
95
+
96
+ </div>
97
+
98
+ <?php
99
+ /**
100
+ * Regenerated Demo List
101
+ *
102
+ * Generated though JS after search demo, filter demo etc.
103
+ */
104
+ ?>
105
+ <script type="text/template" id="tmpl-astra-single-demo">
106
+ <div class="theme astra-theme" tabindex="0" aria-describedby="astra-theme-action astra-theme-name"
107
+ data-demo-id="{{{data.id}}}"
108
+ data-demo-type="{{{data.astra_demo_type}}}"
109
+ data-demo-url="{{{data.astra_demo_url}}}"
110
+ data-demo-api="{{{data.demo_api}}}"
111
+ data-demo-name="{{{data.demo_name}}}"
112
+ data-demo-slug="{{{data.slug}}}"
113
+ data-screenshot="{{{data.screenshot}}}"
114
+ data-content="{{{data.content}}}"
115
+ data-required-plugins="{{data.required_plugins}}">
116
+
117
+ <span class="demo-type {{{data.astra_demo_type}}}">{{{data.astra_demo_type}}}</span>
118
+
119
+ <div class="theme-screenshot">
120
+ <# if ( data.screenshot.length ) { #>
121
+ <img src="{{{data.screenshot}}}" alt="">
122
+ <# } #>
123
+ </div>
124
+
125
+ <a href="{{{data.astra_demo_url}}}" target="_blank">
126
+ <span class="more-details" id="astra-theme-action"><?php esc_html_e( 'Details &amp; Preview', 'astra-sites' ); ?></span>
127
+ </a>
128
+
129
+ <h3 class="theme-name" id="astra-theme-name">{{{data.demo_name}}}</h3>
130
+
131
+ <div class="theme-actions">
132
+ <button class="button preview install-theme-preview"><?php esc_html_e( 'Preview', 'astra-sites' ); ?></button>
133
+
134
+ </div>
135
+
136
+ </div>
137
+ </script>
138
+
139
+ <?php
140
+ /**
141
+ * Single Demo Preview
142
+ */
143
+ ?>
144
+ <script type="text/template" id="tmpl-astra-demo-preview">
145
+ <div class="astra-sites-preview theme-install-overlay wp-full-overlay expanded">
146
+ <div class="wp-full-overlay-sidebar">
147
+ <div class="wp-full-overlay-header"
148
+ data-demo-id="{{{data.id}}}"
149
+ data-demo-type="{{{data.astra_demo_type}}}"
150
+ data-demo-url="{{{data.astra_demo_url}}}"
151
+ data-demo-api="{{{data.demo_api}}}"
152
+ data-demo-name="{{{data.demo_name}}}"
153
+ data-demo-slug="{{{data.slug}}}"
154
+ data-screenshot="{{{data.screenshot}}}"
155
+ data-content="{{{data.content}}}"
156
+ data-required-plugins="{{data.required_plugins}}">
157
+ <button class="close-full-overlay"><span class="screen-reader-text"><?php esc_html_e( 'Close', 'astra-sites' ); ?></span></button>
158
+ <button class="previous-theme"><span class="screen-reader-text"><?php esc_html_e( 'Previous', 'astra-sites' ); ?></span></button>
159
+ <button class="next-theme"><span class="screen-reader-text"><?php esc_html_e( 'Next', 'astra-sites' ); ?></span></button>
160
+ <a class="button hide-if-no-customize astra-demo-import" href="#" data-import="disabled"><?php esc_html_e( 'Install Plugins', 'astra-sites' ); ?></a>
161
+
162
+ </div>
163
+ <div class="wp-full-overlay-sidebar-content">
164
+ <div class="install-theme-info">
165
+
166
+ <span class="demo-type {{{data.astra_demo_type}}}">{{{data.astra_demo_type}}}</span>
167
+ <h3 class="theme-name">{{{data.demo_name}}}</h3>
168
+
169
+ <# if ( data.screenshot.length ) { #>
170
+ <img class="theme-screenshot" src="{{{data.screenshot}}}" alt="">
171
+ <# } #>
172
+
173
+ <div class="theme-details">
174
+ {{{data.content}}}
175
+ </div>
176
+ <a href="#" class="theme-details-read-more"><?php _e( 'Read more', 'astra-sites' ); ?> &hellip;</a>
177
+
178
+ <div class="required-plugins-wrap">
179
+ <h4><?php _e( 'Required Plugins', 'astra-sites' ); ?> </h4>
180
+ <div class="required-plugins"></div>
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ <div class="wp-full-overlay-footer">
186
+ <div class="footer-import-button-wrap">
187
+ <a class="button button-hero hide-if-no-customize astra-demo-import" href="#" data-import="disabled">
188
+ <?php esc_html_e( 'Install Plugins', 'astra-sites' ); ?>
189
+ </a>
190
+ </div>
191
+ <button type="button" class="collapse-sidebar button" aria-expanded="true"
192
+ aria-label="Collapse Sidebar">
193
+ <span class="collapse-sidebar-arrow"></span>
194
+ <span class="collapse-sidebar-label"><?php esc_html_e( 'Collapse', 'astra-sites' ); ?></span>
195
+ </button>
196
+ </div>
197
+ </div>
198
+ <div class="wp-full-overlay-main">
199
+ <iframe src="{{{data.astra_demo_url}}}" title="<?php esc_attr_e( 'Preview', 'astra-sites' ); ?>"></iframe>
200
+ </div>
201
+ </div>
202
+ </script>
203
+
204
+ <?php
205
+
206
+ // Load demo importer welcome.
207
+ } else {
208
+ ?>
209
+ <div class="no-themes">
210
+ <?php
211
+
212
+ /* translators: %1$s & %2$s are a Demo API URL */
213
+ printf( __( '<p> It seems the demo data server, <i><a href="%1$s">%2$s</a></i> is unreachable from your site.</p>', 'astra-sites' ) , esc_url( Astra_Sites::$api_url ), esc_url( Astra_Sites::$api_url ) );
214
+
215
+ _e( '<p class="left-margin"> 1. Sometimes, simple page reload fixes any temporary issues. No kidding!</p>', 'astra-sites' );
216
+
217
+ _e( '<p class="left-margin"> 2. If that does not work, you will need to talk to your server administrator and check if demo server is being blocked by the firewall!</p>', 'astra-sites' );
218
+
219
+ /* translators: %1$s is a support link */
220
+ printf( __( '<p>If that does not help, please open up a <a href="%1$s" target="_blank">Support Ticket</a> and we will be glad take a closer look for you.</p>', 'astra-sites' ), esc_url( 'https://wpastra.com/support/' ) );
221
+ ?>
222
+
223
+ </div>
224
+ </p>
225
+ <?php
226
+ }// End if().
227
+
228
+ do_action( 'astra_sites_after_site_grid', $all_demos );
assets/css/admin.css ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .theme-browser .theme.focus .theme-actions,
2
+ .theme-browser .theme:focus .theme-actions,
3
+ .theme-browser .theme:hover .theme-actions {
4
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
5
+ opacity: 1
6
+ }
7
+ .theme-browser .theme .theme-screenshot:after {
8
+ content: "";
9
+ display: block;
10
+ padding-top: 66.66666%
11
+ }
12
+
13
+ .wrap .demo-type {
14
+ position: absolute;
15
+ z-index: 1;
16
+ color: #fff;
17
+ padding: 0.5em 1em;
18
+ top: -0.5em;
19
+ left: -0.5em;
20
+ text-transform: uppercase;
21
+ }
22
+ .wrap .demo-type.premium {
23
+ background: #0073aa;
24
+ }
25
+ .wrap .demo-type.free {
26
+ display: none;
27
+ }
28
+
29
+ .theme {
30
+ position: relative;
31
+ }
32
+ .wrap .astra-sites-preview .demo-type.premium {
33
+ display: block;
34
+ display: none;
35
+ position: relative;
36
+ margin: 0.5em 0em 1em 0em;
37
+ top: 0;
38
+ left: 0;
39
+ text-align: center;
40
+ }
41
+
42
+ .theme-details-read-more.open {
43
+ margin: 0.5em 0 0 0;
44
+ }
45
+
46
+ .astra-sites-preview .theme-screenshot {
47
+ width: 100%;
48
+ }
49
+
50
+ /**
51
+ * Required Plugins
52
+ */
53
+ .required-plugins.loading {
54
+ text-align: center;
55
+ }
56
+ .required-plugins button {
57
+ float: right;
58
+ }
59
+ .required-plugins .plugin-card {
60
+ float: none;
61
+ width: 100%;
62
+ border: none;
63
+ margin: 0 0 0.8em 0;
64
+ display: flex;
65
+ justify-content: space-between;
66
+ align-items: center;
67
+ transition: background ease 0.8s;
68
+ }
69
+ .required-plugins .plugin-card.plugin-card-update-failed {
70
+ flex-wrap: wrap;
71
+ }
72
+ .required-plugins .spinner {
73
+ float: none;
74
+ }
75
+
76
+ .expanded .wp-full-overlay-footer {
77
+ height: 100px;
78
+ }
79
+
80
+ .wp-full-overlay-footer .view-site,
81
+ .wp-full-overlay-footer .go-pro,
82
+ .wp-full-overlay-footer .astra-demo-import {
83
+ width: 100%;
84
+ text-align: center;
85
+ }
86
+
87
+ .wp-full-overlay-footer .installing:before {
88
+ vertical-align: text-bottom;
89
+ }
90
+
91
+ .required-plugins-wrap h4 {
92
+ margin: 1em 0 0.5em 0;
93
+ padding: 0.5em 0;
94
+ transition: all ease 0.3s;
95
+ }
96
+
97
+ /**
98
+ * Read more link
99
+ */
100
+ .wp-core-ui .theme-details-read-more:focus,
101
+ .wp-core-ui .theme-details-read-more:hover {
102
+ outline: none;
103
+ box-shadow: none;
104
+ }
105
+ .wp-core-ui .theme-details-read-more {
106
+ margin: 10px 0;
107
+ display: none;
108
+ text-decoration: none;
109
+ }
110
+
111
+ /**
112
+ * Go pro.
113
+ */
114
+ .wp-core-ui .go-pro.button[disabled] {
115
+ background-color: #fcb92c !important;
116
+ color: white !important;
117
+ box-shadow: 1px 0 #eab23a !important;
118
+ text-shadow: 1px 0 #6b4e13 !important;
119
+ border-color: #e2a932 !important;
120
+ cursor: pointer;
121
+ }
122
+ .wp-core-ui .view-site .dashicons,
123
+ .wp-core-ui .go-pro .dashicons {
124
+ font-size: 1rem;
125
+ vertical-align: middle;
126
+ }
127
+
128
+ /**
129
+ * Errors
130
+ */
131
+ .plugin-card-update-failed .notice {
132
+ margin-top: 1.5em;
133
+ }
134
+
135
+ .no-themes {
136
+ margin-top: 40px;
137
+ }
138
+
139
+ .no-themes p {
140
+ font-size: 15px;
141
+ }
142
+
143
+ .no-themes .left-margin {
144
+ margin-left: 30px;
145
+ }
146
+
147
+ /**
148
+ *
149
+ */
150
+ .astra-sites-preview .wp-full-overlay-sidebar-content {
151
+ bottom: 100px;
152
+ }
153
+
154
+ .footer-import-button-wrap {
155
+ padding: 10px 20px;
156
+ }
157
+
158
+ .footer-import-button-wrap .button {
159
+ margin: 0;
160
+ }
161
+
162
+ .astra-sites-preview.expanded .wp-full-overlay-footer {
163
+ left: initial;
164
+ }
assets/js/admin.js ADDED
@@ -0,0 +1,694 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(document).ready(function ($) {
2
+ resetPagedCount();
3
+ });
4
+
5
+ /**
6
+ * Enable Demo Import Button.
7
+ */
8
+ function enable_demo_import_button( type = 'free' ) {
9
+
10
+ if( 'free' === type ) {
11
+
12
+ // Get initial required plugins count.
13
+ var remaining = parseInt( astraDemo.requiredPluginsCount ) || 0;
14
+
15
+ // Enable demo import button.
16
+ if( 0 >= remaining ) {
17
+
18
+ jQuery('.astra-demo-import')
19
+ .removeAttr('data-import')
20
+ .addClass('button-primary')
21
+ .text( astraDemo.strings.importDemo );
22
+ }
23
+ } else {
24
+
25
+ var demo_slug = jQuery('.wp-full-overlay-header').attr('data-demo-slug');
26
+
27
+ jQuery('.astra-demo-import')
28
+ .addClass('go-pro button-primary')
29
+ .removeClass('astra-demo-import')
30
+ .attr('target', '_blank')
31
+ .attr('href', astraDemo.getProURL + demo_slug )
32
+ .text( astraDemo.getProText )
33
+ .append('<i class="dashicons dashicons-external"></i>');
34
+ }
35
+ }
36
+
37
+ function resetPagedCount() {
38
+ categoryId = jQuery('.filter-links li .current').data('id');
39
+ jQuery('body').attr('data-astra-demo-paged', '1');
40
+ jQuery('body').attr('data-astra-site-category', categoryId);
41
+ jQuery('body').attr('data-astra-demo-search', '');
42
+ jQuery('body').attr('data-scrolling', false);
43
+ jQuery('body').attr( 'data-required-plugins', 0 )
44
+ }
45
+
46
+ function updatedPagedCount() {
47
+ paged = parseInt(jQuery('body').attr('data-astra-demo-paged'));
48
+ jQuery('body').attr('data-astra-demo-paged', paged + 1);
49
+ window.setTimeout(function () {
50
+ jQuery('body').data('scrolling', false);
51
+ }, 800);
52
+ }
53
+
54
+ jQuery(document).scroll(function (event) {
55
+ var scrollDistance = jQuery(window).scrollTop();
56
+
57
+ var themesBottom = Math.abs(jQuery(window).height() - jQuery('.themes').offset().top - jQuery('.themes').height());
58
+ themesBottom = themesBottom * 20 / 100;
59
+
60
+ ajaxLoading = jQuery('body').data('scrolling');
61
+
62
+ if (scrollDistance > themesBottom && ajaxLoading == false) {
63
+ updatedPagedCount();
64
+ jQuery('body').data('scrolling', true);
65
+ body = jQuery('body');
66
+ id = body.attr('data-astra-site-category');
67
+ search = body.attr('data-astra-demo-search');
68
+ paged = body.attr('data-astra-demo-paged');
69
+
70
+ if (search !== '') {
71
+ id = '';
72
+ } else {
73
+ search = '';
74
+ }
75
+
76
+ jQuery('.no-themes').remove();
77
+
78
+ jQuery.ajax({
79
+ url: astraDemo.ajaxurl,
80
+ type: 'POST',
81
+ dataType: 'json',
82
+ data: {
83
+ action: 'astra-list-sites',
84
+ id: id,
85
+ paged: paged,
86
+ search: search
87
+ },
88
+ })
89
+ .done(function (demos) {
90
+ jQuery('body').removeClass('loading-content');
91
+ renderDemoGrid(demos);
92
+ })
93
+ .fail(function () {
94
+ jQuery('body').removeClass('loading-content');
95
+ jQuery('.spinner').after('<p class="no-themes" style="display:block;">'+astraDemo.strings.responseError+'</p>');
96
+ });
97
+
98
+ }
99
+ });
100
+
101
+ /**
102
+ * Individual Site Preview
103
+ *
104
+ * On click on image, more link & preview button.
105
+ */
106
+ jQuery(document).on('click', '.theme-browser .theme-screenshot, .theme-browser .more-details, .theme-browser .install-theme-preview', function (event) {
107
+ event.preventDefault();
108
+
109
+ $this = jQuery(this).parents('.theme');
110
+ $this.addClass('theme-preview-on');
111
+
112
+ renderDemoPreview($this);
113
+ });
114
+
115
+ jQuery(document).on('click', '.close-full-overlay', function (event) {
116
+ event.preventDefault();
117
+
118
+ jQuery('.theme-install-overlay').css('display', 'none');
119
+ jQuery('.theme-install-overlay').remove();
120
+ jQuery('.theme-preview-on').removeClass('theme-preview-on');
121
+ });
122
+
123
+ jQuery(document).on('click', '.next-theme', function (event) {
124
+ event.preventDefault();
125
+ currentDemo = jQuery('.theme-preview-on')
126
+ currentDemo.removeClass('theme-preview-on');
127
+ nextDemo = currentDemo.nextAll('.theme');
128
+ nextDemo.addClass('theme-preview-on');
129
+
130
+ renderDemoPreview( nextDemo );
131
+
132
+ });
133
+
134
+ jQuery(document).on('click', '.previous-theme', function (event) {
135
+ event.preventDefault();
136
+
137
+ currentDemo = jQuery('.theme-preview-on');
138
+ currentDemo.removeClass('theme-preview-on');
139
+ prevDemo = currentDemo.prevAll('.theme');
140
+ prevDemo.addClass('theme-preview-on');
141
+
142
+ renderDemoPreview(prevDemo);
143
+ });
144
+
145
+ /**
146
+ * Click handler for plugin installs in plugin install view.
147
+ *
148
+ * @since 4.6.0
149
+ *
150
+ * @param {Event} event Event interface.
151
+ */
152
+ jQuery(document).on('click', '.install-now', function (event) {
153
+ event.preventDefault();
154
+
155
+ var $button = jQuery( event.target ),
156
+ $document = jQuery(document);
157
+
158
+ if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
159
+ return;
160
+ }
161
+
162
+ if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
163
+ wp.updates.requestFilesystemCredentials( event );
164
+
165
+ $document.on( 'credential-modal-cancel', function() {
166
+ var $message = $( '.install-now.updating-message' );
167
+
168
+ $message
169
+ .removeClass( 'updating-message' )
170
+ .text( wp.updates.l10n.installNow );
171
+
172
+ wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
173
+ } );
174
+ }
175
+
176
+ wp.updates.installPlugin( {
177
+ slug: $button.data( 'slug' )
178
+ } );
179
+
180
+ } );
181
+
182
+ jQuery(document).on( 'wp-plugin-install-error', function( event, response ) {
183
+
184
+ var $message = jQuery( '.plugin-card-' + response.slug ).find( '.install-now' );
185
+
186
+ $message.removeClass( 'button-disabled' )
187
+ .addClass( 'button-primary' )
188
+ .html( wp.updates.l10n.installNow );
189
+
190
+ });
191
+
192
+ jQuery(document).on( 'wp-plugin-install-success', function( event, response ) {
193
+ event.preventDefault();
194
+
195
+ var $message = jQuery( '.plugin-card-' + response.slug ).find( '.install-now' );
196
+
197
+ // Transform the 'Install' button into an 'Activate' button.
198
+ var $init = $message.data('init');
199
+
200
+ $message.removeClass( 'install-now installed button-disabled updated-message' )
201
+ .addClass('updating-message')
202
+ .html( astraDemo.strings.btnActivating );
203
+
204
+ // WordPress adds "Activate" button after waiting for 1000ms. So we will run our activation after that.
205
+ setTimeout( function() {
206
+
207
+ jQuery.ajax({
208
+ url: astraDemo.ajaxurl,
209
+ type: 'POST',
210
+ dataType: 'json',
211
+ data: {
212
+ 'action' : 'astra-required-plugin-activate',
213
+ 'init' : $init
214
+ },
215
+ })
216
+ .done(function (result) {
217
+
218
+ if( result.success ) {
219
+ $message.removeClass( 'button-primary activate-now updating-message' )
220
+ .attr('disabled', 'disabled')
221
+ .addClass('disabled')
222
+ .text( astraDemo.strings.btnActive );
223
+
224
+ // Enable Demo Import Button
225
+ astraDemo.requiredPluginsCount--;
226
+ enable_demo_import_button();
227
+ }
228
+ });
229
+
230
+ }, 1000 );
231
+
232
+ });
233
+
234
+
235
+ /**
236
+ * Click handler for plugin installs in plugin install view.
237
+ *
238
+ * @since 4.6.0
239
+ *
240
+ * @param {Event} event Event interface.
241
+ */
242
+ jQuery(document).on('click', '.activate-now', function (event) {
243
+ event.preventDefault();
244
+
245
+ var $button = jQuery( event.target ),
246
+ $init = $button.data( 'init' );
247
+
248
+ if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
249
+ return;
250
+ }
251
+
252
+ $button.addClass( 'updating-message' )
253
+ .html( astraDemo.strings.btnActivating );
254
+
255
+ jQuery.ajax({
256
+ url: astraDemo.ajaxurl,
257
+ type: 'POST',
258
+ dataType: 'json',
259
+ data: {
260
+ 'action' : 'astra-required-plugin-activate',
261
+ 'init' : $init
262
+ },
263
+ })
264
+ .done(function (result) {
265
+
266
+ if( result.success ) {
267
+ $button.removeClass( 'button-primary activate-now updating-message' )
268
+ .attr('disabled', 'disabled')
269
+ .addClass('disabled')
270
+ .text( astraDemo.strings.btnActive );
271
+
272
+ // Enable Demo Import Button
273
+ astraDemo.requiredPluginsCount--;
274
+ enable_demo_import_button();
275
+ }
276
+
277
+ })
278
+ .fail(function () {
279
+ });
280
+
281
+ } );
282
+
283
+ function renderDemoPreview(anchor) {
284
+
285
+ var demoId = anchor.data('id') || '',
286
+ apiURL = anchor.data('demo-api') || '',
287
+ demoType = anchor.data('demo-type') || '',
288
+ demoURL = anchor.data('demo-url') || '',
289
+ screenshot = anchor.data('screenshot') || '',
290
+ demo_name = anchor.data('demo-name') || '',
291
+ demo_slug = anchor.data('demo-slug') || '',
292
+ content = anchor.data('content') || '',
293
+ requiredPlugins = anchor.data('required-plugins') || '';
294
+
295
+ var template = wp.template('astra-demo-preview');
296
+
297
+ templateData = [{
298
+ id : demoId,
299
+ astra_demo_type : demoType,
300
+ astra_demo_url : demoURL,
301
+ demo_api : apiURL,
302
+ screenshot : screenshot,
303
+ demo_name : demo_name,
304
+ slug : demo_slug,
305
+ content : content,
306
+ requiredPlugins : requiredPlugins
307
+ }];
308
+
309
+ // Initial set count.
310
+ astraDemo.requiredPluginsCount = requiredPlugins.length || 0;
311
+
312
+ // delete any earlier fullscreen preview before we render new one.
313
+ jQuery('.theme-install-overlay').remove();
314
+
315
+ jQuery('#ast-menu-page').append(template(templateData[0]));
316
+ jQuery('.theme-install-overlay').css('display', 'block');
317
+ checkNextPrevButtons();
318
+
319
+ var desc = jQuery('.theme-details');
320
+ var descHeight = parseInt( desc.outerHeight() );
321
+ var descBtn = jQuery('.theme-details-read-more');
322
+
323
+ if( 'free' === demoType && descHeight >= 55 ) {
324
+
325
+ // Show button.
326
+ descBtn.css( 'display', 'inline-block' );
327
+
328
+ // Set height upto 3 line.
329
+ desc.css( 'height', 57 );
330
+
331
+ // Button Click.
332
+ descBtn.click(function(event) {
333
+
334
+ if( descBtn.hasClass('open') ) {
335
+ desc.animate({ height: 57 },
336
+ 300, function() {
337
+ descBtn.removeClass('open');
338
+ descBtn.html( astraDemo.strings.DescExpand );
339
+ });
340
+ } else {
341
+ desc.animate({ height: descHeight },
342
+ 300, function() {
343
+ descBtn.addClass('open');
344
+ descBtn.html( astraDemo.strings.DescCollapse );
345
+ });
346
+ }
347
+
348
+ });
349
+ }
350
+
351
+ if( 'free' === demoType ) {
352
+
353
+ // or
354
+ var $pluginsFilter = jQuery( '#plugin-filter' ),
355
+ data = {
356
+ _ajax_nonce : astraDemo._ajax_nonce,
357
+ required_plugins : requiredPlugins
358
+ };
359
+
360
+ jQuery('.required-plugins').addClass('loading').html('<span class="spinner is-active"></span>');
361
+
362
+ wp.ajax.post( 'astra-required-plugins', data ).done( function( response ) {
363
+
364
+ // Remove loader.
365
+ jQuery('.required-plugins').removeClass('loading').html('');
366
+
367
+ /**
368
+ * Count remaining plugins.
369
+ * @type number
370
+ */
371
+ var remaining_plugins = 0;
372
+
373
+ /**
374
+ * Not Installed
375
+ *
376
+ * List of not installed required plugins.
377
+ */
378
+ if ( typeof response.notinstalled !== 'undefined' ) {
379
+
380
+ // Add not have installed plugins count.
381
+ remaining_plugins += parseInt( response.notinstalled.length );
382
+
383
+ jQuery( response.notinstalled ).each(function( index, plugin ) {
384
+
385
+ var output = '<div class="plugin-card ';
386
+ output += ' plugin-card-'+plugin.slug+'"';
387
+ output += ' data-slug="'+plugin.slug+'">';
388
+ output += ' <span class="title">'+plugin.name+'</span>';
389
+ output += ' <button class="button install-now"';
390
+ output += ' data-init="' + plugin.init + '"';
391
+ output += ' data-slug="' + plugin.slug + '"';
392
+ output += ' data-name="' + plugin.name + '">';
393
+ output += wp.updates.l10n.installNow;
394
+ output += ' </button>';
395
+ output += '</div>';
396
+
397
+ jQuery('.required-plugins').append(output);
398
+
399
+ });
400
+ }
401
+
402
+ /**
403
+ * Inactive
404
+ *
405
+ * List of not inactive required plugins.
406
+ */
407
+ if ( typeof response.inactive !== 'undefined' ) {
408
+
409
+ // Add inactive plugins count.
410
+ remaining_plugins += parseInt( response.inactive.length );
411
+
412
+ jQuery( response.inactive ).each(function( index, plugin ) {
413
+
414
+ var output = '<div class="plugin-card ';
415
+ output += ' plugin-card-'+plugin.slug+'"';
416
+ output += ' data-slug="'+plugin.slug+'">';
417
+ output += ' <span class="title">'+plugin.name+'</span>';
418
+
419
+ output += ' <button class="button activate-now button-primary"';
420
+ output += ' data-init="' + plugin.init + '">';
421
+ output += wp.updates.l10n.activatePlugin;
422
+ output += ' </button>';
423
+ output += '</div>';
424
+
425
+ jQuery('.required-plugins').append(output);
426
+
427
+ });
428
+ }
429
+
430
+ /**
431
+ * Active
432
+ *
433
+ * List of not active required plugins.
434
+ */
435
+ if ( typeof response.active !== 'undefined' ) {
436
+
437
+ jQuery( response.active ).each(function( index, plugin ) {
438
+
439
+ var output = '<div class="plugin-card ';
440
+ output += ' plugin-card-'+plugin.slug+'"';
441
+ output += ' data-slug="'+plugin.slug+'">';
442
+ output += ' <span class="title">'+plugin.name+'</span>';
443
+ output += ' <button class="button disabled"';
444
+ output += ' data-slug="' + plugin.slug + '"';
445
+ output += ' data-name="' + plugin.name + '">';
446
+ output += astraDemo.strings.btnActive;
447
+ output += ' </button>';
448
+ output += '</div>';
449
+
450
+ jQuery('.required-plugins').append(output);
451
+
452
+ });
453
+ }
454
+
455
+ /**
456
+ * Enable Demo Import Button
457
+ * @type number
458
+ */
459
+ astraDemo.requiredPluginsCount = remaining_plugins;
460
+ enable_demo_import_button();
461
+
462
+ } );
463
+
464
+ } else {
465
+
466
+ // Enable Demo Import Button
467
+ enable_demo_import_button( demoType );
468
+ jQuery('.required-plugins-wrap').remove();
469
+ }
470
+
471
+ return;
472
+ }
473
+
474
+ function checkNextPrevButtons() {
475
+ currentDemo = jQuery('.theme-preview-on');
476
+ nextDemo = currentDemo.nextAll('.theme').length;
477
+ prevDemo = currentDemo.prevAll('.theme').length;
478
+
479
+ if (nextDemo == 0) {
480
+ jQuery('.next-theme').addClass('disabled');
481
+ } else if (nextDemo != 0) {
482
+ jQuery('.next-theme').removeClass('disabled');
483
+ }
484
+
485
+ if (prevDemo == 0) {
486
+ jQuery('.previous-theme').addClass('disabled');
487
+ } else if (prevDemo != 0) {
488
+ jQuery('.previous-theme').removeClass('disabled');
489
+ }
490
+
491
+ return;
492
+ }
493
+
494
+ jQuery(document).on('click', '.filter-links li a', function (event) {
495
+ event.preventDefault();
496
+
497
+ $this = jQuery(this);
498
+ $this.parent('li').siblings().find('.current').removeClass('current');
499
+ $this.addClass('current');
500
+ slug = $this.data('sort');
501
+ id = $this.data('id');
502
+
503
+ resetPagedCount();
504
+ paged = parseInt(jQuery('body').attr('data-astra-demo-paged'));
505
+
506
+ if (slug == 'all') {
507
+ category = 'all';
508
+ } else {
509
+ category = slug;
510
+ }
511
+
512
+ jQuery('body').addClass('loading-content');
513
+ jQuery('.theme-browser .theme').remove();
514
+ jQuery('.no-themes').remove();
515
+ jQuery('#wp-filter-search-input').val('');
516
+
517
+ jQuery.ajax({
518
+ url: astraDemo.ajaxurl,
519
+ type: 'POST',
520
+ dataType: 'json',
521
+ data: {
522
+ action: 'astra-list-sites',
523
+ category: category,
524
+ id: id,
525
+ paged: paged,
526
+ },
527
+ })
528
+ .done(function (demos) {
529
+ jQuery('body').removeClass('loading-content');
530
+ renderDemoGrid(demos);
531
+ })
532
+ .fail(function () {
533
+ jQuery('body').removeClass('loading-content');
534
+ jQuery('.spinner').after('<p class="no-themes" style="display:block;">There was a problem receiving a response from server.</p>');
535
+ });
536
+
537
+ });
538
+
539
+ var ref;
540
+ jQuery(document).on('keyup input', '#wp-filter-search-input', function () {
541
+ $this = jQuery('#wp-filter-search-input').val();
542
+
543
+ id = '';
544
+ if ($this.length < 2) {
545
+ id = 'all';
546
+ }
547
+
548
+ window.clearTimeout(ref);
549
+ ref = window.setTimeout(function () {
550
+ ref = null;
551
+
552
+ resetPagedCount();
553
+ jQuery('body').addClass('loading-content');
554
+ jQuery('.theme-browser .theme').remove();
555
+ jQuery('.no-themes').remove();
556
+ jQuery('body').attr('data-astra-demo-search', $this);
557
+
558
+ jQuery.ajax({
559
+ url: astraDemo.ajaxurl,
560
+ type: 'POST',
561
+ dataType: 'json',
562
+ data: {
563
+ action: 'astra-list-sites',
564
+ search: $this,
565
+ id: id,
566
+ },
567
+ })
568
+ .done(function (demos) {
569
+ jQuery('.filter-links li a[data-id="all"]').addClass('current');
570
+ jQuery('.filter-links li a[data-id="all"]').parent('li').siblings().find('.current').removeClass('current');
571
+ jQuery('body').removeClass('loading-content');
572
+
573
+ if (demos.length > 0) {
574
+ renderDemoGrid(demos);
575
+ } else {
576
+ jQuery('.spinner').after('<p class="no-themes" style="display:block;">'+astraDemo.strings.searchNoFound+'</p>');
577
+ }
578
+
579
+ })
580
+ .fail(function () {
581
+ jQuery('body').removeClass('loading-content');
582
+ jQuery('.spinner').after('<p class="no-themes" style="display:block;">'+astraDemo.strings.responseError+'.</p>');
583
+ });
584
+
585
+ }, 500);
586
+
587
+ });
588
+
589
+ function renderDemoGrid(demos) {
590
+ jQuery.each(demos, function (index, demo) {
591
+
592
+ id = demo.id;
593
+ content = demo.content;
594
+ demo_api = demo.demo_api;
595
+ demo_name = demo.title;
596
+ demo_slug = demo.slug;
597
+ screenshot = demo.featured_image_url;
598
+ astra_demo_url = demo.astra_demo_url;
599
+ astra_demo_type = demo.astra_demo_type;
600
+ requiredPlugins = demo.required_plugins;
601
+
602
+ templateData = [{
603
+ id: id,
604
+ astra_demo_type: astra_demo_type,
605
+ astra_demo_url: astra_demo_url,
606
+ demo_api: demo_api,
607
+ screenshot: screenshot,
608
+ demo_name: demo_name,
609
+ slug: demo_slug,
610
+ content: content,
611
+ required_plugins: requiredPlugins
612
+ }]
613
+
614
+ var template = wp.template('astra-single-demo');
615
+ jQuery('.themes').append(template(templateData[0]));
616
+ });
617
+ }
618
+
619
+ jQuery(document).on('click', '.collapse-sidebar', function (event) {
620
+ event.preventDefault();
621
+
622
+ overlay = jQuery('.wp-full-overlay');
623
+
624
+ if (overlay.hasClass('expanded')) {
625
+ overlay.removeClass('expanded');
626
+ overlay.addClass('collapsed');
627
+ return;
628
+ }
629
+
630
+ if (overlay.hasClass('collapsed')) {
631
+ overlay.removeClass('collapsed');
632
+ overlay.addClass('expanded');
633
+ return;
634
+ }
635
+ });
636
+
637
+ jQuery(document).on('click', '.astra-demo-import', function (event) {
638
+ event.preventDefault();
639
+
640
+ var $this = jQuery(this),
641
+ disabled = $this.attr('data-import');
642
+
643
+ if ( typeof disabled !== 'undefined' && disabled === 'disabled' ) {
644
+
645
+ // Highlight required plugins list.
646
+ var pluginTitle = jQuery('.required-plugins-wrap h4');
647
+ pluginTitle.css({'background-color':'rgba(255, 235, 59, 0.20)'});
648
+ setTimeout(function() {
649
+ pluginTitle.css({'background-color':''});
650
+ }, 1000);
651
+
652
+ return;
653
+ }
654
+
655
+ // Proceed?
656
+ if( ! confirm( astraDemo.strings.importWarning ) ) {
657
+ return;
658
+ }
659
+
660
+ jQuery('.astra-demo-import').attr('data-import', 'disabled')
661
+ .addClass('updating-message installing')
662
+ .text('Importing Demo');
663
+
664
+ $this.closest('.theme').focus();
665
+
666
+ var $theme = $this.closest('.astra-sites-preview').find('.wp-full-overlay-header');
667
+
668
+ var apiURL = $theme.data('demo-api') || '';
669
+
670
+ jQuery.ajax({
671
+ url: astraDemo.ajaxurl,
672
+ type: 'POST',
673
+ dataType: 'json',
674
+ data: {
675
+ action: 'astra-import-demo',
676
+ api_url: apiURL
677
+ },
678
+ })
679
+ .done(function ( demos ) {
680
+
681
+ jQuery('.astra-demo-import').removeClass('updating-message installing')
682
+ .removeAttr('data-import')
683
+ .addClass('view-site')
684
+ .removeClass('astra-demo-import')
685
+ .text( astraDemo.strings.viewSite )
686
+ .attr('target', '_blank')
687
+ .append('<i class="dashicons dashicons-external"></i>')
688
+ .attr('href', astraDemo.siteURL );
689
+ })
690
+ .fail(function ( demos ) {
691
+ jQuery('.astra-demo-import').removeClass('updating-message installing').text('Error.');
692
+ });
693
+
694
+ });
astra-sites.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Plugin Name: Astra Sites
4
+ * Plugin URI: http://www.wpastra.com/pro/
5
+ * Description: Import sites build with Astra theme.
6
+ * Version: 1.0.0
7
+ * Author: Brainstorm Force
8
+ * Author URI: http://www.brainstormforce.com
9
+ * Text Domain: astra-sites
10
+ *
11
+ * @package Astra Sites
12
+ */
13
+
14
+ /**
15
+ * Set constants.
16
+ */
17
+ define( 'ASTRA_SITES_VER', '1.0.0' );
18
+ define( 'ASTRA_SITES_FILE', __FILE__ );
19
+ define( 'ASTRA_SITES_BASE', plugin_basename( ASTRA_SITES_FILE ) );
20
+ define( 'ASTRA_SITES_DIR', plugin_dir_path( ASTRA_SITES_FILE ) );
21
+ define( 'ASTRA_SITES_URI', plugins_url( '/', ASTRA_SITES_FILE ) );
22
+
23
+ require_once ASTRA_SITES_DIR . 'classes/class-astra-sites.php';
classes/class-astra-sites.php ADDED
@@ -0,0 +1,631 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Astra Sites
4
+ *
5
+ * @since 1.0.0
6
+ * @package Astra Sites
7
+ */
8
+
9
+ defined( 'ABSPATH' ) or exit;
10
+
11
+ if ( ! class_exists( 'Astra_Sites' ) ) :
12
+
13
+ /**
14
+ * Astra_Sites
15
+ */
16
+ class Astra_Sites {
17
+
18
+ /**
19
+ * API URL which is used to get the response from.
20
+ *
21
+ * @since 1.0.0
22
+ * @var (String) URL
23
+ */
24
+ public static $api_url;
25
+
26
+ /**
27
+ * Instance of Astra_Sites
28
+ *
29
+ * @since 1.0.0
30
+ * @var (Object) Astra_Sites
31
+ */
32
+ private static $_instance = null;
33
+
34
+ /**
35
+ * Instance of Astra_Sites.
36
+ *
37
+ * @since 1.0.0
38
+ *
39
+ * @return object Class object.
40
+ */
41
+ public static function set_instance() {
42
+ if ( ! isset( self::$_instance ) ) {
43
+ self::$_instance = new self;
44
+ }
45
+
46
+ return self::$_instance;
47
+ }
48
+
49
+ /**
50
+ * Constructor.
51
+ *
52
+ * @since 1.0.0
53
+ */
54
+ private function __construct() {
55
+
56
+ add_action( 'admin_notices', array( $this, 'admin_notices' ) );
57
+
58
+ self::set_api_url();
59
+
60
+ $this->includes();
61
+
62
+ add_action( 'wp_enqueue_scripts', array( $this, 'admin_enqueue' ) );
63
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue' ) );
64
+ add_action( 'wp_ajax_astra-import-demo', array( $this, 'demo_ajax_import' ) );
65
+ add_action( 'wp_ajax_astra-list-sites', array( $this, 'list_demos' ) );
66
+ add_action( 'wp_ajax_astra-required-plugins', array( $this, 'required_plugin' ) );
67
+ add_action( 'wp_ajax_astra-required-plugin-activate', array( $this, 'required_plugin_activate' ) );
68
+ }
69
+
70
+ /**
71
+ * Admin Notices
72
+ *
73
+ * @since 1.0.0
74
+ * @return void
75
+ */
76
+ function admin_notices() {
77
+
78
+ if ( ! defined( 'ASTRA_THEME_SETTINGS' ) ) {
79
+ ?>
80
+ <div class="notice notice-error ast-active-notice is-dismissible">
81
+ <p>
82
+ <?php
83
+ printf(
84
+ /* translators: 1: theme.php file*/
85
+ __( 'Astra Theme needs to be active for you to use currently installed "Astra Sites" plugin. <a href="%1$s">Install & Activate Now</a>', 'astra-sites' ),
86
+ esc_url( admin_url( 'themes.php?theme=astra' ) )
87
+ );
88
+ ?>
89
+ </p>
90
+ </div>
91
+ <?php
92
+ return;
93
+ }
94
+
95
+ add_action( 'plugin_action_links_' . ASTRA_SITES_BASE, array( $this, 'action_links' ) );
96
+ }
97
+
98
+ /**
99
+ * Show action links on the plugin screen.
100
+ *
101
+ * @param mixed $links Plugin Action links.
102
+ * @return array
103
+ */
104
+ function action_links( $links ) {
105
+ $action_links = array(
106
+ 'settings' => '<a href="' . admin_url( 'themes.php?page=astra&action=astra-sites' ) . '" aria-label="' . esc_attr__( 'See Library', 'astra-sites' ) . '">' . esc_html__( 'See Library', 'astra-sites' ) . '</a>',
107
+ );
108
+
109
+ return array_merge( $action_links, $links );
110
+ }
111
+
112
+ /**
113
+ * Setter for $api_url
114
+ *
115
+ * @since 1.0.0
116
+ */
117
+ public static function set_api_url() {
118
+
119
+ self::$api_url = apply_filters( 'astra_demo_api_url', 'https://sites.wpastra.com/wp-json/wp/v2/' );
120
+
121
+ }
122
+
123
+ /**
124
+ * Returns the API URL that depending based on the category, search term and pagination.
125
+ *
126
+ * @since 1.0.0
127
+ *
128
+ * @param object $args Arguments for selecting correct list of demos.
129
+ * args->id = ID of the demo.
130
+ * $args->search = Search term used in the demo.
131
+ * @param string $page Page number for pagination.
132
+ *
133
+ * @return string URL that can be queried to return the demos.
134
+ */
135
+ public static function get_api_url( $args, $page = '1' ) {
136
+
137
+ $request_params = array(
138
+ 'page' => $page,
139
+ 'per_page' => '15',
140
+
141
+ // Use this for premium demos.
142
+ 'purchase_key' => '',
143
+ 'site_url' => '',
144
+ );
145
+
146
+ $args_search = isset( $args->search ) ? $args->search : '';
147
+ $args_id = isset( $args->id ) ? $args->id : '';
148
+
149
+ // Not Search?
150
+ if ( '' !== $args_search ) {
151
+ $request_params['search'] = $args_search;
152
+
153
+ // Not All?
154
+ } elseif ( 'all' != $args_id ) {
155
+ $request_params['astra-site-category'] = $args_id;
156
+ }
157
+
158
+ return add_query_arg( $request_params, self::$api_url . 'astra-sites' );
159
+ }
160
+
161
+ /**
162
+ * Returns the API URL for searching demos basedon taxanomies.
163
+ *
164
+ * @since 1.0.0
165
+ * @return (String) URL that can be queried to return the demos.
166
+ */
167
+ public static function get_taxanomy_api_url() {
168
+ return self::$api_url . 'astra-site-category/';
169
+ }
170
+
171
+ /**
172
+ * Enqueue admin scripts.
173
+ *
174
+ * @since 1.0.0
175
+ */
176
+ public function admin_enqueue() {
177
+
178
+ wp_register_script(
179
+ 'astra-sites-admin', ASTRA_SITES_URI . 'assets/js/admin.js', array(
180
+ 'jquery',
181
+ 'wp-util',
182
+ 'updates',
183
+ ), ASTRA_SITES_VER, true
184
+ );
185
+
186
+ wp_register_style( 'astra-sites-admin', ASTRA_SITES_URI . 'assets/css/admin.css', ASTRA_SITES_VER, true );
187
+
188
+ wp_localize_script(
189
+ 'astra-sites-admin', 'astraDemo', array(
190
+ 'ajaxurl' => esc_url( admin_url( 'admin-ajax.php' ) ),
191
+ 'siteURL' => site_url(),
192
+ 'getProText' => __( 'Purchase', 'astra-sites' ),
193
+ 'getProURL' => esc_url( 'https://wpastra.com/pro/?utm_source=demo-import-panel&utm_campaign=astra-sites&utm_medium=' ),
194
+ '_ajax_nonce' => wp_create_nonce( 'astra-sites' ),
195
+ 'requiredPluginsCount' => 0,
196
+ 'strings' => array(
197
+ 'viewSite' => __( 'Done! View Site', 'astra-sites' ),
198
+ 'btnActivating' => __( 'Activating', 'astra-sites' ) . '&hellip;',
199
+ 'btnActive' => __( 'Active', 'astra-sites' ),
200
+ 'importDemo' => __( 'Import This Site', 'astra-sites' ),
201
+ 'DescExpand' => __( 'Read more', 'astra-sites' ) . '&hellip;',
202
+ 'DescCollapse' => __( 'Hide', 'astra-sites' ),
203
+ 'responseError' => __( 'There was a problem receiving a response from server.', 'astra-sites' ),
204
+ 'searchNoFound' => __( 'No Demos found, Try a different search.', 'astra-sites' ),
205
+ 'importWarning' => __( "Executing Demo Import will make your site similar as ours. Please bear in mind -\n\n1. It is recommended to run import on a fresh WordPress installation.\n\n2. Importing site does not delete any pages or posts. However, it can overwrite your existing content.\n\n3. Copyrighted media will not be imported. Instead it will be replaced with placeholders.", 'astra-sites' ),
206
+ ),
207
+ )
208
+ );
209
+
210
+ }
211
+
212
+ /**
213
+ * Load all the required files in the importer.
214
+ *
215
+ * @since 1.0.0
216
+ */
217
+ private function includes() {
218
+
219
+ require_once ASTRA_SITES_DIR . 'admin/class-astra-sites-admin.php';
220
+
221
+ // Load the Importers.
222
+ require_once ASTRA_SITES_DIR . 'importers/class-astra-sites-helper.php';
223
+ require_once ASTRA_SITES_DIR . 'importers/class-widgets-importer.php';
224
+ require_once ASTRA_SITES_DIR . 'importers/class-astra-customizer-import.php';
225
+ require_once ASTRA_SITES_DIR . 'importers/wxr-importer/class-astra-wxr-importer.php';
226
+ require_once ASTRA_SITES_DIR . 'importers/class-astra-site-options-import.php';
227
+ }
228
+
229
+ /**
230
+ * Required Plugin Activate
231
+ *
232
+ * @since 1.0.0
233
+ */
234
+ public function required_plugin_activate() {
235
+
236
+ if ( ! current_user_can( 'install_plugins' ) || ! isset( $_POST['init'] ) || ! $_POST['init'] ) {
237
+ wp_send_json_error(
238
+ array(
239
+ 'success' => false,
240
+ 'message' => __( 'No plugin specified', 'astra-sites' ),
241
+ )
242
+ );
243
+ }
244
+
245
+ $plugin_init = esc_attr( $_POST['init'] );
246
+
247
+ $activate = activate_plugin( $plugin_init, '', false, true );
248
+
249
+ if ( is_wp_error( $activate ) ) {
250
+ wp_send_json_error(
251
+ array(
252
+ 'success' => false,
253
+ 'message' => $activate->get_error_message(),
254
+ )
255
+ );
256
+ }
257
+
258
+ wp_send_json_success(
259
+ array(
260
+ 'success' => true,
261
+ 'message' => __( 'Plugin Successfully Activated', 'astra-sites' ),
262
+ )
263
+ );
264
+
265
+ }
266
+
267
+ /**
268
+ * Required Plugin
269
+ *
270
+ * @since 1.0.0
271
+ * @return void
272
+ */
273
+ public function required_plugin() {
274
+
275
+ // Verify Nonce.
276
+ check_ajax_referer( 'astra-sites', '_ajax_nonce' );
277
+
278
+ $response = array(
279
+ 'active' => array(),
280
+ 'inactive' => array(),
281
+ 'notinstalled' => array(),
282
+ );
283
+
284
+ if ( ! current_user_can( 'customize' ) ) {
285
+ wp_send_json_error( $response );
286
+ }
287
+
288
+ $required_plugins = ( isset( $_POST['required_plugins'] ) ) ? $_POST['required_plugins'] : array();
289
+
290
+ if ( count( $required_plugins ) > 0 ) {
291
+ foreach ( $required_plugins as $key => $plugin ) {
292
+
293
+ // Inactive plugins.
294
+ if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin['init'] ) && is_plugin_inactive( $plugin['init'] ) ) {
295
+ $response['inactive'][] = $plugin;
296
+
297
+ // Not Installed plugins.
298
+ } elseif ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin['init'] ) ) {
299
+ $response['notinstalled'][] = $plugin;
300
+
301
+ // Active plugins.
302
+ } else {
303
+ $response['active'][] = $plugin;
304
+ }
305
+ }
306
+ }
307
+
308
+ // Send response.
309
+ wp_send_json_success( $response );
310
+ }
311
+
312
+ /**
313
+ * Ajax callback for demo import action.
314
+ *
315
+ * @since 1.0.0
316
+ */
317
+ public function demo_ajax_import() {
318
+
319
+ if ( ! current_user_can( 'customize' ) ) {
320
+ return;
321
+ }
322
+
323
+ $demo_api_uri = isset( $_POST['api_url'] ) ? esc_url( $_POST['api_url'] ) : '';
324
+ $this->import_demo( $demo_api_uri );
325
+
326
+ wp_die();
327
+ }
328
+
329
+ /**
330
+ * Ajax handler for retreiving the list of demos.
331
+ *
332
+ * @since 1.0.0
333
+ * @return (JSON) Json response retreived from the API.
334
+ */
335
+ public function list_demos() {
336
+
337
+ if ( ! current_user_can( 'customize' ) ) {
338
+ return;
339
+ }
340
+
341
+ $args = new stdClass();
342
+ $args->category = isset( $_POST['category'] ) ? esc_attr( $_POST['category'] ) : '';
343
+ $args->id = isset( $_POST['id'] ) ? esc_attr( $_POST['id'] ) : '';
344
+ $args->search = isset( $_POST['search'] ) ? esc_attr( $_POST['search'] ) : '';
345
+ $paged = isset( $_POST['paged'] ) ? esc_attr( $_POST['paged'] ) : '1';
346
+
347
+ return wp_send_json( self::get_astra_demos( $args, $paged ) );
348
+ }
349
+
350
+ /**
351
+ * Get the list of demos.
352
+ *
353
+ * @since 1.0.0
354
+ * @see admin/view-astra-sites.php
355
+ * @return (Array) Demos.
356
+ */
357
+ public static function get_astra_all_demos() {
358
+ $args = new stdClass();
359
+ $args->id = 'all';
360
+
361
+ return self::get_astra_demos( $args );
362
+ }
363
+
364
+ /**
365
+ * Import the demo.
366
+ *
367
+ * @since 1.0.0
368
+ *
369
+ * @param (String) $demo_api_uri API URL for the single demo.
370
+ */
371
+ public function import_demo( $demo_api_uri ) {
372
+
373
+ $demo_data = self::get_astra_single_demo( $demo_api_uri );
374
+
375
+ // Import Enabled Extensions.
376
+ $this->import_astra_enabled_extension( $demo_data['astra-enabled-extensions'] );
377
+
378
+ // Import Widgets data.
379
+ $this->import_widgets( $demo_data['astra-site-widgets-data'] );
380
+
381
+ // Import Customizer Settings.
382
+ $this->import_customizer_settings( $demo_data['astra-site-customizer-data'] );
383
+
384
+ // Import XML.
385
+ $this->import_wxr( $demo_data['astra-site-wxr-path'] );
386
+
387
+ // Import WordPress site options.
388
+ $this->import_site_options( $demo_data['astra-site-options-data'] );
389
+
390
+ // Import Custom 404 extension options.
391
+ $this->import_custom_404_extension_options( $demo_data['astra-custom-404'] );
392
+ }
393
+
394
+ /**
395
+ * Import widgets and assign to correct sidebars.
396
+ *
397
+ * @since 1.0.0
398
+ *
399
+ * @param (Object) $data Widgets data.
400
+ */
401
+ private function import_widgets( $data ) {
402
+
403
+ // bail if wiegets data is not available.
404
+ if ( null == $data ) {
405
+ return;
406
+ }
407
+
408
+ $widgets_importer = Astra_Widget_Importer::instance();
409
+ $widgets_importer->import_widgets_data( $data );
410
+ }
411
+
412
+ /**
413
+ * Import Customizer data.
414
+ *
415
+ * @since 1.0.0
416
+ *
417
+ * @param (Array) $customizer_data Customizer data for the demo to be imported.
418
+ */
419
+ private function import_customizer_settings( $customizer_data ) {
420
+ $customizer_import = Astra_Customizer_Import::instance();
421
+ $customizer_data = $customizer_import->import( $customizer_data );
422
+ }
423
+
424
+ /**
425
+ * Download and import the XML from the demo.
426
+ *
427
+ * @since 1.0.0
428
+ *
429
+ * @param (String) $wxr_url URL of the xml export of the demo to be imported.
430
+ */
431
+ private function import_wxr( $wxr_url ) {
432
+ $wxr_importer = Astra_WXR_Importer::instance();
433
+ $xml_path = $wxr_importer->download_xml( $wxr_url );
434
+ $wxr_importer->import_xml( $xml_path['file'] );
435
+ }
436
+
437
+ /**
438
+ * Import site options - Front Page, Menus, Blog page etc.
439
+ *
440
+ * @since 1.0.0
441
+ *
442
+ * @param (Array) $options Array of required site options from the demo.
443
+ */
444
+ private function import_site_options( $options ) {
445
+ $options_importer = Astra_Site_Options_Import::instance();
446
+ $options_importer->import_options( $options );
447
+ }
448
+
449
+ /**
450
+ * Import settings enabled astra extensions from the demo.
451
+ *
452
+ * @since 1.0.0
453
+ *
454
+ * @param (Array) $saved_extensions Array of enabled extensions.
455
+ */
456
+ private function import_astra_enabled_extension( $saved_extensions ) {
457
+ if ( is_callable( 'AST_Admin_Helper::update_admin_settings_option' ) ) {
458
+ AST_Admin_Helper::update_admin_settings_option( '_astra_ext_enabled_extensions', $saved_extensions );
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Import custom 404 section.
464
+ *
465
+ * @since 1.0.0
466
+ *
467
+ * @param (Array) $options_404 404 Extensions settings from the demo.
468
+ */
469
+ private function import_custom_404_extension_options( $options_404 ) {
470
+ if ( is_callable( 'AST_Admin_Helper::update_admin_settings_option' ) ) {
471
+ AST_Admin_Helper::update_admin_settings_option( '_astra_ext_custom_404', $options_404 );
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Get single demo.
477
+ *
478
+ * @since 1.0.0
479
+ *
480
+ * @param (String) $demo_api_uri API URL of a demo.
481
+ *
482
+ * @return (Array) $astra_demo_data demo data for the demo.
483
+ */
484
+ public static function get_astra_single_demo( $demo_api_uri ) {
485
+
486
+ // default values.
487
+ $remote_args = array();
488
+ $defaults = array(
489
+ 'id' => '',
490
+ 'astra-site-widgets-data' => '',
491
+ 'astra-site-customizer-data' => '',
492
+ 'astra-site-options-data' => '',
493
+ 'astra-site-wxr-path' => '',
494
+ 'astra-enabled-extensions' => '',
495
+ 'astra-custom-404' => '',
496
+ 'required-plugins' => '',
497
+ );
498
+
499
+ $api_args = array(
500
+ 'timeout' => 15,
501
+ );
502
+
503
+ $response = wp_remote_get( $demo_api_uri, $api_args );
504
+
505
+ if ( ! is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) === 200 ) {
506
+ $result = json_decode( wp_remote_retrieve_body( $response ), true );
507
+ $remote_args['id'] = $result['id'];
508
+ $remote_args['astra-site-widgets-data'] = json_decode( $result['astra-site-widgets-data'] );
509
+ $remote_args['astra-site-customizer-data'] = $result['astra-site-customizer-data'];
510
+ $remote_args['astra-site-options-data'] = $result['astra-site-options-data'];
511
+ $remote_args['astra-site-wxr-path'] = $result['astra-site-wxr-path'];
512
+ $remote_args['astra-enabled-extensions'] = $result['astra-enabled-extensions'];
513
+ $remote_args['astra-custom-404'] = $result['astra-custom-404'];
514
+ $remote_args['required-plugins'] = $result['required-plugins'];
515
+ }
516
+
517
+ // Merge remote demo and defaults.
518
+ return wp_parse_args( $remote_args, $defaults );
519
+ }
520
+
521
+ /**
522
+ * Get astra demos.
523
+ *
524
+ * @since 1.0.0
525
+ *
526
+ * @param (Array) $args For selecting the demos (Search terms, pagination etc).
527
+ * @param (String) $paged Page number.
528
+ */
529
+ public static function get_astra_demos( $args, $paged = '1' ) {
530
+
531
+ $url = self::get_api_url( $args, $paged );
532
+
533
+ $astra_demos = array();
534
+
535
+ $api_args = array(
536
+ 'timeout' => 15,
537
+ );
538
+
539
+ $response = wp_remote_get( $url, $api_args );
540
+
541
+ if ( ! is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) === 200 ) {
542
+ $result = json_decode( wp_remote_retrieve_body( $response ), true );
543
+
544
+ // If is array then proceed
545
+ // Else skip it.
546
+ if ( is_array( $result ) ) {
547
+
548
+ foreach ( $result as $key => $demo ) {
549
+
550
+ if ( ! isset( $demo['id'] ) ) {
551
+ continue;
552
+ }
553
+
554
+ $astra_demos[ $key ]['id'] = isset( $demo['id'] ) ? esc_attr( $demo['id'] ) : '';
555
+ $astra_demos[ $key ]['slug'] = isset( $demo['slug'] ) ? esc_attr( $demo['slug'] ) : '';
556
+ $astra_demos[ $key ]['date'] = isset( $demo['date'] ) ? esc_attr( $demo['date'] ) : '';
557
+ $astra_demos[ $key ]['astra_demo_type'] = isset( $demo['astra-site-type'] ) ? sanitize_key( $demo['astra-site-type'] ) : '';
558
+ $astra_demos[ $key ]['astra_demo_url'] = isset( $demo['astra-site-url'] ) ? esc_url( $demo['astra-site-url'] ) : '';
559
+ $astra_demos[ $key ]['title'] = isset( $demo['title']['rendered'] ) ? esc_attr( $demo['title']['rendered'] ) : '';
560
+ $astra_demos[ $key ]['featured_image_url'] = isset( $demo['featured-image-url'] ) ? esc_url( $demo['featured-image-url'] ) : '';
561
+ $astra_demos[ $key ]['demo_api'] = isset( $demo['_links']['self'][0]['href'] ) ? esc_url( $demo['_links']['self'][0]['href'] ) : self::get_api_url( new stdClass() ) . $demo['id'];
562
+ $astra_demos[ $key ]['content'] = isset( $demo['content']['rendered'] ) ? strip_tags( $demo['content']['rendered'] ) : '';
563
+ $astra_demos[ $key ]['required_plugins'] = isset( $demo['required-plugins'] ) ? json_encode( $demo['required-plugins'] ) : '';
564
+ }
565
+
566
+ // Free up memory by unsetting variables that are not required.
567
+ unset( $result );
568
+ unset( $response );
569
+ }
570
+ }
571
+
572
+ return $astra_demos;
573
+
574
+ }
575
+
576
+ /**
577
+ * Get demo categories.
578
+ *
579
+ * @since 1.0.0
580
+ * @return (Array) Array of demo categories.
581
+ */
582
+ public static function get_demo_categories() {
583
+ $categories = array();
584
+
585
+ $api_args = array(
586
+ 'timeout' => 15,
587
+ );
588
+
589
+ $response = wp_remote_get( self::get_taxanomy_api_url(), $api_args );
590
+
591
+ if ( ! is_wp_error( $response ) || 200 === wp_remote_retrieve_response_code( $response ) ) {
592
+ $result = json_decode( wp_remote_retrieve_body( $response ), true );
593
+
594
+ if ( array_key_exists( 'code', $result ) && 'rest_no_route' === $result['code'] ) {
595
+ return $categories;
596
+ }
597
+
598
+ // If is array then proceed
599
+ // Else skip it.
600
+ if ( is_array( $result ) ) {
601
+
602
+ foreach ( $result as $key => $category ) {
603
+ if ( 0 == $category['count'] ) {
604
+ continue;
605
+ }
606
+ $categories[ $key ]['id'] = $category['id'];
607
+ $categories[ $key ]['name'] = $category['name'];
608
+ $categories[ $key ]['slug'] = $category['slug'];
609
+ $categories[ $key ]['count'] = $category['count'];
610
+ $categories[ $key ]['link-category'] = $category['_links']['self'][0]['href'];
611
+ }
612
+
613
+ // Free up memory by unsetting variables that are not required.
614
+ unset( $result );
615
+ unset( $response );
616
+
617
+ }
618
+ }
619
+
620
+ return $categories;
621
+ }
622
+
623
+ }
624
+
625
+ /**
626
+ * Kicking this off by calling 'set_instance()' method
627
+ */
628
+ Astra_Sites::set_instance();
629
+
630
+ endif;
631
+
importers/class-astra-customizer-import.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Customizer Data importer class.
4
+ *
5
+ * @since 1.0.0
6
+ * @package Astra Addon
7
+ */
8
+
9
+ defined( 'ABSPATH' ) or exit;
10
+
11
+ /**
12
+ * Customizer Data importer class.
13
+ *
14
+ * @since 1.0.0
15
+ */
16
+ class Astra_Customizer_Import {
17
+
18
+ /**
19
+ * Instance of Astra_Customizer_Import
20
+ *
21
+ * @since 1.0.0
22
+ * @var Astra_Customizer_Import
23
+ */
24
+ private static $_instance = null;
25
+
26
+ /**
27
+ * Instantiate Astra_Customizer_Import
28
+ *
29
+ * @since 1.0.0
30
+ * @return (Object) Astra_Customizer_Import
31
+ */
32
+ public static function instance() {
33
+
34
+ if ( ! isset( self::$_instance ) ) {
35
+ self::$_instance = new self;
36
+ }
37
+
38
+ return self::$_instance;
39
+ }
40
+
41
+ /**
42
+ * Import customizer options.
43
+ *
44
+ * @since 1.0.0
45
+ *
46
+ * @param (Array) $data customizer options from the demo.
47
+ */
48
+ public function import( $data ) {
49
+ update_option( 'astra-settings', $data );
50
+ }
51
+ }
importers/class-astra-site-options-import.php ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Customizer Site options importer class.
4
+ *
5
+ * @since 1.0.0
6
+ * @package Astra Addon
7
+ */
8
+
9
+ defined( 'ABSPATH' ) or exit;
10
+
11
+ /**
12
+ * Customizer Site options importer class.
13
+ *
14
+ * @since 1.0.0
15
+ */
16
+ class Astra_Site_Options_Import {
17
+
18
+ /**
19
+ * Instance of Astra_Site_Options_Importer
20
+ *
21
+ * @since 1.0.0
22
+ * @var (Object) Astra_Site_Options_Importer
23
+ */
24
+ private static $_instance = null;
25
+
26
+ /**
27
+ * Instanciate Astra_Site_Options_Importer
28
+ *
29
+ * @since 1.0.0
30
+ * @return (Object) Astra_Site_Options_Importer
31
+ */
32
+ public static function instance() {
33
+ if ( ! isset( self::$_instance ) ) {
34
+ self::$_instance = new self();
35
+ }
36
+
37
+ return self::$_instance;
38
+ }
39
+
40
+ /**
41
+ * Import site options.
42
+ *
43
+ * @since 1.0.0
44
+ *
45
+ * @param (Array) $options Array of site options to be imported from the demo.
46
+ */
47
+ public function import_options( $options ) {
48
+ $show_on_front = $options['show_on_front'];
49
+ $page_on_front = get_page_by_title( $options['page_on_front'] );
50
+ $page_for_posts = get_page_by_title( $options['page_for_posts'] );
51
+ $siteorigin_widgets_active = $options['siteorigin_widgets_active'];
52
+
53
+ // Update site options.
54
+ update_option( 'show_on_front', $show_on_front );
55
+ update_option( 'page_on_front', $page_on_front->ID );
56
+ update_option( 'page_for_posts', $page_for_posts->ID );
57
+ update_option( 'siteorigin_widgets_active', $siteorigin_widgets_active );
58
+
59
+ $this->set_nav_menu_locations( $options['nav_menu_locations'] );
60
+
61
+ $this->insert_logo( $options['custom_logo'] );
62
+ }
63
+
64
+ /**
65
+ * In WP nav menu is stored as ( 'menu_location' => 'menu_id' );
66
+ * In export we send 'menu_slug' like ( 'menu_location' => 'menu_slug' );
67
+ * In import we set 'menu_id' from menu slug like ( 'menu_location' => 'menu_id' );
68
+ *
69
+ * @since 1.0.0
70
+ * @param array $nav_menu_locations Array of nav menu locations.
71
+ */
72
+ function set_nav_menu_locations( $nav_menu_locations = array() ) {
73
+
74
+ $menu_locations = array();
75
+
76
+ // Update menu locations.
77
+ foreach ( $nav_menu_locations as $menu => $value ) {
78
+
79
+ $term = get_term_by( 'slug', $value, 'nav_menu' );
80
+
81
+ if ( is_object( $term ) ) {
82
+ $menu_locations[ $menu ] = $term->term_id;
83
+ }
84
+ }
85
+
86
+ set_theme_mod( 'nav_menu_locations', $menu_locations );
87
+ }
88
+
89
+
90
+ /**
91
+ * Insert Logo By URL
92
+ *
93
+ * @since 1.0.0
94
+ * @param string $image_url Logo URL.
95
+ * @return void
96
+ */
97
+ function insert_logo( $image_url = '' ) {
98
+
99
+ // Download Site Logo Image.
100
+ $response = Astra_Sites_Helper::download_file( $image_url );
101
+
102
+ // Is Success?
103
+ if ( $response['success'] ) {
104
+
105
+ // Set attachment data.
106
+ $attachment = array(
107
+ 'post_mime_type' => $response['data']['type'],
108
+ 'post_title' => sanitize_file_name( basename( $image_url ) ),
109
+ 'post_content' => '',
110
+ 'post_status' => 'inherit',
111
+ );
112
+
113
+ // Create the attachment.
114
+ $attach_id = wp_insert_attachment( $attachment, $response['data']['file'] );
115
+
116
+ set_theme_mod( 'custom_logo', $attach_id );
117
+ }
118
+
119
+ }
120
+ }
importers/class-astra-sites-helper.php ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Astra Site Helper
4
+ *
5
+ * @since 1.0.0
6
+ * @package Astra Sites
7
+ */
8
+
9
+ if ( ! class_exists( 'Astra_Sites_Helper' ) ) :
10
+
11
+ /**
12
+ * Astra_Sites_Helper
13
+ *
14
+ * @since 1.0.0
15
+ */
16
+ class Astra_Sites_Helper {
17
+
18
+ /**
19
+ * Instance
20
+ *
21
+ * @access private
22
+ * @var object Instance
23
+ * @since 1.0.0
24
+ */
25
+ private static $instance;
26
+
27
+ /**
28
+ * Initiator
29
+ *
30
+ * @since 1.0.0
31
+ * @return object initialized object of class.
32
+ */
33
+ public static function get_instance() {
34
+ if ( ! isset( self::$instance ) ) {
35
+ self::$instance = new self;
36
+ }
37
+ return self::$instance;
38
+ }
39
+
40
+ /**
41
+ * Constructor
42
+ *
43
+ * @since 1.0.0
44
+ */
45
+ public function __construct() {
46
+ }
47
+
48
+ /**
49
+ * Download File Into Uploads Directory
50
+ *
51
+ * @param string $file Download File URL.
52
+ * @return array Downloaded file data.
53
+ */
54
+ public static function download_file( $file = '' ) {
55
+
56
+ // Gives us access to the download_url() and wp_handle_sideload() functions.
57
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
58
+
59
+ $timeout_seconds = 5;
60
+
61
+ // Download file to temp dir.
62
+ $temp_file = download_url( $file, $timeout_seconds );
63
+
64
+ // WP Error.
65
+ if ( is_wp_error( $temp_file ) ) {
66
+ return array(
67
+ 'success' => false,
68
+ 'data' => $temp_file->get_error_message(),
69
+ );
70
+ }
71
+
72
+ // Array based on $_FILE as seen in PHP file uploads.
73
+ $file_args = array(
74
+ 'name' => basename( $file ),
75
+ 'tmp_name' => $temp_file,
76
+ 'error' => 0,
77
+ 'size' => filesize( $temp_file ),
78
+ );
79
+
80
+ $overrides = array(
81
+
82
+ // Tells WordPress to not look for the POST form
83
+ // fields that would normally be present as
84
+ // we downloaded the file from a remote server, so there
85
+ // will be no form fields
86
+ // Default is true.
87
+ 'test_form' => false,
88
+
89
+ // Setting this to false lets WordPress allow empty files, not recommended.
90
+ // Default is true.
91
+ 'test_size' => true,
92
+
93
+ // A properly uploaded file will pass this test. There should be no reason to override this one.
94
+ 'test_upload' => true,
95
+
96
+ );
97
+
98
+ // Move the temporary file into the uploads directory.
99
+ $results = wp_handle_sideload( $file_args, $overrides );
100
+
101
+ if ( isset( $results['error'] ) ) {
102
+ return array(
103
+ 'success' => false,
104
+ 'data' => $results,
105
+ );
106
+ }
107
+
108
+ // Success!
109
+ return array(
110
+ 'success' => true,
111
+ 'data' => $results,
112
+ );
113
+ }
114
+
115
+ }
116
+
117
+ /**
118
+ * Kicking this off by calling 'get_instance()' method
119
+ */
120
+ Astra_Sites_Helper::get_instance();
121
+
122
+ endif;
importers/class-widgets-importer.php ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Widget Data exporter class.
4
+ *
5
+ * @package Astra Addon
6
+ * @see - https://wordpress.org/plugins/widget-importer-exporter/
7
+ */
8
+
9
+ defined( 'ABSPATH' ) or exit;
10
+
11
+ /**
12
+ * Widget Data exporter class.
13
+ *
14
+ * @see - https://wordpress.org/plugins/widget-importer-exporter/
15
+ */
16
+ class Astra_Widget_Importer {
17
+
18
+ /**
19
+ * Instance of Astra_Widget_Importer
20
+ *
21
+ * @var Astra_Widget_Importer
22
+ */
23
+ private static $_instance = null;
24
+
25
+ public static function instance() {
26
+
27
+ if ( ! isset( self::$_instance ) ) {
28
+ self::$_instance = new self;
29
+ }
30
+
31
+ return self::$_instance;
32
+ }
33
+
34
+ /**
35
+ * Available widgets
36
+ *
37
+ * Gather site's widgets into array with ID base, name, etc.
38
+ * Used by export and import functions.
39
+ *
40
+ * @since 0.4
41
+ * @global array $wp_registered_widget_updates
42
+ * @return array Widget information
43
+ */
44
+ function wie_available_widgets() {
45
+
46
+ global $wp_registered_widget_controls;
47
+
48
+ $widget_controls = $wp_registered_widget_controls;
49
+
50
+ $available_widgets = array();
51
+
52
+ foreach ( $widget_controls as $widget ) {
53
+
54
+ if ( ! empty( $widget['id_base'] ) && ! isset( $available_widgets[ $widget['id_base'] ] ) ) { // no dupes
55
+
56
+ $available_widgets[ $widget['id_base'] ]['id_base'] = $widget['id_base'];
57
+ $available_widgets[ $widget['id_base'] ]['name'] = $widget['name'];
58
+
59
+ }
60
+ }
61
+
62
+ return apply_filters( 'wie_available_widgets', $available_widgets );
63
+ }
64
+
65
+ /**
66
+ * Import widget JSON data
67
+ *
68
+ * @since 0.4
69
+ * @global array $wp_registered_sidebars
70
+ *
71
+ * @param object $data JSON widget data from .wie file
72
+ *
73
+ * @return array Results array
74
+ */
75
+ function import_widgets_data( $data ) {
76
+
77
+ global $wp_registered_sidebars;
78
+
79
+ // Have valid data?
80
+ // If no data or could not decode
81
+ if ( empty( $data ) || ! is_object( $data ) ) {
82
+ wp_die(
83
+ esc_html__( 'Import data could not be read. Please try a different file.', 'astra-sites' ),
84
+ '',
85
+ array(
86
+ 'back_link' => true,
87
+ )
88
+ );
89
+ }
90
+
91
+ // Hook before import
92
+ do_action( 'wie_before_import' );
93
+ $data = apply_filters( 'wie_import_data', $data );
94
+
95
+ // Get all available widgets site supports
96
+ $available_widgets = $this->wie_available_widgets();
97
+
98
+ // Get all existing widget instances
99
+ $widget_instances = array();
100
+ foreach ( $available_widgets as $widget_data ) {
101
+ $widget_instances[ $widget_data['id_base'] ] = get_option( 'widget_' . $widget_data['id_base'] );
102
+ }
103
+
104
+ // Begin results
105
+ $results = array();
106
+
107
+ // Loop import data's sidebars
108
+ foreach ( $data as $sidebar_id => $widgets ) {
109
+
110
+ // Skip inactive widgets
111
+ // (should not be in export file)
112
+ if ( 'wp_inactive_widgets' == $sidebar_id ) {
113
+ continue;
114
+ }
115
+
116
+ // Check if sidebar is available on this site
117
+ // Otherwise add widgets to inactive, and say so
118
+ if ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ) {
119
+ $sidebar_available = true;
120
+ $use_sidebar_id = $sidebar_id;
121
+ $sidebar_message_type = 'success';
122
+ $sidebar_message = '';
123
+ } else {
124
+ $sidebar_available = false;
125
+ $use_sidebar_id = 'wp_inactive_widgets'; // add to inactive if sidebar does not exist in theme
126
+ $sidebar_message_type = 'error';
127
+ $sidebar_message = esc_html__( 'Widget area does not exist in theme (using Inactive)', 'astra-sites' );
128
+ }
129
+
130
+ // Result for sidebar
131
+ $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
132
+ $results[ $sidebar_id ]['message_type'] = $sidebar_message_type;
133
+ $results[ $sidebar_id ]['message'] = $sidebar_message;
134
+ $results[ $sidebar_id ]['widgets'] = array();
135
+
136
+ // Loop widgets
137
+ foreach ( $widgets as $widget_instance_id => $widget ) {
138
+
139
+ $fail = false;
140
+
141
+ // Get id_base (remove -# from end) and instance ID number
142
+ $id_base = preg_replace( '/-[0-9]+$/', '', $widget_instance_id );
143
+ $instance_id_number = str_replace( $id_base . '-', '', $widget_instance_id );
144
+
145
+ // Does site support this widget?
146
+ if ( ! $fail && ! isset( $available_widgets[ $id_base ] ) ) {
147
+ $fail = true;
148
+ $widget_message_type = 'error';
149
+ $widget_message = esc_html__( 'Site does not support widget', 'astra-sites' ); // explain why widget not imported
150
+ }
151
+
152
+ // Filter to modify settings object before conversion to array and import
153
+ // Leave this filter here for backwards compatibility with manipulating objects (before conversion to array below)
154
+ // Ideally the newer wie_widget_settings_array below will be used instead of this
155
+ $widget = apply_filters( 'wie_widget_settings', $widget ); // object
156
+
157
+ // Convert multidimensional objects to multidimensional arrays
158
+ // Some plugins like Jetpack Widget Visibility store settings as multidimensional arrays
159
+ // Without this, they are imported as objects and cause fatal error on Widgets page
160
+ // 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
161
+ // It is probably much more likely that arrays are used than objects, however
162
+ $widget = json_decode( wp_json_encode( $widget ), true );
163
+
164
+ // Filter to modify settings array
165
+ // This is preferred over the older wie_widget_settings filter above
166
+ // Do before identical check because changes may make it identical to end result (such as URL replacements)
167
+ $widget = apply_filters( 'wie_widget_settings_array', $widget );
168
+
169
+ // Does widget with identical settings already exist in same sidebar?
170
+ if ( ! $fail && isset( $widget_instances[ $id_base ] ) ) {
171
+
172
+ // Get existing widgets in this sidebar
173
+ $sidebars_widgets = get_option( 'sidebars_widgets' );
174
+ $sidebar_widgets = isset( $sidebars_widgets[ $use_sidebar_id ] ) ? $sidebars_widgets[ $use_sidebar_id ] : array(); // check Inactive if that's where will go
175
+
176
+ // Loop widgets with ID base
177
+ $single_widget_instances = ! empty( $widget_instances[ $id_base ] ) ? $widget_instances[ $id_base ] : array();
178
+ foreach ( $single_widget_instances as $check_id => $check_widget ) {
179
+
180
+ // Is widget in same sidebar and has identical settings?
181
+ if ( in_array( "$id_base-$check_id", $sidebar_widgets ) && (array) $widget == $check_widget ) {
182
+
183
+ $fail = true;
184
+ $widget_message_type = 'warning';
185
+ $widget_message = esc_html__( 'Widget already exists', 'astra-sites' ); // explain why widget not imported
186
+
187
+ break;
188
+
189
+ }
190
+ }
191
+ }
192
+
193
+ // No failure
194
+ if ( ! $fail ) {
195
+
196
+ // Add widget instance
197
+ $single_widget_instances = get_option( 'widget_' . $id_base ); // all instances for that widget ID base, get fresh every time
198
+ $single_widget_instances = ! empty( $single_widget_instances ) ? $single_widget_instances : array(
199
+ '_multiwidget' => 1,
200
+ ); // start fresh if have to
201
+ $single_widget_instances[] = $widget; // add it
202
+
203
+ // Get the key it was given
204
+ end( $single_widget_instances );
205
+ $new_instance_id_number = key( $single_widget_instances );
206
+
207
+ // If key is 0, make it 1
208
+ // 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)
209
+ if ( '0' === strval( $new_instance_id_number ) ) {
210
+ $new_instance_id_number = 1;
211
+ $single_widget_instances[ $new_instance_id_number ] = $single_widget_instances[0];
212
+ unset( $single_widget_instances[0] );
213
+ }
214
+
215
+ // Move _multiwidget to end of array for uniformity
216
+ if ( isset( $single_widget_instances['_multiwidget'] ) ) {
217
+ $multiwidget = $single_widget_instances['_multiwidget'];
218
+ unset( $single_widget_instances['_multiwidget'] );
219
+ $single_widget_instances['_multiwidget'] = $multiwidget;
220
+ }
221
+
222
+ // Update option with new widget
223
+ $result = update_option( 'widget_' . $id_base, $single_widget_instances );
224
+
225
+ // Assign widget instance to sidebar
226
+ $sidebars_widgets = get_option( 'sidebars_widgets' ); // which sidebars have which widgets, get fresh every time
227
+
228
+ // Avoid rarely fatal error when the option is an empty string
229
+ // https://github.com/churchthemes/widget-importer-exporter/pull/11
230
+ if ( ! $sidebars_widgets ) {
231
+ $sidebars_widgets = array();
232
+ }
233
+
234
+ $new_instance_id = $id_base . '-' . $new_instance_id_number; // use ID number from new widget instance
235
+ $sidebars_widgets[ $use_sidebar_id ][] = $new_instance_id; // add new instance to sidebar
236
+ update_option( 'sidebars_widgets', $sidebars_widgets ); // save the amended data
237
+
238
+ // After widget import action
239
+ $after_widget_import = array(
240
+ 'sidebar' => $use_sidebar_id,
241
+ 'sidebar_old' => $sidebar_id,
242
+ 'widget' => $widget,
243
+ 'widget_type' => $id_base,
244
+ 'widget_id' => $new_instance_id,
245
+ 'widget_id_old' => $widget_instance_id,
246
+ 'widget_id_num' => $new_instance_id_number,
247
+ 'widget_id_num_old' => $instance_id_number,
248
+ );
249
+ do_action( 'wie_after_widget_import', $after_widget_import );
250
+
251
+ // Success message
252
+ if ( $sidebar_available ) {
253
+ $widget_message_type = 'success';
254
+ $widget_message = esc_html__( 'Imported', 'astra-sites' );
255
+ } else {
256
+ $widget_message_type = 'warning';
257
+ $widget_message = esc_html__( 'Imported to Inactive', 'astra-sites' );
258
+ }
259
+ }// End if().
260
+
261
+ // Result for widget instance
262
+ $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)
263
+ $results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['title'] = ! empty( $widget['title'] ) ? $widget['title'] : esc_html__( 'No Title', 'astra-sites' ); // show "No Title" if widget instance is untitled
264
+ $results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['message_type'] = $widget_message_type;
265
+ $results[ $sidebar_id ]['widgets'][ $widget_instance_id ]['message'] = $widget_message;
266
+
267
+ }// End foreach().
268
+ }// End foreach().
269
+
270
+ // Hook after import
271
+ do_action( 'wie_after_import' );
272
+
273
+ // Return results
274
+ return apply_filters( 'wie_import_results', $results );
275
+
276
+ }
277
+
278
+ }
importers/wxr-importer/class-astra-wxr-importer.php ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class Astra WXR Importer
4
+ *
5
+ * @since 1.0.0
6
+ * @package Astra Addon
7
+ */
8
+
9
+ defined( 'ABSPATH' ) or exit;
10
+
11
+ /**
12
+ * Class Astra WXR Importer
13
+ *
14
+ * @since 1.0.0
15
+ */
16
+ class Astra_WXR_Importer {
17
+
18
+ /**
19
+ * Instance of Astra_WXR_Importer
20
+ *
21
+ * @since 1.0.0
22
+ * @var Astra_WXR_Importer
23
+ */
24
+ private static $_instance = null;
25
+
26
+ /**
27
+ * Instantiate Astra_WXR_Importer
28
+ *
29
+ * @since 1.0.0
30
+ * @return (Object) Astra_WXR_Importer.
31
+ */
32
+ public static function instance() {
33
+ if ( ! isset( self::$_instance ) ) {
34
+ self::$_instance = new self();
35
+ }
36
+
37
+ return self::$_instance;
38
+ }
39
+
40
+ /**
41
+ * Constructor.
42
+ *
43
+ * @since 1.0.0
44
+ */
45
+ private function __construct() {
46
+ $this->includes();
47
+
48
+ add_filter( 'upload_mimes', array( $this, 'custom_upload_mimes' ) );
49
+ }
50
+
51
+ /**
52
+ * Add .xml files as supported format in the uploader.
53
+ *
54
+ * @param array $mimes Already supported mime types.
55
+ */
56
+ public function custom_upload_mimes( $mimes ) {
57
+ $mimes = array_merge(
58
+ $mimes, array(
59
+ 'xml' => 'application/xml',
60
+ )
61
+ );
62
+
63
+ return $mimes;
64
+ }
65
+
66
+ /**
67
+ * Include required files.
68
+ *
69
+ * @since 1.0.0
70
+ */
71
+ private function includes() {
72
+ if ( ! class_exists( 'WP_Importer' ) ) {
73
+ defined( 'WP_LOAD_IMPORTERS' ) || define( 'WP_LOAD_IMPORTERS', true );
74
+ require ABSPATH . '/wp-admin/includes/class-wp-importer.php';
75
+ }
76
+ require_once ASTRA_SITES_DIR . 'importers/wxr-importer/class-wxr-importer.php';
77
+ require_once ASTRA_SITES_DIR . 'importers/wxr-importer/class-logger.php';
78
+ }
79
+
80
+ /**
81
+ * Start the xml import.
82
+ *
83
+ * @since 1.0.0
84
+ *
85
+ * @param (String) $path Absolute path to the XML file.
86
+ */
87
+ public function import_xml( $path ) {
88
+ $options = array(
89
+ 'fetch_attachments' => true,
90
+ 'default_author' => 0,
91
+ );
92
+ $logger = new WP_Importer_Logger();
93
+ $importer = new WXR_Importer( $options );
94
+ $importer->set_logger( $logger );
95
+ $result = $importer->import( $path );
96
+ }
97
+
98
+ /**
99
+ * Download and save XML file to uploads directory.
100
+ *
101
+ * @since 1.0.0
102
+ *
103
+ * @param (String) $url URL of the xml file.
104
+ *
105
+ * @return (Array) Attachment array of the downloaded xml file.
106
+ */
107
+ public function download_xml( $url ) {
108
+
109
+ // Download XML file.
110
+ $response = Astra_Sites_Helper::download_file( $url );
111
+
112
+ // Is Success?
113
+ if ( $response['success'] ) {
114
+ return $response['data'];
115
+ }
116
+
117
+ }
118
+
119
+ }
importers/wxr-importer/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 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
+ }
importers/wxr-importer/class-wxr-importer.php ADDED
@@ -0,0 +1,2299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class WXR_Importer extends WP_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 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', 'wordpress-importer' ) );
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 %1$s) is newer than the importer (version %2$s) and may not be supported. Please consider updating.', 'wordpress-importer' ),
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
+ }// End switch().
245
+ }// End while().
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 %1$s) is newer than the importer (version %2$s) and may not be supported. Please consider updating.', 'wordpress-importer' ),
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
+ }// End while().
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 %1$s) is newer than the importer (version %2$s) and may not be supported. Please consider updating.', 'wordpress-importer' ),
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
+ }// End switch().
468
+ }// End while().
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
+ $this->import_end();
479
+ }
480
+
481
+ /**
482
+ * Log an error instance to the logger.
483
+ *
484
+ * @param WP_Error $error Error instance to log.
485
+ */
486
+ protected function log_error( WP_Error $error ) {
487
+ $this->logger->warning( $error->get_error_message() );
488
+
489
+ // Log the data as debug info too
490
+ $data = $error->get_error_data();
491
+ if ( ! empty( $data ) ) {
492
+ $this->logger->debug( var_export( $data, true ) );
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Parses the WXR file and prepares us for the task of processing parsed data
498
+ *
499
+ * @param string $file Path to the WXR file for importing
500
+ */
501
+ protected function import_start( $file ) {
502
+ if ( ! is_file( $file ) ) {
503
+ return new WP_Error( 'wxr_importer.file_missing', __( 'The file does not exist, please try again.', 'wordpress-importer' ) );
504
+ }
505
+
506
+ // Suspend bunches of stuff in WP core
507
+ wp_defer_term_counting( true );
508
+ wp_defer_comment_counting( true );
509
+ wp_suspend_cache_invalidation( true );
510
+
511
+ // Prefill exists calls if told to
512
+ if ( $this->options['prefill_existing_posts'] ) {
513
+ $this->prefill_existing_posts();
514
+ }
515
+ if ( $this->options['prefill_existing_comments'] ) {
516
+ $this->prefill_existing_comments();
517
+ }
518
+ if ( $this->options['prefill_existing_terms'] ) {
519
+ $this->prefill_existing_terms();
520
+ }
521
+
522
+ /**
523
+ * Begin the import.
524
+ *
525
+ * Fires before the import process has begun. If you need to suspend
526
+ * caching or heavy processing on hooks, do so here.
527
+ */
528
+ do_action( 'import_start' );
529
+ }
530
+
531
+ /**
532
+ * Performs post-import cleanup of files and the cache
533
+ */
534
+ protected function import_end() {
535
+ // Re-enable stuff in core
536
+ wp_suspend_cache_invalidation( false );
537
+ wp_cache_flush();
538
+ foreach ( get_taxonomies() as $tax ) {
539
+ delete_option( "{$tax}_children" );
540
+ _get_term_hierarchy( $tax );
541
+ }
542
+
543
+ wp_defer_term_counting( false );
544
+ wp_defer_comment_counting( false );
545
+
546
+ /**
547
+ * Complete the import.
548
+ *
549
+ * Fires after the import process has finished. If you need to update
550
+ * your cache or re-enable processing, do so here.
551
+ */
552
+ do_action( 'import_end' );
553
+ }
554
+
555
+ /**
556
+ * Set the user mapping.
557
+ *
558
+ * @param array $mapping List of map arrays (containing `old_slug`, `old_id`, `new_id`)
559
+ */
560
+ public function set_user_mapping( $mapping ) {
561
+ foreach ( $mapping as $map ) {
562
+ if ( empty( $map['old_slug'] ) || empty( $map['old_id'] ) || empty( $map['new_id'] ) ) {
563
+ $this->logger->warning( __( 'Invalid author mapping', 'wordpress-importer' ) );
564
+ $this->logger->debug( var_export( $map, true ) );
565
+ continue;
566
+ }
567
+
568
+ $old_slug = $map['old_slug'];
569
+ $old_id = $map['old_id'];
570
+ $new_id = $map['new_id'];
571
+
572
+ $this->mapping['user'][ $old_id ] = $new_id;
573
+ $this->mapping['user_slug'][ $old_slug ] = $new_id;
574
+ }
575
+ }
576
+
577
+ /**
578
+ * Set the user slug overrides.
579
+ *
580
+ * Allows overriding the slug in the import with a custom/renamed version.
581
+ *
582
+ * @param string[] $overrides Map of old slug to new slug.
583
+ */
584
+ public function set_user_slug_overrides( $overrides ) {
585
+ foreach ( $overrides as $original => $renamed ) {
586
+ $this->user_slug_override[ $original ] = $renamed;
587
+ }
588
+ }
589
+
590
+ /**
591
+ * Parse a post node into post data.
592
+ *
593
+ * @param DOMElement $node Parent node of post data (typically `item`).
594
+ * @return array|WP_Error Post data array on success, error otherwise.
595
+ */
596
+ protected function parse_post_node( $node ) {
597
+ $data = array();
598
+ $meta = array();
599
+ $comments = array();
600
+ $terms = array();
601
+
602
+ foreach ( $node->childNodes as $child ) {
603
+ // We only care about child elements
604
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
605
+ continue;
606
+ }
607
+
608
+ switch ( $child->tagName ) {
609
+ case 'wp:post_type':
610
+ $data['post_type'] = $child->textContent;
611
+ break;
612
+
613
+ case 'title':
614
+ $data['post_title'] = $child->textContent;
615
+ break;
616
+
617
+ case 'guid':
618
+ $data['guid'] = $child->textContent;
619
+ break;
620
+
621
+ case 'dc:creator':
622
+ $data['post_author'] = $child->textContent;
623
+ break;
624
+
625
+ case 'content:encoded':
626
+ $data['post_content'] = $child->textContent;
627
+ break;
628
+
629
+ case 'excerpt:encoded':
630
+ $data['post_excerpt'] = $child->textContent;
631
+ break;
632
+
633
+ case 'wp:post_id':
634
+ $data['post_id'] = $child->textContent;
635
+ break;
636
+
637
+ case 'wp:post_date':
638
+ $data['post_date'] = $child->textContent;
639
+ break;
640
+
641
+ case 'wp:post_date_gmt':
642
+ $data['post_date_gmt'] = $child->textContent;
643
+ break;
644
+
645
+ case 'wp:comment_status':
646
+ $data['comment_status'] = $child->textContent;
647
+ break;
648
+
649
+ case 'wp:ping_status':
650
+ $data['ping_status'] = $child->textContent;
651
+ break;
652
+
653
+ case 'wp:post_name':
654
+ $data['post_name'] = $child->textContent;
655
+ break;
656
+
657
+ case 'wp:status':
658
+ $data['post_status'] = $child->textContent;
659
+
660
+ if ( $data['post_status'] === 'auto-draft' ) {
661
+ // Bail now
662
+ return new WP_Error(
663
+ 'wxr_importer.post.cannot_import_draft',
664
+ __( 'Cannot import auto-draft posts' ),
665
+ $data
666
+ );
667
+ }
668
+ break;
669
+
670
+ case 'wp:post_parent':
671
+ $data['post_parent'] = $child->textContent;
672
+ break;
673
+
674
+ case 'wp:menu_order':
675
+ $data['menu_order'] = $child->textContent;
676
+ break;
677
+
678
+ case 'wp:post_password':
679
+ $data['post_password'] = $child->textContent;
680
+ break;
681
+
682
+ case 'wp:is_sticky':
683
+ $data['is_sticky'] = $child->textContent;
684
+ break;
685
+
686
+ case 'wp:attachment_url':
687
+ $data['attachment_url'] = $child->textContent;
688
+ break;
689
+
690
+ case 'wp:postmeta':
691
+ $meta_item = $this->parse_meta_node( $child );
692
+ if ( ! empty( $meta_item ) ) {
693
+ $meta[] = $meta_item;
694
+ }
695
+ break;
696
+
697
+ case 'wp:comment':
698
+ $comment_item = $this->parse_comment_node( $child );
699
+ if ( ! empty( $comment_item ) ) {
700
+ $comments[] = $comment_item;
701
+ }
702
+ break;
703
+
704
+ case 'category':
705
+ $term_item = $this->parse_category_node( $child );
706
+ if ( ! empty( $term_item ) ) {
707
+ $terms[] = $term_item;
708
+ }
709
+ break;
710
+ }// End switch().
711
+ }// End foreach().
712
+
713
+ return compact( 'data', 'meta', 'comments', 'terms' );
714
+ }
715
+
716
+ /**
717
+ * Create new posts based on import information
718
+ *
719
+ * Posts marked as having a parent which doesn't exist will become top level items.
720
+ * Doesn't create a new post if: the post type doesn't exist, the given post ID
721
+ * is already noted as imported or a post with the same title and date already exists.
722
+ * Note that new/updated terms, comments and meta are imported for the last of the above.
723
+ */
724
+ protected function process_post( $data, $meta, $comments, $terms ) {
725
+ /**
726
+ * Pre-process post data.
727
+ *
728
+ * @param array $data Post data. (Return empty to skip.)
729
+ * @param array $meta Meta data.
730
+ * @param array $comments Comments on the post.
731
+ * @param array $terms Terms on the post.
732
+ */
733
+ $data = apply_filters( 'wxr_importer.pre_process.post', $data, $meta, $comments, $terms );
734
+ if ( empty( $data ) ) {
735
+ return false;
736
+ }
737
+
738
+ $original_id = isset( $data['post_id'] ) ? (int) $data['post_id'] : 0;
739
+ $parent_id = isset( $data['post_parent'] ) ? (int) $data['post_parent'] : 0;
740
+ $author_id = isset( $data['post_author'] ) ? (int) $data['post_author'] : 0;
741
+
742
+ // Have we already processed this?
743
+ if ( isset( $this->mapping['post'][ $original_id ] ) ) {
744
+ return;
745
+ }
746
+
747
+ $post_type_object = get_post_type_object( $data['post_type'] );
748
+
749
+ // Is this type even valid?
750
+ if ( ! $post_type_object ) {
751
+ $this->logger->warning( sprintf(
752
+ __( 'Failed to import "%1$s": Invalid post type %2$s', 'wordpress-importer' ),
753
+ $data['post_title'],
754
+ $data['post_type']
755
+ ) );
756
+ return false;
757
+ }
758
+
759
+ $post_exists = $this->post_exists( $data );
760
+ if ( $post_exists ) {
761
+ $this->logger->info( sprintf(
762
+ __( '%1$s "%2$s" already exists.', 'wordpress-importer' ),
763
+ $post_type_object->labels->singular_name,
764
+ $data['post_title']
765
+ ) );
766
+
767
+ /**
768
+ * Post processing already imported.
769
+ *
770
+ * @param array $data Raw data imported for the post.
771
+ */
772
+ do_action( 'wxr_importer.process_already_imported.post', $data );
773
+
774
+ // Even though this post already exists, new comments might need importing
775
+ $this->process_comments( $comments, $original_id, $data, $post_exists );
776
+
777
+ return false;
778
+ }
779
+
780
+ // Map the parent post, or mark it as one we need to fix
781
+ $requires_remapping = false;
782
+ if ( $parent_id ) {
783
+ if ( isset( $this->mapping['post'][ $parent_id ] ) ) {
784
+ $data['post_parent'] = $this->mapping['post'][ $parent_id ];
785
+ } else {
786
+ $meta[] = array(
787
+ 'key' => '_wxr_import_parent',
788
+ 'value' => $parent_id,
789
+ );
790
+ $requires_remapping = true;
791
+
792
+ $data['post_parent'] = 0;
793
+ }
794
+ }
795
+
796
+ // Map the author, or mark it as one we need to fix
797
+ $author = sanitize_user( $data['post_author'], true );
798
+ if ( empty( $author ) ) {
799
+ // Missing or invalid author, use default if available.
800
+ $data['post_author'] = $this->options['default_author'];
801
+ } elseif ( isset( $this->mapping['user_slug'][ $author ] ) ) {
802
+ $data['post_author'] = $this->mapping['user_slug'][ $author ];
803
+ } else {
804
+ $meta[] = array(
805
+ 'key' => '_wxr_import_user_slug',
806
+ 'value' => $author,
807
+ );
808
+ $requires_remapping = true;
809
+
810
+ $data['post_author'] = (int) get_current_user_id();
811
+ }
812
+
813
+ // Does the post look like it contains attachment images?
814
+ if ( preg_match( self::REGEX_HAS_ATTACHMENT_REFS, $data['post_content'] ) ) {
815
+ $meta[] = array(
816
+ 'key' => '_wxr_import_has_attachment_refs',
817
+ 'value' => true,
818
+ );
819
+ $requires_remapping = true;
820
+ }
821
+
822
+ // Whitelist to just the keys we allow
823
+ $postdata = array(
824
+ 'import_id' => $data['post_id'],
825
+ );
826
+ $allowed = array(
827
+ 'post_author' => true,
828
+ 'post_date' => true,
829
+ 'post_date_gmt' => true,
830
+ 'post_content' => true,
831
+ 'post_excerpt' => true,
832
+ 'post_title' => true,
833
+ 'post_status' => true,
834
+ 'post_name' => true,
835
+ 'comment_status' => true,
836
+ 'ping_status' => true,
837
+ 'guid' => true,
838
+ 'post_parent' => true,
839
+ 'menu_order' => true,
840
+ 'post_type' => true,
841
+ 'post_password' => true,
842
+ );
843
+ foreach ( $data as $key => $value ) {
844
+ if ( ! isset( $allowed[ $key ] ) ) {
845
+ continue;
846
+ }
847
+
848
+ $postdata[ $key ] = $data[ $key ];
849
+ }
850
+
851
+ $postdata = apply_filters( 'wp_import_post_data_processed', $postdata, $data );
852
+
853
+ if ( 'attachment' === $postdata['post_type'] ) {
854
+ if ( ! $this->options['fetch_attachments'] ) {
855
+ $this->logger->notice( sprintf(
856
+ __( 'Skipping attachment "%s", fetching attachments disabled' ),
857
+ $data['post_title']
858
+ ) );
859
+ /**
860
+ * Post processing skipped.
861
+ *
862
+ * @param array $data Raw data imported for the post.
863
+ * @param array $meta Raw meta data, already processed by {@see process_post_meta}.
864
+ */
865
+ do_action( 'wxr_importer.process_skipped.post', $data, $meta );
866
+ return false;
867
+ }
868
+ $remote_url = ! empty( $data['attachment_url'] ) ? $data['attachment_url'] : $data['guid'];
869
+ $post_id = $this->process_attachment( $postdata, $meta, $remote_url );
870
+ } else {
871
+ $post_id = wp_insert_post( $postdata, true );
872
+ do_action( 'wp_import_insert_post', $post_id, $original_id, $postdata, $data );
873
+ }
874
+
875
+ if ( is_wp_error( $post_id ) ) {
876
+ $this->logger->error( sprintf(
877
+ __( 'Failed to import "%1$s" (%2$s)', 'wordpress-importer' ),
878
+ $data['post_title'],
879
+ $post_type_object->labels->singular_name
880
+ ) );
881
+ $this->logger->debug( $post_id->get_error_message() );
882
+
883
+ /**
884
+ * Post processing failed.
885
+ *
886
+ * @param WP_Error $post_id Error object.
887
+ * @param array $data Raw data imported for the post.
888
+ * @param array $meta Raw meta data, already processed by {@see process_post_meta}.
889
+ * @param array $comments Raw comment data, already processed by {@see process_comments}.
890
+ * @param array $terms Raw term data, already processed.
891
+ */
892
+ do_action( 'wxr_importer.process_failed.post', $post_id, $data, $meta, $comments, $terms );
893
+ return false;
894
+ }
895
+
896
+ // Ensure stickiness is handled correctly too
897
+ if ( $data['is_sticky'] === '1' ) {
898
+ stick_post( $post_id );
899
+ }
900
+
901
+ // map pre-import ID to local ID
902
+ $this->mapping['post'][ $original_id ] = (int) $post_id;
903
+ if ( $requires_remapping ) {
904
+ $this->requires_remapping['post'][ $post_id ] = true;
905
+ }
906
+ $this->mark_post_exists( $data, $post_id );
907
+
908
+ $this->logger->info( sprintf(
909
+ __( 'Imported "%1$s" (%2$s)', 'wordpress-importer' ),
910
+ $data['post_title'],
911
+ $post_type_object->labels->singular_name
912
+ ) );
913
+ $this->logger->debug( sprintf(
914
+ __( 'Post %1$d remapped to %2$d', 'wordpress-importer' ),
915
+ $original_id,
916
+ $post_id
917
+ ) );
918
+
919
+ // Handle the terms too
920
+ $terms = apply_filters( 'wp_import_post_terms', $terms, $post_id, $data );
921
+
922
+ if ( ! empty( $terms ) ) {
923
+ $term_ids = array();
924
+ foreach ( $terms as $term ) {
925
+ $taxonomy = $term['taxonomy'];
926
+ $key = sha1( $taxonomy . ':' . $term['slug'] );
927
+
928
+ if ( isset( $this->mapping['term'][ $key ] ) ) {
929
+ $term_ids[ $taxonomy ][] = (int) $this->mapping['term'][ $key ];
930
+ } else {
931
+ $meta[] = array(
932
+ 'key' => '_wxr_import_term',
933
+ 'value' => $term,
934
+ );
935
+ $requires_remapping = true;
936
+ }
937
+ }
938
+
939
+ foreach ( $term_ids as $tax => $ids ) {
940
+ $tt_ids = wp_set_post_terms( $post_id, $ids, $tax );
941
+ do_action( 'wp_import_set_post_terms', $tt_ids, $ids, $tax, $post_id, $data );
942
+ }
943
+ }
944
+
945
+ $this->process_comments( $comments, $post_id, $data );
946
+ $this->process_post_meta( $meta, $post_id, $data );
947
+
948
+ if ( 'nav_menu_item' === $data['post_type'] ) {
949
+ $this->process_menu_item_meta( $post_id, $data, $meta );
950
+ }
951
+
952
+ /**
953
+ * Post processing completed.
954
+ *
955
+ * @param int $post_id New post ID.
956
+ * @param array $data Raw data imported for the post.
957
+ * @param array $meta Raw meta data, already processed by {@see process_post_meta}.
958
+ * @param array $comments Raw comment data, already processed by {@see process_comments}.
959
+ * @param array $terms Raw term data, already processed.
960
+ */
961
+ do_action( 'wxr_importer.processed.post', $post_id, $data, $meta, $comments, $terms );
962
+ }
963
+
964
+ /**
965
+ * Attempt to create a new menu item from import data
966
+ *
967
+ * Fails for draft, orphaned menu items and those without an associated nav_menu
968
+ * or an invalid nav_menu term. If the post type or term object which the menu item
969
+ * represents doesn't exist then the menu item will not be imported (waits until the
970
+ * end of the import to retry again before discarding).
971
+ *
972
+ * @param array $item Menu item details from WXR file
973
+ */
974
+ protected function process_menu_item_meta( $post_id, $data, $meta ) {
975
+
976
+ $item_type = get_post_meta( $post_id, '_menu_item_type', true );
977
+ $original_object_id = get_post_meta( $post_id, '_menu_item_object_id', true );
978
+ $object_id = null;
979
+
980
+ $this->logger->debug( sprintf( 'Processing menu item %s', $item_type ) );
981
+
982
+ $requires_remapping = false;
983
+ switch ( $item_type ) {
984
+ case 'taxonomy':
985
+ if ( isset( $this->mapping['term_id'][ $original_object_id ] ) ) {
986
+ $object_id = $this->mapping['term_id'][ $original_object_id ];
987
+ } else {
988
+ add_post_meta( $post_id, '_wxr_import_menu_item', wp_slash( $original_object_id ) );
989
+ $requires_remapping = true;
990
+ }
991
+ break;
992
+
993
+ case 'post_type':
994
+ if ( isset( $this->mapping['post'][ $original_object_id ] ) ) {
995
+ $object_id = $this->mapping['post'][ $original_object_id ];
996
+ } else {
997
+ add_post_meta( $post_id, '_wxr_import_menu_item', wp_slash( $original_object_id ) );
998
+ $requires_remapping = true;
999
+ }
1000
+ break;
1001
+
1002
+ case 'custom':
1003
+ // Custom refers to itself, wonderfully easy.
1004
+ $object_id = $post_id;
1005
+ break;
1006
+
1007
+ default:
1008
+ // associated object is missing or not imported yet, we'll retry later
1009
+ $this->missing_menu_items[] = $item;
1010
+ $this->logger->debug( 'Unknown menu item type' );
1011
+ break;
1012
+ }
1013
+
1014
+ if ( $requires_remapping ) {
1015
+ $this->requires_remapping['post'][ $post_id ] = true;
1016
+ }
1017
+
1018
+ if ( empty( $object_id ) ) {
1019
+ // Nothing needed here.
1020
+ return;
1021
+ }
1022
+
1023
+ $this->logger->debug( sprintf( 'Menu item %d mapped to %d', $original_object_id, $object_id ) );
1024
+ update_post_meta( $post_id, '_menu_item_object_id', wp_slash( $object_id ) );
1025
+ }
1026
+
1027
+ /**
1028
+ * If fetching attachments is enabled then attempt to create a new attachment
1029
+ *
1030
+ * @param array $post Attachment post details from WXR
1031
+ * @param string $url URL to fetch attachment from
1032
+ * @return int|WP_Error Post ID on success, WP_Error otherwise
1033
+ */
1034
+ protected function process_attachment( $post, $meta, $remote_url ) {
1035
+ // try to use _wp_attached file for upload folder placement to ensure the same location as the export site
1036
+ // e.g. location is 2003/05/image.jpg but the attachment post_date is 2010/09, see media_handle_upload()
1037
+ $post['upload_date'] = $post['post_date'];
1038
+ foreach ( $meta as $meta_item ) {
1039
+ if ( $meta_item['key'] !== '_wp_attached_file' ) {
1040
+ continue;
1041
+ }
1042
+
1043
+ if ( preg_match( '%^[0-9]{4}/[0-9]{2}%', $meta_item['value'], $matches ) ) {
1044
+ $post['upload_date'] = $matches[0];
1045
+ }
1046
+ break;
1047
+ }
1048
+
1049
+ // if the URL is absolute, but does not contain address, then upload it assuming base_site_url
1050
+ if ( preg_match( '|^/[\w\W]+$|', $remote_url ) ) {
1051
+ $remote_url = rtrim( $this->base_url, '/' ) . $remote_url;
1052
+ }
1053
+
1054
+ $upload = $this->fetch_remote_file( $remote_url, $post );
1055
+ if ( is_wp_error( $upload ) ) {
1056
+ return $upload;
1057
+ }
1058
+
1059
+ $info = wp_check_filetype( $upload['file'] );
1060
+ if ( ! $info ) {
1061
+ return new WP_Error( 'attachment_processing_error', __( 'Invalid file type', 'wordpress-importer' ) );
1062
+ }
1063
+
1064
+ $post['post_mime_type'] = $info['type'];
1065
+
1066
+ // WP really likes using the GUID for display. Allow updating it.
1067
+ // See https://core.trac.wordpress.org/ticket/33386
1068
+ if ( $this->options['update_attachment_guids'] ) {
1069
+ $post['guid'] = $upload['url'];
1070
+ }
1071
+
1072
+ // as per wp-admin/includes/upload.php
1073
+ $post_id = wp_insert_attachment( $post, $upload['file'] );
1074
+ if ( is_wp_error( $post_id ) ) {
1075
+ return $post_id;
1076
+ }
1077
+
1078
+ $attachment_metadata = wp_generate_attachment_metadata( $post_id, $upload['file'] );
1079
+ wp_update_attachment_metadata( $post_id, $attachment_metadata );
1080
+
1081
+ // Map this image URL later if we need to
1082
+ $this->url_remap[ $remote_url ] = $upload['url'];
1083
+
1084
+ // If we have a HTTPS URL, ensure the HTTP URL gets replaced too
1085
+ if ( substr( $remote_url, 0, 8 ) === 'https://' ) {
1086
+ $insecure_url = 'http' . substr( $remote_url, 5 );
1087
+ $this->url_remap[ $insecure_url ] = $upload['url'];
1088
+ }
1089
+
1090
+ if ( $this->options['aggressive_url_search'] ) {
1091
+ // remap resized image URLs, works by stripping the extension and remapping the URL stub.
1092
+ /*
1093
+ if ( preg_match( '!^image/!', $info['type'] ) ) {
1094
+ $parts = pathinfo( $remote_url );
1095
+ $name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2
1096
+
1097
+ $parts_new = pathinfo( $upload['url'] );
1098
+ $name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" );
1099
+
1100
+ $this->url_remap[$parts['dirname'] . '/' . $name] = $parts_new['dirname'] . '/' . $name_new;
1101
+ }*/
1102
+ }
1103
+
1104
+ return $post_id;
1105
+ }
1106
+
1107
+ /**
1108
+ * Parse a meta node into meta data.
1109
+ *
1110
+ * @param DOMElement $node Parent node of meta data (typically `wp:postmeta` or `wp:commentmeta`).
1111
+ * @return array|null Meta data array on success, or null on error.
1112
+ */
1113
+ protected function parse_meta_node( $node ) {
1114
+ foreach ( $node->childNodes as $child ) {
1115
+ // We only care about child elements
1116
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
1117
+ continue;
1118
+ }
1119
+
1120
+ switch ( $child->tagName ) {
1121
+ case 'wp:meta_key':
1122
+ $key = $child->textContent;
1123
+ break;
1124
+
1125
+ case 'wp:meta_value':
1126
+ $value = $child->textContent;
1127
+ break;
1128
+ }
1129
+ }
1130
+
1131
+ if ( empty( $key ) || empty( $value ) ) {
1132
+ return null;
1133
+ }
1134
+
1135
+ return compact( 'key', 'value' );
1136
+ }
1137
+
1138
+ /**
1139
+ * Process and import post meta items.
1140
+ *
1141
+ * @param array $meta List of meta data arrays
1142
+ * @param int $post_id Post to associate with
1143
+ * @param array $post Post data
1144
+ * @return int|WP_Error Number of meta items imported on success, error otherwise.
1145
+ */
1146
+ protected function process_post_meta( $meta, $post_id, $post ) {
1147
+ if ( empty( $meta ) ) {
1148
+ return true;
1149
+ }
1150
+
1151
+ foreach ( $meta as $meta_item ) {
1152
+ /**
1153
+ * Pre-process post meta data.
1154
+ *
1155
+ * @param array $meta_item Meta data. (Return empty to skip.)
1156
+ * @param int $post_id Post the meta is attached to.
1157
+ */
1158
+ $meta_item = apply_filters( 'wxr_importer.pre_process.post_meta', $meta_item, $post_id );
1159
+ if ( empty( $meta_item ) ) {
1160
+ return false;
1161
+ }
1162
+
1163
+ $key = apply_filters( 'import_post_meta_key', $meta_item['key'], $post_id, $post );
1164
+ $value = false;
1165
+
1166
+ if ( '_edit_last' === $key ) {
1167
+ $value = intval( $meta_item['value'] );
1168
+ if ( ! isset( $this->mapping['user'][ $value ] ) ) {
1169
+ // Skip!
1170
+ continue;
1171
+ }
1172
+
1173
+ $value = $this->mapping['user'][ $value ];
1174
+ }
1175
+
1176
+ if ( $key ) {
1177
+ // export gets meta straight from the DB so could have a serialized string
1178
+ if ( ! $value ) {
1179
+ $value = maybe_unserialize( $meta_item['value'] );
1180
+ }
1181
+
1182
+ add_post_meta( $post_id, $key, $value );
1183
+ do_action( 'import_post_meta', $post_id, $key, $value );
1184
+
1185
+ // if the post has a featured image, take note of this in case of remap
1186
+ if ( '_thumbnail_id' === $key ) {
1187
+ $this->featured_images[ $post_id ] = (int) $value;
1188
+ }
1189
+ }
1190
+ }// End foreach().
1191
+
1192
+ return true;
1193
+ }
1194
+
1195
+ /**
1196
+ * Parse a comment node into comment data.
1197
+ *
1198
+ * @param DOMElement $node Parent node of comment data (typically `wp:comment`).
1199
+ * @return array Comment data array.
1200
+ */
1201
+ protected function parse_comment_node( $node ) {
1202
+ $data = array(
1203
+ 'commentmeta' => array(),
1204
+ );
1205
+
1206
+ foreach ( $node->childNodes as $child ) {
1207
+ // We only care about child elements
1208
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
1209
+ continue;
1210
+ }
1211
+
1212
+ switch ( $child->tagName ) {
1213
+ case 'wp:comment_id':
1214
+ $data['comment_id'] = $child->textContent;
1215
+ break;
1216
+ case 'wp:comment_author':
1217
+ $data['comment_author'] = $child->textContent;
1218
+ break;
1219
+
1220
+ case 'wp:comment_author_email':
1221
+ $data['comment_author_email'] = $child->textContent;
1222
+ break;
1223
+
1224
+ case 'wp:comment_author_IP':
1225
+ $data['comment_author_IP'] = $child->textContent;
1226
+ break;
1227
+
1228
+ case 'wp:comment_author_url':
1229
+ $data['comment_author_url'] = $child->textContent;
1230
+ break;
1231
+
1232
+ case 'wp:comment_user_id':
1233
+ $data['comment_user_id'] = $child->textContent;
1234
+ break;
1235
+
1236
+ case 'wp:comment_date':
1237
+ $data['comment_date'] = $child->textContent;
1238
+ break;
1239
+
1240
+ case 'wp:comment_date_gmt':
1241
+ $data['comment_date_gmt'] = $child->textContent;
1242
+ break;
1243
+
1244
+ case 'wp:comment_content':
1245
+ $data['comment_content'] = $child->textContent;
1246
+ break;
1247
+
1248
+ case 'wp:comment_approved':
1249
+ $data['comment_approved'] = $child->textContent;
1250
+ break;
1251
+
1252
+ case 'wp:comment_type':
1253
+ $data['comment_type'] = $child->textContent;
1254
+ break;
1255
+
1256
+ case 'wp:comment_parent':
1257
+ $data['comment_parent'] = $child->textContent;
1258
+ break;
1259
+
1260
+ case 'wp:commentmeta':
1261
+ $meta_item = $this->parse_meta_node( $child );
1262
+ if ( ! empty( $meta_item ) ) {
1263
+ $data['commentmeta'][] = $meta_item;
1264
+ }
1265
+ break;
1266
+ }// End switch().
1267
+ }// End foreach().
1268
+
1269
+ return $data;
1270
+ }
1271
+
1272
+ /**
1273
+ * Process and import comment data.
1274
+ *
1275
+ * @param array $comments List of comment data arrays.
1276
+ * @param int $post_id Post to associate with.
1277
+ * @param array $post Post data.
1278
+ * @return int|WP_Error Number of comments imported on success, error otherwise.
1279
+ */
1280
+ protected function process_comments( $comments, $post_id, $post, $post_exists = false ) {
1281
+
1282
+ $comments = apply_filters( 'wp_import_post_comments', $comments, $post_id, $post );
1283
+ if ( empty( $comments ) ) {
1284
+ return 0;
1285
+ }
1286
+
1287
+ $num_comments = 0;
1288
+
1289
+ // Sort by ID to avoid excessive remapping later
1290
+ usort( $comments, array( $this, 'sort_comments_by_id' ) );
1291
+
1292
+ foreach ( $comments as $key => $comment ) {
1293
+ /**
1294
+ * Pre-process comment data
1295
+ *
1296
+ * @param array $comment Comment data. (Return empty to skip.)
1297
+ * @param int $post_id Post the comment is attached to.
1298
+ */
1299
+ $comment = apply_filters( 'wxr_importer.pre_process.comment', $comment, $post_id );
1300
+ if ( empty( $comment ) ) {
1301
+ return false;
1302
+ }
1303
+
1304
+ $original_id = isset( $comment['comment_id'] ) ? (int) $comment['comment_id'] : 0;
1305
+ $parent_id = isset( $comment['comment_parent'] ) ? (int) $comment['comment_parent'] : 0;
1306
+ $author_id = isset( $comment['comment_user_id'] ) ? (int) $comment['comment_user_id'] : 0;
1307
+
1308
+ // if this is a new post we can skip the comment_exists() check
1309
+ // TODO: Check comment_exists for performance
1310
+ if ( $post_exists ) {
1311
+ $existing = $this->comment_exists( $comment );
1312
+ if ( $existing ) {
1313
+
1314
+ /**
1315
+ * Comment processing already imported.
1316
+ *
1317
+ * @param array $comment Raw data imported for the comment.
1318
+ */
1319
+ do_action( 'wxr_importer.process_already_imported.comment', $comment );
1320
+
1321
+ $this->mapping['comment'][ $original_id ] = $existing;
1322
+ continue;
1323
+ }
1324
+ }
1325
+
1326
+ // Remove meta from the main array
1327
+ $meta = isset( $comment['commentmeta'] ) ? $comment['commentmeta'] : array();
1328
+ unset( $comment['commentmeta'] );
1329
+
1330
+ // Map the parent comment, or mark it as one we need to fix
1331
+ $requires_remapping = false;
1332
+ if ( $parent_id ) {
1333
+ if ( isset( $this->mapping['comment'][ $parent_id ] ) ) {
1334
+ $comment['comment_parent'] = $this->mapping['comment'][ $parent_id ];
1335
+ } else {
1336
+ // Prepare for remapping later
1337
+ $meta[] = array(
1338
+ 'key' => '_wxr_import_parent',
1339
+ 'value' => $parent_id,
1340
+ );
1341
+ $requires_remapping = true;
1342
+
1343
+ // Wipe the parent for now
1344
+ $comment['comment_parent'] = 0;
1345
+ }
1346
+ }
1347
+
1348
+ // Map the author, or mark it as one we need to fix
1349
+ if ( $author_id ) {
1350
+ if ( isset( $this->mapping['user'][ $author_id ] ) ) {
1351
+ $comment['user_id'] = $this->mapping['user'][ $author_id ];
1352
+ } else {
1353
+ // Prepare for remapping later
1354
+ $meta[] = array(
1355
+ 'key' => '_wxr_import_user',
1356
+ 'value' => $author_id,
1357
+ );
1358
+ $requires_remapping = true;
1359
+
1360
+ // Wipe the user for now
1361
+ $comment['user_id'] = 0;
1362
+ }
1363
+ }
1364
+
1365
+ // Run standard core filters
1366
+ $comment['comment_post_ID'] = $post_id;
1367
+ $comment = wp_filter_comment( $comment );
1368
+
1369
+ // wp_insert_comment expects slashed data
1370
+ $comment_id = wp_insert_comment( wp_slash( $comment ) );
1371
+ $this->mapping['comment'][ $original_id ] = $comment_id;
1372
+ if ( $requires_remapping ) {
1373
+ $this->requires_remapping['comment'][ $comment_id ] = true;
1374
+ }
1375
+ $this->mark_comment_exists( $comment, $comment_id );
1376
+
1377
+ /**
1378
+ * Comment has been imported.
1379
+ *
1380
+ * @param int $comment_id New comment ID
1381
+ * @param array $comment Comment inserted (`comment_id` item refers to the original ID)
1382
+ * @param int $post_id Post parent of the comment
1383
+ * @param array $post Post data
1384
+ */
1385
+ do_action( 'wp_import_insert_comment', $comment_id, $comment, $post_id, $post );
1386
+
1387
+ // Process the meta items
1388
+ foreach ( $meta as $meta_item ) {
1389
+ $value = maybe_unserialize( $meta_item['value'] );
1390
+ add_comment_meta( $comment_id, wp_slash( $meta_item['key'] ), wp_slash( $value ) );
1391
+ }
1392
+
1393
+ /**
1394
+ * Post processing completed.
1395
+ *
1396
+ * @param int $post_id New post ID.
1397
+ * @param array $comment Raw data imported for the comment.
1398
+ * @param array $meta Raw meta data, already processed by {@see process_post_meta}.
1399
+ * @param array $post_id Parent post ID.
1400
+ */
1401
+ do_action( 'wxr_importer.processed.comment', $comment_id, $comment, $meta, $post_id );
1402
+
1403
+ $num_comments++;
1404
+ }// End foreach().
1405
+
1406
+ return $num_comments;
1407
+ }
1408
+
1409
+ protected function parse_category_node( $node ) {
1410
+ $data = array(
1411
+ // Default taxonomy to "category", since this is a `<category>` tag
1412
+ 'taxonomy' => 'category',
1413
+ );
1414
+ $meta = array();
1415
+
1416
+ if ( $node->hasAttribute( 'domain' ) ) {
1417
+ $data['taxonomy'] = $node->getAttribute( 'domain' );
1418
+ }
1419
+ if ( $node->hasAttribute( 'nicename' ) ) {
1420
+ $data['slug'] = $node->getAttribute( 'nicename' );
1421
+ }
1422
+
1423
+ $data['name'] = $node->textContent;
1424
+
1425
+ if ( empty( $data['slug'] ) ) {
1426
+ return null;
1427
+ }
1428
+
1429
+ // Just for extra compatibility
1430
+ if ( $data['taxonomy'] === 'tag' ) {
1431
+ $data['taxonomy'] = 'post_tag';
1432
+ }
1433
+
1434
+ return $data;
1435
+ }
1436
+
1437
+ /**
1438
+ * Callback for `usort` to sort comments by ID
1439
+ *
1440
+ * @param array $a Comment data for the first comment
1441
+ * @param array $b Comment data for the second comment
1442
+ * @return int
1443
+ */
1444
+ public static function sort_comments_by_id( $a, $b ) {
1445
+ if ( empty( $a['comment_id'] ) ) {
1446
+ return 1;
1447
+ }
1448
+
1449
+ if ( empty( $b['comment_id'] ) ) {
1450
+ return -1;
1451
+ }
1452
+
1453
+ return $a['comment_id'] - $b['comment_id'];
1454
+ }
1455
+
1456
+ protected function parse_author_node( $node ) {
1457
+ $data = array();
1458
+ $meta = array();
1459
+ foreach ( $node->childNodes as $child ) {
1460
+ // We only care about child elements
1461
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
1462
+ continue;
1463
+ }
1464
+
1465
+ switch ( $child->tagName ) {
1466
+ case 'wp:author_login':
1467
+ $data['user_login'] = $child->textContent;
1468
+ break;
1469
+
1470
+ case 'wp:author_id':
1471
+ $data['ID'] = $child->textContent;
1472
+ break;
1473
+
1474
+ case 'wp:author_email':
1475
+ $data['user_email'] = $child->textContent;
1476
+ break;
1477
+
1478
+ case 'wp:author_display_name':
1479
+ $data['display_name'] = $child->textContent;
1480
+ break;
1481
+
1482
+ case 'wp:author_first_name':
1483
+ $data['first_name'] = $child->textContent;
1484
+ break;
1485
+
1486
+ case 'wp:author_last_name':
1487
+ $data['last_name'] = $child->textContent;
1488
+ break;
1489
+ }
1490
+ }
1491
+
1492
+ return compact( 'data', 'meta' );
1493
+ }
1494
+
1495
+ protected function process_author( $data, $meta ) {
1496
+ /**
1497
+ * Pre-process user data.
1498
+ *
1499
+ * @param array $data User data. (Return empty to skip.)
1500
+ * @param array $meta Meta data.
1501
+ */
1502
+ $data = apply_filters( 'wxr_importer.pre_process.user', $data, $meta );
1503
+ if ( empty( $data ) ) {
1504
+ return false;
1505
+ }
1506
+
1507
+ // Have we already handled this user?
1508
+ $original_id = isset( $data['ID'] ) ? $data['ID'] : 0;
1509
+ $original_slug = $data['user_login'];
1510
+
1511
+ if ( isset( $this->mapping['user'][ $original_id ] ) ) {
1512
+ $existing = $this->mapping['user'][ $original_id ];
1513
+
1514
+ // Note the slug mapping if we need to too
1515
+ if ( ! isset( $this->mapping['user_slug'][ $original_slug ] ) ) {
1516
+ $this->mapping['user_slug'][ $original_slug ] = $existing;
1517
+ }
1518
+
1519
+ return false;
1520
+ }
1521
+
1522
+ if ( isset( $this->mapping['user_slug'][ $original_slug ] ) ) {
1523
+ $existing = $this->mapping['user_slug'][ $original_slug ];
1524
+
1525
+ // Ensure we note the mapping too
1526
+ $this->mapping['user'][ $original_id ] = $existing;
1527
+
1528
+ return false;
1529
+ }
1530
+
1531
+ // Allow overriding the user's slug
1532
+ $login = $original_slug;
1533
+ if ( isset( $this->user_slug_override[ $login ] ) ) {
1534
+ $login = $this->user_slug_override[ $login ];
1535
+ }
1536
+
1537
+ $userdata = array(
1538
+ 'user_login' => sanitize_user( $login, true ),
1539
+ 'user_pass' => wp_generate_password(),
1540
+ );
1541
+
1542
+ $allowed = array(
1543
+ 'user_email' => true,
1544
+ 'display_name' => true,
1545
+ 'first_name' => true,
1546
+ 'last_name' => true,
1547
+ );
1548
+ foreach ( $data as $key => $value ) {
1549
+ if ( ! isset( $allowed[ $key ] ) ) {
1550
+ continue;
1551
+ }
1552
+
1553
+ $userdata[ $key ] = $data[ $key ];
1554
+ }
1555
+
1556
+ $user_id = wp_insert_user( wp_slash( $userdata ) );
1557
+ if ( is_wp_error( $user_id ) ) {
1558
+ $this->logger->error( sprintf(
1559
+ __( 'Failed to import user "%s"', 'wordpress-importer' ),
1560
+ $userdata['user_login']
1561
+ ) );
1562
+ $this->logger->debug( $user_id->get_error_message() );
1563
+
1564
+ /**
1565
+ * User processing failed.
1566
+ *
1567
+ * @param WP_Error $user_id Error object.
1568
+ * @param array $userdata Raw data imported for the user.
1569
+ */
1570
+ do_action( 'wxr_importer.process_failed.user', $user_id, $userdata );
1571
+ return false;
1572
+ }
1573
+
1574
+ if ( $original_id ) {
1575
+ $this->mapping['user'][ $original_id ] = $user_id;
1576
+ }
1577
+ $this->mapping['user_slug'][ $original_slug ] = $user_id;
1578
+
1579
+ $this->logger->info( sprintf(
1580
+ __( 'Imported user "%s"', 'wordpress-importer' ),
1581
+ $userdata['user_login']
1582
+ ) );
1583
+ $this->logger->debug( sprintf(
1584
+ __( 'User %1$d remapped to %2$d', 'wordpress-importer' ),
1585
+ $original_id,
1586
+ $user_id
1587
+ ) );
1588
+
1589
+ // TODO: Implement meta handling once WXR includes it
1590
+ /**
1591
+ * User processing completed.
1592
+ *
1593
+ * @param int $user_id New user ID.
1594
+ * @param array $userdata Raw data imported for the user.
1595
+ */
1596
+ do_action( 'wxr_importer.processed.user', $user_id, $userdata );
1597
+ }
1598
+
1599
+ protected function parse_term_node( $node, $type = 'term' ) {
1600
+ $data = array();
1601
+ $meta = array();
1602
+
1603
+ $tag_name = array(
1604
+ 'id' => 'wp:term_id',
1605
+ 'taxonomy' => 'wp:term_taxonomy',
1606
+ 'slug' => 'wp:term_slug',
1607
+ 'parent' => 'wp:term_parent',
1608
+ 'name' => 'wp:term_name',
1609
+ 'description' => 'wp:term_description',
1610
+ );
1611
+ $taxonomy = null;
1612
+
1613
+ // Special casing!
1614
+ switch ( $type ) {
1615
+ case 'category':
1616
+ $tag_name['slug'] = 'wp:category_nicename';
1617
+ $tag_name['parent'] = 'wp:category_parent';
1618
+ $tag_name['name'] = 'wp:cat_name';
1619
+ $tag_name['description'] = 'wp:category_description';
1620
+ $tag_name['taxonomy'] = null;
1621
+
1622
+ $data['taxonomy'] = 'category';
1623
+ break;
1624
+
1625
+ case 'tag':
1626
+ $tag_name['slug'] = 'wp:tag_slug';
1627
+ $tag_name['parent'] = null;
1628
+ $tag_name['name'] = 'wp:tag_name';
1629
+ $tag_name['description'] = 'wp:tag_description';
1630
+ $tag_name['taxonomy'] = null;
1631
+
1632
+ $data['taxonomy'] = 'post_tag';
1633
+ break;
1634
+ }
1635
+
1636
+ foreach ( $node->childNodes as $child ) {
1637
+ // We only care about child elements
1638
+ if ( $child->nodeType !== XML_ELEMENT_NODE ) {
1639
+ continue;
1640
+ }
1641
+
1642
+ $key = array_search( $child->tagName, $tag_name );
1643
+ if ( $key ) {
1644
+ $data[ $key ] = $child->textContent;
1645
+ }
1646
+ }
1647
+
1648
+ if ( empty( $data['taxonomy'] ) ) {
1649
+ return null;
1650
+ }
1651
+
1652
+ // Compatibility with WXR 1.0
1653
+ if ( $data['taxonomy'] === 'tag' ) {
1654
+ $data['taxonomy'] = 'post_tag';
1655
+ }
1656
+
1657
+ return compact( 'data', 'meta' );
1658
+ }
1659
+
1660
+ protected function process_term( $data, $meta ) {
1661
+ /**
1662
+ * Pre-process term data.
1663
+ *
1664
+ * @param array $data Term data. (Return empty to skip.)
1665
+ * @param array $meta Meta data.
1666
+ */
1667
+ $data = apply_filters( 'wxr_importer.pre_process.term', $data, $meta );
1668
+ if ( empty( $data ) ) {
1669
+ return false;
1670
+ }
1671
+
1672
+ $original_id = isset( $data['id'] ) ? (int) $data['id'] : 0;
1673
+ $parent_id = isset( $data['parent'] ) ? (int) $data['parent'] : 0;
1674
+
1675
+ $mapping_key = sha1( $data['taxonomy'] . ':' . $data['slug'] );
1676
+ $existing = $this->term_exists( $data );
1677
+ if ( $existing ) {
1678
+
1679
+ /**
1680
+ * Term processing already imported.
1681
+ *
1682
+ * @param array $data Raw data imported for the term.
1683
+ */
1684
+ do_action( 'wxr_importer.process_already_imported.term', $data );
1685
+
1686
+ $this->mapping['term'][ $mapping_key ] = $existing;
1687
+ $this->mapping['term_id'][ $original_id ] = $existing;
1688
+ return false;
1689
+ }
1690
+
1691
+ // WP really likes to repeat itself in export files
1692
+ if ( isset( $this->mapping['term'][ $mapping_key ] ) ) {
1693
+ return false;
1694
+ }
1695
+
1696
+ $termdata = array();
1697
+ $allowed = array(
1698
+ 'slug' => true,
1699
+ 'description' => true,
1700
+ );
1701
+
1702
+ // Map the parent comment, or mark it as one we need to fix
1703
+ // TODO: add parent mapping and remapping
1704
+ /*
1705
+ $requires_remapping = false;
1706
+ if ( $parent_id ) {
1707
+ if ( isset( $this->mapping['term'][ $parent_id ] ) ) {
1708
+ $data['parent'] = $this->mapping['term'][ $parent_id ];
1709
+ } else {
1710
+ // Prepare for remapping later
1711
+ $meta[] = array( 'key' => '_wxr_import_parent', 'value' => $parent_id );
1712
+ $requires_remapping = true;
1713
+
1714
+ // Wipe the parent for now
1715
+ $data['parent'] = 0;
1716
+ }
1717
+ }*/
1718
+
1719
+ foreach ( $data as $key => $value ) {
1720
+ if ( ! isset( $allowed[ $key ] ) ) {
1721
+ continue;
1722
+ }
1723
+
1724
+ $termdata[ $key ] = $data[ $key ];
1725
+ }
1726
+
1727
+ $result = wp_insert_term( $data['name'], $data['taxonomy'], $termdata );
1728
+ if ( is_wp_error( $result ) ) {
1729
+ $this->logger->warning( sprintf(
1730
+ __( 'Failed to import %1$s %2$s', 'wordpress-importer' ),
1731
+ $data['taxonomy'],
1732
+ $data['name']
1733
+ ) );
1734
+ $this->logger->debug( $result->get_error_message() );
1735
+ do_action( 'wp_import_insert_term_failed', $result, $data );
1736
+
1737
+ /**
1738
+ * Term processing failed.
1739
+ *
1740
+ * @param WP_Error $result Error object.
1741
+ * @param array $data Raw data imported for the term.
1742
+ * @param array $meta Meta data supplied for the term.
1743
+ */
1744
+ do_action( 'wxr_importer.process_failed.term', $result, $data, $meta );
1745
+ return false;
1746
+ }
1747
+
1748
+ $term_id = $result['term_id'];
1749
+
1750
+ $this->mapping['term'][ $mapping_key ] = $term_id;
1751
+ $this->mapping['term_id'][ $original_id ] = $term_id;
1752
+
1753
+ $this->logger->info( sprintf(
1754
+ __( 'Imported "%1$s" (%2$s)', 'wordpress-importer' ),
1755
+ $data['name'],
1756
+ $data['taxonomy']
1757
+ ) );
1758
+ $this->logger->debug( sprintf(
1759
+ __( 'Term %1$d remapped to %2$d', 'wordpress-importer' ),
1760
+ $original_id,
1761
+ $term_id
1762
+ ) );
1763
+
1764
+ do_action( 'wp_import_insert_term', $term_id, $data );
1765
+
1766
+ /**
1767
+ * Term processing completed.
1768
+ *
1769
+ * @param int $term_id New term ID.
1770
+ * @param array $data Raw data imported for the term.
1771
+ */
1772
+ do_action( 'wxr_importer.processed.term', $term_id, $data );
1773
+ }
1774
+
1775
+ /**
1776
+ * Attempt to download a remote file attachment
1777
+ *
1778
+ * @param string $url URL of item to fetch
1779
+ * @param array $post Attachment details
1780
+ * @return array|WP_Error Local file location details on success, WP_Error otherwise
1781
+ */
1782
+ protected function fetch_remote_file( $url, $post ) {
1783
+ // extract the file name and extension from the url
1784
+ $file_name = basename( $url );
1785
+
1786
+ // get placeholder file in the upload dir with a unique, sanitized filename
1787
+ $upload = wp_upload_bits( $file_name, 0, '', $post['upload_date'] );
1788
+ if ( $upload['error'] ) {
1789
+ return new WP_Error( 'upload_dir_error', $upload['error'] );
1790
+ }
1791
+
1792
+ // fetch the remote url and write it to the placeholder file
1793
+ $response = wp_remote_get( $url, array(
1794
+ 'stream' => true,
1795
+ 'filename' => $upload['file'],
1796
+ ) );
1797
+
1798
+ // request failed
1799
+ if ( is_wp_error( $response ) ) {
1800
+ unlink( $upload['file'] );
1801
+ return $response;
1802
+ }
1803
+
1804
+ $code = (int) wp_remote_retrieve_response_code( $response );
1805
+
1806
+ // make sure the fetch was successful
1807
+ if ( $code !== 200 ) {
1808
+ unlink( $upload['file'] );
1809
+ return new WP_Error(
1810
+ 'import_file_error',
1811
+ sprintf(
1812
+ __( 'Remote server returned %1$d %2$s for %3$s', 'wordpress-importer' ),
1813
+ $code,
1814
+ get_status_header_desc( $code ),
1815
+ $url
1816
+ )
1817
+ );
1818
+ }
1819
+
1820
+ $filesize = filesize( $upload['file'] );
1821
+ $headers = wp_remote_retrieve_headers( $response );
1822
+
1823
+ if ( isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) {
1824
+ unlink( $upload['file'] );
1825
+ return new WP_Error( 'import_file_error', __( 'Remote file is incorrect size', 'wordpress-importer' ) );
1826
+ }
1827
+
1828
+ if ( 0 === $filesize ) {
1829
+ unlink( $upload['file'] );
1830
+ return new WP_Error( 'import_file_error', __( 'Zero size file downloaded', 'wordpress-importer' ) );
1831
+ }
1832
+
1833
+ $max_size = (int) $this->max_attachment_size();
1834
+ if ( ! empty( $max_size ) && $filesize > $max_size ) {
1835
+ unlink( $upload['file'] );
1836
+ $message = sprintf( __( 'Remote file is too large, limit is %s', 'wordpress-importer' ), size_format( $max_size ) );
1837
+ return new WP_Error( 'import_file_error', $message );
1838
+ }
1839
+
1840
+ return $upload;
1841
+ }
1842
+
1843
+ protected function post_process() {
1844
+ // Time to tackle any left-over bits
1845
+ if ( ! empty( $this->requires_remapping['post'] ) ) {
1846
+ $this->post_process_posts( $this->requires_remapping['post'] );
1847
+ }
1848
+ if ( ! empty( $this->requires_remapping['comment'] ) ) {
1849
+ $this->post_process_comments( $this->requires_remapping['comment'] );
1850
+ }
1851
+ }
1852
+
1853
+ protected function post_process_posts( $todo ) {
1854
+ foreach ( $todo as $post_id => $_ ) {
1855
+ $this->logger->debug( sprintf(
1856
+ // Note: title intentionally not used to skip extra processing
1857
+ // for when debug logging is off
1858
+ __( 'Running post-processing for post %d', 'wordpress-importer' ),
1859
+ $post_id
1860
+ ) );
1861
+
1862
+ $data = array();
1863
+
1864
+ $parent_id = get_post_meta( $post_id, '_wxr_import_parent', true );
1865
+ if ( ! empty( $parent_id ) ) {
1866
+ // Have we imported the parent now?
1867
+ if ( isset( $this->mapping['post'][ $parent_id ] ) ) {
1868
+ $data['post_parent'] = $this->mapping['post'][ $parent_id ];
1869
+ } else {
1870
+ $this->logger->warning( sprintf(
1871
+ __( 'Could not find the post parent for "%1$s" (post #%2$d)', 'wordpress-importer' ),
1872
+ get_the_title( $post_id ),
1873
+ $post_id
1874
+ ) );
1875
+ $this->logger->debug( sprintf(
1876
+ __( 'Post %1$d was imported with parent %2$d, but could not be found', 'wordpress-importer' ),
1877
+ $post_id,
1878
+ $parent_id
1879
+ ) );
1880
+ }
1881
+ }
1882
+
1883
+ $author_slug = get_post_meta( $post_id, '_wxr_import_user_slug', true );
1884
+ if ( ! empty( $author_slug ) ) {
1885
+ // Have we imported the user now?
1886
+ if ( isset( $this->mapping['user_slug'][ $author_slug ] ) ) {
1887
+ $data['post_author'] = $this->mapping['user_slug'][ $author_slug ];
1888
+ } else {
1889
+ $this->logger->warning( sprintf(
1890
+ __( 'Could not find the author for "%1$s" (post #%2$d)', 'wordpress-importer' ),
1891
+ get_the_title( $post_id ),
1892
+ $post_id
1893
+ ) );
1894
+ $this->logger->debug( sprintf(
1895
+ __( 'Post %1$d was imported with author "%2$s", but could not be found', 'wordpress-importer' ),
1896
+ $post_id,
1897
+ $author_slug
1898
+ ) );
1899
+ }
1900
+ }
1901
+
1902
+ $has_attachments = get_post_meta( $post_id, '_wxr_import_has_attachment_refs', true );
1903
+ if ( ! empty( $has_attachments ) ) {
1904
+ $post = get_post( $post_id );
1905
+ $content = $post->post_content;
1906
+
1907
+ // Replace all the URLs we've got
1908
+ $new_content = str_replace( array_keys( $this->url_remap ), $this->url_remap, $content );
1909
+ if ( $new_content !== $content ) {
1910
+ $data['post_content'] = $new_content;
1911
+ }
1912
+ }
1913
+
1914
+ if ( get_post_type( $post_id ) === 'nav_menu_item' ) {
1915
+ $this->post_process_menu_item( $post_id );
1916
+ }
1917
+
1918
+ // Do we have updates to make?
1919
+ if ( empty( $data ) ) {
1920
+ $this->logger->debug( sprintf(
1921
+ __( 'Post %d was marked for post-processing, but none was required.', 'wordpress-importer' ),
1922
+ $post_id
1923
+ ) );
1924
+ continue;
1925
+ }
1926
+
1927
+ // Run the update
1928
+ $data['ID'] = $post_id;
1929
+ $result = wp_update_post( $data, true );
1930
+ if ( is_wp_error( $result ) ) {
1931
+ $this->logger->warning( sprintf(
1932
+ __( 'Could not update "%1$s" (post #%2$d) with mapped data', 'wordpress-importer' ),
1933
+ get_the_title( $post_id ),
1934
+ $post_id
1935
+ ) );
1936
+ $this->logger->debug( $result->get_error_message() );
1937
+ continue;
1938
+ }
1939
+
1940
+ // Clear out our temporary meta keys
1941
+ delete_post_meta( $post_id, '_wxr_import_parent' );
1942
+ delete_post_meta( $post_id, '_wxr_import_user_slug' );
1943
+ delete_post_meta( $post_id, '_wxr_import_has_attachment_refs' );
1944
+ }// End foreach().
1945
+ }
1946
+
1947
+ protected function post_process_menu_item( $post_id ) {
1948
+ $menu_object_id = get_post_meta( $post_id, '_wxr_import_menu_item', true );
1949
+ if ( empty( $menu_object_id ) ) {
1950
+ // No processing needed!
1951
+ return;
1952
+ }
1953
+
1954
+ $menu_item_type = get_post_meta( $post_id, '_menu_item_type', true );
1955
+ switch ( $menu_item_type ) {
1956
+ case 'taxonomy':
1957
+ if ( isset( $this->mapping['term_id'][ $menu_object_id ] ) ) {
1958
+ $menu_object = $this->mapping['term_id'][ $menu_object_id ];
1959
+ }
1960
+ break;
1961
+
1962
+ case 'post_type':
1963
+ if ( isset( $this->mapping['post'][ $menu_object_id ] ) ) {
1964
+ $menu_object = $this->mapping['post'][ $menu_object_id ];
1965
+ }
1966
+ break;
1967
+
1968
+ default:
1969
+ // Cannot handle this.
1970
+ return;
1971
+ }
1972
+
1973
+ if ( ! empty( $menu_object ) ) {
1974
+ update_post_meta( $post_id, '_menu_item_object_id', wp_slash( $menu_object ) );
1975
+ } else {
1976
+ $this->logger->warning( sprintf(
1977
+ __( 'Could not find the menu object for "%1$s" (post #%2$d)', 'wordpress-importer' ),
1978
+ get_the_title( $post_id ),
1979
+ $post_id
1980
+ ) );
1981
+ $this->logger->debug( sprintf(
1982
+ __( 'Post %1$d was imported with object "%2$d" of type "%3$s", but could not be found', 'wordpress-importer' ),
1983
+ $post_id,
1984
+ $menu_object_id,
1985
+ $menu_item_type
1986
+ ) );
1987
+ }
1988
+
1989
+ delete_post_meta( $post_id, '_wxr_import_menu_item' );
1990
+ }
1991
+
1992
+
1993
+ protected function post_process_comments( $todo ) {
1994
+ foreach ( $todo as $comment_id => $_ ) {
1995
+ $data = array();
1996
+
1997
+ $parent_id = get_comment_meta( $comment_id, '_wxr_import_parent', true );
1998
+ if ( ! empty( $parent_id ) ) {
1999
+ // Have we imported the parent now?
2000
+ if ( isset( $this->mapping['comment'][ $parent_id ] ) ) {
2001
+ $data['comment_parent'] = $this->mapping['comment'][ $parent_id ];
2002
+ } else {
2003
+ $this->logger->warning( sprintf(
2004
+ __( 'Could not find the comment parent for comment #%d', 'wordpress-importer' ),
2005
+ $comment_id
2006
+ ) );
2007
+ $this->logger->debug( sprintf(
2008
+ __( 'Comment %1$d was imported with parent %2$d, but could not be found', 'wordpress-importer' ),
2009
+ $comment_id,
2010
+ $parent_id
2011
+ ) );
2012
+ }
2013
+ }
2014
+
2015
+ $author_id = get_comment_meta( $comment_id, '_wxr_import_user', true );
2016
+ if ( ! empty( $author_id ) ) {
2017
+ // Have we imported the user now?
2018
+ if ( isset( $this->mapping['user'][ $author_id ] ) ) {
2019
+ $data['user_id'] = $this->mapping['user'][ $author_id ];
2020
+ } else {
2021
+ $this->logger->warning( sprintf(
2022
+ __( 'Could not find the author for comment #%d', 'wordpress-importer' ),
2023
+ $comment_id
2024
+ ) );
2025
+ $this->logger->debug( sprintf(
2026
+ __( 'Comment %1$d was imported with author %2$d, but could not be found', 'wordpress-importer' ),
2027
+ $comment_id,
2028
+ $author_id
2029
+ ) );
2030
+ }
2031
+ }
2032
+
2033
+ // Do we have updates to make?
2034
+ if ( empty( $data ) ) {
2035
+ continue;
2036
+ }
2037
+
2038
+ // Run the update
2039
+ $data['comment_ID'] = $comment_ID;
2040
+ $result = wp_update_comment( wp_slash( $data ) );
2041
+ if ( empty( $result ) ) {
2042
+ $this->logger->warning( sprintf(
2043
+ __( 'Could not update comment #%d with mapped data', 'wordpress-importer' ),
2044
+ $comment_id
2045
+ ) );
2046
+ continue;
2047
+ }
2048
+
2049
+ // Clear out our temporary meta keys
2050
+ delete_comment_meta( $comment_id, '_wxr_import_parent' );
2051
+ delete_comment_meta( $comment_id, '_wxr_import_user' );
2052
+ }// End foreach().
2053
+ }
2054
+
2055
+ /**
2056
+ * Use stored mapping information to update old attachment URLs
2057
+ */
2058
+ protected function replace_attachment_urls_in_content() {
2059
+ global $wpdb;
2060
+ // make sure we do the longest urls first, in case one is a substring of another
2061
+ uksort( $this->url_remap, array( $this, 'cmpr_strlen' ) );
2062
+
2063
+ foreach ( $this->url_remap as $from_url => $to_url ) {
2064
+ // remap urls in post_content
2065
+ $query = $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url );
2066
+ $wpdb->query( $query );
2067
+
2068
+ // remap enclosure urls
2069
+ $query = $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url );
2070
+ $result = $wpdb->query( $query );
2071
+ }
2072
+ }
2073
+
2074
+ /**
2075
+ * Update _thumbnail_id meta to new, imported attachment IDs
2076
+ */
2077
+ function remap_featured_images() {
2078
+ // cycle through posts that have a featured image
2079
+ foreach ( $this->featured_images as $post_id => $value ) {
2080
+ if ( isset( $this->processed_posts[ $value ] ) ) {
2081
+ $new_id = $this->processed_posts[ $value ];
2082
+
2083
+ // only update if there's a difference
2084
+ if ( $new_id !== $value ) {
2085
+ update_post_meta( $post_id, '_thumbnail_id', $new_id );
2086
+ }
2087
+ }
2088
+ }
2089
+ }
2090
+
2091
+ /**
2092
+ * Decide if the given meta key maps to information we will want to import
2093
+ *
2094
+ * @param string $key The meta key to check
2095
+ * @return string|bool The key if we do want to import, false if not
2096
+ */
2097
+ public function is_valid_meta_key( $key ) {
2098
+ // skip attachment metadata since we'll regenerate it from scratch
2099
+ // skip _edit_lock as not relevant for import
2100
+ if ( in_array( $key, array( '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ) ) ) {
2101
+ return false;
2102
+ }
2103
+
2104
+ return $key;
2105
+ }
2106
+
2107
+ /**
2108
+ * Decide what the maximum file size for downloaded attachments is.
2109
+ * Default is 0 (unlimited), can be filtered via import_attachment_size_limit
2110
+ *
2111
+ * @return int Maximum attachment file size to import
2112
+ */
2113
+ protected function max_attachment_size() {
2114
+ return apply_filters( 'import_attachment_size_limit', 0 );
2115
+ }
2116
+
2117
+ /**
2118
+ * Added to http_request_timeout filter to force timeout at 60 seconds during import
2119
+ *
2120
+ * @access protected
2121
+ * @return int 60
2122
+ */
2123
+ function bump_request_timeout( $val ) {
2124
+ return 60;
2125
+ }
2126
+
2127
+ // return the difference in length between two strings
2128
+ function cmpr_strlen( $a, $b ) {
2129
+ return strlen( $b ) - strlen( $a );
2130
+ }
2131
+
2132
+ /**
2133
+ * Prefill existing post data.
2134
+ *
2135
+ * This preloads all GUIDs into memory, allowing us to avoid hitting the
2136
+ * database when we need to check for existence. With larger imports, this
2137
+ * becomes prohibitively slow to perform SELECT queries on each.
2138
+ *
2139
+ * By preloading all this data into memory, it's a constant-time lookup in
2140
+ * PHP instead. However, this does use a lot more memory, so for sites doing
2141
+ * small imports onto a large site, it may be a better tradeoff to use
2142
+ * on-the-fly checking instead.
2143
+ */
2144
+ protected function prefill_existing_posts() {
2145
+ global $wpdb;
2146
+ $posts = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts}" );
2147
+
2148
+ foreach ( $posts as $item ) {
2149
+ $this->exists['post'][ $item->guid ] = $item->ID;
2150
+ }
2151
+ }
2152
+
2153
+ /**
2154
+ * Does the post exist?
2155
+ *
2156
+ * @param array $data Post data to check against.
2157
+ * @return int|bool Existing post ID if it exists, false otherwise.
2158
+ */
2159
+ protected function post_exists( $data ) {
2160
+ // Constant-time lookup if we prefilled
2161
+ $exists_key = $data['guid'];
2162
+
2163
+ if ( $this->options['prefill_existing_posts'] ) {
2164
+ return isset( $this->exists['post'][ $exists_key ] ) ? $this->exists['post'][ $exists_key ] : false;
2165
+ }
2166
+
2167
+ // No prefilling, but might have already handled it
2168
+ if ( isset( $this->exists['post'][ $exists_key ] ) ) {
2169
+ return $this->exists['post'][ $exists_key ];
2170
+ }
2171
+
2172
+ // Still nothing, try post_exists, and cache it
2173
+ $exists = post_exists( $data['post_title'], $data['post_content'], $data['post_date'] );
2174
+ $this->exists['post'][ $exists_key ] = $exists;
2175
+
2176
+ return $exists;
2177
+ }
2178
+
2179
+ /**
2180
+ * Mark the post as existing.
2181
+ *
2182
+ * @param array $data Post data to mark as existing.
2183
+ * @param int $post_id Post ID.
2184
+ */
2185
+ protected function mark_post_exists( $data, $post_id ) {
2186
+ $exists_key = $data['guid'];
2187
+ $this->exists['post'][ $exists_key ] = $post_id;
2188
+ }
2189
+
2190
+ /**
2191
+ * Prefill existing comment data.
2192
+ *
2193
+ * @see self::prefill_existing_posts() for justification of why this exists.
2194
+ */
2195
+ protected function prefill_existing_comments() {
2196
+ global $wpdb;
2197
+ $posts = $wpdb->get_results( "SELECT comment_ID, comment_author, comment_date FROM {$wpdb->comments}" );
2198
+
2199
+ foreach ( $posts as $item ) {
2200
+ $exists_key = sha1( $item->comment_author . ':' . $item->comment_date );
2201
+ $this->exists['comment'][ $exists_key ] = $item->comment_ID;
2202
+ }
2203
+ }
2204
+
2205
+ /**
2206
+ * Does the comment exist?
2207
+ *
2208
+ * @param array $data Comment data to check against.
2209
+ * @return int|bool Existing comment ID if it exists, false otherwise.
2210
+ */
2211
+ protected function comment_exists( $data ) {
2212
+ $exists_key = sha1( $data['comment_author'] . ':' . $data['comment_date'] );
2213
+
2214
+ // Constant-time lookup if we prefilled
2215
+ if ( $this->options['prefill_existing_comments'] ) {
2216
+ return isset( $this->exists['comment'][ $exists_key ] ) ? $this->exists['comment'][ $exists_key ] : false;
2217
+ }
2218
+
2219
+ // No prefilling, but might have already handled it
2220
+ if ( isset( $this->exists['comment'][ $exists_key ] ) ) {
2221
+ return $this->exists['comment'][ $exists_key ];
2222
+ }
2223
+
2224
+ // Still nothing, try comment_exists, and cache it
2225
+ $exists = comment_exists( $data['comment_author'], $data['comment_date'] );
2226
+ $this->exists['comment'][ $exists_key ] = $exists;
2227
+
2228
+ return $exists;
2229
+ }
2230
+
2231
+ /**
2232
+ * Mark the comment as existing.
2233
+ *
2234
+ * @param array $data Comment data to mark as existing.
2235
+ * @param int $comment_id Comment ID.
2236
+ */
2237
+ protected function mark_comment_exists( $data, $comment_id ) {
2238
+ $exists_key = sha1( $data['comment_author'] . ':' . $data['comment_date'] );
2239
+ $this->exists['comment'][ $exists_key ] = $comment_id;
2240
+ }
2241
+
2242
+ /**
2243
+ * Prefill existing term data.
2244
+ *
2245
+ * @see self::prefill_existing_posts() for justification of why this exists.
2246
+ */
2247
+ protected function prefill_existing_terms() {
2248
+ global $wpdb;
2249
+ $query = "SELECT t.term_id, tt.taxonomy, t.slug FROM {$wpdb->terms} AS t";
2250
+ $query .= " JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id";
2251
+ $terms = $wpdb->get_results( $query );
2252
+
2253
+ foreach ( $terms as $item ) {
2254
+ $exists_key = sha1( $item->taxonomy . ':' . $item->slug );
2255
+ $this->exists['term'][ $exists_key ] = $item->term_id;
2256
+ }
2257
+ }
2258
+
2259
+ /**
2260
+ * Does the term exist?
2261
+ *
2262
+ * @param array $data Term data to check against.
2263
+ * @return int|bool Existing term ID if it exists, false otherwise.
2264
+ */
2265
+ protected function term_exists( $data ) {
2266
+ $exists_key = sha1( $data['taxonomy'] . ':' . $data['slug'] );
2267
+
2268
+ // Constant-time lookup if we prefilled
2269
+ if ( $this->options['prefill_existing_terms'] ) {
2270
+ return isset( $this->exists['term'][ $exists_key ] ) ? $this->exists['term'][ $exists_key ] : false;
2271
+ }
2272
+
2273
+ // No prefilling, but might have already handled it
2274
+ if ( isset( $this->exists['term'][ $exists_key ] ) ) {
2275
+ return $this->exists['term'][ $exists_key ];
2276
+ }
2277
+
2278
+ // Still nothing, try comment_exists, and cache it
2279
+ $exists = term_exists( $data['slug'], $data['taxonomy'] );
2280
+ if ( is_array( $exists ) ) {
2281
+ $exists = $exists['term_id'];
2282
+ }
2283
+
2284
+ $this->exists['term'][ $exists_key ] = $exists;
2285
+
2286
+ return $exists;
2287
+ }
2288
+
2289
+ /**
2290
+ * Mark the term as existing.
2291
+ *
2292
+ * @param array $data Term data to mark as existing.
2293
+ * @param int $term_id Term ID.
2294
+ */
2295
+ protected function mark_term_exists( $data, $term_id ) {
2296
+ $exists_key = sha1( $data['taxonomy'] . ':' . $data['slug'] );
2297
+ $this->exists['term'][ $exists_key ] = $term_id;
2298
+ }
2299
+ }
readme.txt ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Astra Sites ===
2
+ Contributors: brainstormforce
3
+ Donate link: https://wpastra.com/pro/
4
+ Tags: demo
5
+ Requires at least: 4.4
6
+ Tested up to: 4.8.1
7
+ Stable tag: 1.0.0
8
+ License: GPLv2 or later
9
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
+
11
+ Import Astra sites with just one click.
12
+
13
+ == Description ==
14
+
15
+ Import Astra sites with just one click.
16
+
17
+ == Installation ==
18
+
19
+ 1. Upload the plugin files to the `/wp-content/plugins/astra-sites` directory, or install the plugin through the WordPress plugins screen directly.
20
+ 1. Activate the plugin through the 'Plugins' screen in WordPress
21
+ 1. Use the Appearance->Astra-> Astra Sites to select the page to be displayed as header and footer.
22
+
23
+ == Changelog ==
24
+
25
+ = 1.0 =
26
+ * Initial Release