\n"
14 | "X-Generator: grunt-wp-i18n 0.5.4\n"
15 |
16 | #: admin/cloner-admin-clone-site.php:71
17 | msgid "Clone"
18 | msgstr ""
19 |
20 | #: admin/cloner-admin-clone-site.php:101 admin/cloner-admin-clone-site.php:102
21 | #: admin/views/clone-site.php:2 admin/views/clone-site.php:29 cloner.php:202
22 | msgid "Clone Site"
23 | msgstr ""
24 |
25 | #: admin/cloner-admin-clone-site.php:120
26 | msgid ""
27 | "The blog that you are trying to copy does not exist, Try "
28 | "another."
29 | msgstr ""
30 |
31 | #: admin/cloner-admin-clone-site.php:128
32 | msgid ""
33 | "The site that you are trying to copy (%s) cannot be cloned [ID %d], Try another."
35 | msgstr ""
36 |
37 | #: admin/cloner-admin-clone-site.php:182 integration/integration.php:8
38 | msgid "Destination"
39 | msgstr ""
40 |
41 | #: admin/cloner-admin-clone-site.php:183
42 | msgid "Options"
43 | msgstr ""
44 |
45 | #: admin/cloner-admin-clone-site.php:186
46 | msgid "Advanced Options"
47 | msgstr ""
48 |
49 | #: admin/cloner-admin-clone-site.php:234
50 | msgid "The blog that you are trying to copy does not exist"
51 | msgstr ""
52 |
53 | #: admin/cloner-admin-clone-site.php:257 integration/integration.php:34
54 | #: integration/integration.php:40
55 | msgid "Please, insert a site name"
56 | msgstr ""
57 |
58 | #: admin/cloner-admin-clone-site.php:266 integration/integration.php:47
59 | #: integration/integration.php:56
60 | msgid "Missing or invalid site address."
61 | msgstr ""
62 |
63 | #: admin/cloner-admin-clone-site.php:273 integration/integration.php:74
64 | msgid "The blog already exists"
65 | msgstr ""
66 |
67 | #: admin/cloner-admin-clone-site.php:316 admin/cloner-admin-clone-site.php:328
68 | msgid "The site you are trying to replace does not exist"
69 | msgstr ""
70 |
71 | #: admin/cloner-admin-clone-site.php:321
72 | msgid "You cannot copy a blog to itself"
73 | msgstr ""
74 |
75 | #: admin/cloner-admin-clone-site.php:387
76 | msgid "Unknown error"
77 | msgstr ""
78 |
79 | #: admin/cloner-admin-settings.php:74
80 | msgid "Cloner Settings"
81 | msgstr ""
82 |
83 | #. Plugin Name of the plugin/theme
84 | msgid "Cloner"
85 | msgstr ""
86 |
87 | #: admin/cloner-admin-settings.php:122 helpers/settings.php:66
88 | msgid "Settings"
89 | msgstr ""
90 |
91 | #: admin/cloner-admin-settings.php:139
92 | msgid "You need to check at least one option"
93 | msgstr ""
94 |
95 | #: admin/views/clone-site.php:7
96 | msgid "Cloning"
97 | msgstr ""
98 |
99 | #: admin/views/clone-site.php:14
100 | msgid "Choose a different site"
101 | msgstr ""
102 |
103 | #: admin/views/clone-site.php:194
104 | msgid "Ignore these tables"
105 | msgstr ""
106 |
107 | #: admin/views/clone-site.php:195
108 | msgid "Copy these tables"
109 | msgstr ""
110 |
111 | #: admin/views/confirmation.php:10
112 | msgid "Confirm action"
113 | msgstr ""
114 |
115 | #: admin/views/confirmation.php:36
116 | msgid "Watch Out!"
117 | msgstr ""
118 |
119 | #: admin/views/confirmation.php:40
120 | msgid ""
121 | "You have chosen a URL that already exists. If you choose ‘Continue’, "
122 | "all existing site content and settings on %s will be completely "
123 | "overwritten with content and settings from %s This change is permanent "
124 | "and can’t be undone, so please be careful. "
125 | msgstr ""
126 |
127 | #: admin/views/confirmation.php:68
128 | msgid "Continue"
129 | msgstr ""
130 |
131 | #: admin/views/confirmation.php:69
132 | msgid "No, please I want to go back"
133 | msgstr ""
134 |
135 | #: admin/views/meta-boxes/advanced.php:1
136 | msgid ""
137 | "You have chosen to clone the main blog. Please select tables you "
138 | "want cloned. Beware, network-only tables can take up a lot of "
139 | "space and a long time to clone."
140 | msgstr ""
141 |
142 | #: admin/views/meta-boxes/destination.php:5
143 | #: integration/views/multi-domains-destination-meta-box.php:37
144 | msgid "Create a new Site"
145 | msgstr ""
146 |
147 | #: admin/views/meta-boxes/destination.php:9
148 | #: admin/views/meta-boxes/destination.php:13
149 | #: admin/views/meta-boxes/destination.php:30
150 | #: integration/views/multi-domains-destination-meta-box.php:45
151 | #: integration/views/multi-domains-destination-meta-box.php:65
152 | msgid "Domain"
153 | msgstr ""
154 |
155 | #: admin/views/meta-boxes/destination.php:9
156 | #: admin/views/meta-boxes/destination.php:13
157 | #: integration/views/multi-domains-destination-meta-box.php:45
158 | msgid "Type your site name here..."
159 | msgstr ""
160 |
161 | #: admin/views/meta-boxes/destination.php:15
162 | #: integration/views/multi-domains-destination-meta-box.php:50
163 | msgid "Only lowercase letters (a-z) and numbers are allowed."
164 | msgstr ""
165 |
166 | #: admin/views/meta-boxes/destination.php:24
167 | #: integration/views/multi-domains-destination-meta-box.php:59
168 | msgid "Replace existing Site"
169 | msgstr ""
170 |
171 | #: admin/views/meta-boxes/destination.php:30
172 | #: integration/views/multi-domains-destination-meta-box.php:65
173 | msgid "Start writing to search an existing site"
174 | msgstr ""
175 |
176 | #: admin/views/meta-boxes/destination.php:31
177 | #: integration/views/multi-domains-destination-meta-box.php:66
178 | msgid "Leave it blank to clone to the main site"
179 | msgstr ""
180 |
181 | #: admin/views/meta-boxes/options.php:1
182 | msgid "Site Title"
183 | msgstr ""
184 |
185 | #: admin/views/meta-boxes/options.php:2
186 | msgid "Clone blog title"
187 | msgstr ""
188 |
189 | #: admin/views/meta-boxes/options.php:3
190 | msgid "Keep the destination blog title"
191 | msgstr ""
192 |
193 | #: admin/views/meta-boxes/options.php:6
194 | msgid "Overwrite blog title with"
195 | msgstr ""
196 |
197 | #: admin/views/meta-boxes/options.php:12
198 | msgid "Search Engine Visibility"
199 | msgstr ""
200 |
201 | #: admin/views/meta-boxes/options.php:15
202 | msgid "Discourage search engines from indexing the cloned site"
203 | msgstr ""
204 |
205 | #: admin/views/settings.php:10
206 | msgid "Settings updated"
207 | msgstr ""
208 |
209 | #: admin/views/settings.php:16
210 | msgid "Getting started:"
211 | msgstr ""
212 |
213 | #: admin/views/settings.php:19
214 | msgid "Navigate to Network Admin » Sites"
215 | msgstr ""
216 |
217 | #: admin/views/settings.php:23
218 | msgid "Hover over any site & click 'Clone'"
219 | msgstr ""
220 |
221 | #: admin/views/settings.php:27
222 | msgid "Select content you want to be copied:"
223 | msgstr ""
224 |
225 | #: admin/views/settings.php:50
226 | msgid "Replace urls and images"
227 | msgstr ""
228 |
229 | #: admin/views/settings.php:58
230 | msgid "Replace links"
231 | msgstr ""
232 |
233 | #: admin/views/settings.php:65
234 | msgid "Replace images"
235 | msgstr ""
236 |
237 | #: cloner.php:126
238 | msgid ""
239 | "Cloner has been successfully installed, it can be configured from Settings » Cloner"
241 | msgstr ""
242 |
243 | #: cloner.php:127
244 | msgid "Dismiss"
245 | msgstr ""
246 |
247 | #: copier/class.copier-attachment.php:95
248 | msgid "Wrong attachment specified"
249 | msgstr ""
250 |
251 | #: copier/class.copier-attachment.php:101
252 | msgid "Attachment ( ID= %d ) does not exist in the source blog"
253 | msgstr ""
254 |
255 | #: copier/class.copier-attachment.php:373
256 | msgid "Remote server did not respond for file: %s"
257 | msgstr ""
258 |
259 | #: copier/class.copier-attachment.php:379
260 | msgid "Remote server returned error response %1$d %2$s - %3$s"
261 | msgstr ""
262 |
263 | #: copier/class.copier-attachment.php:386
264 | msgid "Remote file is incorrect size"
265 | msgstr ""
266 |
267 | #: copier/class.copier-attachment.php:391
268 | msgid "Zero size file downloaded"
269 | msgstr ""
270 |
271 | #: copier/class.copier-attachment.php:397
272 | msgid "Remote file is too large, limit is %s"
273 | msgstr ""
274 |
275 | #: copier/class.copier-menus.php:32
276 | msgid "No Menus to Copy"
277 | msgstr ""
278 |
279 | #: copier/class.copier-menus.php:62 copier/class.copier-menus.php:79
280 | msgid "There was an error trying to copy the menu. ID: "
281 | msgstr ""
282 |
283 | #: copier/class.copier-post-types.php:25
284 | msgid "No Custom Post Types to copy"
285 | msgstr ""
286 |
287 | #: copier/class.copier-post-types.php:100
288 | msgid "No posts to copy"
289 | msgstr ""
290 |
291 | #: copier/class.copier-settings.php:68
292 | msgid "Error copying settings"
293 | msgstr ""
294 |
295 | #: copier/class.copier-tables.php:94
296 | msgid "Insertion Error: %s"
297 | msgstr ""
298 |
299 | #: copier/class.copier-tables.php:188
300 | msgid ""
301 | "Deletion Error: %1$s - The template was not applied. (New Blog Templates - "
302 | "While clearing %2$s)"
303 | msgstr ""
304 |
305 | #: copier/class.copier-terms.php:316 copier/class.copier-terms.php:552
306 | msgid "Invalid taxonomy"
307 | msgstr ""
308 |
309 | #: copier/class.copier-terms.php:331 copier/class.copier-terms.php:567
310 | msgid "Invalid term ID"
311 | msgstr ""
312 |
313 | #: copier/class.copier-terms.php:334 copier/class.copier-terms.php:570
314 | msgid "A name is required for this term"
315 | msgstr ""
316 |
317 | #: copier/class.copier-terms.php:340 copier/class.copier-terms.php:576
318 | msgid "Parent term does not exist."
319 | msgstr ""
320 |
321 | #: copier/class.copier-terms.php:395 copier/class.copier-terms.php:643
322 | msgid "A term with the name provided already exists with this parent."
323 | msgstr ""
324 |
325 | #: copier/class.copier-terms.php:398
326 | msgid "A term with the name provided already exists in this taxonomy."
327 | msgstr ""
328 |
329 | #: copier/class.copier-terms.php:424 copier/class.copier-terms.php:648
330 | #: copier/class.copier-terms.php:656 copier/class.copier-terms.php:686
331 | msgid "Could not insert term into the database"
332 | msgstr ""
333 |
334 | #: copier/class.copier-terms.php:641
335 | msgid "A term with the name and slug provided already exists with this parent."
336 | msgstr ""
337 |
338 | #: copier/class.copier-terms.php:661
339 | msgid "A term with the name and slug provided already exists."
340 | msgstr ""
341 |
342 | #: copier/copier.php:346
343 | msgid "Error getting option"
344 | msgstr ""
345 |
346 | #: copier/copier.php:376
347 | msgid "Process Finished"
348 | msgstr ""
349 |
350 | #: copier/copier.php:397
351 | msgid "No attachments to copy"
352 | msgstr ""
353 |
354 | #: copier/copier.php:433
355 | msgid "No menus to copy"
356 | msgstr ""
357 |
358 | #: copier/copier.php:468
359 | msgid "Error getting class (%s)"
360 | msgstr ""
361 |
362 | #: copier/copier.php:501
363 | msgid "Custom Post Types Copied"
364 | msgstr ""
365 |
366 | #: copier/copier.php:503 copier/copier.php:526 copier/copier.php:529
367 | msgid "%s Copied"
368 | msgstr ""
369 |
370 | #: copier/copier.php:506
371 | msgid "%s Menu Copied"
372 | msgstr ""
373 |
374 | #: copier/copier.php:624
375 | msgid "We're setting up your new blog. Please wait..."
376 | msgstr ""
377 |
378 | #: copier/copier.php:650
379 | msgid "New blog Setup"
380 | msgstr ""
381 |
382 | #: copier/copier.php:680
383 | msgid ""
384 | "Please, set WP_DEBUG and WP_DEBUG_DISPLAY to false if you want this screen "
385 | "to work automatically instead of manually"
386 | msgstr ""
387 |
388 | #: copier/copier.php:683
389 | msgid ""
390 | "XDebug is activated, turn it off if you want this screen to work "
391 | "automatically instead of manually"
392 | msgstr ""
393 |
394 | #: copier/copier.php:685 copier/copier.php:691
395 | msgid "Redirecting to dashboard..."
396 | msgstr ""
397 |
398 | #: copier/copier.php:693
399 | msgid "Start"
400 | msgstr ""
401 |
402 | #: copier/copier.php:695
403 | msgid "Next step"
404 | msgstr ""
405 |
406 | #: copier/copier.php:697
407 | msgid "Return to dashboard"
408 | msgstr ""
409 |
410 | #: copier/copier.php:742
411 | msgid "An error has occured"
412 | msgstr ""
413 |
414 | #: copier/copier.php:795 copier/copier.php:799
415 | msgid "Security Error"
416 | msgstr ""
417 |
418 | #: helpers/settings.php:67
419 | msgid "Posts"
420 | msgstr ""
421 |
422 | #: helpers/settings.php:68
423 | msgid "Pages"
424 | msgstr ""
425 |
426 | #: helpers/settings.php:69
427 | msgid "Custom Post Types"
428 | msgstr ""
429 |
430 | #: helpers/settings.php:70
431 | msgid "Terms"
432 | msgstr ""
433 |
434 | #: helpers/settings.php:71
435 | msgid "Menus"
436 | msgstr ""
437 |
438 | #: helpers/settings.php:72
439 | msgid "Users"
440 | msgstr ""
441 |
442 | #: helpers/settings.php:73
443 | msgid "Comments"
444 | msgstr ""
445 |
446 | #: helpers/settings.php:74
447 | msgid "Attachments"
448 | msgstr ""
449 |
450 | #: helpers/settings.php:75
451 | msgid "Custom tables"
452 | msgstr ""
453 |
454 | #. Plugin URI of the plugin/theme
455 | msgid "https://premium.wpmudev.org/project/cloner"
456 | msgstr ""
457 |
458 | #. Description of the plugin/theme
459 | msgid "Clone sites in a network installation"
460 | msgstr ""
461 |
462 | #. Author of the plugin/theme
463 | msgid "WPMU DEV"
464 | msgstr ""
465 |
466 | #. Author URI of the plugin/theme
467 | msgid "http://premium.wpmudev.org/"
468 | msgstr ""
--------------------------------------------------------------------------------
/copier/class.copier-post-types.php:
--------------------------------------------------------------------------------
1 | type == false ) {
24 | $this->log( 'class.copier-post-types. No Custom Post Types to copy' );
25 | return new WP_Error( 'wrong_post_type', __( 'No Custom Post Types to copy', WPMUDEV_COPIER_LANG_DOMAIN ) );
26 | }
27 |
28 |
29 | /**
30 | * Allows to remove some hooks when inserting the posts
31 | *
32 | * Many plugins uses posts actions to publish in FB or to send emails.
33 | * This filter is useful if we want to try to avoid it.
34 | * The hooks are removed by default.
35 | *
36 | * The filter removes the following hooks:
37 | * - save_post
38 | * - wp_insert_post
39 | * - transition_post_status
40 | * - It also disables wp_mail just in case
41 | *
42 | * @param Boolean $remove_hooks Remove hooks if set to true (true by default)
43 | */
44 | $remove_hooks = apply_filters( 'wpmudev_copier_remove_insert_post_filters', true );
45 |
46 | if ( $remove_hooks ) {
47 | add_filter( 'wp_mail', array( $this, 'disable_wp_mail' ), 1 );
48 | remove_all_actions( 'save_post' );
49 | remove_all_actions( 'wp_insert_post' );
50 | remove_all_actions( 'transition_post_status' );
51 | }
52 |
53 | if ( ! $post_id ) {
54 | // If we pass the post IDs, clear_posts must be executed outside
55 | $this->clear_posts();
56 | }
57 |
58 | switch_to_blog( $this->source_blog_id );
59 | /**
60 | * Filter the posts query variables.
61 | *
62 | * Allows to modify the posts query on the source blog ID.
63 | *
64 | * @param Array. WP_Query attributes to perform the search
65 | */
66 | $args = apply_filters( 'wpmudev_copier_get_source_posts_args', array(
67 | 'posts_per_page' => -1,
68 | 'ignore_sticky_posts' => true,
69 | 'post_status' => array( 'publish', 'pending', 'draft', 'future', 'private', 'inherit' ),
70 | 'post_type' => $this->type,
71 | ) );
72 |
73 | if ( $post_id ) {
74 | if ( ! is_array( $post_id ) ) {
75 | $post_id = array( $post_id );
76 | }
77 | $args['post__in'] = $post_id;
78 | }
79 |
80 | $this->log( 'class.copier-post-types. Get source posts arguments:' );
81 | $this->log( $args );
82 | $all_posts = get_posts( $args );
83 |
84 | // Adding the metadata
85 | foreach ( $all_posts as $key => $post ) {
86 |
87 | $meta_keys = array_keys( get_post_meta( $post->ID ) );
88 | $all_posts[ $key ]->meta = array();
89 | foreach ( $meta_keys as $meta_key ) {
90 | $all_posts[ $key ]->meta[ $meta_key ] = get_post_meta( $post->ID, $meta_key, true );
91 | }
92 |
93 | $all_posts[ $key ]->is_sticky = is_sticky( $post->ID );
94 | }
95 |
96 | restore_current_blog();
97 |
98 | if ( empty( $all_posts ) ) {
99 | $this->log( 'class.copier-post-types. No Posts to copy' );
100 | return new WP_Error( 'empty_posts', __( 'No posts to copy', WPMUDEV_COPIER_LANG_DOMAIN ) );
101 | }
102 |
103 | // Array that relations the source posts with the destination posts
104 | $posts_mapping = array();
105 | foreach ( $all_posts as $post ) {
106 | $new_post = (array)$post;
107 | $new_post['import_id'] = $new_post['ID'];
108 | unset( $new_post['ID'] );
109 |
110 |
111 | if ( isset( $this->args['update_date'] ) && $this->args['update_date'] && $new_post['post_status'] != 'future' ) {
112 | // Do we have to update post dates?
113 | $current_time = current_time( 'mysql', false );
114 | $current_gmt_time = current_time( 'mysql', false );
115 |
116 | $new_post['post_date'] = $current_time;
117 | $new_post['post_modified'] = $current_time;
118 | $new_post['post_date_gmt'] = $current_gmt_time;
119 | $new_post['post_modified_gmt'] = $current_gmt_time;
120 |
121 | // Deprecated
122 | if ( $this->type == 'page' )
123 | do_action('blog_templates-update-pages-dates', $this->template, get_current_blog_id(), $this->user_id, $new_post, $post->ID );
124 |
125 | // Deprecated
126 | if ( $this->type == 'post' )
127 | do_action('blog_templates-update-posts-dates', $this->template, get_current_blog_id(), $this->user_id, $new_post, $post->ID );
128 |
129 | }
130 |
131 | /**
132 | * Fires before a post is copied.
133 | *
134 | * Useful for modifying a post attributes
135 | *
136 | * @param Integer $source_blog_id Blog ID from where we are copying the post.
137 | * @param Integer $post_id Source Post ID.
138 | * @param Array $new_post New post attributes.
139 | * @param Integer $user_id Post Author.
140 | * @param Array $template Only applies when using New Blog Templates. Includes the template attributes
141 | */
142 | do_action( 'wpmudev_copier-copy-post', $this->source_blog_id, $post->ID, $new_post, $this->user_id, $this->template, $post );
143 |
144 | if ( $remove_hooks ) {
145 | $action = "new_to_" . $new_post['post_status'];
146 | remove_all_actions( $action );
147 |
148 | $action = $new_post['post_status'] . "_" . $new_post['post_type'];
149 | remove_all_actions( $action );
150 | }
151 |
152 |
153 | // If the user is set in arguments, let's use that one
154 | if ( $this->user_id )
155 | $new_post['post_author'] = $this->user_id;
156 |
157 | /**
158 | * Replace links and/or images urls in content
159 | */
160 | $types_to_replace = apply_filters(
161 | 'wpmudev_copier_types-to-replace',
162 | implode( '|', $settings['to_replace'] ),
163 | $post,
164 | $this
165 | );
166 |
167 | if ( ! empty( $types_to_replace ) ) {
168 | $new_post['post_content'] = $this->replace_content_urls( $new_post['post_content'], $types_to_replace );
169 | }
170 |
171 | /**
172 | * Allows to change the cloned post before inserting it
173 | *
174 | * @param Array $new_post New post properties
175 | * @param Integer $source_blog_id Blog ID from where we are copying the post.
176 | * @param Integer $post_id Source Post ID.
177 | * @param Integer $user_id Post Author.
178 | * @param Array $template Only applies when using New Blog Templates. Includes the template attributes
179 | */
180 | $new_post = apply_filters( 'wpmudev_copier_insert_new_post', $new_post, $this->source_blog_id, $post->ID, $this->user_id, $this->template );
181 |
182 | $new_post_id = @wp_insert_post( $new_post );
183 |
184 |
185 | if ( ! is_wp_error( $new_post_id ) ) {
186 | // Map the post
187 | $posts_mapping[ $post->ID ] = $new_post_id;
188 |
189 | // And insert metadata
190 | foreach ( $post->meta as $meta_key => $meta_value )
191 | update_post_meta( $new_post_id, $meta_key, $meta_value );
192 |
193 | if ( $post->is_sticky )
194 | stick_post( $new_post_id );
195 |
196 | }
197 | else {
198 | $this->log( 'class.copier-post-types. Error cloning post:' );
199 | $this->log( $new_post );
200 | }
201 |
202 | }
203 |
204 | // Now remap parents
205 | foreach ( $posts_mapping as $post_id ) {
206 | $current_parent_id = wp_get_post_parent_id( $post_id );
207 | if ( ! empty( $current_parent_id ) && isset( $posts_mapping[ $current_parent_id ] ) ) {
208 | $postarr = array(
209 | 'ID' => $post_id,
210 | 'post_parent' => $posts_mapping[ $current_parent_id ]
211 | );
212 | wp_update_post( $postarr );
213 | }
214 |
215 | }
216 |
217 | /**
218 | * Fires after all posts have been copied.
219 | *
220 | * @param Integer $source_blog_id Blog ID from where we are copying the post.
221 | * @param Integer $posts_mapping Map of posts [source_post_id] => $new_post_id.
222 | * @param Integer $user_id Post Author.
223 | * @param Array $template Only applies when using New Blog Templates. Includes the template attributes
224 | */
225 | do_action( 'wpmudev_copier-copied-posts', $this->source_blog_id, $posts_mapping, $this->user_id, $this->template, $this->type );
226 |
227 | if ( $remove_hooks ) {
228 | remove_filter( 'wp_mail', array( $this, 'disable_wp_mail' ), 1 );
229 | }
230 |
231 | return $posts_mapping;
232 |
233 | }
234 |
235 | /**
236 | * Replace urls in post content
237 | *
238 | * @param String $content
239 | * @param String $types
240 | * @param String $original_url
241 | * @param String $new_url
242 | * @return String
243 | */
244 | public function replace_content_urls( $content = '', $types = false, $original_url = false, $new_url = false ) {
245 |
246 | if ( ! $types ) {
247 | return $content;
248 | }
249 |
250 | if ( ! $original_url ) {
251 | $original_url = get_site_url( $this->source_blog_id );
252 | }
253 |
254 | if ( ! $new_url ) {
255 | $new_url = get_site_url( get_current_blog_id() );
256 | }
257 |
258 | $reg_exp = '/(' . $types . ')=("[^"]*")/';
259 | preg_match_all( $reg_exp, $content, $matches );
260 |
261 | foreach ( $matches as $match_key => $match_array ) {
262 |
263 | foreach ( $match_array as $match ) {
264 | $replace_with = str_replace( $original_url, $new_url, $match );
265 | $content = str_replace( $match, $replace_with, $content );
266 | }
267 | }
268 |
269 | return $content;
270 |
271 | }
272 |
273 | /**
274 | * Disable the mails. Some subscriptions plugins could send emails when a new post is created
275 | *
276 | * @param type $phpmailer
277 | * @return type
278 | */
279 | public function disable_wp_mail( $args ) {
280 | $args['to'] = '';
281 | return $args;
282 | }
283 |
284 | public function clear_posts() {
285 | /**
286 | * Filter the deletion posts query variables.
287 | *
288 | * Before inserting any post we need to delete the current posts in the
289 | * destination blog. This filter allows to modify the deletion posts query on the destination blog.
290 | *
291 | * @param Array. WP_Query attributes to perform the search.
292 | */
293 | $args = apply_filters( 'wpmudev_copier_get_delete_posts_args', array(
294 | 'posts_per_page' => -1,
295 | 'ignore_sticky_posts' => true,
296 | 'post_status' => 'any',
297 | 'fields' => 'ids',
298 | 'post_type' => $this->type
299 | ) );
300 |
301 | $this->log( 'class.copier-post-types.php. Clearing posts. Arguments:' );
302 | $this->log( $args );
303 |
304 | $all_posts = get_posts( $args );
305 |
306 | remove_action( 'before_delete_post', '_reset_front_page_settings_for_post' );
307 | remove_action( 'wp_trash_post', '_reset_front_page_settings_for_post' );
308 |
309 | foreach ( $all_posts as $post_id ) {
310 | @wp_delete_post( $post_id, true );
311 | }
312 |
313 |
314 | }
315 |
316 | }
317 |
318 |
319 | }
--------------------------------------------------------------------------------
/admin/cloner-admin-clone-site.php:
--------------------------------------------------------------------------------
1 | plugin_slug = 'cloner';
35 |
36 | // Add the options page and menu item.
37 | add_action( 'network_admin_menu', array( $this, 'add_plugin_clone_site_menu' ) );
38 |
39 | add_filter( 'manage_sites_action_links', array( &$this, 'add_site_action_link' ), 10, 2 );
40 |
41 | add_action( 'admin_enqueue_scripts', array( $this, 'add_javascript' ) );
42 | add_action( 'admin_enqueue_scripts', array( $this, 'add_css' ) );
43 |
44 | if ( ! defined( 'WPMUDEV_CLONER_ASSETS_URL' ) )
45 | define( 'WPMUDEV_CLONER_ASSETS_URL', plugin_dir_url( __FILE__ ) . 'assets' );
46 |
47 | }
48 |
49 | /**
50 | * Return an instance of this class.
51 | *
52 | * @since 1.0.0
53 | *
54 | * @return object A single instance of this class.
55 | */
56 | public static function get_instance() {
57 |
58 | // If the single instance hasn't been set, set it now.
59 | if ( null == self::$instance )
60 | self::$instance = new self;
61 |
62 | return self::$instance;
63 | }
64 |
65 | /**
66 | * Add a new action link in the Network Sites Page that clones a site
67 | */
68 | public function add_site_action_link( $links, $blog_id ) {
69 | if ( cloner_is_blog_clonable( $blog_id ) ) {
70 | $clone_url = add_query_arg( 'blog_id', $blog_id, network_admin_url( 'index.php?page=clone_site' ) );
71 | $links['clone'] = '' . __( 'Clone', WPMUDEV_CLONER_LANG_DOMAIN ) . '';
72 | }
73 |
74 |
75 | return $links;
76 | }
77 |
78 | function add_javascript() {
79 | if ( get_current_screen()->id == $this->plugin_screen_hook_suffix . '-network' ) {
80 | wp_enqueue_script( 'jquery-ui-autocomplete' );
81 | wp_enqueue_script( 'jquery-multi-select-css', WPMUDEV_CLONER_PLUGIN_URL . 'admin/assets/jquery-multi-select/js/jquery-multi-select.js', array( 'jquery' ), WPMUDEV_CLONER_VERSION );
82 | wp_enqueue_script('post');
83 | }
84 | }
85 |
86 | function add_css() {
87 | if ( get_current_screen()->id == $this->plugin_screen_hook_suffix . '-network' )
88 | wp_enqueue_style( 'jquery-multi-select-css', WPMUDEV_CLONER_PLUGIN_URL . 'admin/assets/jquery-multi-select/css/multi-select.css', array(), WPMUDEV_CLONER_VERSION );
89 | }
90 |
91 |
92 | /**
93 | * Register the administration menu for this plugin into the WordPress Dashboard menu.
94 | *
95 | * @since 1.0.0
96 | */
97 | public function add_plugin_clone_site_menu() {
98 |
99 | $this->plugin_screen_hook_suffix = add_submenu_page(
100 | null,
101 | __( 'Clone Site', WPMUDEV_CLONER_LANG_DOMAIN ),
102 | __( 'Clone Site', WPMUDEV_CLONER_LANG_DOMAIN ),
103 | 'manage_network',
104 | 'clone_site',
105 | array( &$this, 'display_admin_page' )
106 | );
107 |
108 | // Sanitize the form when the menu is loaded
109 | add_action( 'load-' . $this->plugin_screen_hook_suffix, array( $this, 'sanitize_clone_form' ) );
110 | add_action( 'load-' . $this->plugin_screen_hook_suffix, array( $this, 'validate_blog_to_clone' ) );
111 |
112 | }
113 |
114 | function validate_blog_to_clone() {
115 | $blog_id = absint( $_REQUEST['blog_id'] );
116 | $blog_details = get_blog_details( $blog_id );
117 |
118 | if ( ! $blog_details ) {
119 | $message = sprintf(
120 | __( 'The blog that you are trying to copy does not exist, Try another.', WPMUDEV_CLONER_LANG_DOMAIN ),
121 | network_admin_url( 'sites.php' )
122 | );
123 | wp_die( $message );
124 | }
125 |
126 | if ( ! cloner_is_blog_clonable( $blog_id ) ) {
127 | $message = sprintf(
128 | __( 'The site that you are trying to copy (%s) cannot be cloned [ID %d], Try another.', WPMUDEV_CLONER_LANG_DOMAIN ),
129 | $blog_details->blogname,
130 | $blog_id,
131 | network_admin_url( 'sites.php' )
132 | );
133 | wp_die( $message );
134 | }
135 | }
136 |
137 | /**
138 | * Render the settings page for this plugin.
139 | *
140 | * @since 1.0.0
141 | */
142 | public function display_admin_page() {
143 | global $current_site;
144 |
145 | $blog_id = absint( $_REQUEST['blog_id'] );
146 | $blog_details = get_blog_details( $blog_id );
147 |
148 |
149 | $domain = '';
150 | $subdomain = '';
151 | if ( is_subdomain_install() ) {
152 | if ( $blog_id == 1 ) {
153 | $domain = $blog_details->domain;
154 | }
155 | else {
156 | $_domain = explode( '.', $blog_details->domain, 2 );
157 | $subdomain = $_domain[0] . '.';
158 | $domain = $_domain[1];
159 | }
160 | }
161 | else {
162 | $domain = $blog_details->domain;
163 | $subdomain = $blog_details->path;
164 | }
165 |
166 |
167 | $selected_array = json_encode( array() );
168 | if ( $blog_id === 1 ) {
169 | $additional_tables = copier_get_additional_tables( $blog_id );
170 | $additional_tables_previous_selection = get_site_option( 'cloner_main_site_tables_selected', array() );
171 | $selected_array = json_encode( $additional_tables_previous_selection );
172 | }
173 |
174 |
175 |
176 | $form_url = add_query_arg(
177 | array(
178 | 'action' => 'clone'
179 | )
180 | );
181 |
182 | add_meta_box( 'cloner-destination', __( 'Destination', WPMUDEV_CLONER_LANG_DOMAIN), array( $this, 'destination_meta_box' ), 'cloner', 'normal' );
183 | add_meta_box( 'cloner-options', __( 'Options', WPMUDEV_CLONER_LANG_DOMAIN), array( $this, 'options_meta_box' ), 'cloner', 'normal' );
184 |
185 | if ( ! empty( $additional_tables ) && $blog_id == 1 )
186 | add_meta_box( 'cloner-advanced', __( 'Advanced Options', WPMUDEV_CLONER_LANG_DOMAIN), array( $this, 'advanced_options_meta_box' ), 'cloner', 'normal' );
187 |
188 | do_action( 'wpmudev_cloner_clone_site_screen' );
189 | include_once( 'views/clone-site.php' );
190 | }
191 |
192 | public function destination_meta_box() {
193 | global $current_site;
194 |
195 | include_once( 'views/meta-boxes/destination.php' );
196 | }
197 |
198 | public function options_meta_box() {
199 | $blog_id = absint( $_REQUEST['blog_id'] );
200 | $blog_public = get_blog_option( $blog_id, 'blog_public' );
201 | $blog_public = $blog_public == '1' ? true : false;
202 |
203 | include_once( 'views/meta-boxes/options.php' );
204 | }
205 |
206 | public function advanced_options_meta_box() {
207 | $blog_id = absint( $_REQUEST['blog_id'] );
208 | $additional_tables = copier_get_additional_tables( $blog_id );
209 | $additional_tables_previous_selection = get_site_option( 'cloner_main_site_tables_selected', array() );
210 |
211 | include_once( 'views/meta-boxes/advanced.php' );
212 | }
213 |
214 |
215 |
216 |
217 |
218 | /**
219 | * Sanitize the clone form
220 | */
221 | function sanitize_clone_form() {
222 | global $current_site;
223 |
224 | if ( empty( $_REQUEST['clone-site-submit'] ) )
225 | return;
226 |
227 | $blog_id = ! empty( $_REQUEST['blog_id'] ) ? absint( $_REQUEST['blog_id'] ) : 0;
228 | $blog_details = get_blog_details( $blog_id );
229 |
230 | check_admin_referer( 'clone-site-' . $blog_id, '_wpnonce_clone-site' );
231 |
232 | // Does the source blog exists?
233 | if ( ! $blog_id || empty( $blog_details ) ) {
234 | add_settings_error( 'cloner', 'source_blog_not_exist', __( 'The blog that you are trying to copy does not exist', WPMUDEV_CLONER_LANG_DOMAIN ) );
235 | return;
236 | }
237 |
238 | $selection = empty( $_REQUEST['cloner-clone-selection'] ) ? false : $_REQUEST['cloner-clone-selection'];
239 | $blog_title_selection = empty( $_REQUEST['cloner_blog_title'] ) ? 'clone' : $_REQUEST['cloner_blog_title'];
240 | $new_blog_title = ! empty( $_REQUEST['replace_blog_title'] ) ? $_REQUEST['replace_blog_title'] : 0;
241 |
242 | $args = array();
243 | $additional_tables_selected = array();
244 |
245 | if ( $blog_id === 1 ) {
246 | $additional_tables_selected = empty( $_REQUEST['additional_tables'] ) ? array() : $_REQUEST['additional_tables'];
247 | update_site_option( 'cloner_main_site_tables_selected', $additional_tables_selected );
248 | }
249 |
250 | switch ( $selection ) {
251 | case 'create': {
252 | // Checking if the blog already exists
253 | // Sanitize the domain/subfolder
254 | $blog = ! empty( $_REQUEST['blog_create'] ) ? $_REQUEST['blog_create'] : false;
255 |
256 | if ( ! $blog ) {
257 | add_settings_error( 'cloner', 'source_blog_not_exist', __( 'Please, insert a site name', WPMUDEV_CLONER_LANG_DOMAIN ) );
258 | return;
259 | }
260 |
261 | $domain = '';
262 | if ( preg_match( '|^([a-zA-Z0-9-])+$|', $blog ) )
263 | $domain = strtolower( $blog );
264 |
265 | if ( empty( $domain ) ) {
266 | add_settings_error( 'cloner', 'source_blog_not_exist', __( 'Missing or invalid site address.', WPMUDEV_CLONER_LANG_DOMAIN ) );
267 | return;
268 | }
269 |
270 | $destination_blog_details = get_blog_details( $domain );
271 |
272 | if ( ! empty( $destination_blog_details ) ) {
273 | add_settings_error( 'cloner', 'source_blog_not_exist', __( 'The blog already exists', WPMUDEV_CLONER_LANG_DOMAIN ) );
274 | return;
275 | }
276 |
277 | if ( 'clone' == $blog_title_selection ) {
278 | $new_blog_title = $blog_details->blogname;
279 | }
280 |
281 |
282 | do_action( 'wpmudev_cloner_pre_clone_actions', $selection, $blog_id, $args, false );
283 | $errors = get_settings_errors( 'cloner' );
284 | if ( ! empty( $errors ) )
285 | return;
286 |
287 | break;
288 | }
289 | case 'replace': {
290 | $destination_blog_id = isset( $_REQUEST['blog_replace'] ) ? absint( $_REQUEST['blog_replace'] ) : false;
291 |
292 | if ( ! $destination_blog_id ) {
293 | // try to check the blog name
294 | $blog_name = isset( $_REQUEST['blog_replace_autocomplete'] ) ? $_REQUEST['blog_replace_autocomplete'] : '';
295 | /// Hack for WordPress bug (https://core.trac.wordpress.org/ticket/34450)
296 | if ( is_subdomain_install() ) {
297 | $temp_domain = $current_site->domain;
298 | $current_site->domain = preg_replace( '|^www\.|', '', $current_site->domain );
299 | }
300 |
301 | $destination_blog_details = get_blog_details( $blog_name );
302 |
303 | if ( is_subdomain_install() )
304 | $current_site->domain = $temp_domain;
305 |
306 | if ( empty( $destination_blog_details ) ) {
307 | $destination_blog_id = false;
308 | }
309 | else {
310 | $destination_blog_id = $destination_blog_details->blog_id;
311 | }
312 |
313 | }
314 |
315 | if ( ! $destination_blog_id ) {
316 | add_settings_error( 'cloner', 'source_blog_not_exist', __( 'The site you are trying to replace does not exist', WPMUDEV_CLONER_LANG_DOMAIN ) );
317 | return;
318 | }
319 |
320 | if ( $destination_blog_id == $blog_id ) {
321 | add_settings_error( 'cloner', 'source_blog_not_exist', __( 'You cannot copy a blog to itself', WPMUDEV_CLONER_LANG_DOMAIN ) );
322 | return;
323 | }
324 |
325 | $destination_blog_details = get_blog_details( $destination_blog_id );
326 |
327 | if ( empty( $destination_blog_details ) ) {
328 | add_settings_error( 'cloner', 'source_blog_not_exist', __( 'The site you are trying to replace does not exist', WPMUDEV_CLONER_LANG_DOMAIN ) );
329 | return;
330 | }
331 |
332 |
333 | // The blog must be overwritten because it already exists
334 | $args['override'] = absint( $destination_blog_details->blog_id );
335 |
336 | if ( is_subdomain_install() ) {
337 | $domain = explode( '.', $destination_blog_details->domain );
338 | $domain = $domain[0];
339 | }
340 | else {
341 | $domain = str_replace( '/', '', $destination_blog_details->path );
342 | }
343 |
344 | do_action( 'wpmudev_cloner_pre_clone_actions', $selection, $blog_id, $args, $destination_blog_id );
345 | $errors = get_settings_errors( 'cloner' );
346 | if ( ! empty( $errors ) )
347 | return;
348 |
349 | if ( 'clone' == $blog_title_selection ) {
350 | $new_blog_title = $blog_details->blogname;
351 | }
352 | elseif ( 'keep' == $blog_title_selection ) {
353 | $new_blog_title = $destination_blog_details->blogname;
354 | }
355 |
356 | $blog_public = false;
357 | if ( isset( $_REQUEST['cloner_blog_public'] ) )
358 | $blog_public = true;
359 |
360 |
361 | if ( ! isset( $_REQUEST['confirm'] ) ) {
362 | // Display a confirmation screen.
363 | $back_url = add_query_arg(
364 | array(
365 | 'page' => 'clone_site',
366 | 'blog_id' => $blog_id
367 | ),
368 | network_admin_url( 'admin.php' )
369 | );
370 |
371 | include_once( 'views/confirmation.php' );
372 |
373 | wp_die();
374 | }
375 |
376 | break;
377 | }
378 | default: {
379 | $result = apply_filters( 'wpmudev_cloner_pre_clone_actions_switch_default', false, $selection, $blog_title_selection, $new_blog_title, $blog_id, $blog_details );
380 |
381 | if ( is_wp_error( $result ) ) {
382 | add_settings_error('cloner', $result->get_error_code(), $result->get_error_message());
383 | return;
384 | }
385 |
386 | if ( ! $result ) {
387 | add_settings_error('cloner', 'cloner_error', __( 'Unknown error', WPMUDEV_COPIER_LANG_DOMAIN ) );
388 | return;
389 | }
390 |
391 | if ( ! is_array( $result ) )
392 | return;
393 |
394 | extract( $result );
395 |
396 | break;
397 | }
398 | }
399 |
400 | $args['new_blog_title'] = empty( $new_blog_title ) ? false : $new_blog_title;
401 |
402 |
403 | // New Blog Templates integration
404 | if ( class_exists( 'blog_templates' ) ) {
405 | $action_order = defined('NBT_APPLY_TEMPLATE_ACTION_ORDER') && NBT_APPLY_TEMPLATE_ACTION_ORDER ? NBT_APPLY_TEMPLATE_ACTION_ORDER : 9999;
406 | // Set to *very high* so this runs after every other action; also, accepts 6 params so we can get to meta
407 | remove_action( 'wpmu_new_blog', array( 'blog_templates', 'set_blog_defaults'), apply_filters('blog_templates-actions-action_order', $action_order), 6);
408 | }
409 |
410 | $current_site = get_current_site();
411 |
412 | if ( empty( $new_domain ) ) {
413 | if ( is_subdomain_install() ) {
414 | $new_domain = $domain . '.' . preg_replace( '|^www\.|', '', $current_site->domain );
415 | }
416 | else {
417 | $new_domain = $current_site->domain;
418 | }
419 | }
420 |
421 | if ( empty( $new_path ) ) {
422 | if ( is_subdomain_install() ) {
423 | $new_path = '';
424 | }
425 | else {
426 | $new_path = $current_site->path . trailingslashit( $domain ); //$path = '/' . $domain; // Do NOT assume the root to be server root
427 | }
428 | }
429 |
430 | $args['additional_tables'] = $additional_tables_selected;
431 |
432 | // Set everything needed to clone the site
433 | $result = $this->pre_clone_actions( $blog_id, $new_domain, $new_path, $args );
434 |
435 | if ( is_integer( $result ) ) {
436 | $redirect_to = get_admin_url( $result );
437 | wp_redirect( $redirect_to );
438 | exit;
439 | }
440 |
441 | if ( is_wp_error( $result ) ) {
442 | add_settings_error( 'cloner', 'error_creating_site', $result->get_error_message() );
443 | }
444 |
445 | }
446 |
447 | /**
448 | * Set everything needed to clone a site:
449 | *
450 | * Create a new empty site if we are not overrriding an existsing blog
451 | * Change the blog Name
452 | */
453 | public function pre_clone_actions( $source_blog_id, $domain, $path, $args ) {
454 | global $wpdb;
455 | $network_id = get_current_network_id();
456 |
457 | $defaults = array(
458 | 'override' => false,
459 | 'new_blog_title' => false
460 | );
461 | $args = wp_parse_args( $args, $defaults );
462 | extract( $args );
463 |
464 | $source_blog_details = get_blog_details( $source_blog_id );
465 |
466 | $blog_title = empty( $new_blog_title ) ? $source_blog_details->blogname : $new_blog_title;
467 |
468 | $blog_details = get_blog_details( $override );
469 | if ( empty( $blog_details ) )
470 | $override = false;
471 |
472 | $new_blog_id = $override;
473 | if ( ! $override ) {
474 | // Not overrriding, let's create an empty blog
475 | $new_blog_id = $this->create_empty_blog( $domain, $path, $blog_title, $network_id );
476 | }
477 |
478 |
479 | if ( ! is_integer( $new_blog_id ) ) {
480 | return new WP_Error( 'create_empty_blog', strip_tags( $new_blog_id ) );
481 | }
482 |
483 | if ( $blog_title ) {
484 | update_blog_option( $new_blog_id, 'blogname', $blog_title );
485 | }
486 |
487 | if ( is_main_site( $source_blog_id ) || $source_blog_id === 1 )
488 | add_action( 'copier_set_copier_args', array( $this, 'set_copier_tables_for_main_site' ), 1 );
489 |
490 | add_filter( 'copier_set_copier_args', array( $this, 'set_copier_args' ) );
491 |
492 | // And set copier arguments
493 | $result = copier_set_copier_args( $source_blog_id, $new_blog_id );
494 |
495 | return $new_blog_id;
496 | }
497 |
498 | function create_empty_blog( $domain, $path, $blog_title, $site_id = 1 ) {
499 | if ( empty($path) )
500 | $path = '/';
501 |
502 | // Check if the domain has been used already. We should return an error message.
503 | if ( domain_exists($domain, $path, $site_id) )
504 | return false;
505 |
506 | // Need to back up wpdb table names, and create a new wp_blogs entry for new blog.
507 | // Need to get blog_id from wp_blogs, and create new table names.
508 | // Must restore table names at the end of function.
509 | /*
510 | if ( ! $blog_id = insert_blog($domain, $path, $site_id) )
511 | return false;
512 |
513 | switch_to_blog($blog_id);
514 | install_blog($blog_id);
515 | restore_current_blog();
516 | */
517 |
518 | if ( ! $blog_id = wpmu_create_blog( $domain, $path, $blog_title, get_current_user_id(), array(), $site_id ) ){
519 | return false;
520 | }
521 |
522 | return $blog_id;
523 | }
524 |
525 | public function set_copier_args( $args ) {
526 | if ( isset( $_REQUEST['cloner_blog_public'] ) )
527 | $args['blog_public'] = '0';
528 | else
529 | $args['blog_public'] = '1';
530 |
531 | return $args;
532 | }
533 |
534 | /**
535 | * If we are copying the main site, we need to exclude network tables
536 | * but this is up to the user, so let's make whatever we can.
537 | *
538 | * @param Array $option The current options to clone
539 | * @return Array new clone options
540 | */
541 | function set_copier_tables_for_main_site( $option ) {
542 | if ( isset( $option['to_copy']['tables'] ) ) {
543 | // Get the tables selected, they should be saved already
544 | $option['to_copy']['tables'] = array( 'tables' => get_site_option( 'cloner_main_site_tables_selected', array() ) );
545 | }
546 |
547 | return $option;
548 | }
549 |
550 | }
551 |
--------------------------------------------------------------------------------
/admin/assets/jquery-multi-select/js/jquery-multi-select.dev.js:
--------------------------------------------------------------------------------
1 | /*
2 | * MultiSelect v0.9.11
3 | * Copyright (c) 2012 Louis Cuny
4 | *
5 | * This program is free software. It comes without any warranty, to
6 | * the extent permitted by applicable law. You can redistribute it
7 | * and/or modify it under the terms of the WTFPL
8 | * To Public License, Version 2, as published by Sam Hocevar. See
9 | * http://sam.zoy.org/wtfpl/COPYING for more details.
10 | */
11 |
12 | !function ($) {
13 |
14 | "use strict";
15 |
16 |
17 | /* MULTISELECT CLASS DEFINITION
18 | * ====================== */
19 |
20 | var MultiSelect = function (element, options) {
21 | this.options = options;
22 | this.$element = $(element);
23 | this.$container = $('', { 'class': "ms-container" });
24 | this.$selectableContainer = $('', { 'class': 'ms-selectable' });
25 | this.$selectionContainer = $('', { 'class': 'ms-selection' });
26 | this.$selectableUl = $('', { 'class': "ms-list", 'tabindex' : '-1' });
27 | this.$selectionUl = $('', { 'class': "ms-list", 'tabindex' : '-1' });
28 | this.scrollTo = 0;
29 | this.elemsSelector = 'li:visible:not(.ms-optgroup-label,.ms-optgroup-container,.'+options.disabledClass+')';
30 | };
31 |
32 | MultiSelect.prototype = {
33 | constructor: MultiSelect,
34 |
35 | init: function(){
36 | var that = this,
37 | ms = this.$element;
38 |
39 | if (ms.next('.ms-container').length === 0){
40 | ms.css({ position: 'absolute', left: '-9999px' });
41 | ms.attr('id', ms.attr('id') ? ms.attr('id') : Math.ceil(Math.random()*1000)+'multiselect');
42 | this.$container.attr('id', 'ms-'+ms.attr('id'));
43 | this.$container.addClass(that.options.cssClass);
44 | ms.find('option').each(function(){
45 | that.generateLisFromOption(this);
46 | });
47 |
48 | this.$selectionUl.find('.ms-optgroup-label').hide();
49 |
50 | if (that.options.selectableHeader){
51 | that.$selectableContainer.append(that.options.selectableHeader);
52 | }
53 | that.$selectableContainer.append(that.$selectableUl);
54 | if (that.options.selectableFooter){
55 | that.$selectableContainer.append(that.options.selectableFooter);
56 | }
57 |
58 | if (that.options.selectionHeader){
59 | that.$selectionContainer.append(that.options.selectionHeader);
60 | }
61 | that.$selectionContainer.append(that.$selectionUl);
62 | if (that.options.selectionFooter){
63 | that.$selectionContainer.append(that.options.selectionFooter);
64 | }
65 |
66 | that.$container.append(that.$selectableContainer);
67 | that.$container.append(that.$selectionContainer);
68 | ms.after(that.$container);
69 |
70 | that.activeMouse(that.$selectableUl);
71 | that.activeKeyboard(that.$selectableUl);
72 |
73 | var action = that.options.dblClick ? 'dblclick' : 'click';
74 |
75 | that.$selectableUl.on(action, '.ms-elem-selectable', function(){
76 | that.select($(this).data('ms-value'));
77 | });
78 | that.$selectionUl.on(action, '.ms-elem-selection', function(){
79 | that.deselect($(this).data('ms-value'));
80 | });
81 |
82 | that.activeMouse(that.$selectionUl);
83 | that.activeKeyboard(that.$selectionUl);
84 |
85 | ms.on('focus', function(){
86 | that.$selectableUl.focus();
87 | })
88 | }
89 |
90 | var selectedValues = ms.find('option:selected').map(function(){ return $(this).val(); }).get();
91 | that.select(selectedValues, 'init');
92 |
93 | if (typeof that.options.afterInit === 'function') {
94 | that.options.afterInit.call(this, this.$container);
95 | }
96 | },
97 |
98 | 'generateLisFromOption' : function(option, index, $container){
99 | var that = this,
100 | ms = that.$element,
101 | attributes = "",
102 | $option = $(option);
103 |
104 | for (var cpt = 0; cpt < option.attributes.length; cpt++){
105 | var attr = option.attributes[cpt];
106 |
107 | if(attr.name !== 'value' && attr.name !== 'disabled'){
108 | attributes += attr.name+'="'+attr.value+'" ';
109 | }
110 | }
111 | var selectableLi = $(''+that.escapeHTML($option.text())+''),
112 | selectedLi = selectableLi.clone(),
113 | value = $option.val(),
114 | elementId = that.sanitize(value);
115 |
116 | selectableLi
117 | .data('ms-value', value)
118 | .addClass('ms-elem-selectable')
119 | .attr('id', elementId+'-selectable');
120 |
121 | selectedLi
122 | .data('ms-value', value)
123 | .addClass('ms-elem-selection')
124 | .attr('id', elementId+'-selection')
125 | .hide();
126 |
127 | if ($option.prop('disabled') || ms.prop('disabled')){
128 | selectedLi.addClass(that.options.disabledClass);
129 | selectableLi.addClass(that.options.disabledClass);
130 | }
131 |
132 | var $optgroup = $option.parent('optgroup');
133 |
134 | if ($optgroup.length > 0){
135 | var optgroupLabel = $optgroup.attr('label'),
136 | optgroupId = that.sanitize(optgroupLabel),
137 | $selectableOptgroup = that.$selectableUl.find('#optgroup-selectable-'+optgroupId),
138 | $selectionOptgroup = that.$selectionUl.find('#optgroup-selection-'+optgroupId);
139 |
140 | if ($selectableOptgroup.length === 0){
141 | var optgroupContainerTpl = '',
142 | optgroupTpl = '';
143 |
144 | $selectableOptgroup = $(optgroupContainerTpl);
145 | $selectionOptgroup = $(optgroupContainerTpl);
146 | $selectableOptgroup.attr('id', 'optgroup-selectable-'+optgroupId);
147 | $selectionOptgroup.attr('id', 'optgroup-selection-'+optgroupId);
148 | $selectableOptgroup.append($(optgroupTpl));
149 | $selectionOptgroup.append($(optgroupTpl));
150 | if (that.options.selectableOptgroup){
151 | $selectableOptgroup.find('.ms-optgroup-label').on('click', function(){
152 | var values = $optgroup.children(':not(:selected, :disabled)').map(function(){ return $(this).val() }).get();
153 | that.select(values);
154 | });
155 | $selectionOptgroup.find('.ms-optgroup-label').on('click', function(){
156 | var values = $optgroup.children(':selected:not(:disabled)').map(function(){ return $(this).val() }).get();
157 | that.deselect(values);
158 | });
159 | }
160 | that.$selectableUl.append($selectableOptgroup);
161 | that.$selectionUl.append($selectionOptgroup);
162 | }
163 | index = index == undefined ? $selectableOptgroup.find('ul').children().length : index + 1;
164 | selectableLi.insertAt(index, $selectableOptgroup.children());
165 | selectedLi.insertAt(index, $selectionOptgroup.children());
166 | } else {
167 | index = index == undefined ? that.$selectableUl.children().length : index;
168 |
169 | selectableLi.insertAt(index, that.$selectableUl);
170 | selectedLi.insertAt(index, that.$selectionUl);
171 | }
172 | },
173 |
174 | 'addOption' : function(options){
175 | var that = this;
176 |
177 | if (options.value) options = [options];
178 | $.each(options, function(index, option){
179 | if (option.value && that.$element.find("option[value='"+option.value+"']").length === 0){
180 | var $option = $(''),
181 | index = parseInt((typeof option.index === 'undefined' ? that.$element.children().length : option.index)),
182 | $container = option.nested == undefined ? that.$element : $("optgroup[label='"+option.nested+"']")
183 |
184 | $option.insertAt(index, $container);
185 | that.generateLisFromOption($option.get(0), index, option.nested);
186 | }
187 | })
188 | },
189 |
190 | 'escapeHTML' : function(text){
191 | return $("").text(text).html();
192 | },
193 |
194 | 'activeKeyboard' : function($list){
195 | var that = this;
196 |
197 | $list.on('focus', function(){
198 | $(this).addClass('ms-focus');
199 | })
200 | .on('blur', function(){
201 | $(this).removeClass('ms-focus');
202 | })
203 | .on('keydown', function(e){
204 | switch (e.which) {
205 | case 40:
206 | case 38:
207 | e.preventDefault();
208 | e.stopPropagation();
209 | that.moveHighlight($(this), (e.which === 38) ? -1 : 1);
210 | return;
211 | case 37:
212 | case 39:
213 | e.preventDefault();
214 | e.stopPropagation();
215 | that.switchList($list);
216 | return;
217 | case 9:
218 | if(that.$element.is('[tabindex]')){
219 | e.preventDefault();
220 | var tabindex = parseInt(that.$element.attr('tabindex'), 10);
221 | tabindex = (e.shiftKey) ? tabindex-1 : tabindex+1;
222 | $('[tabindex="'+(tabindex)+'"]').focus();
223 | return;
224 | }else{
225 | if(e.shiftKey){
226 | that.$element.trigger('focus');
227 | }
228 | }
229 | }
230 | if($.inArray(e.which, that.options.keySelect) > -1){
231 | e.preventDefault();
232 | e.stopPropagation();
233 | that.selectHighlighted($list);
234 | return;
235 | }
236 | });
237 | },
238 |
239 | 'moveHighlight': function($list, direction){
240 | var $elems = $list.find(this.elemsSelector),
241 | $currElem = $elems.filter('.ms-hover'),
242 | $nextElem = null,
243 | elemHeight = $elems.first().outerHeight(),
244 | containerHeight = $list.height(),
245 | containerSelector = '#'+this.$container.prop('id');
246 |
247 | $elems.removeClass('ms-hover');
248 | if (direction === 1){ // DOWN
249 |
250 | $nextElem = $currElem.nextAll(this.elemsSelector).first();
251 | if ($nextElem.length === 0){
252 | var $optgroupUl = $currElem.parent();
253 |
254 | if ($optgroupUl.hasClass('ms-optgroup')){
255 | var $optgroupLi = $optgroupUl.parent(),
256 | $nextOptgroupLi = $optgroupLi.next(':visible');
257 |
258 | if ($nextOptgroupLi.length > 0){
259 | $nextElem = $nextOptgroupLi.find(this.elemsSelector).first();
260 | } else {
261 | $nextElem = $elems.first();
262 | }
263 | } else {
264 | $nextElem = $elems.first();
265 | }
266 | }
267 | } else if (direction === -1){ // UP
268 |
269 | $nextElem = $currElem.prevAll(this.elemsSelector).first();
270 | if ($nextElem.length === 0){
271 | var $optgroupUl = $currElem.parent();
272 |
273 | if ($optgroupUl.hasClass('ms-optgroup')){
274 | var $optgroupLi = $optgroupUl.parent(),
275 | $prevOptgroupLi = $optgroupLi.prev(':visible');
276 |
277 | if ($prevOptgroupLi.length > 0){
278 | $nextElem = $prevOptgroupLi.find(this.elemsSelector).last();
279 | } else {
280 | $nextElem = $elems.last();
281 | }
282 | } else {
283 | $nextElem = $elems.last();
284 | }
285 | }
286 | }
287 | if ($nextElem.length > 0){
288 | $nextElem.addClass('ms-hover');
289 | var scrollTo = $list.scrollTop() + $nextElem.position().top -
290 | containerHeight / 2 + elemHeight / 2;
291 |
292 | $list.scrollTop(scrollTo);
293 | }
294 | },
295 |
296 | 'selectHighlighted' : function($list){
297 | var $elems = $list.find(this.elemsSelector),
298 | $highlightedElem = $elems.filter('.ms-hover').first();
299 |
300 | if ($highlightedElem.length > 0){
301 | if ($list.parent().hasClass('ms-selectable')){
302 | this.select($highlightedElem.data('ms-value'));
303 | } else {
304 | this.deselect($highlightedElem.data('ms-value'));
305 | }
306 | $elems.removeClass('ms-hover');
307 | }
308 | },
309 |
310 | 'switchList' : function($list){
311 | $list.blur();
312 | this.$container.find(this.elemsSelector).removeClass('ms-hover');
313 | if ($list.parent().hasClass('ms-selectable')){
314 | this.$selectionUl.focus();
315 | } else {
316 | this.$selectableUl.focus();
317 | }
318 | },
319 |
320 | 'activeMouse' : function($list){
321 | var that = this;
322 |
323 | $('body').on('mouseenter', that.elemsSelector, function(){
324 | $(this).parents('.ms-container').find(that.elemsSelector).removeClass('ms-hover');
325 | $(this).addClass('ms-hover');
326 | });
327 |
328 | $('body').on('mouseleave', that.elemsSelector, function () {
329 | $(this).parents('.ms-container').find(that.elemsSelector).removeClass('ms-hover');;
330 | });
331 | },
332 |
333 | 'refresh' : function() {
334 | this.destroy();
335 | this.$element.multiSelect(this.options);
336 | },
337 |
338 | 'destroy' : function(){
339 | $("#ms-"+this.$element.attr("id")).remove();
340 | this.$element.css('position', '').css('left', '')
341 | this.$element.removeData('multiselect');
342 | },
343 |
344 | 'select' : function(value, method){
345 | if (typeof value === 'string'){ value = [value]; }
346 |
347 | var that = this,
348 | ms = this.$element,
349 | msIds = $.map(value, function(val){ return(that.sanitize(val)); }),
350 | selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable').filter(':not(.'+that.options.disabledClass+')'),
351 | selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection').filter(':not(.'+that.options.disabledClass+')'),
352 | options = ms.find('option:not(:disabled)').filter(function(){ return($.inArray(this.value, value) > -1); });
353 |
354 | if (method === 'init'){
355 | selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
356 | selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection');
357 | }
358 |
359 | if (selectables.length > 0){
360 | selectables.addClass('ms-selected').hide();
361 | selections.addClass('ms-selected').show();
362 |
363 | options.prop('selected', true);
364 |
365 | that.$container.find(that.elemsSelector).removeClass('ms-hover');
366 |
367 | var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
368 | if (selectableOptgroups.length > 0){
369 | selectableOptgroups.each(function(){
370 | var selectablesLi = $(this).find('.ms-elem-selectable');
371 | if (selectablesLi.length === selectablesLi.filter('.ms-selected').length){
372 | $(this).find('.ms-optgroup-label').hide();
373 | }
374 | });
375 |
376 | var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
377 | selectionOptgroups.each(function(){
378 | var selectionsLi = $(this).find('.ms-elem-selection');
379 | if (selectionsLi.filter('.ms-selected').length > 0){
380 | $(this).find('.ms-optgroup-label').show();
381 | }
382 | });
383 | } else {
384 | if (that.options.keepOrder && method !== 'init'){
385 | var selectionLiLast = that.$selectionUl.find('.ms-selected');
386 | if((selectionLiLast.length > 1) && (selectionLiLast.last().get(0) != selections.get(0))) {
387 | selections.insertAfter(selectionLiLast.last());
388 | }
389 | }
390 | }
391 | if (method !== 'init'){
392 | ms.trigger('change');
393 | if (typeof that.options.afterSelect === 'function') {
394 | that.options.afterSelect.call(this, value);
395 | }
396 | }
397 | }
398 | },
399 |
400 | 'deselect' : function(value){
401 | if (typeof value === 'string'){ value = [value]; }
402 |
403 | var that = this,
404 | ms = this.$element,
405 | msIds = $.map(value, function(val){ return(that.sanitize(val)); }),
406 | selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
407 | selections = this.$selectionUl.find('#' + msIds.join('-selection, #')+'-selection').filter('.ms-selected').filter(':not(.'+that.options.disabledClass+')'),
408 | options = ms.find('option').filter(function(){ return($.inArray(this.value, value) > -1); });
409 |
410 | if (selections.length > 0){
411 | selectables.removeClass('ms-selected').show();
412 | selections.removeClass('ms-selected').hide();
413 | options.prop('selected', false);
414 |
415 | that.$container.find(that.elemsSelector).removeClass('ms-hover');
416 |
417 | var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
418 | if (selectableOptgroups.length > 0){
419 | selectableOptgroups.each(function(){
420 | var selectablesLi = $(this).find('.ms-elem-selectable');
421 | if (selectablesLi.filter(':not(.ms-selected)').length > 0){
422 | $(this).find('.ms-optgroup-label').show();
423 | }
424 | });
425 |
426 | var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
427 | selectionOptgroups.each(function(){
428 | var selectionsLi = $(this).find('.ms-elem-selection');
429 | if (selectionsLi.filter('.ms-selected').length === 0){
430 | $(this).find('.ms-optgroup-label').hide();
431 | }
432 | });
433 | }
434 | ms.trigger('change');
435 | if (typeof that.options.afterDeselect === 'function') {
436 | that.options.afterDeselect.call(this, value);
437 | }
438 | }
439 | },
440 |
441 | 'select_all' : function(){
442 | var ms = this.$element,
443 | values = ms.val();
444 |
445 | ms.find('option:not(":disabled")').prop('selected', true);
446 | this.$selectableUl.find('.ms-elem-selectable').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').hide();
447 | this.$selectionUl.find('.ms-optgroup-label').show();
448 | this.$selectableUl.find('.ms-optgroup-label').hide();
449 | this.$selectionUl.find('.ms-elem-selection').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').show();
450 | this.$selectionUl.focus();
451 | ms.trigger('change');
452 | if (typeof this.options.afterSelect === 'function') {
453 | var selectedValues = $.grep(ms.val(), function(item){
454 | return $.inArray(item, values) < 0;
455 | });
456 | this.options.afterSelect.call(this, selectedValues);
457 | }
458 | },
459 |
460 | 'deselect_all' : function(){
461 | var ms = this.$element,
462 | values = ms.val();
463 |
464 | ms.find('option').prop('selected', false);
465 | this.$selectableUl.find('.ms-elem-selectable').removeClass('ms-selected').show();
466 | this.$selectionUl.find('.ms-optgroup-label').hide();
467 | this.$selectableUl.find('.ms-optgroup-label').show();
468 | this.$selectionUl.find('.ms-elem-selection').removeClass('ms-selected').hide();
469 | this.$selectableUl.focus();
470 | ms.trigger('change');
471 | if (typeof this.options.afterDeselect === 'function') {
472 | this.options.afterDeselect.call(this, values);
473 | }
474 | },
475 |
476 | sanitize: function(value){
477 | var hash = 0, i, character;
478 | if (value.length == 0) return hash;
479 | var ls = 0;
480 | for (i = 0, ls = value.length; i < ls; i++) {
481 | character = value.charCodeAt(i);
482 | hash = ((hash<<5)-hash)+character;
483 | hash |= 0; // Convert to 32bit integer
484 | }
485 | return hash;
486 | }
487 | };
488 |
489 | /* MULTISELECT PLUGIN DEFINITION
490 | * ======================= */
491 |
492 | $.fn.multiSelect = function () {
493 | var option = arguments[0],
494 | args = arguments;
495 |
496 | return this.each(function () {
497 | var $this = $(this),
498 | data = $this.data('multiselect'),
499 | options = $.extend({}, $.fn.multiSelect.defaults, $this.data(), typeof option === 'object' && option);
500 |
501 | if (!data){ $this.data('multiselect', (data = new MultiSelect(this, options))); }
502 |
503 | if (typeof option === 'string'){
504 | data[option](args[1]);
505 | } else {
506 | data.init();
507 | }
508 | });
509 | };
510 |
511 | $.fn.multiSelect.defaults = {
512 | keySelect: [32],
513 | selectableOptgroup: false,
514 | disabledClass : 'disabled',
515 | dblClick : false,
516 | keepOrder: false,
517 | cssClass: ''
518 | };
519 |
520 | $.fn.multiSelect.Constructor = MultiSelect;
521 |
522 | $.fn.insertAt = function(index, $parent) {
523 | return this.each(function() {
524 | if (index === 0) {
525 | $parent.prepend(this);
526 | } else {
527 | $parent.children().eq(index - 1).after(this);
528 | }
529 | });
530 | }
531 |
532 | }(window.jQuery);
--------------------------------------------------------------------------------