├── .gitignore ├── admin ├── views │ ├── index.php │ ├── meta-boxes │ │ ├── options.php │ │ ├── advanced.php │ │ └── destination.php │ ├── confirmation.php │ ├── settings.php │ └── clone-site.php ├── assets │ ├── images │ │ ├── step_1.jpg │ │ └── step_2.jpg │ └── jquery-multi-select │ │ ├── img │ │ └── switch.png │ │ ├── css │ │ └── multi-select.css │ │ └── js │ │ ├── jquery-multi-select.js │ │ └── jquery-multi-select.dev.js ├── ajax.php ├── cloner-admin-settings.php └── cloner-admin-clone-site.php ├── .gitmodules ├── helpers ├── general.php └── settings.php ├── tmp └── log-search.log ├── uninstall.php ├── tests ├── test-clone-menus.php └── bootstrap.php ├── copier-filters.php ├── copier ├── integration │ ├── jetpack.php │ ├── wpmu-dev-seo.php │ ├── cookie-notice.php │ ├── epanel.php │ ├── popover.php │ ├── wp-https.php │ ├── ubermenu.php │ ├── revslider.php │ ├── custom-sidebars-pro.php │ ├── buddypress.php │ ├── membership.php │ ├── shortcodes-ultimate.php │ ├── autoblog.php │ ├── marketpress.php │ └── woocommerce.php ├── class.copier-widgets.php ├── class.copier-posts.php ├── class.copier-nav_menu_item.php ├── class.copier.php ├── class.copier-pages.php ├── class.copier-users.php ├── class.copier-comments.php ├── integration.php ├── class.copier-cpts.php ├── class.copier-tables.php ├── class.copier-settings.php ├── class.copier-menus.php └── class.copier-post-types.php ├── .travis.yml ├── phpunit.xml ├── package.json ├── dev-README.md ├── bin └── install-wp-tests.sh ├── integration ├── views │ └── multi-domains-destination-meta-box.php └── integration.php ├── README.md ├── Gruntfile.js ├── changelog.txt ├── cloner.php └── lang └── wpmudev-cloner.pot /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ -------------------------------------------------------------------------------- /admin/views/index.php: -------------------------------------------------------------------------------- 1 | factory->blog->create_object( 7 | $this->factory->blog->generate_args() 8 | ); 9 | 10 | 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /copier-filters.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | source_blog_id ); 13 | $sidebars_widgets = wp_get_sidebars_widgets(); 14 | restore_current_blog(); 15 | 16 | wp_set_sidebars_widgets( $sidebars_widgets ); 17 | 18 | return true; 19 | 20 | } 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /copier/integration/ubermenu.php: -------------------------------------------------------------------------------- 1 | $bp_source_page_id ) { 18 | if ( isset( $posts_mapping[ $bp_source_page_id ] ) ) 19 | $new_options[ $bp_page_slug ] = $posts_mapping[ $bp_source_page_id ]; 20 | } 21 | 22 | update_option( 'bp-pages', $new_options ); 23 | } 24 | 25 | add_action( 'wpmudev_copier-copied-posts', 'copier_copy_buddypress_pages_option', 10, 2 ); 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloner", 3 | "version": "1.8.0-beta-2", 4 | "main": "cloner.php", 5 | "scripts": { 6 | "build": "grunt build" 7 | }, 8 | "projectEditUrl": "https://premium.wpmudev.org/wp-admin/edit.php?post_type=project&page=projects-manage&manage_files=679841", 9 | "description": "", 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "grunt": "~0.4.5", 13 | "grunt-checktextdomain": "^0.1.1", 14 | "grunt-contrib-clean": "~0.5.0", 15 | "grunt-contrib-compress": "~0.8.0", 16 | "grunt-contrib-copy": "~0.5.0", 17 | "grunt-contrib-jshint": "^1.0.0", 18 | "grunt-contrib-uglify": "^1.0.1", 19 | "grunt-contrib-watch": "^1.0.0", 20 | "grunt-exec": "~0.4.5", 21 | "grunt-open": "^0.2.3", 22 | "grunt-phpunit": "^0.3.6", 23 | "grunt-potomo": "~2.1.0", 24 | "grunt-search": "^0.1.8", 25 | "grunt-wp-i18n": "~0.5.0", 26 | "load-grunt-tasks": "~0.2.0" 27 | }, 28 | "author": "WPMU DEV", 29 | "license": "GPLv2" 30 | } 31 | -------------------------------------------------------------------------------- /admin/views/meta-boxes/options.php: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

5 | 9 | 10 |

11 | 12 |

13 | 17 | 18 | -------------------------------------------------------------------------------- /copier/integration/membership.php: -------------------------------------------------------------------------------- 1 | add_cap('membershipadmin'); 27 | $user->add_cap('membershipadmindashboard'); 28 | $user->add_cap('membershipadminmembers'); 29 | $user->add_cap('membershipadminlevels'); 30 | $user->add_cap('membershipadminsubscriptions'); 31 | $user->add_cap('membershipadmincoupons'); 32 | $user->add_cap('membershipadminpurchases'); 33 | $user->add_cap('membershipadmincommunications'); 34 | $user->add_cap('membershipadmingroups'); 35 | $user->add_cap('membershipadminpings'); 36 | $user->add_cap('membershipadmingateways'); 37 | $user->add_cap('membershipadminoptions'); 38 | $user->add_cap('membershipadminupdatepermissions'); 39 | update_user_meta( $user_id, 'membership_permissions_updated', 'yes'); 40 | restore_current_blog(); 41 | } 42 | add_action( 'wpmudev_copier-copy-options', 'copier_add_membership_caps', 10, 2 ); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /copier/integration/shortcodes-ultimate.php: -------------------------------------------------------------------------------- 1 | 'post_id', 28 | 'su_permalink' => 'id', 29 | 'su_subpages' => 'p', 30 | 'su_meta' => 'post_id', 31 | 'su_posts' => 'id' 32 | ); 33 | 34 | foreach ( $shortcodes as $shortcode => $attribute ) { 35 | // Get all posts that may have the content block shortcode in them 36 | $all_posts = copier_search_posts_with_shortcode( $shortcode ); 37 | 38 | copier_replace_shortcode_attributes( $shortcode, $all_posts, $attribute, $posts_map ); 39 | } 40 | 41 | } 42 | add_action( 'wpmudev_copier-copy-after_copying', 'copier_replace_shortcodes_ultimate_post_shortcode', 10, 3 ); 43 | } 44 | -------------------------------------------------------------------------------- /admin/views/meta-boxes/advanced.php: -------------------------------------------------------------------------------- 1 |

select tables you want cloned. Beware, network-only tables can take up a lot of space and a long time to clone.', WPMUDEV_CLONER_LANG_DOMAIN ); ?>

2 | 13 | 33 |
34 | 35 | -------------------------------------------------------------------------------- /copier/class.copier-posts.php: -------------------------------------------------------------------------------- 1 | type = 'post'; 15 | add_filter( 'wpmudev_copier_get_source_posts_args', array( $this, 'set_get_source_posts_args' ) ); 16 | } 17 | 18 | public function get_default_args() { 19 | return array( 20 | 'categories' => 'all', 21 | 'update_date' => false, 22 | 'batch_size' => false, // Allows to copy posts in batches 23 | 'batch_page' => false // Indicates what number of batch are we trying to copy 24 | ); 25 | } 26 | 27 | /** 28 | * Set WP_Query arguments in the parent class 29 | * 30 | * @param Array $args Current arguments 31 | * @return Array New arguments 32 | */ 33 | public function set_get_source_posts_args( $args ) { 34 | global $wpdb; 35 | 36 | if ( isset( $this->args['categories'] ) && is_array( $this->args['categories'] ) && count( $this->args['categories'] ) > 0 ) { 37 | // Are we only copying a few categories? 38 | $args['category__in'] = $this->args['categories']; 39 | } 40 | 41 | return $args; 42 | } 43 | 44 | 45 | } 46 | } -------------------------------------------------------------------------------- /dev-README.md: -------------------------------------------------------------------------------- 1 | # Cloner 2 | 3 | ## Development guide 4 | 5 | ### Branches 6 | There are two main branches: 7 | 8 | * `development`: Every development work should be done here first 9 | * `master`: Whenever a new version is ready, merge `development` branch into this one and push 10 | 11 | ### Development workflow 12 | 13 | Cloner contains a few automated tasks that helps the developer to make faster and less buggy releases. 14 | 15 | #### Requirements: 16 | 17 | 1. Install nodejs: [https://github.com/joyent/node/wiki/installing-node.js-via-package-manager] 18 | 2. Install Grunt globally `sudo npm install -g grunt` 19 | 3. Execute `git submodule update --init --recursive` to download every submodule 20 | 21 | #### Dependencies installation 22 | 23 | Cloner requires a few node dependencies for development. Use `npm install` to install all of them. 24 | 25 | #### Releasing versions 26 | 27 | 1. Make sure that the version in `cloner.php` matches with the version in `package.json`, otherwise the build will fail. 28 | 2. If you have make changes in copier folder, you'll need to create a commit for that submodule also. 29 | 3. Update all Git submodules with `git submodule update --remote` 30 | 4. Now execute `npm run build`. A new folder called `build` will be created where you can grab the zip file for the new version. 31 | 5. Language files, JS Lint and text domains verification are done during the execution of this script so developer doesn't need to worry about these tasks. 32 | 33 | Don't forget to create a new tag in Git! 34 | -------------------------------------------------------------------------------- /copier/class.copier-nav_menu_item.php: -------------------------------------------------------------------------------- 1 | type = 'nav_menu_item'; 15 | add_filter( 'wpmudev_copier_get_source_posts_args', array( $this, 'set_get_source_posts_args' ) ); 16 | } 17 | 18 | public function get_default_args() { 19 | return array( 20 | 'categories' => 'all', 21 | 'update_date' => false, 22 | 'batch_size' => false, // Allows to copy posts in batches 23 | 'batch_page' => false // Indicates what number of batch are we trying to copy 24 | ); 25 | } 26 | 27 | /** 28 | * Set WP_Query arguments in the parent class 29 | * 30 | * @param Array $args Current arguments 31 | * @return Array New arguments 32 | */ 33 | public function set_get_source_posts_args( $args ) { 34 | global $wpdb; 35 | 36 | if ( isset( $this->args['categories'] ) && is_array( $this->args['categories'] ) && count( $this->args['categories'] ) > 0 ) { 37 | // Are we only copying a few categories? 38 | $args['category__in'] = $this->args['categories']; 39 | } 40 | 41 | return $args; 42 | } 43 | 44 | 45 | } 46 | } -------------------------------------------------------------------------------- /copier/class.copier.php: -------------------------------------------------------------------------------- 1 | source_blog_id = $source_blog_id; 14 | 15 | // The template selected. This is only for New Blog Templates 16 | // It does not make sense with Cloner Plugin 17 | $this->template = $template; 18 | 19 | // The user ID that created the new blog 20 | $this->user_id = $user_id; 21 | 22 | // Parse the arguments 23 | $this->args = wp_parse_args( $args, $this->get_default_args() ); 24 | } 25 | 26 | /** 27 | * Set default arguments for every subclass 28 | * 29 | * @return Array 30 | */ 31 | public abstract function get_default_args(); 32 | 33 | /** 34 | * The main method. This will copy all the stuff that the subclass manages of 35 | * 36 | * @return Mixed Can be a boolean or additional information about what have been done 37 | */ 38 | public abstract function copy(); 39 | 40 | protected function log( $message ) { 41 | if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { 42 | $date = current_time( 'mysql' ); 43 | if ( ! is_string( $message ) ) { 44 | $message = print_r( $message, true ); 45 | } 46 | $message = '[' . $date . '] - ' . $message . "\n"; 47 | file_put_contents( trailingslashit( WP_CONTENT_DIR ) . 'debug-cloner.log', $message, FILE_APPEND ); 48 | } 49 | } 50 | 51 | public function _get_microtime() { 52 | return microtime( true ); 53 | } 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /copier/class.copier-pages.php: -------------------------------------------------------------------------------- 1 | type = 'page'; 15 | add_filter( 'wpmudev_copier_get_source_posts_args', array( $this, 'set_get_source_posts_args' ) ); 16 | } 17 | 18 | public function get_default_args() { 19 | return array( 20 | 'pages_ids' => 'all', 21 | 'update_date' => false, 22 | 'batch_size' => false, // Allows to copy posts in batches 23 | 'batch_page' => false // Indicates what number of batch are we trying to copy 24 | ); 25 | } 26 | 27 | public function copy( $post_id = false ) { 28 | $posts_mapping = parent::copy( $post_id ); 29 | 30 | // Remap the page on front and page for posts 31 | $page_on_front = get_option( 'page_on_front' ); 32 | if ( false !== $page_on_front ) { 33 | if ( isset( $posts_mapping[ $page_on_front ] ) ) 34 | update_option( 'page_on_front', $posts_mapping[ $page_on_front ] ); 35 | } 36 | 37 | $page_for_posts = get_option( 'page_for_posts' ); 38 | if ( false !== $page_for_posts ) { 39 | if ( isset( $posts_mapping[ $page_for_posts ] ) ) 40 | update_option( 'page_for_posts', $posts_mapping[ $page_for_posts ] ); 41 | } 42 | 43 | return $posts_mapping; 44 | } 45 | 46 | /** 47 | * Set WP_Query arguments in the parent class 48 | * 49 | * @param Array $args Current arguments 50 | * @return Array New arguments 51 | */ 52 | public function set_get_source_posts_args( $args ) { 53 | global $wpdb; 54 | 55 | if ( is_array( $this->args['pages_ids'] ) && count( $this->args['pages_ids'] ) > 0 ) 56 | $args['post__in'] = $this->args['pages_ids']; 57 | 58 | return $args; 59 | } 60 | 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /admin/views/meta-boxes/destination.php: -------------------------------------------------------------------------------- 1 |
2 |

3 | 7 |

8 | 9 |
10 | .domain ); ?> 11 | 12 | domain . $current_site->path ?>
13 | 14 | 15 |

16 | 17 | 18 |
19 | 20 |
21 |

22 | 26 |

27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /admin/ajax.php: -------------------------------------------------------------------------------- 1 | blogs} WHERE site_id = '{$wpdb->siteid}' "; 21 | 22 | $s = $_REQUEST['term']; 23 | 24 | $wild = '%'; 25 | if ( false !== strpos( $s, '*' ) ) { 26 | $s = trim( $s, '*' ); 27 | } 28 | if ( is_numeric( $s ) ) { 29 | $query .= $wpdb->prepare( " AND ( {$wpdb->blogs}.blog_id = %s )", $s ); 30 | } 31 | elseif ( is_subdomain_install() ) { 32 | $blog_s = str_replace( '.' . $current_site->domain, '', $s ); 33 | $blog_s = $wild . addcslashes( $blog_s, '_%\\' ) . $wild; 34 | $query .= $wpdb->prepare( " AND ( {$wpdb->blogs}.domain LIKE %s ) ", $blog_s ); 35 | } 36 | else { 37 | if ( $s != trim('/', $current_site->path) ) { 38 | $blog_s = addcslashes( $current_site->path . $s, '_%\\' ) . $wild . addcslashes( '/', '_%\\' ); 39 | } else { 40 | $blog_s = addcslashes( $s, '_%\\' ); 41 | } 42 | $query .= $wpdb->prepare( " AND ( {$wpdb->blogs}.path LIKE %s )", $blog_s ); 43 | } 44 | 45 | $query .= $wpdb->prepare( " AND blog_id != %d", $exclude_blog_id ); 46 | 47 | $query .= " LIMIT 10"; 48 | 49 | $blogs = $wpdb->get_results( $query ); 50 | 51 | foreach ( $blogs as $blog ) { 52 | $details = get_blog_details( $blog->blog_id ); 53 | if ( is_subdomain_install() ) { 54 | $path = $details->domain; 55 | } 56 | else { 57 | $path = trim( $details->path, '/' ); 58 | } 59 | 60 | $return[] = array( 61 | 'domain' => $path, 62 | 'blog_name' => $details->blogname, 63 | 'blog_id' => $blog->blog_id, 64 | ); 65 | } 66 | 67 | $return = apply_filters( 'cloner_autocomplete_sites', $return ); 68 | wp_die( json_encode( $return ) ); 69 | } -------------------------------------------------------------------------------- /helpers/settings.php: -------------------------------------------------------------------------------- 1 | array( 48 | 'settings', 49 | 'widgets', 50 | 'posts', 51 | 'pages', 52 | 'cpts', 53 | 'terms', 54 | 'menus', 55 | 'users', 56 | 'comments', 57 | 'attachment', 58 | 'tables' 59 | ), 60 | 'to_replace' => array() // Let member decide if he needs to replace urls 61 | ); 62 | } 63 | 64 | function wpmudev_cloner_get_settings_labels() { 65 | return array( 66 | 'settings' => __( 'Settings', WPMUDEV_CLONER_LANG_DOMAIN ), 67 | 'posts' => __( 'Posts', WPMUDEV_CLONER_LANG_DOMAIN ), 68 | 'pages' => __( 'Pages', WPMUDEV_CLONER_LANG_DOMAIN ), 69 | 'cpts' => __( 'Custom Post Types', WPMUDEV_CLONER_LANG_DOMAIN ), 70 | 'terms' => __( 'Terms', WPMUDEV_CLONER_LANG_DOMAIN ), 71 | 'menus' => __( 'Menus', WPMUDEV_CLONER_LANG_DOMAIN ), 72 | 'users' => __( 'Users', WPMUDEV_CLONER_LANG_DOMAIN ), 73 | 'comments' => __( 'Comments', WPMUDEV_CLONER_LANG_DOMAIN ), 74 | 'attachment' => __( 'Attachments', WPMUDEV_CLONER_LANG_DOMAIN ), 75 | 'tables' => __( 'Custom tables', WPMUDEV_CLONER_LANG_DOMAIN ) 76 | ); 77 | } -------------------------------------------------------------------------------- /admin/assets/jquery-multi-select/css/multi-select.css: -------------------------------------------------------------------------------- 1 | .ms-container{ 2 | background: transparent url('../img/switch.png') no-repeat 50% 50%; 3 | width: 370px; 4 | } 5 | 6 | .ms-container:after{ 7 | content: "."; 8 | display: block; 9 | height: 0; 10 | line-height: 0; 11 | font-size: 0; 12 | clear: both; 13 | min-height: 0; 14 | visibility: hidden; 15 | } 16 | 17 | .ms-container .ms-selectable, .ms-container .ms-selection{ 18 | background: #fff; 19 | color: #555555; 20 | float: left; 21 | width: 45%; 22 | } 23 | .ms-container .ms-selection{ 24 | float: right; 25 | } 26 | 27 | .ms-container .ms-list{ 28 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 29 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 30 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 31 | -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; 32 | -moz-transition: border linear 0.2s, box-shadow linear 0.2s; 33 | -ms-transition: border linear 0.2s, box-shadow linear 0.2s; 34 | -o-transition: border linear 0.2s, box-shadow linear 0.2s; 35 | transition: border linear 0.2s, box-shadow linear 0.2s; 36 | border: 1px solid #ccc; 37 | -webkit-border-radius: 3px; 38 | -moz-border-radius: 3px; 39 | border-radius: 3px; 40 | position: relative; 41 | height: 200px; 42 | padding: 0; 43 | overflow-y: auto; 44 | } 45 | 46 | .ms-container .ms-list.ms-focus{ 47 | border-color: rgba(82, 168, 236, 0.8); 48 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 49 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 50 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 51 | outline: 0; 52 | outline: thin dotted \9; 53 | } 54 | 55 | .ms-container ul{ 56 | margin: 0; 57 | list-style-type: none; 58 | padding: 0; 59 | } 60 | 61 | .ms-container .ms-optgroup-container{ 62 | width: 100%; 63 | } 64 | 65 | .ms-container .ms-optgroup-label{ 66 | margin: 0; 67 | padding: 5px 0px 0px 5px; 68 | cursor: pointer; 69 | color: #999; 70 | } 71 | 72 | .ms-container .ms-selectable li.ms-elem-selectable, 73 | .ms-container .ms-selection li.ms-elem-selection{ 74 | border-bottom: 1px #eee solid; 75 | padding: 2px 10px; 76 | color: #555; 77 | font-size: 14px; 78 | } 79 | 80 | .ms-container .ms-selectable li.ms-hover, 81 | .ms-container .ms-selection li.ms-hover{ 82 | cursor: pointer; 83 | color: #fff; 84 | text-decoration: none; 85 | background-color: #08c; 86 | } 87 | 88 | .ms-container .ms-selectable li.disabled, 89 | .ms-container .ms-selection li.disabled{ 90 | background-color: #eee; 91 | color: #aaa; 92 | cursor: text; 93 | } -------------------------------------------------------------------------------- /copier/integration/autoblog.php: -------------------------------------------------------------------------------- 1 | id; 15 | 16 | if ( ! $source_blog_id ) 17 | return; 18 | 19 | $autoblog_on = false; 20 | 21 | switch_to_blog( $source_blog_id ); 22 | // Is Autoblog activated? 23 | if ( ! function_exists( 'is_plugin_active' ) ) 24 | include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); 25 | 26 | if ( is_plugin_active( 'autoblog/autoblogpremium.php' ) ) 27 | $autoblog_on = true; 28 | 29 | // We'll need this values later 30 | $source_url = get_site_url( $source_blog_id ); 31 | $source_url_ssl = get_site_url( $source_blog_id, '', 'https' ); 32 | 33 | restore_current_blog(); 34 | 35 | if ( ! $autoblog_on ) 36 | return; 37 | 38 | $autoblog_table = $wpdb->base_prefix . 'autoblog'; 39 | $current_blog_id = get_current_blog_id(); 40 | 41 | // First, delete the current feeds 42 | $wpdb->query( $wpdb->prepare( "DELETE FROM $autoblog_table WHERE blog_id = %d AND site_id = %d", $current_blog_id, $current_site_id ) ); 43 | 44 | // Getting all the feed data for the source blog ID 45 | 46 | $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $autoblog_table WHERE blog_id = %d AND site_id = %d", $source_blog_id, $current_site_id ) ); 47 | 48 | if ( ! empty( $results ) ) { 49 | 50 | $current_url = get_site_url( $current_blog_id ); 51 | $current_url_ssl = get_site_url( $current_blog_id, '', 'https' ); 52 | 53 | foreach ( $results as $row ) { 54 | // Getting the feed metadata 55 | $feed_meta = maybe_unserialize( $row->feed_meta ); 56 | 57 | // We need to replace the source blog URL for the new one 58 | $feed_meta = str_replace( $source_url, $current_url, $feed_meta ); 59 | $feed_meta = str_replace( $source_url_ssl, $current_url_ssl, $feed_meta ); 60 | 61 | // Also the blog ID 62 | $feed_meta['blog'] = $current_blog_id; 63 | 64 | $row->feed_meta = maybe_serialize( $feed_meta ); 65 | 66 | // Inserting feed for the new blog 67 | $wpdb->insert( 68 | $autoblog_table, 69 | array( 70 | 'site_id' => $current_site_id, 71 | 'blog_id' => $current_blog_id, 72 | 'feed_meta' => $row->feed_meta, 73 | 'active' => $row->active, 74 | 'nextcheck' => $row->nextcheck, 75 | 'lastupdated' => $row->lastupdated 76 | ), 77 | array( '%d', '%d', '%s', '%d', '%d', '%d' ) 78 | ); 79 | } 80 | } 81 | 82 | } 83 | add_action( 'wpmudev_copier-copy-options', 'copier_copy_autoblog_feeds', 10, 1 ); 84 | 85 | } -------------------------------------------------------------------------------- /copier/class.copier-users.php: -------------------------------------------------------------------------------- 1 | ID ); 19 | } 20 | 21 | switch_to_blog( $this->source_blog_id ); 22 | $template_users = get_users(); 23 | $template_users_ids = wp_list_pluck( $template_users, 'ID' ); 24 | if ( ! empty( $this->user_id ) && ! in_array( $this->user_id, $template_users_ids ) ) { 25 | $template_users[] = get_user_by( 'id', $this->user_id ); 26 | } 27 | restore_current_blog(); 28 | 29 | 30 | foreach( $template_users as $user ) { 31 | // Deprecated 32 | $user = apply_filters( 'blog_templates-copy-user_entry', $user, $this->template, get_current_blog_id(), $this->user_id ); 33 | 34 | /** 35 | * Filter a user attributes before adding it to the destination blog. 36 | * 37 | * @param Array $user User attributes. 38 | * @param Integer $user_id Administrator Blog ID. 39 | * @param Array $template Only applies when using New Blog Templates. Includes the template attributes. 40 | */ 41 | $user = apply_filters( 'wpmudev_copier-copy-user_entry', $user, $this->user_id, $this->template ); 42 | if ( $user->ID == $this->user_id ) { 43 | add_user_to_blog( get_current_blog_id(), $user->ID, 'administrator' ); 44 | } 45 | elseif ( ! empty( $user->roles[0] ) ) { 46 | add_user_to_blog( get_current_blog_id(), $user->ID, $user->roles[0] ); 47 | } 48 | } 49 | 50 | // Deprecated 51 | do_action( 'blog_templates-copy-users', $this->template, get_current_blog_id(), $this->user_id ); 52 | 53 | /** 54 | * Fires after the users have been copied. 55 | * 56 | * @param Integer $user_id Blog Administrator ID. 57 | * @param Integer $source_blog_id Source Blog ID from where we are copying the users. 58 | * @param Array $template Only applies when using New Blog Templates. Includes the template attributes. 59 | */ 60 | do_action( 'wpmudev_copier-copy-users', $this->user_id, $this->source_blog_id, $this->template ); 61 | 62 | return true; 63 | } 64 | 65 | 66 | } 67 | } -------------------------------------------------------------------------------- /copier/integration/marketpress.php: -------------------------------------------------------------------------------- 1 | post_type_included( $args['post_type'], 'mp_order' ) ) 33 | { 34 | $post_status = isset( $args['post_status'] ) ? $args['post_status'] : ''; 35 | $args['post_status'] = $this->extend_post_statuses( $post_status ); 36 | 37 | } 38 | 39 | return $args; 40 | } 41 | 42 | /** 43 | * @param array|string $current_types The post types included in this clone 44 | * @param string $search_type The post type we want to check if exists in this clone 45 | * 46 | * @return bool 47 | */ 48 | public function post_type_included( $current_types, $search_type ) { 49 | 50 | $type_included = false; 51 | 52 | if ( is_array( $current_types ) ) { 53 | $type_included = in_array( $search_type, $current_types ); 54 | } 55 | else { 56 | $type_included = ( $type_included == $current_types ); 57 | } 58 | 59 | return $type_included; 60 | } 61 | 62 | /** 63 | * @param array $_statuses Default statuses used in query to fetch posts 64 | * 65 | * @return array 66 | */ 67 | public function extend_post_statuses( $_statuses ) { 68 | 69 | $statuses = array( 70 | 'order_received', 71 | 'order_paid', 72 | 'order_shipped', 73 | 'order_closed' 74 | ); 75 | 76 | if ( ! is_array( $_statuses ) ) { 77 | array_unshift( $statuses, $_statuses ); 78 | } 79 | else { 80 | $statuses = array_merge( $statuses, $_statuses ); 81 | } 82 | 83 | return $statuses; 84 | } 85 | 86 | } 87 | 88 | if ( ! function_exists( 'marketpress_cloner_integration' ) ) { 89 | 90 | function marketpress_cloner_integration() { 91 | return Marketpress_Cloner_Integration::get_instance(); 92 | } 93 | 94 | function marketpress_maybe_load_cloner_integration( $copy, $copier ) { 95 | 96 | if ( 'cpts' == $copy ) { 97 | marketpress_cloner_integration(); 98 | } 99 | 100 | } 101 | 102 | add_action( 'wpmudev_copier-before-copy', 'marketpress_maybe_load_cloner_integration', 10, 2 ); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | 14 | WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} 15 | WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} 16 | 17 | set -ex 18 | 19 | download() { 20 | if [ `which curl` ]; then 21 | curl -s "$1" > "$2"; 22 | elif [ `which wget` ]; then 23 | wget -nv -O "$2" "$1" 24 | fi 25 | } 26 | 27 | install_wp() { 28 | 29 | if [ -d $WP_CORE_DIR ]; then 30 | return; 31 | fi 32 | 33 | mkdir -p $WP_CORE_DIR 34 | 35 | if [ $WP_VERSION == 'latest' ]; then 36 | local ARCHIVE_NAME='latest' 37 | else 38 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 39 | fi 40 | 41 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz 42 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR 43 | 44 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 45 | } 46 | 47 | install_test_suite() { 48 | # portable in-place argument for both GNU sed and Mac OSX sed 49 | if [[ $(uname -s) == 'Darwin' ]]; then 50 | local ioption='-i .bak' 51 | else 52 | local ioption='-i' 53 | fi 54 | 55 | # set up testing suite if it doesn't yet exist 56 | if [ ! -d $WP_TESTS_DIR ]; then 57 | # set up testing suite 58 | mkdir -p $WP_TESTS_DIR 59 | svn co --quiet http://develop.svn.wordpress.org/trunk/tests/phpunit/includes/ $WP_TESTS_DIR/includes 60 | fi 61 | 62 | cd $WP_TESTS_DIR 63 | 64 | if [ ! -f wp-tests-config.php ]; then 65 | download https://develop.svn.wordpress.org/trunk/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 66 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" "$WP_TESTS_DIR"/wp-tests-config.php 67 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 68 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 69 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 70 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 71 | fi 72 | 73 | } 74 | 75 | install_db() { 76 | # parse DB_HOST for port or socket references 77 | local PARTS=(${DB_HOST//\:/ }) 78 | local DB_HOSTNAME=${PARTS[0]}; 79 | local DB_SOCK_OR_PORT=${PARTS[1]}; 80 | local EXTRA="" 81 | 82 | if ! [ -z $DB_HOSTNAME ] ; then 83 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 84 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 85 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 86 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 87 | elif ! [ -z $DB_HOSTNAME ] ; then 88 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 89 | fi 90 | fi 91 | 92 | # create database 93 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 94 | } 95 | 96 | install_wp 97 | install_test_suite 98 | install_db 99 | -------------------------------------------------------------------------------- /integration/views/multi-domains-destination-meta-box.php: -------------------------------------------------------------------------------- 1 | 1 ) { 9 | $primary = wp_list_filter( $domains, array( 'domain_name' => DOMAIN_CURRENT_SITE ) ); 10 | $else = wp_list_filter( $domains, array( 'domain_name' => DOMAIN_CURRENT_SITE ), 'NOT' ); 11 | 12 | $domains = array_merge( $primary, $else ); 13 | 14 | $super_admin = is_super_admin(); 15 | $show_restricted_domains = $multi_dm->show_restricted_domains(); 16 | $posted_domain = isset( $_POST['domain'] ) ? $_POST['domain'] : ''; 17 | 18 | $the_domain = ''; 26 | } else { 27 | $the_domain = '' . $domains[0]['domain_name'] . ''; 28 | } 29 | 30 | ?> 31 | 32 |
33 | 34 |

35 | 39 |

40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 | 50 |

51 | 52 | 53 |
54 | 55 |
56 |

57 | 61 |

62 | 63 |
64 | 65 |
66 | 67 | 68 |
69 |
70 | 71 | 5 | 6 | > 7 | 8 | 9 | 10 | <?php _e( 'Confirm action', WPMUDEV_COPIER_LANG_DOMAIN ); ?> 11 | 17 | 33 | 34 | 35 |
36 |

37 |

38 | that already exists. If you choose ‘Continue’, all existing site content and settings on %s will be completely overwritten with content and settings from %s This change is permanent and can’t be undone, so please be careful. ', WPMUDEV_CLONER_LANG_DOMAIN ), 41 | '' . get_site_url( $destination_blog_details->blog_id ) . '', 42 | '' . get_site_url( $blog_details->blog_id ) . '' 43 | ); 44 | ?> 45 |

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 | -------------------------------------------------------------------------------- /admin/views/settings.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |

6 | 7 | 8 | 9 | 10 |

11 | 12 | 13 |

14 | 15 |
16 |

17 |
18 | 19 |

Network Admin » Sites', WPMUDEV_CLONER_LANG_DOMAIN ), network_admin_url( 'sites.php' ) ); ?>

20 |
21 |
22 | 23 |

24 |
25 |
26 | 27 |

28 |
    29 | 30 | $label ): ?> 31 |
  • 32 | 36 |
  • 37 | 38 |
    39 | 40 | 41 | 42 | 43 | 44 |
45 |
46 | 47 |
48 | 49 |

50 | 51 |

52 | 53 |
    54 | 55 |
  • 56 | 60 |
  • 61 | 62 |
  • 63 | 67 |
  • 68 | 69 |
70 | 71 |
72 | 73 | 74 | 75 |
76 | 77 | 101 | 102 |
103 | -------------------------------------------------------------------------------- /copier/integration/woocommerce.php: -------------------------------------------------------------------------------- 1 | prefix . 'woocommerce_termmeta' ) 13 | return $row; 14 | 15 | $mapped_terms = get_transient( 'copier_woocommerce_terms' ); 16 | if ( ! $mapped_terms ) 17 | return $row; 18 | 19 | $old_term_id = $row['woocommerce_term_id']; 20 | if ( array_key_exists( $old_term_id, $mapped_terms ) ) 21 | $row['woocommerce_term_id'] = $mapped_terms[ $old_term_id ]; 22 | 23 | return $row; 24 | } 25 | } 26 | 27 | if ( ! function_exists( 'copier_woocommerce_save_mapped_terms' ) ) { 28 | add_action( 'wpmudev_copier-copy-terms', 'copier_woocommerce_save_mapped_terms', 10, 4 ); 29 | function copier_woocommerce_save_mapped_terms( $user_id, $source_blog_id, $template, $mapped_terms ) { 30 | if ( ! function_exists( 'WC' ) ) 31 | return; 32 | 33 | set_transient( 'copier_woocommerce_terms', $mapped_terms, 3600 ); //Let's save for 60 minutes 34 | } 35 | } 36 | 37 | if ( ! function_exists( 'copier_woocommerce_delete_transient' ) ) { 38 | add_action( 'wpmudev_copier-copy-after_copying', 'copier_woocommerce_delete_transient' ); 39 | function copier_woocommerce_delete_transient() { 40 | delete_transient( 'copier_woocommerce_terms' ); 41 | } 42 | } 43 | 44 | if ( ! function_exists( 'copier_woocommerce_order_status' ) ) { 45 | add_filter( 'wpmudev_copier_get_source_posts_args', 'copier_woocommerce_order_status' ); 46 | /** 47 | * Add WooCommerce Order statuses to get_posts arguments so they are cloned too. 48 | * 49 | * @param Array $args 50 | * 51 | * @return Array 52 | */ 53 | function copier_woocommerce_order_status( $args ) { 54 | if ( ! function_exists( 'WC' ) ) 55 | return $args; 56 | 57 | $args['post_type'] = ( ! is_array( $args['post_type'] ) ) ? array( $args['post_type'] ) : $args['post_type']; 58 | 59 | if ( ! in_array( 'shop_order', $args['post_type'] ) ) 60 | return $args; 61 | 62 | $args['post_status'] = array_merge( $args['post_status'], array( 63 | 'wc-pending', 64 | 'wc-processing', 65 | 'wc-on-hold', 66 | 'wc-completed', 67 | 'wc-cancelled', 68 | 'wc-refunded', 69 | 'wc-failed', 70 | 'wc-expired' 71 | ) ); 72 | 73 | return $args; 74 | } 75 | } 76 | 77 | 78 | if ( ! function_exists( 'copier_woocommerce_follow_up_email_status' ) ) { 79 | add_filter( 'wpmudev_copier_get_source_posts_args', 'copier_woocommerce_follow_up_email_status' ); 80 | /** 81 | * Add WooCommerce Follow Up Email statuses to get_posts arguments so they are cloned too. 82 | * 83 | * @param Array $args 84 | * 85 | * @return Array 86 | */ 87 | function copier_woocommerce_follow_up_email_status( $args ) { 88 | if ( ! function_exists( 'WC' ) ) 89 | return $args; 90 | 91 | $args['post_type'] = ( ! is_array( $args['post_type'] ) ) ? array( $args['post_type'] ) : $args['post_type']; 92 | 93 | if ( ! in_array( 'follow_up_email', $args['post_type'] ) ) 94 | return $args; 95 | 96 | $args['post_status'] = array_merge( $args['post_status'], array( 97 | 'fue-active', 98 | 'fue-inactive', 99 | 'fue-archived' 100 | ) ); 101 | 102 | return $args; 103 | } 104 | } -------------------------------------------------------------------------------- /copier/class.copier-comments.php: -------------------------------------------------------------------------------- 1 | array() 13 | ); 14 | } 15 | 16 | public function copy() { 17 | global $wpdb; 18 | 19 | // Delete current comments in the new blog 20 | $current_comments = get_comments(); 21 | foreach ( $current_comments as $comment ) { 22 | wp_delete_comment( $comment->comment_ID, true ); 23 | } 24 | 25 | // Get the source comments and their metadata 26 | switch_to_blog( $this->source_blog_id ); 27 | $_source_comments = get_comments(); 28 | $source_comments = array(); 29 | foreach ( $_source_comments as $source_comment ) { 30 | $item = $source_comment; 31 | $item->meta = get_comment_meta( $source_comment->comment_ID ); 32 | $source_comments[] = $item; 33 | } 34 | restore_current_blog(); 35 | 36 | // Deprecated 37 | $source_comments = apply_filters( 'blog_templates_source_comments', $source_comments, $this->source_blog_id, $this->user_id ); 38 | 39 | /** 40 | * Filter the comments got from the source Blog ID. 41 | * 42 | * These comments will be those that we will copy to the detsination blog ID. 43 | * 44 | * @param Array $source_comments Source comments and their attributes. 45 | * @param Integer $source_blog_id Source Blog ID. 46 | */ 47 | $source_comments = apply_filters( 'wpmudev_copier_source_comments', $source_comments, $this->source_blog_id ); 48 | 49 | // This array saves the relationships between the old comments and the new 50 | $comments_remap = array(); 51 | 52 | foreach ( $source_comments as $source_comment ) { 53 | $comment = (array)$source_comment; 54 | 55 | $source_comment_id = $comment['comment_ID']; 56 | unset( $comment['comment_ID'] ); 57 | 58 | // Remap the post ID 59 | if ( ! isset( $this->args['posts_mapping'][ $comment['comment_post_ID'] ] ) ) 60 | continue; 61 | 62 | $comment['comment_post_ID'] = $this->args['posts_mapping'][ $comment['comment_post_ID'] ]; 63 | 64 | // Insert the new comment 65 | $new_comment_id = wp_insert_comment( $comment ); 66 | 67 | // And add it to the mapping array 68 | if ( $new_comment_id ) 69 | $comments_remap[ $source_comment_id ] = $new_comment_id; 70 | } 71 | 72 | unset( $source_comments ); 73 | 74 | // Now, let's remap the parent comments 75 | $comments = get_comments(); 76 | foreach ( $comments as $_comment ) { 77 | $comment = (array)$_comment; 78 | 79 | if ( $comment['comment_parent'] && isset( $comments_remap[ $comment['comment_parent'] ] ) ) { 80 | $comment['comment_parent'] = $comments_remap[ $comment['comment_parent'] ]; 81 | wp_update_comment( $comment ); 82 | } 83 | } 84 | 85 | return true; 86 | } 87 | 88 | } 89 | } -------------------------------------------------------------------------------- /integration/integration.php: -------------------------------------------------------------------------------- 1 | $domain ) ); 45 | 46 | if ( empty( $search_domain_results ) ) { 47 | return new WP_Error( 'source_blog_not_exist', __( 'Missing or invalid site address.', WPMUDEV_CLONER_LANG_DOMAIN ) ); 48 | } 49 | 50 | 51 | $subdomain = ''; 52 | if ( preg_match( '|^([a-zA-Z0-9-])+$|', $blog ) ) 53 | $subdomain = strtolower( $blog ); 54 | 55 | if ( empty( $subdomain ) ) { 56 | return new WP_Error( 'source_blog_not_exist', __( 'Missing or invalid site address.', WPMUDEV_CLONER_LANG_DOMAIN ) ); 57 | } 58 | 59 | $full_address = ''; 60 | 61 | // Check if the blog exists 62 | if ( is_subdomain_install() ) { 63 | $new_domain = $subdomain . '.' . $domain; 64 | $new_path = ''; 65 | $blog_exists = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->blogs WHERE domain LIKE %s", '%' . $new_domain . '%' ) ); 66 | } 67 | else { 68 | $new_domain = $domain; 69 | $new_path = $current_site->path . trailingslashit( $subdomain ); 70 | $blog_exists = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->blogs WHERE domain LIKE %s AND path = %s", '%' . $new_domain . '%', $new_path . '/' ) ); 71 | } 72 | 73 | if ( ! empty( $blog_exists ) ) { 74 | return new WP_Error( 'blog_already_exists', __( 'The blog already exists', WPMUDEV_CLONER_LANG_DOMAIN ) ); 75 | } 76 | 77 | 78 | if ( 'clone' == $blog_title_selection ) { 79 | $new_blog_title = $blog_details->blogname; 80 | } 81 | 82 | 83 | return array( 84 | 'new_blog_title' => $new_blog_title, 85 | 'new_domain' => $new_domain, 86 | 'new_path' => $new_path 87 | ); 88 | } 89 | 90 | return false; 91 | } 92 | 93 | if ( ! is_subdomain_install() ) { 94 | add_filter( 'cloner_autocomplete_sites', 'cloner_multi_domains_autocomplete_sites' ); 95 | function cloner_multi_domains_autocomplete_sites( $sites ) { 96 | global $wpdb; 97 | foreach ( $sites as $key => $site ) { 98 | $domain = $wpdb->get_var( $wpdb->prepare( "SELECT domain FROM $wpdb->blogs WHERE blog_id = %d", $site['blog_id'] ) ); 99 | $sites[ $key ]['blog_name'] = $site['blog_name'] . " ( $domain )"; 100 | } 101 | return $sites; 102 | } 103 | 104 | } 105 | 106 | 107 | -------------------------------------------------------------------------------- /copier/integration.php: -------------------------------------------------------------------------------- 1 | 'any', 51 | 'posts_per_page' => - 1, 52 | 'ignore_sticky_posts' => true, 53 | 's' => $shortcode 54 | ) ); 55 | } 56 | } 57 | 58 | 59 | if ( ! function_exists( 'copier_replace_shortcode_attributes' ) ) { 60 | /** 61 | * Replace a shortcode attribute in a list of posts based on a post mapping array 62 | * 63 | * @param String $shortcode Shortcode slug 64 | * @param Array $all_posts List of posts 65 | * @param String $shortcode_attribute Shortcode attribute that we are searching for 66 | * @param Array $posts_map List of source/destination post IDs [source_post_id] => [new_post_id] 67 | */ 68 | function copier_replace_shortcode_attributes( $shortcode, $all_posts, $shortcode_attribute, $posts_map ) { 69 | // Shortcode patterns 70 | $shortcode_pattern = get_shortcode_regex(); 71 | 72 | foreach ( $all_posts as $post ) { 73 | $_post = (array) $post; 74 | 75 | // Search for shortcodes in the post content 76 | if ( 77 | preg_match_all( '/' . $shortcode_pattern . '/s', $_post['post_content'], $matches ) 78 | && array_key_exists( 2, $matches ) 79 | && in_array( $shortcode, $matches[2] ) 80 | ) { 81 | $do_replace = false; 82 | foreach ( $matches[2] as $key => $shortcode_type ) { 83 | 84 | if ( $shortcode == $shortcode_type ) { 85 | // Yeah! We have found the shortcode in this post, let's replace the ID if we can 86 | 87 | // Get the shortcode attributes 88 | $atts = shortcode_parse_atts( $matches[3][ $key ] ); 89 | 90 | if ( isset( $atts[ $shortcode_attribute ] ) ) { 91 | // There is an ID attribute, let's replace it 92 | $source_post_id = absint( $atts[ $shortcode_attribute ] ); 93 | 94 | if ( ! isset( $posts_map[ $source_post_id ] ) ) { 95 | // There's not such post ID mapped in the array, let's continue 96 | continue; 97 | } 98 | 99 | if ( $source_post_id == $posts_map[ $source_post_id ] ) { 100 | continue; 101 | } 102 | 103 | $new_post_id = $posts_map[ $source_post_id ]; 104 | 105 | // Get the original full shortcode 106 | $full_shortcode = $matches[0][ $key ]; 107 | 108 | // Replace the ID 109 | $new_atts_ids = str_replace( (string) $source_post_id, $new_post_id, $atts[ $shortcode_attribute ] ); 110 | 111 | // Now replace the attributes in the source shortcode 112 | $new_full_shortcode = str_replace( $atts[ $shortcode_attribute ], $new_atts_ids, $full_shortcode ); 113 | 114 | // And finally replace the source shortcode for the new one in the post content 115 | $_post['post_content'] = str_replace( $full_shortcode, $new_full_shortcode, $_post['post_content'] ); 116 | 117 | // So we have found a replacement to make, haven't we? 118 | $do_replace = true; 119 | 120 | } 121 | 122 | } 123 | } 124 | 125 | if ( $do_replace ) { 126 | // Update the post! 127 | $postarr = array( 128 | 'ID' => $_post['ID'], 129 | 'post_content' => $_post['post_content'] 130 | ); 131 | 132 | wp_update_post( $postarr ); 133 | } 134 | } 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /admin/cloner-admin-settings.php: -------------------------------------------------------------------------------- 1 | plugin_slug = 'cloner'; 35 | 36 | // Add the options page and menu item. 37 | add_action( 'network_admin_menu', array( $this, 'add_plugin_settings_menu' ) ); 38 | 39 | // Add an action link pointing to the options page. 40 | $plugin_basename = plugin_basename( plugin_dir_path( realpath( dirname( __FILE__ ) ) ) . $this->plugin_slug . '.php' ); 41 | add_filter( 'network_admin_plugin_action_links_' . $plugin_basename, array( $this, 'add_action_links' ) ); 42 | 43 | if ( ! defined( 'WPMUDEV_CLONER_ASSETS_URL' ) ) 44 | define( 'WPMUDEV_CLONER_ASSETS_URL', plugin_dir_url( __FILE__ ) . 'assets' ); 45 | 46 | } 47 | 48 | /** 49 | * Return an instance of this class. 50 | * 51 | * @since 1.0.0 52 | * 53 | * @return object A single instance of this class. 54 | */ 55 | public static function get_instance() { 56 | 57 | // If the single instance hasn't been set, set it now. 58 | if ( null == self::$instance ) 59 | self::$instance = new self; 60 | 61 | return self::$instance; 62 | } 63 | 64 | 65 | /** 66 | * Register the administration menu for this plugin into the WordPress Dashboard menu. 67 | * 68 | * @since 1.0.0 69 | */ 70 | public function add_plugin_settings_menu() { 71 | 72 | $this->plugin_screen_hook_suffix = add_submenu_page( 73 | 'settings.php', 74 | __( 'Cloner Settings', WPMUDEV_CLONER_LANG_DOMAIN ), 75 | __( 'Cloner', WPMUDEV_CLONER_LANG_DOMAIN ), 76 | 'manage_network', 77 | $this->plugin_slug, 78 | array( $this, 'display_plugin_admin_page' ) 79 | ); 80 | 81 | add_action( 'load-' . $this->plugin_screen_hook_suffix, array( $this, 'sanitize_settings_form' ) ); 82 | 83 | } 84 | 85 | /** 86 | * Render the settings page for this plugin. 87 | * 88 | * @since 1.0.0 89 | */ 90 | public function display_plugin_admin_page() { 91 | $to_copy_labels = wpmudev_cloner_get_settings_labels(); 92 | $to_copy_labels = apply_filters( 'wpmudev_cloner_to_copy_labels_settings', $to_copy_labels ); 93 | 94 | $settings = wpmudev_cloner_get_settings(); 95 | 96 | $errors = get_settings_errors( 'wpmudev_cloner_settings' ); 97 | 98 | $updated = false; 99 | if ( isset( $_GET['updated'] ) ) 100 | $updated = true; 101 | 102 | extract( $settings ); 103 | 104 | if( ! isset( $to_replace ) ){ 105 | $to_replace = array(); 106 | } 107 | 108 | include_once( 'views/settings.php' ); 109 | } 110 | 111 | /** 112 | * Add settings action link to the plugins page. 113 | * 114 | * @since 1.0.0 115 | */ 116 | public function add_action_links( $links ) { 117 | $menu_page_url = menu_page_url( $this->plugin_slug, false ); 118 | 119 | if ( $menu_page_url ) { 120 | $links = array_merge( 121 | array( 122 | 'settings' => '' . __( 'Settings', WPMUDEV_CLONER_LANG_DOMAIN ) . '' 123 | ), 124 | $links 125 | ); 126 | } 127 | 128 | return $links; 129 | 130 | } 131 | 132 | public function sanitize_settings_form() { 133 | if ( empty( $_POST['submit'] ) ) 134 | return; 135 | 136 | check_admin_referer( 'wpmudev_cloner_settings' ); 137 | 138 | if ( empty( $_POST['to_copy'] ) ) { 139 | add_settings_error( 'wpmudev_cloner_settings', 'empty-settings', __( 'You need to check at least one option', WPMUDEV_CLONER_LANG_DOMAIN ) ); 140 | return; 141 | } 142 | 143 | $settings = wpmudev_cloner_get_settings(); 144 | 145 | $to_copy = array_keys( $_POST['to_copy'] ); 146 | $settings['to_copy'] = $to_copy; 147 | $settings['to_replace'] = ( isset( $_POST['to_replace'] ) ) ? array_keys( $_POST['to_replace'] ) : array(); 148 | 149 | wpmudev_cloner_update_settings( $settings ); 150 | 151 | $redirect = add_query_arg( 152 | array( 153 | 'page' => $this->plugin_slug, 154 | 'updated' => 'true' 155 | ), 156 | network_admin_url( 'settings.php' ) 157 | ); 158 | 159 | wp_redirect( $redirect ); 160 | exit(); 161 | 162 | 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /copier/class.copier-cpts.php: -------------------------------------------------------------------------------- 1 | source_blog_id ); 21 | $post_types = $wpdb->get_col( "SELECT DISTINCT post_type FROM $wpdb->posts WHERE post_type NOT IN $exclude_post_types" ); 22 | restore_current_blog(); 23 | 24 | $post_types = apply_filters( 'wpmudev_copier_copy_post_types', $post_types, $source_blog_id, $args, $user_id, $template ); 25 | 26 | $this->log( 'Copying Post Types:' ); 27 | $this->log( $post_types ); 28 | 29 | if ( ! empty( $post_types ) ) { 30 | $this->type = $post_types; 31 | } 32 | 33 | add_action( 'wpmudev_copier-copied-posts', array( $this, 'copy_network_tables_opts' ), 10, 5 ); 34 | 35 | } 36 | 37 | /** 38 | * Fires after all CPTs have been copied. 39 | * Useful for copying options from custom network-wide tables that associate blog ids, such as Hustle 40 | * 41 | * @param Integer $source_blog_id Blog ID from where we are copying the post. 42 | * @param Integer $posts_mapping Map of posts [source_post_id] => $new_post_id. 43 | * @param Integer $user_id Post Author. 44 | * @param Array $template Only applies when using New Blog Templates. Includes the template attributes 45 | * @param String $type Post Type ( post, page or cpt ) 46 | */ 47 | public function copy_network_tables_opts( $source_blog_id, $posts_mapping, $user_id, $template, $type ) { 48 | 49 | if ( $type != $this->type ) { 50 | return; 51 | } 52 | 53 | global $wpdb; 54 | 55 | /* 56 | * Clone Hustle modules and modules meta 57 | */ 58 | $hustle_plugin = 'hustle' . DIRECTORY_SEPARATOR . 'opt-in.php'; 59 | $hustle_modules_table = $wpdb->base_prefix . 'hustle_modules'; 60 | $hustle_modules_meta_table = $wpdb->base_prefix . 'hustle_modules_meta'; 61 | $new_blog_id = get_current_blog_id(); 62 | 63 | /* 64 | * No need to continue if Huslte plugin is not active in source blog 65 | * Also make sure these DB tables exist. May seem like a waste of resources, but these tables appeared after Hustle v 3.x 66 | */ 67 | switch_to_blog( $source_blog_id ); 68 | if ( ! is_plugin_active( $hustle_plugin ) || 69 | $wpdb->get_var("SHOW TABLES LIKE '{$hustle_modules_table}'") != $hustle_modules_table || 70 | $wpdb->get_var("SHOW TABLES LIKE '{$hustle_modules_meta_table}'") != $hustle_modules_meta_table 71 | ) { 72 | restore_current_blog(); 73 | return; 74 | } 75 | restore_current_blog(); 76 | 77 | $hustle_modules = $wpdb->get_results( 78 | $wpdb->prepare( 79 | "SELECT * FROM {$hustle_modules_table} WHERE blog_id=%d" 80 | , $source_blog_id 81 | ) 82 | ); 83 | 84 | if ( ! empty( $hustle_modules ) ) { 85 | 86 | // Foreach modules get module meta and insert with new module id 87 | foreach ($hustle_modules as $key => $hustle_module ) { 88 | 89 | $module_meta = $wpdb->get_results( 90 | $wpdb->prepare( 91 | "SELECT * FROM {$hustle_modules_meta_table} WHERE module_id=%d" 92 | , $hustle_module->module_id 93 | ) 94 | ); 95 | 96 | $wpdb->insert( $hustle_modules_table, 97 | array( 98 | 'blog_id' => $new_blog_id, 99 | 'module_name' => $hustle_module->module_name, 100 | 'module_type' => $hustle_module->module_type, 101 | 'active' => $hustle_module->active, 102 | 'test_mode' => $hustle_module->test_mode 103 | ), 104 | array( 105 | '%d', 106 | '%s', 107 | '%s', 108 | '%d', 109 | '%d' 110 | ) 111 | ); 112 | 113 | $new_module_id = $wpdb->insert_id; 114 | 115 | if ( ! is_wp_error( $new_module_id ) && ! empty( $module_meta ) ){ 116 | foreach ( $module_meta as $key => $meta ) { 117 | 118 | $meta = apply_filters( 'wpmudev_copier_copy_hustle_meta', $meta ); 119 | if ( $meta ){ 120 | $wpdb->insert( $hustle_modules_meta_table, 121 | array( 122 | 'module_id' => $new_module_id, 123 | 'meta_key' => $meta->meta_key, 124 | 'meta_value' => $meta->meta_value 125 | ), 126 | array( 127 | '%d', 128 | '%s', 129 | '%s' 130 | ) 131 | ); 132 | } 133 | } 134 | } 135 | 136 | } // END Foreach module 137 | }// END if ( ! empty( $hustle_modules ) ) 138 | 139 | } 140 | 141 | public function get_default_args() { 142 | return array( 143 | 'update_date' => false 144 | ); 145 | } 146 | 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloner 2 | 3 | **INACTIVE NOTICE: This plugin is unsupported by WPMUDEV, we've published it here for those technical types who might want to fork and maintain it for their needs.** 4 | 5 | ## Translations 6 | 7 | Translation files can be found at https://github.com/wpmudev/translations 8 | 9 | ## Cloner will copy any WordPress site on your Multisite network and move it to any URL on the same network with a click. 10 | 11 | Making design changes, adding a new line of code or tweaking your theme on a live site is risky business. Just activating the wrong plugin can break your site. Cloner lets you manage and move sites across your network with ease for stress-free editing. 12 | 13 | ### Migrate, Stage, Backup 14 | 15 | Cloner makes light work of site migration, backups and safe staging and deployment. Use default settings and copy your entire site or select only the elements you want to transfer. Perfect for managing live sites. 16 | 17 | ![cloner-settings-735x470](https://premium.wpmudev.org/wp-content/uploads/2014/08/cloner-settings-735x470.jpg) 18 | 19 | Toggle "on" any of content you want to clone. 20 | 21 | ![cloner-site-list-1470x940](https://premium.wpmudev.org/wp-content/uploads/2014/08/cloner-site-list-1470x940-800x511.jpg) 22 | 23 | It's simple site replication integrated into Multisite. 24 | 25 | ### Simple and Convenient 26 | 27 | Cloner combines sophisticated duplicate and move functions with simple setup and use. The ‘clone’ option integrates with the ‘sites’ action menu for convenient access. Navigate to ‘All Sites’, hover and click–it really is that easy. 28 | 29 | ### The Perfect Match–for You 30 | 31 | Click the ‘Clone Site’ button and watch as cloner makes quick work of building a replica. Port users, posts, custom post types, pages, comments, terms, menus, attachments and settings for a perfect match, or quickly toggle what content to duplicate for a custom clone. 32 | 33 | ![cloner-site-735x470](https://premium.wpmudev.org/wp-content/uploads/2014/08/cloner-site-735x470.jpg) 34 | 35 | When your site is ready to go it's easy to push it live. 36 | 37 | ### Stress-free Testing 38 | 39 | Work behind the scenes and remove the stress associated with developing and designing on a live site. Stage your site at a private location on your network, make changes to the staged site, then use Cloner to push your changes live. 40 | 41 | ### Pro Sites Awesomeness 42 | 43 | [If you run your own hosting service powered by Pro Sites](http://premium.wpmudev.org/project/pro-sites/) your clients will love Cloner. Give your network a competitive edge with simple internal site migration and staging. Offer high traffic sites a test environment – plus save time moving sites. 44 | 45 | ![prosites](https://premium.wpmudev.org/wp-content/uploads/2014/08/prosites.png) 46 | 47 | Build your own WordPress.com or Edublogs with Pro Sites. 48 | 49 | 50 | ## Usage 51 | 52 | ### To Get Started 53 | 54 | Start by reading [Installing plugins](https://wpmudev.com/docs/using-wordpress/installing-wordpress-plugins/) section in our comprehensive [WordPress and WordPress Multisite Manual](https://premium.wpmudev.org/manuals/) if you are new to WordPress. 55 | 56 | ### Configuring the Settings 57 | 58 | Once installed and network-activated, you’ll see a new menu item in your network admin: Settings > Cloner. 59 | 60 | ![Cloner Settings - Network Admin](https://premium.wpmudev.org/wp-content/uploads/2014/08/Cloner-Settings-Network-Admin.png) 61 | 62 | Click the _Settings > Cloner _sub-menu item to configure the settings. ![Cloner - content types to copy](https://premium.wpmudev.org/wp-content/uploads/2014/08/Cloner-content-types.png) 63 | 64 | Select all the items you'd like to be copied with the clone process and click the _Save Changes_ button. Next, click on the _Sites_ menu item to go to the sites list in your network admin. ![Network admin Sites list](https://premium.wpmudev.org/wp-content/uploads/2014/08/Cloner-Sites.png) 65 | 66 | Locate a sub-site in the list that you'd like to copy, hover your mouse over the site's name and click _Clone_ to clone the site. ![Cloner - Sites list - clone option](https://premium.wpmudev.org/wp-content/uploads/2014/08/Cloner-Sites-list-options.png) 67 | 68 | You'll now be taken to the Clone Site page where you have a few cloning options. ![Cloner - Clone Site](https://premium.wpmudev.org/wp-content/uploads/2014/08/Cloner-Clone-Site.png) 69 | 70 | 1\. Enter the name for a new site to create 71 | 72 | 2\. Or enter the name of site to replace 73 | 74 | 3\. Click the Clone Site button 75 | 76 | 77 | _Create a new site_ lets you create a new site that will be a duplicate of the selected site. _Replace existing site_ lets you overwrite an existing site with a copy of the selected site. Begin entering the name of an existing site and an auto-complete list of sites will show, from which you can select one. Press the _Clone Site_ button to commence the cloning process. If you've selected to replace an existing site, you'll see a confirmation page asking if you want to proceed. Press the Continue button to proceed. Once the clone process is started, you'll see a status page notifying you of the progress. 78 | 79 | ![Cloner - setting up your new blog](https://premium.wpmudev.org/wp-content/uploads/2014/08/Cloner-setting-up-your-new-blog.png) 80 | 81 | Once the process is completed, you'll automatically be taken to the cloned site's dashboard. That's all there is to it! 82 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | require('load-grunt-tasks')(grunt); 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | 6 | checktextdomain: { 7 | options:{ 8 | report_missing: false, 9 | text_domain: 'cloner', 10 | keywords: [ 11 | '__:1,2d', 12 | '_e:1,2d', 13 | '_x:1,2c,3d', 14 | 'esc_html__:1,2d', 15 | 'esc_html_e:1,2d', 16 | 'esc_html_x:1,2c,3d', 17 | 'esc_attr__:1,2d', 18 | 'esc_attr_e:1,2d', 19 | 'esc_attr_x:1,2c,3d', 20 | '_ex:1,2c,3d', 21 | '_n:1,2,4d', 22 | '_nx:1,2,4c,5d', 23 | '_n_noop:1,2,3d', 24 | '_nx_noop:1,2,3c,4d' 25 | ] 26 | }, 27 | files: { 28 | src: [ 29 | '**/*.php', // Include all files 30 | '!node_modules/**', // Exclude node_modules/ 31 | '!tests/**', // Exclude tests/ 32 | '!admin/assets/shared-ui/**', // Exclude WPMU DEV Shared UI 33 | '!externals/**' 34 | ], 35 | expand: true 36 | } 37 | }, 38 | 39 | copy: { 40 | main: { 41 | src: [ 42 | '**', 43 | '!npm-debug.log', 44 | '!node_modules/**', 45 | '!build/**', 46 | '!bin/**', 47 | '!.git/**', 48 | '!Gruntfile.js', 49 | '!package.json', 50 | '!.gitignore', 51 | '!.gitmodules', 52 | '!sourceMap.map', 53 | '!phpunit.xml.dist', 54 | '!travis.yml', 55 | '!tests/**', 56 | '!**/Gruntfile.js', 57 | '!**/package.json', 58 | '!**/README.md', 59 | '!lite-vs-pro.txt', 60 | '!composer.json', 61 | '!vendor/**', 62 | '!tmp/**', 63 | '!phpunit.xml', 64 | '!**/*~' 65 | ], 66 | dest: 'build/<%= pkg.name %>/' 67 | } 68 | }, 69 | 70 | // Generate POT files. 71 | makepot: { 72 | options: { 73 | type: 'wp-plugin', 74 | domainPath: 'lang', 75 | potHeaders: { 76 | 'report-msgid-bugs-to': 'https://wpmudev.org', 77 | 'language-team': 'LANGUAGE ' 78 | } 79 | }, 80 | dist: { 81 | options: { 82 | potFilename: 'wpmudev-cloner.pot', 83 | exclude: [ 84 | 'tests/.*', 85 | 'node_modules/.*', 86 | 'externals/*' 87 | ] 88 | } 89 | } 90 | }, 91 | 92 | clean: { 93 | main: ['build/*'] 94 | }, 95 | 96 | compress: { 97 | main: { 98 | options: { 99 | mode: 'zip', 100 | archive: './build/<%= pkg.name %>-<%= pkg.version %>.zip' 101 | }, 102 | expand: true, 103 | cwd: 'build/<%= pkg.name %>/', 104 | src: ['**/*'], 105 | dest: '<%= pkg.name %>/' 106 | } 107 | }, 108 | 109 | search: { 110 | files: { 111 | src: ['<%= pkg.main %>'] 112 | }, 113 | options: { 114 | logFile: 'tmp/log-search.log', 115 | searchString: /^[ \t\/*#@]*Version:(.*)$/mig, 116 | onMatch: function(match) { 117 | var regExp = /^[ \t\/*#@]*Version:(.*)$/mig; 118 | var groupedMatches = regExp.exec( match.match ); 119 | var versionFound = groupedMatches[1].trim(); 120 | if ( versionFound != grunt.file.readJSON('package.json').version ) { 121 | grunt.fail.fatal("Plugin version does not match with package.json version. Please, fix."); 122 | } 123 | }, 124 | onComplete: function( matches ) { 125 | if ( ! matches.numMatches ) { 126 | if ( ! grunt.file.readJSON('package.json').main ) { 127 | grunt.fail.fatal("main field is not defined in package.json. Please, add the plugin main file on that field."); 128 | } 129 | else { 130 | grunt.fail.fatal("Version Plugin header not found in " + grunt.file.readJSON('package.json').main + " file or the file does not exist" ); 131 | } 132 | 133 | } 134 | } 135 | } 136 | }, 137 | 138 | open: { 139 | dev : { 140 | path: '<%= pkg.projectEditUrl %>', 141 | app: 'Google Chrome' 142 | } 143 | } 144 | }); 145 | 146 | grunt.loadNpmTasks('grunt-search'); 147 | 148 | grunt.registerTask('version-compare', [ 'search' ] ); 149 | 150 | grunt.registerTask('build', [ 151 | 'version-compare', 152 | 'clean', 153 | 'checktextdomain', 154 | 'makepot', 155 | 'copy', 156 | 'compress' 157 | ]); 158 | }; -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | Plugin Name: Cloner 2 | Author: Ignacio Cruz, Ricardo Freitas, Panos Lyrakis 3 | Tags: multisite plugin, wordpress plugin, wordpress multisite, multisite, new blogs and users 4 | Requires at least: 3.8 5 | Tested up to: 4.6 6 | 7 | 8 | Change Log: 9 | ---------------------------------------------------------------------- 10 | ---------------------------------------------------------------------- 11 | 12 | 13 | 1.8.0 - 2019-05-30 14 | ---------------------------------------------------------------------- 15 | - Fix: Pages not clonned with php7.2 16 | - Fix: Cloner not cloning cpts 17 | - Fix: Cloner runs when re-visiting source site 18 | - Fix: Clone MarketPress orders 19 | - Fix: DB Error Message 'options' table not exists 20 | - New: Replace link and image urls in posts content 21 | - Fix: Multi Domains integration in sub directory installs 22 | - Fix: Foreign Bey constraint DB error 23 | - Fix: WooCommerce integration warnings 24 | - Fix: Deprecated function wp_get_http 25 | - Fix: Menus not cloned 26 | 27 | 1.7.7 - 2016-12-19 28 | ---------------------------------------------------------------------- 29 | - Fix: Warnings when copying tables 30 | - Fix: Fatal error when cloningn Revolution Slider tables 31 | - Updated WPMU DEV Dashboard notification 32 | 33 | 1.7.6 - 2016-09-01 34 | ---------------------------------------------------------------------- 35 | - Fixed: Main email was not cloned properly 36 | - Fixed: Main site tables were not cloned 37 | 38 | 1.7.5 - 2016-08-29 39 | ---------------------------------------------------------------------- 40 | - Fixed: Blog title was set empty when cloning a site. 41 | 42 | 1.7.4 - 2016-08-26 43 | ---------------------------------------------------------------------- 44 | - Fixed: A default user was always created even if Users option was unselected in Cloner Settings. 45 | 46 | 1.7.3 - 2016-08-19 47 | ---------------------------------------------------------------------- 48 | - Fixed: Deprecated notice in WP 4.6 49 | 50 | 1.7.2 - 2016-04-08 51 | ---------------------------------------------------------------------- 52 | - Fixed: BuddyPress warning 53 | - New: Ubermenu Integration 54 | 55 | 1.7.1 - 2015-08-19 56 | ---------------------------------------------------------------------- 57 | - Fixed: Wrong URLs when cloning sites in a subfolder installation 58 | - Improved Infinite SEO integration 59 | 60 | 1.7 - 2015-08-11 61 | ---------------------------------------------------------------------- 62 | - WP 4.3 update. 63 | - Improved copying terms performance and refactored some parts of the code 64 | - Fixed: Terms were not copied with the same ID 65 | 66 | 1.6.2 - 2015-07-21 67 | ---------------------------------------------------------------------- 68 | - Enhancement: Added WooCommerce integration 69 | 70 | 1.6.2 - 2015-07-16 71 | ---------------------------------------------------------------------- 72 | - Fixed: Redirecting issue in subfolders installations 73 | 74 | 1.6.1 - 2015-07-13 75 | ---------------------------------------------------------------------- 76 | - New: Added Multi Domains Plugin integration 77 | - Fixed: Menus copier was not copying CSS classes 78 | 79 | 1.6 - 2014-12-01 80 | ---------------------------------------------------------------------- 81 | - Enhancement: Terms are now inserted trying to respect the source blog term ID 82 | - Enhancement: Added new hooks 83 | - Fixed: If "Update dates" parameter was passed to the posts copier, the dates were not being updated 84 | 85 | 1.5.2 - 2014-11-03 86 | ---------------------------------------------------------------------- 87 | - Fixed: Bug with Custom Sidebars Pro 88 | 89 | 1.5.1 - 2014-10-28 90 | ---------------------------------------------------------------------- 91 | - Fixed: Custom subelements in menus were not copying their URLs 92 | - Fixed: Menus were not keeping the order in some cases 93 | - Fixed: Autoblog integration bug 94 | - Fixed: issue with file extension in integration.php 95 | - Enhancement: Added new filter before inserting new post 96 | - Enhancement: Using list of modules instead of readdir to save resources 97 | 98 | 1.5 - 2014-10-01 99 | ---------------------------------------------------------------------- 100 | - Fixed: Settings>Reading options were not preserved. 101 | - Fixed: Images links were not replaced correctly in some cases. 102 | - Fixed: Privacy settings were not copied correctly. 103 | - Fixed: Header/Background adn context meta for attachments were not copied. 104 | - Enhanced: Widgets are now copied separate to avoid some cache issues. 105 | - Enhanced: SSL Verify parameter removed when copying images. 106 | - Enhanced: Better UI styles. 107 | - Enhanced: Added PHP 5.3 support. 108 | - Enhanced: Added more filters. 109 | 110 | 1.4 - 2014-09-15 111 | ---------------------------------------------------------------------- 112 | - New: New option to override or keep the cloned site name 113 | - New: New option to select the cloned site privacy 114 | - New: Ultimate Shortcodes Integration. 115 | - New: Custom Sidebars Pro integration. 116 | - Enhanced: Posts/Pages/CPTs and Attachments IDs are now preserved in the cloned site 117 | - Enhanced: Menus are now cloned one by one to avoid timeouts. 118 | - Minor bugs fixes 119 | 120 | 1.3 - 2014-09-08 121 | ---------------------------------------------------------------------- 122 | - Fixed: Warning when creating a new site. 123 | - Fixed: Issue in autocomplete for some sites setup. 124 | - Enhanced: Added Ultimate Shortcodes integration. 125 | - Enhanced: Added new hooks. 126 | - Enhanced: Attachments in destination blog are now deleted before cloning. 127 | - Enhanced: Attachments can now be cloned based only on URL instead of attachment IDs 128 | - Other minor bugfixes. 129 | 130 | 1.2 - 2014-08-27 131 | ---------------------------------------------------------------------- 132 | - Enhanced: UI revamped. 133 | - Enhanced: New "Copy Custom Post Types" option. 134 | - Enhanced: Main site is now available to copy. 135 | - Enhanced: Posts are now copy with any status. 136 | - Enhanced: Autocomplete in Cloner screen has been improved. 137 | - Enhanced: Better memory usage. 138 | - Enhanced: New hooks added. 139 | - Fixed: Blog status were not copied. 140 | - Fixed minor issues. 141 | 142 | 1.1 - 2014-08-18 143 | ---------------------------------------------------------------------- 144 | - Added BuddyPress integration 145 | 146 | 1.0 - 2014-08-14 147 | ---------------------------------------------------------------------- 148 | - First release 149 | -------------------------------------------------------------------------------- /admin/views/clone-site.php: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | 5 | 6 |

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | 17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 |
32 | 33 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /cloner.php: -------------------------------------------------------------------------------- 1 | set_constants(); 48 | $this->includes(); 49 | 50 | add_action( 'init', array( $this, 'load_plugin_textdomain' ) ); 51 | add_action( 'init', array( $this, 'init_plugin' ) ); 52 | add_action( 'init', array( $this, 'maybe_upgrade' ) ); 53 | 54 | add_action( 'network_admin_notices', array( $this, 'display_installation_admin_notice' ) ); 55 | 56 | add_filter( 'copier_set_copier_args', array( $this, 'set_copier_args' ), 10, 3 ); 57 | 58 | add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_link' ), 40 ); 59 | 60 | if ( is_network_admin() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) { 61 | require_once( WPMUDEV_CLONER_PLUGIN_DIR . 'admin/cloner-admin-settings.php' ); 62 | add_action( 'plugins_loaded', array( 'WPMUDEV_Cloner_Admin_Settings', 'get_instance' ) ); 63 | 64 | require_once( WPMUDEV_CLONER_PLUGIN_DIR . 'admin/cloner-admin-clone-site.php' ); 65 | add_action( 'plugins_loaded', array( 'WPMUDEV_Cloner_Admin_Clone_Site', 'get_instance' ) ); 66 | } 67 | 68 | if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) 69 | require_once( WPMUDEV_CLONER_PLUGIN_DIR . 'admin/ajax.php' ); 70 | 71 | } 72 | 73 | 74 | 75 | private function set_constants() { 76 | if ( ! defined( 'WPMUDEV_CLONER_PLUGIN_DIR' ) ) 77 | define( 'WPMUDEV_CLONER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 78 | 79 | if ( ! defined( 'WPMUDEV_CLONER_PLUGIN_URL' ) ) 80 | define( 'WPMUDEV_CLONER_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 81 | 82 | if ( ! defined( 'WPMUDEV_CLONER_LANG_DOMAIN' ) ) 83 | define( 'WPMUDEV_CLONER_LANG_DOMAIN', 'wpmudev-cloner' ); 84 | 85 | //Define the same language domain for the copier classes. 86 | if ( ! defined( 'WPMUDEV_COPIER_LANG_DOMAIN' ) ) 87 | define( 'WPMUDEV_COPIER_LANG_DOMAIN', 'wpmudev-cloner' ); 88 | 89 | if ( ! defined( 'WPMUDEV_CLONER_VERSION' ) ) 90 | define( 'WPMUDEV_CLONER_VERSION', '1.8.0-beta-2' ); 91 | } 92 | 93 | private function includes() { 94 | include_once( WPMUDEV_CLONER_PLUGIN_DIR . 'integration/integration.php' ); 95 | include_once( WPMUDEV_CLONER_PLUGIN_DIR . 'copier/copier.php' ); 96 | include_once( WPMUDEV_CLONER_PLUGIN_DIR . 'copier-filters.php' ); 97 | include_once( WPMUDEV_CLONER_PLUGIN_DIR . 'helpers/general.php' ); 98 | include_once( WPMUDEV_CLONER_PLUGIN_DIR . 'helpers/settings.php' ); 99 | 100 | //load dashboard notice 101 | global $wpmudev_notices; 102 | $wpmudev_notices[] = array( 'id'=> 910773, 'name'=> 'Cloner', 'screens' => array( 'admin_page_clone_site-network', 'settings_page_cloner-network' ) ); 103 | include_once( WPMUDEV_CLONER_PLUGIN_DIR . 'externals/wpmudev-dash-notification.php' ); 104 | } 105 | 106 | public function load_plugin_textdomain() { 107 | $domain = WPMUDEV_CLONER_LANG_DOMAIN; 108 | $locale = apply_filters( 'plugin_locale', get_locale(), $domain ); 109 | 110 | load_textdomain( $domain, trailingslashit( WP_LANG_DIR ) . $domain . '/' . $domain . '-' . $locale . '.mo' ); 111 | load_plugin_textdomain( $domain, false, basename( WPMUDEV_CLONER_PLUGIN_DIR ) . '/lang/' ); 112 | } 113 | 114 | public function init_plugin() { 115 | if ( is_network_admin() && isset( $_GET['cloner_dismiss_install_notice'] ) ) { 116 | update_site_option( 'wpmudev_cloner_installation_notice_done', true ); 117 | } 118 | } 119 | 120 | public function display_installation_admin_notice() { 121 | 122 | if ( is_super_admin() && ! get_site_option( 'wpmudev_cloner_installation_notice_done' ) ) { 123 | $dismiss_url = add_query_arg( 'cloner_dismiss_install_notice', 'true' ); 124 | ?> 125 |
126 |

Settings » Cloner', WPMUDEV_CLONER_LANG_DOMAIN ), network_admin_url( 'settings.php?page=cloner' ) ); ?>

127 |

128 |
129 |
130 | $value ) { 152 | if ( ! in_array( $to_copy_option, $settings['to_copy'] ) && $to_copy_option != 'widgets' && $to_copy_option != 'nav_menu_item' ) 153 | unset( $option['to_copy'][ $to_copy_option ] ); 154 | } 155 | 156 | return $option; 157 | } 158 | 159 | 160 | public function maybe_upgrade() { 161 | $current_version_saved = get_site_option( 'wpmudev_cloner_version', '1.1' ); 162 | 163 | if ( WPMUDEV_CLONER_VERSION === $current_version_saved) 164 | return; 165 | 166 | if ( version_compare( $current_version_saved, '1.2', '<' ) ) { 167 | $settings = wpmudev_cloner_get_settings(); 168 | $settings['to_copy'][] = 'cpts'; 169 | wpmudev_cloner_update_settings( $settings ); 170 | } 171 | 172 | update_site_option( 'wpmudev_cloner_version', WPMUDEV_CLONER_VERSION ); 173 | } 174 | 175 | /** 176 | * Add a "Clone Site" link in admin bar for Super Admins 177 | */ 178 | public function add_admin_bar_link() { 179 | global $wp_admin_bar; 180 | 181 | if ( ! current_user_can( 'manage_network' ) ) 182 | return; 183 | 184 | if ( is_network_admin() ) 185 | return; 186 | 187 | if ( ! cloner_is_blog_clonable( get_current_blog_id() ) ) 188 | return; 189 | 190 | $url = network_admin_url( 'index.php' ); 191 | $url = add_query_arg( 192 | array( 193 | 'page' => 'clone_site', 194 | 'blog_id' => get_current_blog_id() 195 | ), 196 | $url 197 | ); 198 | 199 | $wp_admin_bar->add_menu( array( 200 | 'parent' => 'site-name', 201 | 'id' => 'clone-site', 202 | 'title' => __( 'Clone Site', WPMUDEV_CLONER_LANG_DOMAIN ), 203 | 'href' => $url, 204 | ) ); 205 | } 206 | 207 | } 208 | 209 | function wpmudev_cloner() { 210 | return WPMUDEV_Cloner::get_instance(); 211 | } 212 | 213 | wpmudev_cloner(); 214 | -------------------------------------------------------------------------------- /copier/class.copier-tables.php: -------------------------------------------------------------------------------- 1 | array(), 9 | 'create_tables' => false 10 | ); 11 | } 12 | 13 | public function copy() { 14 | global $wpdb; 15 | 16 | // Prefixes 17 | $new_prefix = $wpdb->prefix; 18 | $template_prefix = $wpdb->get_blog_prefix( $this->source_blog_id ); 19 | 20 | $tables_to_copy = $this->args['tables']; 21 | 22 | // If create_tables = true, we'll need at least to create all the tables 23 | // Empty or not 24 | if ( $this->args['create_tables'] ) 25 | $all_source_tables = wp_list_pluck( copier_get_additional_tables( $this->source_blog_id ), 'prefix.name' ); 26 | else 27 | $all_source_tables = $tables_to_copy; 28 | 29 | // Deprecated 30 | $all_source_tables = apply_filters( 'nbt_copy_additional_tables', $all_source_tables ); 31 | 32 | /** 33 | * Filter the source tables list. 34 | * 35 | * This list includes all the source tables that we are going to copy except 36 | * for the native WordPress tables. 37 | * 38 | * @param Array $all_source_tables Source tables list. 39 | */ 40 | $all_source_tables = apply_filters( 'wpmudev_copier_copy_additional_tables', $all_source_tables ); 41 | 42 | $wpdb->query( 'SET foreign_key_checks = 0' ); 43 | 44 | foreach ( $all_source_tables as $table ) { 45 | // Copy content too? 46 | $add = in_array( $table, $tables_to_copy ); 47 | $table = esc_sql( $table ); 48 | 49 | // MultiDB Hack 50 | if ( is_a( $wpdb, 'm_wpdb' ) ) 51 | $tablebase = end( explode( '.', $table, 2 ) ); 52 | else 53 | $tablebase = $table; 54 | 55 | $new_table = $new_prefix . substr( $tablebase, strlen( $template_prefix ) ); 56 | 57 | $result = $wpdb->get_results( "SHOW TABLES LIKE '{$new_table}'", ARRAY_N ); 58 | if ( ! empty( $result ) ) { 59 | // The table is already present in the new blog 60 | // Clear it 61 | $this->clear_table( $new_table ); 62 | 63 | if ( $add ) { 64 | // And copy the content if needed 65 | $result = $this->copy_table( $new_table ); 66 | if ( is_wp_error( $result ) ) { 67 | $wpdb->query( "ROLLBACK;" ); 68 | return $result; 69 | } 70 | } 71 | } 72 | else { 73 | // The table does not exist in the new blog 74 | // Let's create it 75 | $create_script = current( $wpdb->get_col( 'SHOW CREATE TABLE ' . $table, 1 ) ); 76 | 77 | if ( $create_script && preg_match( '/\(.*\)/s', $create_script, $match ) ) { 78 | $table_body = $match[0]; 79 | $wpdb->query( "CREATE TABLE IF NOT EXISTS {$new_table} {$table_body}" ); 80 | 81 | if ( $add ) { 82 | // And copy the content if needed 83 | if ( is_a( $wpdb, 'm_wpdb' ) ) { 84 | $rows = $wpdb->get_results( "SELECT * FROM {$table}", ARRAY_A ); 85 | foreach ( $rows as $row ) { 86 | $wpdb->insert( $new_table, $row ); 87 | } 88 | } else { 89 | $wpdb->query( "INSERT INTO {$new_table} SELECT * FROM {$table}" ); 90 | } 91 | } 92 | 93 | } 94 | 95 | if ( ! empty( $wpdb->last_error ) ) { 96 | $error = new WP_Error( 'insertion_error', sprintf( __( 'Insertion Error: %s', WPMUDEV_COPIER_LANG_DOMAIN ), $wpdb->last_error ) ); 97 | $wpdb->query("ROLLBACK;"); 98 | return $error; 99 | } 100 | } 101 | 102 | } 103 | 104 | $wpdb->query("COMMIT;"); 105 | $wpdb->query( 'SET foreign_key_checks = 1' ); 106 | 107 | return true; 108 | } 109 | 110 | function copy_table( $dest_table ) { 111 | global $wpdb; 112 | 113 | // Deprecated 114 | do_action( 'blog_templates-copying_table', $dest_table, $this->source_blog_id ); 115 | 116 | /** 117 | * Fires before menus are copied. 118 | * 119 | * @param String $dest_table Destination table name 120 | * @param Integer $source_blog_id Source Blog ID from where we are copying the table. 121 | */ 122 | do_action( 'wpmudev_copier-copying_table', $dest_table, $this->source_blog_id ); 123 | 124 | $destination_prefix = $wpdb->prefix; 125 | 126 | //Switch to the template blog, then grab the values 127 | switch_to_blog( $this->source_blog_id ); 128 | $template_prefix = $wpdb->prefix; 129 | $source_table = str_replace( $destination_prefix, $template_prefix, $dest_table ); 130 | $templated = $wpdb->get_results( "SELECT * FROM {$source_table}" ); 131 | restore_current_blog(); //Switch back to the newly created blog 132 | 133 | if ( count( $templated ) ) 134 | $to_remove = $this->get_fields_to_remove($dest_table, $templated[0]); 135 | 136 | //Now, insert the templated settings into the newly created blog 137 | foreach ($templated as $row) { 138 | $row = (array)$row; 139 | 140 | foreach ( $row as $key => $value ) { 141 | if ( in_array( $key, $to_remove ) ) 142 | unset( $row[ $key ] ); 143 | } 144 | 145 | // Deprecated 146 | $row = apply_filters('blog_templates-process_row', $row, $dest_table, $this->source_blog_id); 147 | 148 | /** 149 | * Filter a table row. 150 | * 151 | * This filters helps to modify any row of a table that we are copying. 152 | * 153 | * @param Array $row Table Row. 154 | * @param String $dest_table Destination table name. 155 | * @param Integer $source_blog_id Source blog ID from where we are copying the table. 156 | */ 157 | $row = apply_filters('wpmudev_copier-process_row', $row, $dest_table, $this->source_blog_id ); 158 | 159 | if ( ! $row ) 160 | continue; 161 | 162 | $wpdb->insert( $dest_table, $row ); 163 | if ( ! empty( $wpdb->last_error ) ) { 164 | return new WP_Error( 'copy_table', __( 'Error copying table: ' . $dest_table, WPMUDEV_COPIER_LANG_DOMAIN ) ); 165 | } 166 | } 167 | 168 | return true; 169 | } 170 | 171 | 172 | public function clear_table( $table ) { 173 | global $wpdb; 174 | 175 | // Deprecated 176 | do_action( 'blog_templates-clearing_table', $table ); 177 | 178 | /** 179 | * Fires before a table is cleared. 180 | * 181 | * @param String $table Destination table name 182 | */ 183 | do_action( 'wpmudev_copier-clearing_table', $table ); 184 | 185 | // Deprecated 186 | $where = apply_filters( 'blog_templates-clear_table_where', "", $table ); 187 | $where = apply_filters( 'wpmudev_copier-clear_table_where', "", $table ); 188 | 189 | $wpdb->query( "DELETE FROM $table $where" ); 190 | 191 | if ( $wpdb->last_error ) 192 | return new WP_Error( 'deletion_error', sprintf( __( 'Deletion Error: %1$s - The template was not applied. (New Blog Templates - While clearing %2$s)', WPMUDEV_COPIER_LANG_DOMAIN ), $wpdb->last_error, $table ) ); 193 | 194 | return true; 195 | } 196 | 197 | /** 198 | * Added to automate comparing the two tables, and making sure no old fields that have been removed get copied to the new table 199 | * 200 | * @param mixed $new_table_name 201 | * @param mixed $old_table_row 202 | * 203 | * @since 1.0 204 | */ 205 | function get_fields_to_remove( $new_table_name, $old_table_row ) { 206 | //make sure we have something to compare it to 207 | if ( empty( $old_table_row ) ) 208 | return false; 209 | 210 | //We need the old table row to be in array format, so we can use in_array() 211 | $old_table_row = (array)$old_table_row; 212 | 213 | global $wpdb; 214 | 215 | //Get the new table structure 216 | $new_table = (array)$wpdb->get_results( "SHOW COLUMNS FROM {$new_table_name}" ); 217 | 218 | $new_fields = array(); 219 | foreach( $new_table as $row ) { 220 | $new_fields[] = $row->Field; 221 | } 222 | 223 | $results = array(); 224 | 225 | //Now, go through the columns in the old table, and check if there are any that don't show up in the new table 226 | foreach ( $old_table_row as $key => $value ) { 227 | if ( ! in_array( $key,$new_fields ) ) { //If the new table doesn't have this field 228 | //There's a column that isn't in the new one, make note of that 229 | $results[] = $key; 230 | } 231 | } 232 | 233 | //Return the results array, which should contain all of the fields that don't appear in the new table 234 | return $results; 235 | } 236 | 237 | 238 | } 239 | } -------------------------------------------------------------------------------- /admin/assets/jquery-multi-select/js/jquery-multi-select.js: -------------------------------------------------------------------------------- 1 | !function(e){"use strict";var t=function(t,n){this.options=n;this.$element=e(t);this.$container=e("
",{"class":"ms-container"});this.$selectableContainer=e("
",{"class":"ms-selectable"});this.$selectionContainer=e("
",{"class":"ms-selection"});this.$selectableUl=e("
    ",{"class":"ms-list",tabindex:"-1"});this.$selectionUl=e("
      ",{"class":"ms-list",tabindex:"-1"});this.scrollTo=0;this.elemsSelector="li:visible:not(.ms-optgroup-label,.ms-optgroup-container,."+n.disabledClass+")"};t.prototype={constructor:t,init:function(){var t=this,n=this.$element;if(n.next(".ms-container").length===0){n.css({position:"absolute",left:"-9999px"});n.attr("id",n.attr("id")?n.attr("id"):Math.ceil(Math.random()*1e3)+"multiselect");this.$container.attr("id","ms-"+n.attr("id"));this.$container.addClass(t.options.cssClass);n.find("option").each(function(){t.generateLisFromOption(this)});this.$selectionUl.find(".ms-optgroup-label").hide();if(t.options.selectableHeader){t.$selectableContainer.append(t.options.selectableHeader)}t.$selectableContainer.append(t.$selectableUl);if(t.options.selectableFooter){t.$selectableContainer.append(t.options.selectableFooter)}if(t.options.selectionHeader){t.$selectionContainer.append(t.options.selectionHeader)}t.$selectionContainer.append(t.$selectionUl);if(t.options.selectionFooter){t.$selectionContainer.append(t.options.selectionFooter)}t.$container.append(t.$selectableContainer);t.$container.append(t.$selectionContainer);n.after(t.$container);t.activeMouse(t.$selectableUl);t.activeKeyboard(t.$selectableUl);var r=t.options.dblClick?"dblclick":"click";t.$selectableUl.on(r,".ms-elem-selectable",function(){t.select(e(this).data("ms-value"))});t.$selectionUl.on(r,".ms-elem-selection",function(){t.deselect(e(this).data("ms-value"))});t.activeMouse(t.$selectionUl);t.activeKeyboard(t.$selectionUl);n.on("focus",function(){t.$selectableUl.focus()})}var i=n.find("option:selected").map(function(){return e(this).val()}).get();t.select(i,"init");if(typeof t.options.afterInit==="function"){t.options.afterInit.call(this,this.$container)}},generateLisFromOption:function(t,n,r){var i=this,s=i.$element,o="",u=e(t);for(var a=0;a"+i.escapeHTML(u.text())+""),c=l.clone(),h=u.val(),p=i.sanitize(h);l.data("ms-value",h).addClass("ms-elem-selectable").attr("id",p+"-selectable");c.data("ms-value",h).addClass("ms-elem-selection").attr("id",p+"-selection").hide();if(u.prop("disabled")||s.prop("disabled")){c.addClass(i.options.disabledClass);l.addClass(i.options.disabledClass)}var d=u.parent("optgroup");if(d.length>0){var v=d.attr("label"),m=i.sanitize(v),g=i.$selectableUl.find("#optgroup-selectable-"+m),y=i.$selectionUl.find("#optgroup-selection-"+m);if(g.length===0){var b='
    • ',w='
      • '+v+"
      ";g=e(b);y=e(b);g.attr("id","optgroup-selectable-"+m);y.attr("id","optgroup-selection-"+m);g.append(e(w));y.append(e(w));if(i.options.selectableOptgroup){g.find(".ms-optgroup-label").on("click",function(){var t=d.children(":not(:selected, :disabled)").map(function(){return e(this).val()}).get();i.select(t)});y.find(".ms-optgroup-label").on("click",function(){var t=d.children(":selected:not(:disabled)").map(function(){return e(this).val()}).get();i.deselect(t)})}i.$selectableUl.append(g);i.$selectionUl.append(y)}n=n==undefined?g.find("ul").children().length:n+1;l.insertAt(n,g.children());c.insertAt(n,y.children())}else{n=n==undefined?i.$selectableUl.children().length:n;l.insertAt(n,i.$selectableUl);c.insertAt(n,i.$selectionUl)}},addOption:function(t){var n=this;if(t.value)t=[t];e.each(t,function(t,r){if(r.value&&n.$element.find("option[value='"+r.value+"']").length===0){var i=e('"),t=parseInt(typeof r.index==="undefined"?n.$element.children().length:r.index),s=r.nested==undefined?n.$element:e("optgroup[label='"+r.nested+"']");i.insertAt(t,s);n.generateLisFromOption(i.get(0),t,r.nested)}})},escapeHTML:function(t){return e("
      ").text(t).html()},activeKeyboard:function(t){var n=this;t.on("focus",function(){e(this).addClass("ms-focus")}).on("blur",function(){e(this).removeClass("ms-focus")}).on("keydown",function(r){switch(r.which){case 40:case 38:r.preventDefault();r.stopPropagation();n.moveHighlight(e(this),r.which===38?-1:1);return;case 37:case 39:r.preventDefault();r.stopPropagation();n.switchList(t);return;case 9:if(n.$element.is("[tabindex]")){r.preventDefault();var i=parseInt(n.$element.attr("tabindex"),10);i=r.shiftKey?i-1:i+1;e('[tabindex="'+i+'"]').focus();return}else{if(r.shiftKey){n.$element.trigger("focus")}}}if(e.inArray(r.which,n.options.keySelect)>-1){r.preventDefault();r.stopPropagation();n.selectHighlighted(t);return}})},moveHighlight:function(e,t){var n=e.find(this.elemsSelector),r=n.filter(".ms-hover"),i=null,s=n.first().outerHeight(),o=e.height(),u="#"+this.$container.prop("id");n.removeClass("ms-hover");if(t===1){i=r.nextAll(this.elemsSelector).first();if(i.length===0){var a=r.parent();if(a.hasClass("ms-optgroup")){var f=a.parent(),l=f.next(":visible");if(l.length>0){i=l.find(this.elemsSelector).first()}else{i=n.first()}}else{i=n.first()}}}else if(t===-1){i=r.prevAll(this.elemsSelector).first();if(i.length===0){var a=r.parent();if(a.hasClass("ms-optgroup")){var f=a.parent(),c=f.prev(":visible");if(c.length>0){i=c.find(this.elemsSelector).last()}else{i=n.last()}}else{i=n.last()}}}if(i.length>0){i.addClass("ms-hover");var h=e.scrollTop()+i.position().top-o/2+s/2;e.scrollTop(h)}},selectHighlighted:function(e){var t=e.find(this.elemsSelector),n=t.filter(".ms-hover").first();if(n.length>0){if(e.parent().hasClass("ms-selectable")){this.select(n.data("ms-value"))}else{this.deselect(n.data("ms-value"))}t.removeClass("ms-hover")}},switchList:function(e){e.blur();this.$container.find(this.elemsSelector).removeClass("ms-hover");if(e.parent().hasClass("ms-selectable")){this.$selectionUl.focus()}else{this.$selectableUl.focus()}},activeMouse:function(t){var n=this;e("body").on("mouseenter",n.elemsSelector,function(){e(this).parents(".ms-container").find(n.elemsSelector).removeClass("ms-hover");e(this).addClass("ms-hover")});e("body").on("mouseleave",n.elemsSelector,function(){e(this).parents(".ms-container").find(n.elemsSelector).removeClass("ms-hover");})},refresh:function(){this.destroy();this.$element.multiSelect(this.options)},destroy:function(){e("#ms-"+this.$element.attr("id")).remove();this.$element.css("position","").css("left","");this.$element.removeData("multiselect")},select:function(t,n){if(typeof t==="string"){t=[t]}var r=this,i=this.$element,s=e.map(t,function(e){return r.sanitize(e)}),o=this.$selectableUl.find("#"+s.join("-selectable, #")+"-selectable").filter(":not(."+r.options.disabledClass+")"),u=this.$selectionUl.find("#"+s.join("-selection, #")+"-selection").filter(":not(."+r.options.disabledClass+")"),a=i.find("option:not(:disabled)").filter(function(){return e.inArray(this.value,t)>-1});if(n==="init"){o=this.$selectableUl.find("#"+s.join("-selectable, #")+"-selectable"),u=this.$selectionUl.find("#"+s.join("-selection, #")+"-selection")}if(o.length>0){o.addClass("ms-selected").hide();u.addClass("ms-selected").show();a.prop("selected",true);r.$container.find(r.elemsSelector).removeClass("ms-hover");var f=r.$selectableUl.children(".ms-optgroup-container");if(f.length>0){f.each(function(){var t=e(this).find(".ms-elem-selectable");if(t.length===t.filter(".ms-selected").length){e(this).find(".ms-optgroup-label").hide()}});var l=r.$selectionUl.children(".ms-optgroup-container");l.each(function(){var t=e(this).find(".ms-elem-selection");if(t.filter(".ms-selected").length>0){e(this).find(".ms-optgroup-label").show()}})}else{if(r.options.keepOrder&&n!=="init"){var c=r.$selectionUl.find(".ms-selected");if(c.length>1&&c.last().get(0)!=u.get(0)){u.insertAfter(c.last())}}}if(n!=="init"){i.trigger("change");if(typeof r.options.afterSelect==="function"){r.options.afterSelect.call(this,t)}}}},deselect:function(t){if(typeof t==="string"){t=[t]}var n=this,r=this.$element,i=e.map(t,function(e){return n.sanitize(e)}),s=this.$selectableUl.find("#"+i.join("-selectable, #")+"-selectable"),o=this.$selectionUl.find("#"+i.join("-selection, #")+"-selection").filter(".ms-selected").filter(":not(."+n.options.disabledClass+")"),u=r.find("option").filter(function(){return e.inArray(this.value,t)>-1});if(o.length>0){s.removeClass("ms-selected").show();o.removeClass("ms-selected").hide();u.prop("selected",false);n.$container.find(n.elemsSelector).removeClass("ms-hover");var a=n.$selectableUl.children(".ms-optgroup-container");if(a.length>0){a.each(function(){var t=e(this).find(".ms-elem-selectable");if(t.filter(":not(.ms-selected)").length>0){e(this).find(".ms-optgroup-label").show()}});var f=n.$selectionUl.children(".ms-optgroup-container");f.each(function(){var t=e(this).find(".ms-elem-selection");if(t.filter(".ms-selected").length===0){e(this).find(".ms-optgroup-label").hide()}})}r.trigger("change");if(typeof n.options.afterDeselect==="function"){n.options.afterDeselect.call(this,t)}}},select_all:function(){var t=this.$element,n=t.val();t.find('option:not(":disabled")').prop("selected",true);this.$selectableUl.find(".ms-elem-selectable").filter(":not(."+this.options.disabledClass+")").addClass("ms-selected").hide();this.$selectionUl.find(".ms-optgroup-label").show();this.$selectableUl.find(".ms-optgroup-label").hide();this.$selectionUl.find(".ms-elem-selection").filter(":not(."+this.options.disabledClass+")").addClass("ms-selected").show();this.$selectionUl.focus();t.trigger("change");if(typeof this.options.afterSelect==="function"){var r=e.grep(t.val(),function(t){return e.inArray(t,n)<0});this.options.afterSelect.call(this,r)}},deselect_all:function(){var e=this.$element,t=e.val();e.find("option").prop("selected",false);this.$selectableUl.find(".ms-elem-selectable").removeClass("ms-selected").show();this.$selectionUl.find(".ms-optgroup-label").hide();this.$selectableUl.find(".ms-optgroup-label").show();this.$selectionUl.find(".ms-elem-selection").removeClass("ms-selected").hide();this.$selectableUl.focus();e.trigger("change");if(typeof this.options.afterDeselect==="function"){this.options.afterDeselect.call(this,t)}},sanitize:function(e){var t=0,n,r;if(e.length==0)return t;var i=0;for(n=0,i=e.length;n_get_microtime(); 15 | 16 | wp_cache_delete( 'notoptions', 'options' ); 17 | wp_cache_delete( 'alloptions', 'options' ); 18 | 19 | do_action( 'wpmudev_copier_before_copy_settings', $this->source_blog_id ); 20 | 21 | $source_blog_user_roles = $wpdb->get_blog_prefix( $this->source_blog_id ) . 'user_roles'; 22 | $exclude_settings = array( 23 | 'siteurl', 24 | 'blogname', 25 | 'admin_email', 26 | 'new_admin_email', 27 | 'home', 28 | 'upload_path', 29 | 'db_version', 30 | 'secret', 31 | 'fileupload_url', 32 | 'nonce_salt', 33 | 'copier-pending', 34 | 'stylesheet', 35 | 'active_plugins', 36 | $source_blog_user_roles 37 | ); 38 | 39 | /** 40 | * Filter the excclude settings Array. 41 | * 42 | * Those settings names included in the array will not 43 | * be copied to the destination blog. 44 | * 45 | * @param Array $exclude_settings Exclude Settings list. 46 | */ 47 | $exclude_settings = apply_filters( 'wpmudev_copier_exclude_settings', $exclude_settings ); 48 | 49 | $the_options = $wpdb->get_col( "SELECT option_name FROM $wpdb->options" ); 50 | $the_options = apply_filters( 'wpmudev_copier_delete_options', $the_options ); 51 | $this->log( 'class.copier-post-types.php. Deleting ' . count( $the_options ) . ' options' ); 52 | foreach ( $the_options as $option_name ) { 53 | if ( ! in_array( $option_name, $exclude_settings ) ) { 54 | // Better use delete_option instead of doing it directly in DB 55 | // This will clean cache if needed 56 | $deleted = delete_option( $option_name ); 57 | } 58 | } 59 | 60 | $exclude_settings = esc_sql( $exclude_settings ); 61 | $exclude_settings_where = "`option_name` != '" . implode( "' AND `option_name` != '", $exclude_settings ) . "'"; 62 | 63 | //$exclude_settings = apply_filters( 'blog_template_exclude_settings', $exclude_settings_where ); 64 | //$wpdb->query( "DELETE FROM $wpdb->options WHERE $exclude_settings_where" ); 65 | 66 | if ( $wpdb->last_error ) { 67 | $this->log( 'class.copier-settings.php. Error copying settings: ' . $wpdb->last_error ); 68 | return new WP_Error( 'settings_error', __( 'Error copying settings', WPMUDEV_COPIER_LANG_DOMAIN ) ); 69 | } 70 | 71 | if ( ! function_exists( 'get_plugins' ) ) { 72 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 73 | } 74 | 75 | switch_to_blog( $this->source_blog_id ); 76 | $src_blog_settings = $wpdb->get_results( "SELECT * FROM $wpdb->options WHERE $exclude_settings_where" ); 77 | $template_prefix = $wpdb->prefix; 78 | 79 | // Get the source theme mods 80 | $themes_mods = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'theme_mods_%' "); 81 | 82 | // Get the source active theme 83 | $template_theme = wp_get_theme(); 84 | 85 | // List of active plugins 86 | $all_plugins = get_plugins(); 87 | $source_plugins = array(); 88 | foreach( $all_plugins as $plugin_slug => $plugin ) { 89 | if ( is_plugin_active( $plugin_slug ) ) 90 | $source_plugins[] = $plugin_slug; 91 | } 92 | 93 | restore_current_blog(); 94 | 95 | 96 | $new_prefix = $wpdb->prefix; 97 | 98 | $this->log( 'class.copier-post-types.php. Copyng ' . count( $src_blog_settings ) . ' options' ); 99 | foreach ( $src_blog_settings as $row ) { 100 | 101 | //Make sure none of the options are using wp_X_ convention, and if they are, replace the value with the new blog ID 102 | $row->option_name = str_replace( $template_prefix, $new_prefix, $row->option_name ); 103 | //if ( 'sidebars_widgets' != $row->option_name ) /* <-- Added this to prevent unserialize() call choking on badly formatted widgets pickled array */ 104 | //$row->option_value = str_replace( $template_prefix, $new_prefix, $row->option_value ); 105 | 106 | // Deprecated 107 | $row = apply_filters( 'blog_templates-copy-options_row', $row, $this->template, get_current_blog_id(), $this->user_id ); 108 | 109 | /** 110 | * Filter a single setting row for database insertion. 111 | * 112 | * @param Array $row Setting row prepared for database. 113 | * @param Integer $source_blog_id Source Blog ID. 114 | */ 115 | $row = apply_filters( 'wpmudev_copier-copy-options_row', $row, $this->source_blog_id ); 116 | 117 | if ( ! $row ) 118 | continue; // Prevent empty row insertion 119 | 120 | wp_cache_delete( $row->option_name, 'options' ); 121 | 122 | $added = add_option( $row->option_name, maybe_unserialize( $row->option_value ), null, $row->autoload ); 123 | 124 | if ( ! $added ) 125 | $updated = update_option( $row->option_name, maybe_unserialize( $row->option_value ) ); 126 | 127 | 128 | } 129 | 130 | // Now the user roles 131 | $user_roles = get_blog_option( $this->source_blog_id, $source_blog_user_roles ); 132 | if ( $user_roles ) { 133 | $destination_user_roles = $wpdb->prefix . 'user_roles'; 134 | update_option( $destination_user_roles, $user_roles ); 135 | } 136 | 137 | // Activate plugins 138 | $deactivate_plugins = array(); 139 | foreach( $all_plugins as $plugin_slug => $plugin ) { 140 | if ( ! in_array( $plugin_slug, $source_plugins ) && is_plugin_active( $plugin_slug ) ) 141 | $deactivate_plugins[] = $plugin_slug; 142 | } 143 | deactivate_plugins( $deactivate_plugins, false, false ); 144 | 145 | foreach ( $source_plugins as $plugin_slug ) { 146 | if ( ! is_plugin_active( $plugin_slug ) ) 147 | activate_plugin( $plugin_slug, null, false, true ); 148 | } 149 | 150 | // We are going to switcth the theme manually 151 | switch_theme( $template_theme->get_stylesheet() ); 152 | 153 | // Themes mods 154 | foreach ( $themes_mods as $mod ) { 155 | $theme_slug = str_replace( 'theme_mods_', '', $mod->option_name ); 156 | $mods = maybe_unserialize( $mod->option_value ); 157 | 158 | if ( isset( $mods['nav_menu_locations'] ) ) 159 | unset( $mods['nav_menu_locations'] ); 160 | 161 | if ( 162 | apply_filters( 'nbt_change_attachments_urls', true ) // Deprecated 163 | /** This filter is documented in class.copier-attachment.php */ 164 | && apply_filters( 'wpmudev_copier_change_attachments_urls', true ) 165 | ) 166 | array_walk_recursive( $mods, array( &$this, 'set_theme_mods_url' ), array( $this->source_blog_id, get_current_blog_id() ) ); 167 | 168 | update_option( "theme_mods_$theme_slug", $mods ); 169 | } 170 | 171 | // Set blog status 172 | $source_blog_details = get_blog_details( $this->source_blog_id ); 173 | 174 | if ( ! empty( $source_blog_details ) ) { 175 | $source_blog_details = (array)$source_blog_details; 176 | extract( $source_blog_details ); 177 | 178 | update_blog_status( get_current_blog_id(), 'public', $public ); 179 | update_blog_status( get_current_blog_id(), 'archived', $archived ); 180 | update_blog_status( get_current_blog_id(), 'mature', $mature ); 181 | update_blog_status( get_current_blog_id(), 'spam', $spam ); 182 | update_blog_status( get_current_blog_id(), 'deleted', $deleted ); 183 | } 184 | 185 | if ( in_array( 'admin_email', $exclude_settings ) ) { 186 | $source_admin_email = get_blog_option( $this->source_blog_id, 'admin_email' ); 187 | update_option( 'admin_email', $source_admin_email ); 188 | delete_option( 'new_admin_email' ); 189 | } 190 | 191 | // Deprecated 192 | do_action( 'blog_templates-copy-options', $this->template,$this->source_blog_id, $this->user_id ); 193 | 194 | /** 195 | * Fires before menus are copied. 196 | * 197 | * @param Integer $source_blog_id Source Blog ID from where we are copying the settings 198 | * @param Integer $user_id User ID that created the blog. 199 | * @param Array $template Only applies when using New Blog Templates. Includes the template attributes 200 | */ 201 | do_action( 'wpmudev_copier-copy-options', $this->source_blog_id, $this->user_id, $this->template ); 202 | 203 | $this->log( 'Settings copy. Elapsed time: ' . ( $this->_get_microtime() - $start_time ) ); 204 | return true; 205 | } 206 | 207 | function set_theme_mods_url( &$item, $key, $userdata = array() ) { 208 | $template_blog_id = $userdata[0]; 209 | $new_blog_id = $userdata[1]; 210 | 211 | 212 | if ( is_object( $item ) && ! empty( $item->attachment_id ) ) { 213 | // Let's copy this attachment and replace it 214 | $args = array( 215 | 'attachment_id' => $item->attachment_id 216 | ); 217 | $attachment_copier = copier_get_copier( 'attachment', $this->source_blog_id, $args, $this->user_id, $this->template ); 218 | $result = $attachment_copier->copy(); 219 | if ( ! is_wp_error( $result ) ) { 220 | $attachment_id = $result['new_attachment_id']; 221 | 222 | add_filter( 'wp_get_attachment_url', 'copier_set_correct_wp_get_attachment_url' ); 223 | $url = wp_get_attachment_url( $attachment_id ); 224 | remove_filter( 'wp_get_attachment_url', 'copier_set_correct_wp_get_attachment_url' ); 225 | 226 | $item->attachment_id = $attachment_id; 227 | $item->url = $url; 228 | $item->thumbnail_url = $url; 229 | } 230 | } 231 | 232 | 233 | } 234 | 235 | } 236 | } -------------------------------------------------------------------------------- /copier/class.copier-menus.php: -------------------------------------------------------------------------------- 1 | array(), 15 | 'menu_id' => false, 16 | ); 17 | } 18 | 19 | public function change_insert_post_ID( $data, $postarr ) { 20 | if ( !empty( self::$origin_menu_item_id ) && 'nav_menu_item' === $data['post_type'] ) { 21 | $data['ID'] = self::$origin_menu_item_id; 22 | } 23 | 24 | return $data; 25 | } 26 | 27 | 28 | 29 | public function copy() { 30 | 31 | if ( $this->args['menu_id'] === false ) 32 | return new WP_Error( 'wrong_menu', __( 'No Menus to Copy', WPMUDEV_COPIER_LANG_DOMAIN ) ); 33 | 34 | // Deprecated 35 | do_action( 'blog_templates-copying_menu', $this->source_blog_id, get_current_blog_id() ); 36 | 37 | /** 38 | * Fires before menus are copied. 39 | * 40 | * @param Integer $this->source_blog_id Source Blog ID from where we are copying the menu 41 | * @param Integer $current_blog_id Blog ID where we are copying the menu 42 | * @param Integer $menu_id. The key of the array got from wp_get_nav_menus() 43 | */ 44 | do_action( 'wpmudev_copier-copying_menu', $this->source_blog_id, get_current_blog_id(), $this->args['menu_id'] ); 45 | 46 | // Get the source menus and their menu items 47 | switch_to_blog( $this->source_blog_id ); 48 | $source_menus = wp_get_nav_menus(); 49 | 50 | $source_menu = false; 51 | foreach ( $source_menus as $_source_menu ) { 52 | if ( $_source_menu->term_id == $this->args['menu_id'] ) { 53 | $source_menu = $_source_menu; 54 | $source_menu->items = wp_get_nav_menu_items( $source_menu->term_id ); 55 | $source_site_url = home_url(); 56 | } 57 | } 58 | 59 | restore_current_blog(); 60 | 61 | if ( ! $source_menu ) 62 | return new WP_Error( 'wrong_menu', sprintf( __( 'There was an error trying to copy the menu. ID: ', WPMUDEV_COPIER_LANG_DOMAIN ), $this->args['menu_id'] ) ); 63 | 64 | // Array that saves relationships to remap parents later 65 | $menu_items_remap = array(); 66 | 67 | // Now copy the menu 68 | 69 | // Create a new menu object 70 | $menu_args = array( 71 | 'menu-name' => $source_menu->name, 72 | 'description' => $source_menu->description 73 | ); 74 | 75 | // Insert a new menu 76 | $new_menu_id = wp_update_nav_menu_object( 0, $menu_args ); 77 | 78 | if ( ! $new_menu_id || is_wp_error( $new_menu_id ) ) 79 | return new WP_Error( 'insert_menu_error', sprintf( __( 'There was an error trying to copy the menu. ID: ', WPMUDEV_COPIER_LANG_DOMAIN ), $this->args['menu_id'] ) ); 80 | 81 | add_filter( 'wp_insert_post_data', array( $this, 'change_insert_post_ID' ), 10 ,2 ); 82 | 83 | foreach ( $source_menu->items as $menu_item ) { 84 | self::$origin_menu_item_id = $menu_item->ID; 85 | 86 | $new_item_args = array( 87 | 'menu-item-object' => $menu_item->object, 88 | 'menu-item-type' => $menu_item->type, 89 | 'menu-item-title' => $menu_item->title, 90 | 'menu-item-description' => $menu_item->description, 91 | 'menu-item-attr-title' => $menu_item->attr_title, 92 | 'menu-item-position' => $menu_item->menu_order, 93 | 'menu-item-target' => $menu_item->target, 94 | 'menu-item-classes' => $menu_item->classes, 95 | 'menu-item-xfn' => $menu_item->xfn, 96 | 'menu-item-status' => $menu_item->post_status, 97 | 'menu-item-url' => $menu_item->url 98 | ); 99 | 100 | if ( is_array( $new_item_args['menu-item-classes'] ) ) 101 | $new_item_args['menu-item-classes'] = implode( ' ', $new_item_args['menu-item-classes'] ); 102 | 103 | 104 | if ( 'custom' != $menu_item->type ) { 105 | // If not custom, try to link the real object (post/page/whatever) 106 | if ( 'post_type' == $new_item_args['menu-item-type'] ) { 107 | $new_item_args['menu-item-object-id'] = 0; 108 | if ( isset( $this->args['posts_mapping'][ $menu_item->object_id ] ) ) 109 | $new_item_args['menu-item-object-id'] = $this->args['posts_mapping'][ $menu_item->object_id ]; 110 | } 111 | elseif ( 'taxonomy' == $new_item_args['menu-item-type'] ) { 112 | // Let's grab the source term slug. We might have copied it, who knows? 113 | switch_to_blog( $this->source_blog_id ); 114 | $term = get_term( $menu_item->object_id, $menu_item->object ); 115 | restore_current_blog(); 116 | 117 | if ( ! $term ) 118 | continue; 119 | 120 | $new_blog_term = get_term_by( 'slug', $term->slug, $menu_item->object ); 121 | 122 | if ( ! $new_blog_term ) 123 | continue; 124 | 125 | // We have found the term in the new blog 126 | $new_item_args['menu-item-object-id'] = $new_blog_term->term_id; 127 | 128 | } 129 | } 130 | else { 131 | $new_item_args['menu-item-url'] = str_replace( $source_site_url, home_url(), $menu_item->url ); 132 | } 133 | 134 | // And insert/update the menu item 135 | $new_menu_item_id = @wp_update_nav_menu_item( $new_menu_id, 0, $new_item_args ); 136 | 137 | if ( ! $new_menu_item_id || is_wp_error( $new_menu_item_id ) ) 138 | continue; 139 | 140 | // Also, map the menu item 141 | $menu_items_remap[ $menu_item->ID ] = $new_menu_item_id; 142 | } 143 | 144 | remove_filter( 'wp_insert_post_data', array( $this, 'change_insert_post_ID' ) ); 145 | 146 | // Now remap the parents 147 | $items = wp_get_nav_menu_items( $new_menu_id, 'nav_menu' ); 148 | 149 | if ( ! empty( $items ) ) { 150 | foreach ( $source_menu->items as $source_menu_item ) { 151 | 152 | if ( empty( $source_menu_item->menu_item_parent ) ) 153 | continue; 154 | 155 | // Search the new menu item that is mapped to the source menu item 156 | $item_correspondence = false; 157 | foreach ( $items as $item ) { 158 | if ( $item->ID == $menu_items_remap[ $source_menu_item->ID ] ) 159 | $item_correspondence = $item; 160 | } 161 | 162 | if ( ! $item_correspondence ) 163 | continue; 164 | 165 | if ( ! isset( $menu_items_remap[ $source_menu_item->menu_item_parent ] ) ) 166 | continue; 167 | 168 | $item_args = array( 169 | 'menu-item-object-id' => $item_correspondence->object_id, 170 | 'menu-item-object' => $item_correspondence->object, 171 | 'menu-item-type' => $item_correspondence->type, 172 | 'menu-item-title' => $item_correspondence->title, 173 | 'menu-item-description' => $item_correspondence->description, 174 | 'menu-item-position' => $item_correspondence->menu_order, 175 | 'menu-item-attr-title' => $item_correspondence->attr_title, 176 | 'menu-item-target' => $item_correspondence->target, 177 | 'menu-item-classes' => $item_correspondence->classes, 178 | 'menu-item-xfn' => $item_correspondence->xfn, 179 | 'menu-item-status' => $item_correspondence->post_status, 180 | 'menu-item-parent-id' => $menu_items_remap[ $source_menu_item->menu_item_parent ], 181 | 'menu-item-url' => $item_correspondence->url 182 | ); 183 | 184 | @wp_update_nav_menu_item( $new_menu_id, $item_correspondence->db_id, $item_args ); 185 | } 186 | } 187 | 188 | $new_menu = wp_get_nav_menu_object( $new_menu_id ); 189 | 190 | // If there's a menu widget in the sidebar we may need to set the new category ID 191 | $widget_menu_settings = get_option( 'widget_nav_menu' ); 192 | 193 | if ( is_array( $widget_menu_settings ) ) { 194 | 195 | $new_widget_menu_settings = $widget_menu_settings; 196 | 197 | foreach ( $widget_menu_settings as $widget_key => $widget_settings ) { 198 | if ( ! empty( $widget_settings['nav_menu'] ) && $this->args['menu_id'] == $widget_settings['nav_menu'] ) { 199 | $new_widget_menu_settings[ $widget_key ]['nav_menu'] = $new_menu_id; 200 | } 201 | } 202 | 203 | update_option( 'widget_nav_menu', $new_widget_menu_settings ); 204 | } 205 | 206 | 207 | /** 208 | * Fired after a menu has been cloned 209 | * 210 | * @param array $args Array of arguments passed to this class 211 | * @param integer $new_menu_id ID of the new menu 212 | */ 213 | do_action( 'wpmudev_copier-copied_menu', $this->args, $new_menu_id, $this->source_blog_id ); 214 | 215 | 216 | return array( 217 | 'menu_name' => $new_menu->name, 218 | 'menu_id' => $new_menu->term_id 219 | ); 220 | } 221 | 222 | /** 223 | * Set the menu locations 224 | * 225 | * As menus IDs have changed we need to remap the menu locations 226 | * 227 | * @param Integer $source_blog_id 228 | * @param Array $menu_mapping Relationships between source menu ID and destination menu ID 229 | */ 230 | public static function set_menu_locations( $source_blog_id, $menu_mapping ) { 231 | 232 | // Set menu locations 233 | switch_to_blog( $source_blog_id ); 234 | $source_menu_locations = get_theme_mod( 'nav_menu_locations', array() ); 235 | restore_current_blog(); 236 | 237 | $new_menu_locations = $source_menu_locations; 238 | foreach ( $source_menu_locations as $location => $menu_id ) { 239 | if ( ! isset( $menu_mapping[ $menu_id ] ) ) 240 | continue; 241 | 242 | $new_menu_locations[ $location ] = $menu_mapping[ $menu_id ]; 243 | } 244 | set_theme_mod( 'nav_menu_locations', $new_menu_locations ); 245 | } 246 | 247 | /** 248 | * Set the menu options 249 | * 250 | * As menus IDs have changed we need to remap the menu options 251 | * 252 | * @param Integer $source_blog_id 253 | * @param Array $menu_mapping Relationships between source menu ID and destination menu ID 254 | */ 255 | public static function set_menu_options( $source_blog_id, $menu_mapping ) { 256 | 257 | switch_to_blog( $source_blog_id ); 258 | $source_menu_options = get_option( 'nav_menu_options', array() ); 259 | restore_current_blog(); 260 | 261 | // Set menu options 262 | $new_menu_options = $source_menu_options; 263 | if ( isset( $source_menu_options['auto_add'] ) ) { 264 | foreach ( $source_menu_options['auto_add'] as $key => $menu_id ) { 265 | if ( ! isset( $menu_mapping[ $menu_id ] ) ) 266 | continue; 267 | 268 | $new_menu_options['auto_add'][ $key ] = $menu_mapping[ $menu_id ]; 269 | } 270 | } 271 | update_option( 'nav_menu_options', $new_menu_options ); 272 | } 273 | 274 | } 275 | 276 | } -------------------------------------------------------------------------------- /lang/wpmudev-cloner.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 WPMU DEV 2 | # This file is distributed under the same license as the Cloner package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Cloner 1.8.0-beta-2\n" 6 | "Report-Msgid-Bugs-To: https://wpmudev.org\n" 7 | "POT-Creation-Date: 2018-05-09 19:51:09+00:00\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=utf-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "PO-Revision-Date: 2018-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language-Team: LANGUAGE \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 = '
          • '+optgroupLabel+'
          '; 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); --------------------------------------------------------------------------------