├── .distignore ├── .env.testing ├── .lando.yml ├── LICENSE ├── README.md ├── assets ├── css │ ├── bea-csf-admin-add.css │ ├── bea-csf-admin-edit.css │ ├── bea-csf-admin-notifications.css │ └── bea-csf-admin.css └── js │ ├── bea-csf-admin-add.js │ ├── bea-csf-admin-client.js │ ├── bea-csf-admin-notifications.js │ └── lou-multi-select │ ├── LICENSE.txt │ ├── README.markdown │ ├── bower.json │ ├── css │ └── multi-select.css │ ├── img │ └── switch.png │ ├── js │ └── jquery.multi-select.js │ └── multi-select.jquery.json ├── bea-content-sync-fusion.php ├── classes ├── addons │ ├── advanced-custom-fields-exclusion.php │ ├── advanced-custom-fields.php │ ├── events-calendar-series.php │ ├── gutenberg.php │ ├── multisite-clone-duplicator.php │ ├── polylang.php │ ├── post-types-order.php │ ├── revisionize.php │ ├── woocommerce.php │ ├── woocommerce │ │ ├── product-attributes.php │ │ ├── product-variation.php │ │ └── product.php │ └── yoast-seo.php ├── admin │ ├── admin-blog.php │ ├── admin-client-metaboxes.php │ ├── admin-dashboard-widgets.php │ ├── admin-list.php │ ├── admin-metaboxes.php │ ├── admin-notifications.php │ ├── admin-restrictions.php │ ├── admin-synchronizations-network.php │ ├── admin-terms-metaboxes.php │ └── admin-terms.php ├── cli │ ├── _helper.php │ ├── flush.php │ ├── migration.php │ ├── queue.php │ ├── relation.php │ └── resync.php ├── client-relations.php ├── client.php ├── client │ ├── attachment.php │ ├── p2p.php │ ├── post_type.php │ └── taxonomy.php ├── media.php ├── models │ ├── async.php │ ├── relations.php │ ├── synchronization.php │ └── synchronizations.php ├── multisite.php ├── plugin.php ├── query.php ├── seo.php └── server │ ├── attachment.php │ ├── p2p.php │ ├── post_type.php │ └── taxonomy.php ├── codeception.dist.yml ├── composer.json ├── composer.lock ├── cron └── cronjob.sh ├── functions ├── api.php └── template.php ├── grumphp.yml ├── languages ├── bea-content-sync-fusion-fr_FR.mo ├── bea-content-sync-fusion-fr_FR.po └── bea-content-sync-fusion.pot ├── views └── admin │ ├── blog-widget-list.php │ ├── blog-widget-status.php │ ├── client-metabox.php │ ├── client-page-settings-notification.php │ ├── client-terms-form.php │ ├── server-metabox-attachment.php │ ├── server-metabox-auto.php │ ├── server-metabox-manual.php │ ├── server-page-add.php │ ├── server-page-queue.php │ ├── server-page-settings.php │ └── server-term-metabox-manual.php └── wp-cli.yml /.distignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | .gitignore 4 | bin 5 | .idea 6 | .distignore 7 | .DS_Store 8 | .stickler.yml 9 | composer.json 10 | composer.lock 11 | README.md 12 | tests -------------------------------------------------------------------------------- /.env.testing: -------------------------------------------------------------------------------- 1 | TEST_SITE_DB_DSN=mysql:host=test;dbname=test 2 | TEST_SITE_DB_HOST=test 3 | TEST_SITE_DB_NAME=test 4 | TEST_SITE_DB_USER=test 5 | TEST_SITE_DB_PASSWORD=test 6 | TEST_SITE_TABLE_PREFIX=wp_ 7 | TEST_SITE_ADMIN_USERNAME=admin 8 | TEST_SITE_ADMIN_PASSWORD=admin 9 | TEST_SITE_WP_ADMIN_PATH=/wp-admin 10 | WP_ROOT_FOLDER=/app/wordpress/ 11 | TEST_DB_NAME=test 12 | TEST_DB_HOST=test 13 | TEST_DB_USER=test 14 | TEST_DB_PASSWORD=test 15 | TEST_TABLE_PREFIX=wp_ 16 | TEST_SITE_WP_URL=https://bea-content-sync-fusion.lndo.site 17 | TEST_SITE_WP_DOMAIN=bea-content-sync-fusion.lndo.site 18 | TEST_SITE_ADMIN_EMAIL=admin@admin.fr 19 | -------------------------------------------------------------------------------- /.lando.yml: -------------------------------------------------------------------------------- 1 | name: bea-content-sync-fusion 2 | recipe: wordpress 3 | config: 4 | webroot: ./wordpress/ 5 | env: 6 | - .env.testing 7 | services: 8 | appserver: 9 | run_as_root: 10 | - ln -s /app/ /app/wordpress/wp-content/plugins/bea-content-sync-fusion 11 | test: 12 | type: mysql:5.7 13 | portforward: true 14 | creds: 15 | user: test 16 | password: test 17 | database: test 18 | events: 19 | # Runs composer install and a custom php script before your app starts 20 | pre-start: 21 | - appserver: cd $LANDO_MOUNT && composer install 22 | tooling: 23 | setup: 24 | service: appserver 25 | description: 'Setup env' 26 | cmd: 27 | - echo "➡️ Reset DB ?" 28 | - if [ -f /app/wordpress/wp-config.php ]; then echo "wp-config exists reset DB" && wp db reset --yes --path=/app/wordpress ; else echo "No wp-config.php, no reset" ; fi 29 | - echo "➡️ Reset config" 30 | - if [ -f /app/wordpress/wp-config.php ]; then echo "wp-config exists deleting it" && rm -f /app/wordpress/wp-config.php; else echo "No wp-config.php, no rm" ; fi 31 | - echo "➡️ Create config" 32 | - wp config create --dbname=wordpress --dbuser=wordpress --dbpass=wordpress --dbhost=database --path=/app/wordpress 33 | - echo "➡️ Install WP" 34 | - wp core multisite-install --url=https://bea-content-sync-fusion.lndo.site --title="Content sync fusion" --admin_password=admin --admin_email=admin@admin.fr --path=wordpress --skip-email 35 | - echo "➡️ Install query monitor" 36 | - wp plugin install --activate-network query-monitor 37 | - echo "➡️ Activate plugin" 38 | - wp plugin activate --network bea-content-sync-fusion 39 | - echo "➡️ Add new sites" 40 | - wp site create --slug=site-1 --title="Site 1" 41 | - wp site create --slug=site-2 --title="Site 2" 42 | - wp site create --slug=site-3 --title="Site 3" 43 | - wp site create --slug=site-4 --title="Site 4" 44 | - echo "➡️ Copye .htaccess" 45 | - cp /app/tests/bin/htaccess /app/wordpress/.htaccess 46 | test-unit: 47 | service: appserver 48 | cmd: composer test-unit 49 | description: Run our PHPunit tests 50 | test-wpunit: 51 | service: appserver 52 | cmd: composer test-wpunit 53 | description: Run our wpunit tests 54 | test-acceptance: 55 | service: appserver 56 | cmd: composer test-acceptance 57 | description: Run our acceptance tests 58 | test-functional: 59 | service: appserver 60 | cmd: composer test-functional 61 | description: Run our functional tests -------------------------------------------------------------------------------- /assets/css/bea-csf-admin-add.css: -------------------------------------------------------------------------------- 1 | /* Multiselect JS helper */ 2 | .ms-container { width:100%; } 3 | .custom-header { text-align: center;font-weight:700;} 4 | 5 | /* Form */ 6 | form p label { font-size:17px;display:block; margin:0 0 5px; } 7 | form p { padding:5px 0;border-bottom:1px solid #ccc; } 8 | .description { display:block; margin:5px 0; } -------------------------------------------------------------------------------- /assets/css/bea-csf-admin-edit.css: -------------------------------------------------------------------------------- 1 | .locked-content a { 2 | color: #333; 3 | cursor: default; 4 | } -------------------------------------------------------------------------------- /assets/css/bea-csf-admin-notifications.css: -------------------------------------------------------------------------------- 1 | /* Multiselect JS helper */ 2 | .ms-container { width:100%; } 3 | .custom-header { text-align: center;font-weight:700;} -------------------------------------------------------------------------------- /assets/css/bea-csf-admin.css: -------------------------------------------------------------------------------- 1 | .bea-csf-acf-exclusion { 2 | display: block; 3 | text-align: right; 4 | float: right; 5 | } -------------------------------------------------------------------------------- /assets/js/bea-csf-admin-add.js: -------------------------------------------------------------------------------- 1 | jQuery('.multiple-helper').multiSelect({ 2 | selectableHeader: "
" + beaCsfAdminAdd.selectableHeader + "
", 3 | selectionHeader: "
" + beaCsfAdminAdd.selectionHeader + "
" 4 | }); -------------------------------------------------------------------------------- /assets/js/bea-csf-admin-client.js: -------------------------------------------------------------------------------- 1 | // Remove some elements from interface 2 | jQuery(document).ready(function () { 3 | // Terms edition 4 | jQuery('.locked-term-parent').closest('td.column-name').find('strong a').attr('href', '#'); 5 | jQuery('.locked-term-parent').closest('td.column-name').prev('th').html(''); 6 | jQuery('.locked-term-parent').closest('tr').addClass('locked-content'); 7 | 8 | jQuery('.postbox span.screen-reader-text .bea-csf-acf-exclusion').remove(); 9 | }); -------------------------------------------------------------------------------- /assets/js/bea-csf-admin-notifications.js: -------------------------------------------------------------------------------- 1 | jQuery('.multiple-helper').multiSelect({ 2 | selectableHeader: "
" + beaCsfAdminNotifications.selectableHeader + "
", 3 | selectionHeader: "
" + beaCsfAdminNotifications.selectionHeader + "
" 4 | }); -------------------------------------------------------------------------------- /assets/js/lou-multi-select/LICENSE.txt: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /assets/js/lou-multi-select/README.markdown: -------------------------------------------------------------------------------- 1 | # jquery.multi-select.js 2 | 3 | Usage and Demos [http://loudev.com](http://loudev.com "jquery.multi-select.js") 4 | -------------------------------------------------------------------------------- /assets/js/lou-multi-select/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiselect", 3 | "version": "0.9.12", 4 | "description" : "A user-friendlier drop-in replacement for the standard select with multiple attribute activated.", 5 | "license" : "WTFPL", 6 | "main": [ 7 | "./css/multi-select.css", 8 | "./img/switch.png", 9 | "./js/jquery.multi-select.js" 10 | ], 11 | "dependencies" : { 12 | "jquery" : ">= 1.7.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/js/lou-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 | } -------------------------------------------------------------------------------- /assets/js/lou-multi-select/img/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeAPI/bea-content-sync-fusion/484d1dfc6403b1545d3c88bb35cd81289c94c0f6/assets/js/lou-multi-select/img/switch.png -------------------------------------------------------------------------------- /assets/js/lou-multi-select/multi-select.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-select", 3 | "title": "multiselect", 4 | "description": "This is a user-friendlier drop-in replacement for the standard '; 46 | $output .= ''; 47 | $output .= ''; 48 | $output .= ''; 49 | $output .= ''; 50 | 51 | echo $output; 52 | } 53 | 54 | /** 55 | * Set a WP_Query query_var depending $_GET query ! 56 | * 57 | * @param WP_Query $query 58 | * 59 | * @return boolean 60 | */ 61 | public static function parse_query( WP_Query $query ) { 62 | if ( ! isset( $_GET['attachment-bea-csf-filter'] ) || ! $query->is_main_query() || empty( $_GET['attachment-bea-csf-filter'] ) ) { 63 | return false; 64 | } 65 | 66 | $query->set( 'bea_csf_filter', stripslashes( $_GET['attachment-bea-csf-filter'] ) ); 67 | return true; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /classes/admin/admin-notifications.php: -------------------------------------------------------------------------------- 1 | $users_id ) { 22 | $_POST['sync_notifications'][ $sync_id ] = array_map( 'intval', $_POST['sync_notifications'][ $sync_id ] ); 23 | } 24 | update_option( BEA_CSF_OPTION . '-notifications', $_POST['sync_notifications'] ); 25 | } 26 | } 27 | 28 | public static function admin_menu() { 29 | add_options_page( __( 'Content Sync Notifications', 'bea-content-sync-fusion' ), __( 'Sync Notification', 'bea-content-sync-fusion' ), 'manage_options', 'bea-csfc-notifications', array( __CLASS__, 'render_page' ) ); 30 | } 31 | 32 | /** 33 | * Register JS and CSS for client part 34 | * 35 | * @param string $hook_suffix 36 | */ 37 | public static function admin_enqueue_scripts( $hook_suffix = '' ) { 38 | if ( isset( $hook_suffix ) && $hook_suffix == 'settings_page_bea-csfc-notifications' ) { 39 | wp_enqueue_script( 'lou-multi-select', BEA_CSF_URL . 'assets/js/lou-multi-select/js/jquery.multi-select.js', array( 'jquery' ), '0.9.8', true ); 40 | wp_enqueue_script( 'bea-csf-admin-notifications', BEA_CSF_URL . 'assets/js/bea-csf-admin-notifications.js', array( 'lou-multi-select' ), BEA_CSF_VERSION, true ); 41 | wp_localize_script( 42 | 'bea-csf-admin-notifications', 43 | 'beaCsfAdminNotifications', 44 | array( 45 | 'selectableHeader' => __( 'Selectable users', 'bea-content-sync-fusion' ), 46 | 'selectionHeader' => __( 47 | 'Selection users', 48 | 'bea-content-sync-fusion' 49 | ), 50 | ) 51 | ); 52 | wp_enqueue_style( 'lou-multi-select', BEA_CSF_URL . 'assets/js/lou-multi-select/css/multi-select.css', array(), '0.9.8', 'screen' ); 53 | wp_enqueue_style( 'bea-csf-admin-notifications', BEA_CSF_URL . 'assets/css/bea-csf-admin-notifications.css', array(), BEA_CSF_VERSION ); 54 | } 55 | } 56 | 57 | public static function render_page() { 58 | global $wpdb; 59 | 60 | // Prepare users array 61 | $users = array(); 62 | 63 | // Get users admin/editor on this blog 64 | $roles = array( 'administrator', 'editor' ); 65 | foreach ( $roles as $role ) { 66 | $users_query = new WP_User_Query( 67 | array( 68 | 'role' => $role, 69 | 'fields' => 'all', 70 | ) 71 | ); 72 | if ( $users_query->get_total() > 0 ) { 73 | $users = array_merge( $users_query->get_results(), $users ); 74 | } 75 | } 76 | 77 | // Get syncs with notifications enabled 78 | $syncs = BEA_CSF_Synchronizations::get( 79 | array( 80 | 'notifications' => 1, 81 | 'receivers' => $wpdb->blogid, 82 | ), 83 | 'AND', 84 | false, 85 | true 86 | ); 87 | 88 | // Get current values 89 | $current_values = get_option( BEA_CSF_OPTION . '-notifications' ); 90 | 91 | // Include template 92 | include( BEA_CSF_DIR . 'views/admin/client-page-settings-notification.php' ); 93 | 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /classes/admin/admin-restrictions.php: -------------------------------------------------------------------------------- 1 | blogid, 71 | $post->ID 72 | ); 73 | 74 | // Get syncs model for current post_type, with any mode status (manual and auto) 75 | $_has_syncs = BEA_CSF_Synchronizations::get( 76 | array( 77 | 'post_type' => $post->post_type, 78 | 'emitters' => $wpdb->blogid, 79 | ), 80 | 'AND', 81 | false, 82 | true 83 | ); 84 | 85 | if ( null !== $_origin_key && empty( $_has_syncs ) ) { 86 | if ( 'pending' === $post->post_status ) { 87 | $actions['view'] = '' . __( 'Preview' ) . ''; 88 | 89 | if ( current_user_can( 'publish_post', $post->ID ) ) { 90 | $actions['publish'] = sprintf( 91 | '%s', 92 | wp_nonce_url( 93 | add_query_arg( 94 | array( 95 | 'action' => 'bea-csf-publish', 96 | 'ID' => $post->ID, 97 | ) 98 | ), 99 | 'bea-csf-publish' 100 | ), 101 | __( 'Publish', 'bea-content-sync-fusion' ) 102 | ); 103 | } 104 | } 105 | } 106 | 107 | return $actions; 108 | } 109 | 110 | /** 111 | * Check admin GET action for allow publishing from our custom link 112 | */ 113 | public static function admin_init_check_post_publishing() { 114 | if ( isset( $_GET['action'] ) && 'bea-csf-publish' === $_GET['action'] && isset( $_GET['ID'] ) && (int) $_GET['ID'] > 0 ) { 115 | check_admin_referer( 'bea-csf-publish' ); 116 | 117 | $args = array( 118 | 'ID' => (int) $_GET['ID'], 119 | 'post_status' => 'publish', 120 | ); 121 | 122 | // to ensure post_name is set instead wp_publish_post 123 | wp_update_post( $args ); 124 | 125 | $redirect_url = remove_query_arg( 'action' ); 126 | $redirect_url = remove_query_arg( 'ID', $redirect_url ); 127 | $redirect_url = remove_query_arg( '_wpnonce', $redirect_url ); 128 | wp_redirect( $redirect_url ); 129 | exit(); 130 | } 131 | } 132 | 133 | /** 134 | * Remove some actions on tag list when a post have an original key 135 | * 136 | * @param array $actions 137 | * @param WP_Term $term 138 | * 139 | * @return array 140 | */ 141 | public static function tag_row_actions( array $actions, WP_Term $term ) { 142 | global $wpdb; 143 | 144 | $_origin_key = BEA_CSF_Relations::current_object_is_synchronized( 'taxonomy', $wpdb->blogid, $term->term_id ); 145 | 146 | // Get syncs model for current post_type, with any mode status (manual and auto) 147 | $_has_syncs = BEA_CSF_Admin_Terms_Metaboxes::taxonomy_has_sync( $term->taxonomy ); 148 | 149 | if ( null !== $_origin_key && empty( $_has_syncs ) ) { 150 | unset( $actions['edit'], $actions['inline hide-if-no-js'], $actions['delete'] ); 151 | if( isset( $actions['view'] ) ) { // Check if the term has a single view 152 | $actions['view'] .= ''; 153 | } 154 | } 155 | 156 | return $actions; 157 | } 158 | 159 | /** 160 | * Block request for term edition 161 | * 162 | * @return void 163 | */ 164 | public static function admin_init_check_term_edition() { 165 | global $wpdb; 166 | 167 | // Not an edit page / edit request / bulk delete 168 | if ( empty( $_REQUEST['tag_ID'] ) && empty( $_REQUEST['tax_ID'] ) && empty( $_REQUEST['taxonomy'] ) && empty( $_REQUEST['delete_tags'] ) ) { 169 | return; 170 | } 171 | $tags = []; 172 | if ( ! empty( $_REQUEST['tag_ID'] ) ) { 173 | $tags[] = $_REQUEST['tag_ID']; 174 | } elseif ( ! empty( $_REQUEST['tax_ID'] ) ) { 175 | $tags[] = $_REQUEST['tax_ID']; 176 | } elseif ( ! empty( $_REQUEST['delete_tags'] ) ) { 177 | $tags = (array) $_REQUEST['delete_tags']; 178 | } 179 | 180 | $taxonomy_name = $_REQUEST['taxonomy'] ?? ''; 181 | 182 | if ( empty( $taxonomy_name ) ) { 183 | return; 184 | } 185 | 186 | foreach ( $tags as $tag ) { 187 | $current_term = get_term( (int) $tag, $_REQUEST['taxonomy'] ); 188 | 189 | // Term not exist ? 190 | if ( empty( $current_term ) || is_wp_error( $current_term ) ) { 191 | return; 192 | } 193 | 194 | $_origin_key = BEA_CSF_Relations::current_object_is_synchronized( 'taxonomy', $wpdb->blogid, $current_term->term_id ); 195 | 196 | // Get syncs model for current post_type, with any mode status (manual and auto) 197 | $_has_syncs = BEA_CSF_Admin_Terms_Metaboxes::taxonomy_has_sync( $current_term->taxonomy ); 198 | 199 | $_has_syncs = apply_filters( 'bea_csf_taxonomy_caps', $_has_syncs ); 200 | 201 | if ( null !== $_origin_key && empty( $_has_syncs ) ) { 202 | wp_die( __( 'You are not allowed to edit this content. You must update it from your master site.', 'bea-content-sync-fusion' ) ); 203 | } 204 | } 205 | } 206 | 207 | /** 208 | * Play with Capabilities API for remove "edit/delete" cap when a post have an original key 209 | * 210 | * @param array $caps 211 | * @param string $cap 212 | * @param integer $user_id 213 | * @param array $args 214 | * 215 | * @return array 216 | */ 217 | public static function map_meta_cap( $caps, $cap, $user_id, $args ) { 218 | global $wpdb; 219 | 220 | $capabilities = apply_filters( 'bea_csf_capabilities', self::$capabilities_to_check ); 221 | if ( ! is_array( $capabilities ) ) { 222 | return $caps; 223 | } 224 | 225 | if ( in_array( $cap, $capabilities ) ) { 226 | $post = isset( $args[0] ) ? get_post( $args[0] ) : null; 227 | if ( empty( $post ) || is_wp_error( $post ) ) { 228 | return $caps; 229 | } 230 | 231 | $_origin_key = BEA_CSF_Relations::current_object_is_synchronized( 232 | array( 233 | 'posttype', 234 | 'attachment', 235 | ), 236 | $wpdb->blogid, 237 | $post->ID 238 | ); 239 | 240 | // Get syncs model for current post_type, with any mode status (manual and auto) 241 | $_has_syncs = BEA_CSF_Synchronizations::get( 242 | array( 243 | 'post_type' => $post->post_type, 244 | 'emitters' => $wpdb->blogid, 245 | ), 246 | 'AND', 247 | false, 248 | true 249 | ); 250 | 251 | $_has_syncs = apply_filters( 'bea_csf_post_caps', $_has_syncs ); 252 | 253 | if ( null !== $_origin_key && empty( $_has_syncs ) ) { 254 | $caps[] = 'do_not_allow'; 255 | } 256 | } 257 | 258 | return $caps; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /classes/admin/admin-terms-metaboxes.php: -------------------------------------------------------------------------------- 1 | $wpdb->blogid, 27 | ), 28 | 'AND', 29 | false, 30 | true 31 | ); 32 | if ( empty( $syncs ) ) { 33 | return false; 34 | } 35 | 36 | foreach ( $syncs as $sync ) { 37 | if ( ! isset( $sync->taxonomies ) ) { 38 | continue; 39 | } 40 | 41 | if ( is_string( $sync->taxonomies ) && $taxonomy === $sync->taxonomies ) { 42 | return $sync; 43 | } 44 | 45 | if ( is_array( $sync->taxonomies ) && in_array( $taxonomy, $sync->taxonomies ) ) { 46 | return $sync; 47 | } 48 | } 49 | 50 | return false; 51 | } 52 | 53 | public static function taxonomy_has_manual_sync( $taxonomy = '' ) { 54 | global $wpdb; 55 | 56 | // Get syncs for current post_type and mode set to "manual" 57 | $syncs_with_manual_state = BEA_CSF_Synchronizations::get( 58 | array( 59 | 'mode' => 'manual', 60 | 'emitters' => $wpdb->blogid, 61 | ), 62 | 'AND', 63 | false, 64 | true 65 | ); 66 | if ( empty( $syncs_with_manual_state ) ) { 67 | return false; 68 | } 69 | 70 | // Can allow filter receivers site 71 | $syncs_with_manual_state = apply_filters( 'bea_csf_syncs_with_manual_state_term', $syncs_with_manual_state ); 72 | 73 | foreach ( $syncs_with_manual_state as $sync ) { 74 | if ( in_array( $taxonomy, $sync->taxonomies ) ) { 75 | return $sync; 76 | } 77 | } 78 | 79 | return false; 80 | } 81 | 82 | public static function form( $term ) { 83 | $sync_obj = self::taxonomy_has_manual_sync( $term->taxonomy ); 84 | if ( false === $sync_obj ) { 85 | return false; 86 | } 87 | 88 | // Use nonce for verification 89 | wp_nonce_field( plugin_basename( __FILE__ ), BEA_CSF_OPTION . '-term-nonce-manual' ); 90 | 91 | // Get values for current term 92 | $current_values = (array) get_term_meta( $term->term_id, '_term_receivers', true ); 93 | 94 | // Get sites destination from syncs 95 | $sync_receivers = $sync_obj->get_field( 'receivers' ); 96 | $sync_receivers = BEA_CSF_Admin_Synchronizations_Network::get_sites( $sync_receivers ); 97 | 98 | // Include template 99 | include( BEA_CSF_DIR . 'views/admin/server-term-metabox-manual.php' ); 100 | } 101 | 102 | public static function save( $term_id, $tt_id, $taxonomy ) { 103 | global $wpdb; 104 | 105 | $term = get_term( $term_id, $taxonomy ); 106 | if ( false === $term || is_wp_error( $term ) ) { 107 | return false; 108 | } 109 | 110 | // verify this came from the our screen and with proper authorization, 111 | // because hook can be triggered at other times 112 | if ( ! isset( $_POST[ BEA_CSF_OPTION . '-term-nonce-manual' ] ) || ! wp_verify_nonce( $_POST[ BEA_CSF_OPTION . '-term-nonce-manual' ], plugin_basename( __FILE__ ) ) ) { 113 | return false; 114 | } 115 | 116 | // Set default value 117 | $new_term_receivers = array(); 118 | 119 | // Get _POST data if form is filled 120 | if ( isset( $_POST['term_receivers'] ) && ! empty( $_POST['term_receivers'] ) ) { 121 | $new_term_receivers = array_map( 'intval', $_POST['term_receivers'] ); 122 | } 123 | 124 | // Get previous values 125 | $old_term_receivers = (array) get_term_meta( $term->term_id, '_term_receivers', true ); 126 | $old_term_receivers = array_filter( $old_term_receivers, 'trim' ); 127 | 128 | // Set new value 129 | update_term_meta( $term->term_id, '_term_receivers', $new_term_receivers ); 130 | 131 | // Calcul difference for send delete notification for uncheck action 132 | $receivers_to_delete = array_diff( $old_term_receivers, $new_term_receivers ); 133 | 134 | if ( ! empty( $receivers_to_delete ) && ! empty( $old_term_receivers ) ) { 135 | // Theses values have just deleted, delete content for clients ! 136 | do_action( 'bea-csf' . '/' . 'Taxonomy' . '/' . 'delete' . '/' . $taxonomy . '/' . $wpdb->blogid, $term, false, $receivers_to_delete, true, true ); 137 | } 138 | 139 | return true; 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /classes/admin/admin-terms.php: -------------------------------------------------------------------------------- 1 | term_id, '_origin_key', true ); 27 | if ( empty( $_origin_key ) ) { 28 | return false; 29 | } 30 | 31 | // SPLIT data 32 | $_origin_key = explode( ':', $_origin_key ); 33 | 34 | // Set variables 35 | $_origin_blog_id = $_origin_key[0]; 36 | $_origin_term_id = $_origin_key[1]; 37 | $_origin_taxonomy = $term->taxonomy; 38 | 39 | // Test if term id is valid 40 | if ( self::is_valid_term_id( $_origin_term_id, $_origin_taxonomy, $_origin_blog_id ) == false ) { 41 | $_origin_term_id = 0; 42 | } 43 | 44 | // Include template 45 | include( BEA_CSF_DIR . 'views/admin/client-terms-form.php' ); 46 | } 47 | 48 | public static function save( $term_id, $tt_id, $taxonomy ) { 49 | // verify this came from the our screen and with proper authorization, 50 | // because save_post can be triggered at other times 51 | if ( ! isset( $_POST['bea-csf-admin-terms'] ) || ! wp_verify_nonce( $_POST['bea-csf-admin-terms'], plugin_basename( __FILE__ ) ) ) { 52 | return false; 53 | } 54 | 55 | if ( isset( $_POST['term_emitter'] ) && 56 | isset( $_POST['term_emitter']['blog_id'] ) && intval( $_POST['term_emitter']['blog_id'] ) > 0 && 57 | isset( $_POST['term_emitter']['term_id'] ) && intval( $_POST['term_emitter']['term_id'] ) > 0 58 | ) { 59 | 60 | update_term_meta( $term_id, '_origin_key', $_POST['term_emitter']['blog_id'] . ':' . $_POST['term_emitter']['term_id'] ); 61 | } 62 | 63 | } 64 | 65 | public static function is_valid_term_id( $term_id = 0, $taxonomy = '', $blog_id = 0 ) { 66 | if ( self::is_valid_blog_id( $blog_id ) ) { 67 | switch_to_blog( $blog_id ); 68 | $term_exists = get_term( (int) $term_id, $taxonomy ); 69 | $term_exists = ( empty( $term_exists ) || is_wp_error( $term_exists ) ) ? false : true; 70 | restore_current_blog(); 71 | 72 | return $term_exists; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | public static function is_valid_blog_id( $blog_id = 0 ) { 79 | foreach ( BEA_CSF_Synchronizations::get_sites_from_network() as $site ) { 80 | if ( $site['blog_id'] == $blog_id ) { 81 | return true; 82 | } 83 | } 84 | 85 | return false; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /classes/cli/_helper.php: -------------------------------------------------------------------------------- 1 | array_keys( $taxonomies ), 33 | 'hide_empty' => false, 34 | 'orderby' => 'term_id', 35 | ) 36 | ); 37 | $results = get_terms( $terms_args ); 38 | if ( is_wp_error( $results ) || empty( $results ) ) { 39 | WP_CLI::debug( 'No terms found for taxonomies : %s', implode( ',', array_keys( $taxonomies ) ) ); 40 | 41 | return false; 42 | } 43 | 44 | return self::hierarchical_sort_terms( $results ); 45 | } 46 | 47 | /** 48 | * Sort an array of WP_Terms by hierarchical order (parents first, then children, then grand-children...) 49 | * 50 | * @param WP_Term[] $terms 51 | * @param int $parent 52 | * @return WP_Term[] 53 | * @author Ingrid Azéma 54 | */ 55 | private static function hierarchical_sort_terms( array $terms, int $parent = 0 ): array { 56 | $sorted_terms = []; 57 | foreach ( $terms as $term ) { 58 | if ( $term->parent !== $parent || ! $term instanceof WP_Term ) { 59 | continue; 60 | } 61 | $sorted_terms[] = $term; 62 | $children = self::hierarchical_sort_terms( $terms, $term->term_id ); 63 | $sorted_terms = array_merge( $sorted_terms, $children ); 64 | } 65 | 66 | return $sorted_terms; 67 | } 68 | 69 | /** 70 | * Synchronization all terms from any taxonomies 71 | * 72 | * @param array $taxonomies 73 | * @param array $terms_args 74 | * 75 | * @return array|bool 76 | */ 77 | public static function get_terms( $taxonomies = array(), $terms_args = array() ) { 78 | // Get terms objects 79 | $terms_args = wp_parse_args( 80 | $terms_args, 81 | array( 82 | 'taxonomy' => $taxonomies, 83 | 'hide_empty' => false, 84 | 'orderby' => 'term_id', 85 | ) 86 | ); 87 | $results = get_terms( $terms_args ); 88 | if ( is_wp_error( $results ) || empty( $results ) ) { 89 | WP_CLI::debug( 'No terms found for taxonomies : %s', implode( ',', $taxonomies ) ); 90 | 91 | return false; 92 | } 93 | 94 | return $results; 95 | } 96 | 97 | /** 98 | * 99 | * Get all attachments 100 | * 101 | * @param array $args 102 | * 103 | * @return false|array 104 | */ 105 | public static function get_attachments( $args = array() ) { 106 | $default_args = array( 107 | 'post_type' => 'attachment', 108 | 'post_status' => 'any', 109 | 'nopaging' => true, 110 | 'update_post_meta_cache' => false, 111 | 'update_post_term_cache' => false, 112 | 'no_found_rows' => true, 113 | 'cache_results' => false, 114 | ); 115 | 116 | $args = wp_parse_args( $args, $default_args ); 117 | $results = get_posts( $args ); 118 | if ( empty( $results ) ) { 119 | WP_CLI::debug( "No attachment found\n" ); 120 | 121 | return false; 122 | } 123 | 124 | return $results; 125 | } 126 | 127 | /** 128 | * 129 | * Synchronization all posts from any post types 130 | * 131 | * @param array $args 132 | * 133 | * @return false|array 134 | */ 135 | public static function get_posts( $args = array() ) { 136 | global $wp_post_types; 137 | 138 | $default_args = array( 139 | 'post_type' => array_keys( $wp_post_types ), 140 | 'post_status' => 'any', 141 | 'nopaging' => true, 142 | 'update_post_meta_cache' => false, 143 | 'update_post_term_cache' => false, 144 | 'no_found_rows' => true, 145 | 'cache_results' => false, 146 | ); 147 | 148 | $args = wp_parse_args( $args, $default_args ); 149 | $results = get_posts( $args ); 150 | if ( empty( $results ) ) { 151 | WP_CLI::debug( "No posts found for post_type %s\n", $args['post_type'] ); 152 | 153 | return false; 154 | } 155 | 156 | return $results; 157 | } 158 | 159 | /** 160 | * 161 | * Synchronization all P2P connections 162 | * 163 | * @param array $args 164 | * 165 | * @return bool|array 166 | */ 167 | public static function get_p2p_connections( $args = array() ) { 168 | global $wpdb; 169 | 170 | // $args = wp_parse_args( $args, array() ); // TODO: Implement P2P restriction query 171 | 172 | $results = (array) $wpdb->get_col( "SELECT p2p_id FROM $wpdb->p2p" ); 173 | if ( empty( $results ) ) { 174 | WP_CLI::debug( 'No P2P connection found' ); 175 | 176 | return false; 177 | } 178 | 179 | return $results; 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /classes/cli/flush.php: -------------------------------------------------------------------------------- 1 | get_home_url( $blog_id, '/' ), 50 | ), 51 | false, 52 | false 53 | ); 54 | 55 | $progress->tick(); 56 | } 57 | $progress->finish(); 58 | } 59 | 60 | /** 61 | * Flush all contents synchronized for a specific blog_id 62 | * 63 | * @param $args 64 | * @param $params 65 | */ 66 | public function site( $args, $params ) { 67 | $types_relation = []; 68 | 69 | if ( isset( $params['taxonomies'] ) && 'true' === $params['taxonomies'] ) { 70 | $types_relation[] = 'taxonomy'; 71 | } 72 | 73 | if ( isset( $params['attachments'] ) && 'true' === $params['attachments'] ) { 74 | $types_relation[] = 'attachment'; 75 | } 76 | 77 | if ( isset( $params['post_type'] ) && 'true' === $params['post_type'] ) { 78 | $types_relation[] = 'posttype'; 79 | } 80 | 81 | // Get data to delete 82 | $items_to_delete = BEA_CSF_Relations::get_types_relation_by_receiver_blog_id( get_current_blog_id(), $types_relation ); 83 | 84 | if ( empty( $items_to_delete ) ) { 85 | WP_CLI::warning( __( 'No content to flush', 'bea-content-sync-fusion' ) ); 86 | return; 87 | } 88 | 89 | $total = count( $items_to_delete ); 90 | 91 | WP_CLI::success( __( 'Start of content flushing', 'bea-content-sync-fusion' ) ); 92 | 93 | $progress = \WP_CLI\Utils\make_progress_bar( 'Loop on content to flush', $total ); 94 | foreach ( $items_to_delete as $relation ) { 95 | if ( $relation->type === 'taxonomy' ) { 96 | $term = self::get_term_by_id( $relation->receiver_id ); 97 | if ( $term != false ) { 98 | wp_delete_term( $term->term_id, $term->taxonomy ); 99 | } 100 | } else { 101 | wp_delete_post( $relation->receiver_id, true ); 102 | } 103 | 104 | $progress->tick(); 105 | } 106 | $progress->finish(); 107 | 108 | WP_CLI::success( __( 'End of content flushing', 'bea-content-sync-fusion' ) ); 109 | 110 | WP_CLI::run_command( array( 'cache', 'flush' ) ); 111 | } 112 | 113 | /** 114 | * Get term without knowing it's taxonomy. Not very nice, though. 115 | * Source: https://wordpress.stackexchange.com/questions/23571/get-term-by-id-without-taxonomy 116 | * 117 | * @uses type $wpdb 118 | * @uses get_term() 119 | * 120 | * @param int|object $term 121 | * @param string $output 122 | * @param string $filter 123 | */ 124 | public static function get_term_by_id( $term, $output = OBJECT, $filter = 'raw' ) { 125 | global $wpdb; 126 | 127 | if ( empty( $term ) ) { 128 | $error = new WP_Error( 'invalid_term', __( 'Empty Term' ) ); 129 | 130 | return $error; 131 | } 132 | 133 | $taxonomy = $wpdb->get_var( $wpdb->prepare( "SELECT t.taxonomy FROM $wpdb->term_taxonomy AS t WHERE t.term_id = %s LIMIT 1", $term ) ); 134 | 135 | return get_term( $term, $taxonomy, $output, $filter ); 136 | } 137 | 138 | } 139 | 140 | WP_CLI::add_command( 141 | 'content-sync-fusion flush', 142 | 'BEA_CSF_Cli_Flush', 143 | array( 144 | 'shortdesc' => __( 'All commands related "flush features" to the BEA Content Sync Fusion plugin', 'bea-content-sync-fusion' ), 145 | ) 146 | ); 147 | -------------------------------------------------------------------------------- /classes/cli/migration.php: -------------------------------------------------------------------------------- 1 | $current_network->id, 24 | 'number' => 99999999, 25 | ) 26 | ) as $blog ) { 27 | switch_to_blog( $blog->blog_id ); 28 | 29 | // Table exists ? 30 | if ( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $wpdb->postmeta ) ) !== $wpdb->postmeta ) { 31 | restore_current_blog(); 32 | continue; 33 | } 34 | 35 | $blog_id = (int) $blog->blog_id; // Ensure the blog_id is an integer 36 | $meta_key = '_origin_key'; // Define the meta_key explicitly 37 | 38 | // Use prepare to ensure safe query construction 39 | $selects[] = $wpdb->prepare( "( 40 | SELECT pm.post_id AS post_id, pm.meta_value AS meta_value, %d AS blog_id 41 | FROM {$wpdb->postmeta} AS pm 42 | WHERE pm.meta_key = %s 43 | )", $blog_id, $meta_key ); 44 | 45 | restore_current_blog(); 46 | } 47 | 48 | $union_all_query = implode( ' UNION ALL ', $selects ); 49 | 50 | return $wpdb->get_results( "SELECT post_id, meta_value, blog_id FROM ( $union_all_query ) AS wp" ); 51 | } 52 | 53 | /** 54 | * Exec migration and create relation item from metadata 55 | * 56 | * @param $args 57 | * @param $params 58 | */ 59 | public function process( $args, $params ) { 60 | // Get metadata content to sync 61 | $meta_blog_ids = $this->get_blog_ids_with_meta_key(); 62 | if ( empty( $meta_blog_ids ) ) { 63 | WP_CLI::warning( __( 'No meta to migrate', 'bea-content-sync-fusion' ) ); 64 | return; 65 | } 66 | 67 | $total = count( $meta_blog_ids ); 68 | 69 | $progress = \WP_CLI\Utils\make_progress_bar( 'Loop on site with metadata to migrate', $total ); 70 | foreach ( $meta_blog_ids as $meta_blog_id ) { 71 | $_origin_key = explode( ':', $meta_blog_id->meta_value ); 72 | 73 | BEA_CSF_Relations::insert( 'attachment', $_origin_key[0], $_origin_key[1], $meta_blog_id->blog_id, $meta_blog_id->post_id ); 74 | $progress->tick(); 75 | } 76 | 77 | $progress->finish(); 78 | } 79 | 80 | /** 81 | * Remove all symoblic links from old version of this plugin 82 | */ 83 | public function clean_symbolic_links() { 84 | $output = shell_exec( sprintf( 'find %s -type l -delete', WP_CONTENT_DIR ) ); 85 | 86 | WP_CLI::success( 'Symbolic links flushed - ' . $output ); 87 | } 88 | } 89 | 90 | WP_CLI::add_command( 91 | 'content-sync-fusion migration', 92 | 'BEA_CSF_Cli_Migration', 93 | array( 94 | 'shortdesc' => __( 'Allow to migrate old relation structure (meta data) to relations tables for the BEA Content Sync Fusion plugin', 'bea-content-sync-fusion' ), 95 | ) 96 | ); 97 | -------------------------------------------------------------------------------- /classes/cli/queue.php: -------------------------------------------------------------------------------- 1 | ] 15 | * : Use the alternative queue for the action. 16 | * --- 17 | * default: false 18 | * options: 19 | * - true 20 | * - false 21 | * --- 22 | * 23 | * ## EXAMPLES 24 | * 25 | * wp content-sync-fusion queue status --alternativeq=false 26 | * 27 | */ 28 | public function status( $args, $params ) { 29 | // Use maintenance queue ? 30 | if ( isset( $params['alternativeq'] ) && 'true' === $params['alternativeq'] ) { 31 | BEA_CSF_Async::switch_to_maintenance_queue(); 32 | } 33 | 34 | // Get all blogs with content 35 | $blog_ids = BEA_CSF_Async::get_blog_ids_from_queue(); 36 | 37 | $results = array(); 38 | foreach ( $blog_ids as $blog_id ) { 39 | $results[] = array( 40 | 'blog_id' => $blog_id, 41 | 'counter' => BEA_CSF_Async::get_counter( $blog_id ), 42 | ); 43 | } 44 | WP_CLI\Utils\format_items( 'table', $results, array( 'blog_id', 'counter' ) ); 45 | 46 | WP_CLI::success( sprintf( __( '%d items waiting on the queue', 'bea-content-sync-fusion' ), BEA_CSF_Async::get_counter() ) ); 47 | } 48 | 49 | /** 50 | * Exec cron by get all blogs with content, and proceed to sync ! 51 | * 52 | * @param $args 53 | * @param $params 54 | * 55 | * @throws \WP_CLI\ExitException 56 | * 57 | * ## OPTIONS 58 | * 59 | * [--alternativeq=] 60 | * : Use the alternative queue for the action 61 | * --- 62 | * default: false 63 | * options: 64 | * - true 65 | * - false 66 | * --- 67 | * 68 | * ## EXAMPLES 69 | * 70 | * wp content-sync-fusion queue pull --alternativeq=false 71 | * 72 | */ 73 | public function process( $args, $params ) { 74 | // Use maintenance queue ? 75 | if ( isset( $params['alternativeq'] ) && 'true' === $params['alternativeq'] ) { 76 | BEA_CSF_Async::switch_to_maintenance_queue(); 77 | } else { 78 | $params['alternativeq'] = 'false'; 79 | } 80 | 81 | // Get blogs ID with content to sync 82 | $blog_ids = BEA_CSF_Async::get_blog_ids_from_queue(); 83 | if ( empty( $blog_ids ) ) { 84 | WP_CLI::warning( __( 'No content to synchronize', 'bea-content-sync-fusion' ) ); 85 | 86 | return; 87 | } 88 | 89 | $total = count( $blog_ids ); 90 | 91 | $progress = \WP_CLI\Utils\make_progress_bar( 'Loop on site with content to synchronize', $total ); 92 | foreach ( $blog_ids as $blog_id ) { 93 | // Fill thearray with the current blog url 94 | $params['url'] = get_home_url( $blog_id, '/' ); 95 | 96 | WP_CLI::launch_self( 97 | 'content-sync-fusion queue pull', 98 | array(), 99 | $params, 100 | false, 101 | false // Allow debug with this value to true 102 | ); 103 | 104 | $progress->tick(); 105 | } 106 | $progress->finish(); 107 | } 108 | 109 | /** 110 | * Flushes the queue database. 111 | * 112 | * ## OPTIONS 113 | * 114 | * [--alternativeq=] 115 | * : Use the alternative queue for the action 116 | * --- 117 | * default: false 118 | * options: 119 | * - true 120 | * - false 121 | * --- 122 | * 123 | * ## EXAMPLES 124 | * 125 | * wp content-sync-fusion queue flush --alternativeq=false 126 | * 127 | */ 128 | public function flush( $args, $params ) { 129 | // Use maintenance queue ? 130 | if ( isset( $params['alternativeq'] ) && 'true' === $params['alternativeq'] ) { 131 | BEA_CSF_Async::switch_to_maintenance_queue(); 132 | } 133 | 134 | BEA_CSF_Async::truncate(); 135 | WP_CLI::success( __( 'Queue flushed with success !', 'bea-content-sync-fusion' ) ); 136 | } 137 | 138 | /** 139 | * Pull content for all the sites into the network. 140 | * 141 | * ## OPTIONS 142 | * 143 | * [--alternativeq=] 144 | * : Use the alternative queue for the action 145 | * --- 146 | * default: false 147 | * options: 148 | * - true 149 | * - false 150 | * --- 151 | * 152 | * [--quantity=] 153 | * : Quantity of content to process 154 | * --- 155 | * default: 500 156 | * --- 157 | * 158 | * ## EXAMPLES 159 | * 160 | * wp content-sync-fusion queue pull --alternativeq=false --quantity=20 161 | **/ 162 | public function pull( $args, $params ) { 163 | // Use maintenance queue ? 164 | if ( isset( $params['alternativeq'] ) && 'true' === $params['alternativeq'] ) { 165 | BEA_CSF_Async::switch_to_maintenance_queue(); 166 | } 167 | 168 | // Allow override quantity with CLI param 169 | $quantity = BEA_CSF_CRON_QTY; 170 | if ( isset( $params['quantity'] ) && intval( $params['quantity'] ) > 0 ) { 171 | $quantity = intval( $params['quantity'] ); 172 | } 173 | 174 | // Get data to sync 175 | $items_to_sync = BEA_CSF_Async::get_results( $quantity, get_current_blog_id() ); 176 | 177 | if ( empty( $items_to_sync ) ) { 178 | WP_CLI::warning( __( 'No content to synchronize', 'bea-content-sync-fusion' ) ); 179 | 180 | return; 181 | } 182 | 183 | $total = count( $items_to_sync ); 184 | 185 | WP_CLI::success( __( 'Start of content synchronization', 'bea-content-sync-fusion' ) ); 186 | 187 | $progress = \WP_CLI\Utils\make_progress_bar( 'Loop on content to synchronize', $total ); 188 | foreach ( $items_to_sync as $item_to_sync ) { 189 | BEA_CSF_Async::process( $item_to_sync ); 190 | 191 | $progress->tick(); 192 | } 193 | $progress->finish(); 194 | 195 | WP_CLI::success( __( 'End of content synchronization', 'bea-content-sync-fusion' ) ); 196 | } 197 | 198 | /** 199 | * Get all blogs "url" with content to synchronized 200 | * 201 | * @param $args 202 | * @param $params 203 | * 204 | * ## OPTIONS 205 | * 206 | * [--alternativeq] 207 | * : Use the alternative queue for the action 208 | * --- 209 | * default: false 210 | * options: 211 | * - true 212 | * - false 213 | * --- 214 | * 215 | * ## EXAMPLES 216 | * 217 | * wp content-sync-fusion queue get_sites --alternativeq=false --quantity=20 218 | 219 | */ 220 | public function get_sites( $args, $params ) { 221 | // Use maintenance queue ? 222 | if ( isset( $params['alternativeq'] ) && 'true' === $params['alternativeq'] ) { 223 | BEA_CSF_Async::switch_to_maintenance_queue(); 224 | } else { 225 | $params['alternativeq'] = 'false'; 226 | } 227 | 228 | // Get blogs ID with content to sync 229 | $blog_ids = BEA_CSF_Async::get_blog_ids_from_queue(); 230 | if ( empty( $blog_ids ) ) { 231 | return; 232 | } 233 | 234 | foreach ( $blog_ids as $blog_id ) { 235 | WP_CLI::line( get_home_url( $blog_id, '/' ) ); 236 | } 237 | } 238 | } 239 | 240 | WP_CLI::add_command( 241 | 'content-sync-fusion queue', 242 | 'BEA_CSF_Cli_Queue', 243 | array( 244 | 'shortdesc' => __( 'All commands related "queue features" to the BEA Content Sync Fusion plugin', 'bea-content-sync-fusion' ), 245 | ) 246 | ); 247 | -------------------------------------------------------------------------------- /classes/cli/relation.php: -------------------------------------------------------------------------------- 1 | 19 | * : The emitter 20 | * 21 | * 22 | * : The receiver 23 | * 24 | * ## EXAMPLES 25 | * 26 | * wp content-sync-fusion relation mirror 1 5 27 | * 28 | * @when after_wp_load 29 | * 30 | * @param $args 31 | * @param $params 32 | * 33 | * @throws \WP_CLI\ExitException 34 | */ 35 | public function mirror( $args, $params ) { 36 | if ( count( $args ) !== 2 ) { 37 | WP_CLI::error( 'Missing one parameter for emitter/receiver blog id' ); 38 | } 39 | 40 | $this->emitter_blog_id = absint( $args[0] ); 41 | $this->receiver_blog_id = absint( $args[1] ); 42 | 43 | if ( 1 === $this->receiver_blog_id ) { 44 | WP_CLI::error( 'You can\'t mirror a site to id 1' ); 45 | } 46 | 47 | switch_to_blog( $this->emitter_blog_id ); 48 | 49 | $syncs = $this->get_emitter_syncs(); 50 | if ( empty( $syncs ) ) { 51 | WP_CLI::error( 'No sync registered for this emitter blog' ); 52 | } 53 | 54 | $attachments = false; 55 | $post_types = [ 56 | 'auto' => [], 57 | 'manual' => [], 58 | ]; 59 | $taxonomies = []; 60 | 61 | foreach ( $syncs as $sync ) { 62 | /** @var BEA_CSF_Synchronization $sync */ 63 | 64 | $receiver_blogs = $sync->get_receivers(); 65 | if ( ! in_array( $this->receiver_blog_id, $receiver_blogs, true ) ) { 66 | WP_CLI::debug( sprintf( 'Skip sync %s because blog is not receiver', $sync->label ) ); 67 | continue; 68 | } 69 | 70 | if ( ! empty( $sync->post_type ) ) { 71 | if ( 'attachment' === $sync->post_type ) { 72 | $attachments = true; 73 | } else { 74 | $post_types[ $sync->mode ][] = $sync->post_type; 75 | } 76 | } 77 | 78 | if ( ! empty( $sync->taxonomies ) ) { 79 | $taxonomies = array_merge( array_values( $taxonomies ), array_values( $sync->taxonomies ) ); 80 | } 81 | } 82 | 83 | if ( true === $attachments ) { 84 | $this->mirror_attachments(); 85 | } 86 | 87 | if ( ! empty( $post_types['auto'] ) ) { 88 | $this->mirror_post_types( $post_types['auto'], false ); 89 | } 90 | 91 | if ( ! empty( $post_types['manual'] ) ) { 92 | $this->mirror_post_types( $post_types['manual'], true ); 93 | } 94 | 95 | if ( ! empty( $taxonomies ) ) { 96 | $this->mirror_taxonomies( $taxonomies ); 97 | } 98 | 99 | WP_CLI::success( sprintf( 'Mirror success, %d relations added !', $this->global_counter ) ); 100 | } 101 | 102 | /** 103 | * Mirroring attachments 104 | */ 105 | private function mirror_attachments() { 106 | $results = BEA_CSF_Cli_Helper::get_attachments(); 107 | 108 | // Loop on attachments 109 | foreach ( (array) $results as $result ) { 110 | if ( ! is_a( $result, 'WP_Post' ) ) { 111 | continue; 112 | } 113 | 114 | $this->global_counter ++; 115 | 116 | BEA_CSF_Relations::merge( 'attachment', $this->emitter_blog_id, $result->ID, $this->receiver_blog_id, $result->ID ); 117 | } 118 | } 119 | 120 | /** 121 | * Mirroring post types 122 | * 123 | * @param array $post_types 124 | * @param bool $is_manual 125 | */ 126 | private function mirror_post_types( $post_types, $is_manual = false ) { 127 | $results = BEA_CSF_Cli_Helper::get_posts( [ 'post_type' => $post_types ] ); 128 | 129 | // Loop on posts 130 | foreach ( (array) $results as $result ) { 131 | if ( ! is_a( $result, 'WP_Post' ) ) { 132 | continue; 133 | } 134 | 135 | $this->global_counter ++; 136 | 137 | BEA_CSF_Relations::merge( 'posttype', $this->emitter_blog_id, $result->ID, $this->receiver_blog_id, $result->ID ); 138 | 139 | if ( true === $is_manual ) { 140 | $meta_key = '_b' . $this->emitter_blog_id . '_post_receivers'; 141 | 142 | $_post_receivers = (array) get_post_meta( $result->ID, $meta_key, true ); 143 | $_post_receivers[] = $this->receiver_blog_id; 144 | $_post_receivers = array_unique( $_post_receivers ); 145 | update_post_meta( $result->ID, $meta_key, $_post_receivers ); 146 | } 147 | } 148 | } 149 | 150 | /** 151 | * Mirroring taxonomies 152 | * 153 | * @param $taxonomies array with taxo names 154 | */ 155 | private function mirror_taxonomies( $taxonomies ) { 156 | $results = BEA_CSF_Cli_Helper::get_terms( $taxonomies ); 157 | 158 | // Loop on terms 159 | foreach ( (array) $results as $result ) { 160 | if ( ! isset( $result->term_id ) ) { 161 | continue; 162 | } 163 | 164 | $this->global_counter ++; 165 | 166 | BEA_CSF_Relations::merge( 'taxonomy', $this->emitter_blog_id, $result->term_id, $this->receiver_blog_id, $result->term_id ); 167 | } 168 | } 169 | 170 | /** 171 | * Get sync for emitter 172 | * 173 | * @return array 174 | */ 175 | private function get_emitter_syncs() { 176 | $has_syncs = BEA_CSF_Synchronizations::get( 177 | [ 'emitters' => $this->emitter_blog_id ], 178 | 'AND', 179 | false, 180 | true 181 | ); 182 | 183 | return $has_syncs; 184 | } 185 | } 186 | 187 | WP_CLI::add_command( 188 | 'content-sync-fusion relation', 189 | 'BEA_CSF_Cli_Relation', 190 | array( 191 | 'shortdesc' => __( 'All commands related "relation features" to the BEA Content Sync Fusion plugin', 'bea-content-sync-fusion' ), 192 | ) 193 | ); 194 | -------------------------------------------------------------------------------- /classes/cli/resync.php: -------------------------------------------------------------------------------- 1 | all( $args, $params ); 31 | 32 | delete_network_option( BEA_CSF_Synchronizations::get_option_network_id(), 'bea-csf-multisite-resync-blogs' ); 33 | } 34 | 35 | /** 36 | * Loop on all blogs for resync contents to synchronized 37 | * 38 | * @param $args 39 | * @param $params 40 | * 41 | * @throws \WP_CLI\ExitException 42 | */ 43 | public function all( $args, $params ) { 44 | // Use maintenance queue ? 45 | if ( isset( $params['alternativeq'] ) && 'true' === $params['alternativeq'] ) { 46 | BEA_CSF_Async::switch_to_maintenance_queue(); 47 | } else { 48 | $params['alternativeq'] = 'false'; 49 | } 50 | 51 | // Restrict to some receivers ? 52 | if ( ! isset( $params['receivers'] ) ) { 53 | $params['receivers'] = 'false'; 54 | } 55 | 56 | // Optionally params 57 | $params['attachments'] = ! isset( $params['attachments'] ) ? 'false' : $params['attachments']; 58 | $params['post_type'] = ! isset( $params['post_type'] ) ? 'false' : $params['post_type']; 59 | $params['taxonomies'] = ! isset( $params['taxonomies'] ) ? 'false' : $params['taxonomies']; 60 | $params['p2p'] = ! isset( $params['p2p'] ) ? 'false' : $params['p2p']; 61 | 62 | // Default WP_Site_Query arguments 63 | $site_args = array( 64 | 'number' => PHP_INT_MAX, 65 | 'order' => 'ASC', 66 | 'orderby' => 'id', 67 | 'count' => false, 68 | 'fields' => 'ids', 69 | 'no_found_rows' => false, 70 | ); 71 | 72 | // Get emitters blogs list from plugin settings 73 | if ( isset( $params['smart'] ) && 'true' === $params['smart'] ) { 74 | $site_args['site__in'] = BEA_CSF_Synchronizations::get_emitters_blogs_ids(); 75 | } 76 | 77 | // Restrict to some emitters ? 78 | if ( isset( $params['emitters'] ) ) { 79 | $site_args['site__in'] = explode( ',', $params['emitters'] ); 80 | } 81 | 82 | // Restrict to some networks ? 83 | if ( isset( $params['emitters_network'] ) ) { 84 | $site_args['network__in'] = explode( ',', $params['emitters_network'] ); 85 | } 86 | 87 | // Get blogs ID to resync 88 | $site_query = new WP_Site_Query( $site_args ); 89 | if ( empty( $site_query->sites ) ) { 90 | WP_CLI::warning( __( 'No site to resync', 'bea-content-sync-fusion' ) ); 91 | 92 | return; 93 | } 94 | 95 | $progress = \WP_CLI\Utils\make_progress_bar( 'Loop on site with content to resync', $site_query->found_sites ); 96 | foreach ( $site_query->sites as $blog_id ) { 97 | 98 | $result = WP_CLI::launch_self( 99 | 'content-sync-fusion resync site', 100 | array(), 101 | array( 102 | 'alternativeq' => $params['alternativeq'], 103 | 'attachments' => $params['attachments'], 104 | 'post_type' => $params['post_type'], 105 | 'taxonomies' => $params['taxonomies'], 106 | 'p2p' => $params['p2p'], 107 | 'receivers' => $params['receivers'], 108 | 'url' => get_home_url( $blog_id, '/' ), 109 | ), 110 | false, 111 | false // enable for debug 112 | ); 113 | // var_dump( $result ); 114 | 115 | $progress->tick(); 116 | } 117 | $progress->finish(); 118 | } 119 | 120 | /** 121 | * Flush all contents synchronized for a specific blog_id 122 | * 123 | * @param $args 124 | * @param $params 125 | */ 126 | public function site( $args, $params ) { 127 | // Get syncs for current site 128 | $has_syncs = BEA_CSF_Synchronizations::get( 129 | array( 130 | 'emitters' => get_current_blog_id(), 131 | ), 132 | 'AND', 133 | false, 134 | true 135 | ); 136 | if ( empty( $has_syncs ) ) { 137 | WP_CLI::warning( __( 'No sync data emission for this website', 'bea-content-sync-fusion' ) ); 138 | 139 | return; 140 | } 141 | 142 | // Use maintenance queue ? 143 | if ( isset( $params['alternativeq'] ) && 'true' === $params['alternativeq'] ) { 144 | BEA_CSF_Async::switch_to_maintenance_queue(); 145 | } 146 | 147 | // Restrict to some receivers ? 148 | if ( isset( $params['receivers'] ) && 'false' !== $params['receivers'] ) { 149 | $this->receivers_blog_ids = explode( ',', $params['receivers'] ); 150 | $this->receivers_blog_ids = array_filter( array_map( 'intval', $this->receivers_blog_ids ) ); 151 | 152 | add_filter( 'bea_csf.pre_pre_send_data', array( $this, '_bea_csf_pre_pre_send_data' ), 10, 2 ); 153 | } 154 | 155 | // Get data to resync 156 | $data_to_sync = array(); 157 | 158 | // Get terms with params argument 159 | if ( ! isset( $params['taxonomies'] ) || 'false' === $params['taxonomies'] ) { 160 | $data_to_sync['terms'] = array(); 161 | } else { 162 | // TODO: Manage "any" and filtering 163 | $data_to_sync['terms'] = BEA_CSF_Cli_Helper::get_all_terms(); 164 | } 165 | 166 | // Get attachments with params argument 167 | if ( ! isset( $params['attachments'] ) || 'false' === $params['attachments'] ) { 168 | $data_to_sync['attachments'] = array(); 169 | } else { 170 | // TODO: Manage "any" and filtering 171 | $data_to_sync['attachments'] = BEA_CSF_Cli_Helper::get_attachments(); 172 | } 173 | 174 | // Get posts with params argument 175 | if ( ! isset( $params['post_type'] ) || 'false' === $params['post_type'] ) { 176 | $data_to_sync['posts'] = array(); 177 | } else { 178 | // TODO: Manage "any" and filtering 179 | $data_to_sync['posts'] = BEA_CSF_Cli_Helper::get_posts(); 180 | } 181 | 182 | // Get P2P with params argument 183 | if ( ! isset( $params['p2p'] ) || 'false' === $params['p2p'] ) { 184 | $data_to_sync['p2p'] = array(); 185 | } else { 186 | // TODO: Manage "any" and filtering 187 | $data_to_sync['p2p'] = BEA_CSF_Cli_Helper::get_p2p_connections(); 188 | } 189 | 190 | // Make a mega-count 191 | $total = count( $data_to_sync['terms'] ) + count( $data_to_sync['attachments'] ) + count( $data_to_sync['posts'] ) + count( $data_to_sync['p2p'] ); 192 | 193 | // No item ? 194 | if ( false === $total ) { 195 | WP_CLI::warning( __( 'No content to resync', 'bea-content-sync-fusion' ) ); 196 | 197 | return; 198 | } 199 | 200 | WP_CLI::success( __( 'Start of content resyncing', 'bea-content-sync-fusion' ) ); 201 | 202 | $progress = \WP_CLI\Utils\make_progress_bar( 'Loop on content to resync', $total ); 203 | 204 | // Loop on terms 205 | foreach ( (array) $data_to_sync['terms'] as $result ) { 206 | if ( ! isset( $result->term_id ) ) { 207 | continue; 208 | } 209 | 210 | do_action( 'edited_term', $result->term_id, $result->term_taxonomy_id, $result->taxonomy ); 211 | 212 | $progress->tick(); 213 | } 214 | 215 | // Loop on attachments 216 | foreach ( (array) $data_to_sync['attachments'] as $result ) { 217 | if ( ! is_a( $result, 'WP_Post' ) ) { 218 | continue; 219 | } 220 | 221 | do_action( 'edit_attachment', $result->ID ); 222 | 223 | $progress->tick(); 224 | } 225 | 226 | // Loop on posts 227 | foreach ( $data_to_sync['posts'] as $result ) { 228 | if ( ! is_a( $result, 'WP_Post' ) ) { 229 | continue; 230 | } 231 | 232 | do_action( 'transition_post_status', $result->post_status, $result->post_status, $result ); 233 | do_action( 'save_post', $result->ID, $result, true ); 234 | 235 | $progress->tick(); 236 | } 237 | 238 | // Loop on P2P 239 | foreach ( $data_to_sync['p2p'] as $result_id ) { 240 | do_action( 'p2p_created_connection', $result_id ); 241 | 242 | $progress->tick(); 243 | } 244 | $progress->finish(); 245 | 246 | WP_CLI::success( __( 'End of content resync', 'bea-content-sync-fusion' ) ); 247 | } 248 | 249 | /** 250 | * Drop variable content for keep blog_id only to the freshly new created blogs ! 251 | * 252 | * @param $receiver_blog_id 253 | * @param $sync 254 | * 255 | * @return bool 256 | */ 257 | public function _bea_csf_pre_pre_send_data( $receiver_blog_id, BEA_CSF_Synchronization $sync ) { 258 | if ( empty( $this->receivers_blog_ids ) || ! is_array( $this->receivers_blog_ids ) ) { 259 | return $receiver_blog_id; 260 | } 261 | 262 | if ( in_array( $receiver_blog_id, $this->receivers_blog_ids, true ) ) { 263 | return $receiver_blog_id; 264 | } 265 | 266 | return false; 267 | } 268 | 269 | } 270 | 271 | WP_CLI::add_command( 272 | 'content-sync-fusion resync', 273 | 'BEA_CSF_Cli_Resync', 274 | array( 275 | 'shortdesc' => __( 'All commands related "resync features" to the BEA Content Sync Fusion plugin', 'bea-content-sync-fusion' ), 276 | ) 277 | ); 278 | -------------------------------------------------------------------------------- /classes/client-relations.php: -------------------------------------------------------------------------------- 1 | 0 ) { 20 | do_action( 'bea_csf.before_delete_attachment', $attachment_id, $data ); 21 | 22 | wp_delete_attachment( $attachment_id, true ); 23 | 24 | BEA_CSF_Relations::delete_by_receiver( 'attachment', (int) $GLOBALS['wpdb']->blogid, (int) $attachment_id ); 25 | 26 | // Delete additional if reciprocal synchro 27 | BEA_CSF_Relations::delete_by_emitter_and_receiver( 'attachment', (int) $GLOBALS['wpdb']->blogid, (int) $attachment_id, (int) $data['blogid'], (int) $data['ID'] ); 28 | 29 | do_action( 'bea_csf.after_delete_attachment', $attachment_id, $data ); 30 | } 31 | 32 | return apply_filters( 'bea_csf.client.attachment.delete', true, $sync_fields ); 33 | } 34 | 35 | 36 | /** 37 | * Delete a attachment, take the master ID and try to find the new ID for delete it ! 38 | * 39 | * @param array $data 40 | * @param array $sync_fields 41 | * 42 | * @return array[WP_Error 43 | */ 44 | public static function merge( array $data, array $sync_fields ) { 45 | if ( empty( $data ) || ! is_array( $data ) ) { 46 | return new WP_Error( 'invalid_datas', 'Error - Datas is invalid.' ); 47 | } 48 | 49 | if ( ! isset( $data['blogid'] ) ) { 50 | return new WP_Error( 'missing_blog_id', 'Error - Missing a blog ID for allow insertion.' ); 51 | } 52 | 53 | // Translate for current media ID 54 | $current_media_id = BEA_CSF_Relations::get_object_for_any( 'attachment', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['ID'], $data['ID'] ); 55 | 56 | // Find local parent ? 57 | if ( isset( $data['post_parent'] ) ) { 58 | $current_parent_id = BEA_CSF_Relations::get_object_for_any( 'posttype', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['post_parent'], $data['post_parent'] ); 59 | $data['post_parent'] = ! empty( $current_parent_id ) && (int) $current_parent_id > 0 ? $current_parent_id : 0; 60 | } 61 | 62 | // Clone data for post insertion 63 | $data_for_post = $data; 64 | unset( $data_for_post['taxonomies'], $data_for_post['terms'], $data_for_post['post_custom'], $data_for_post['metadata'] ); 65 | 66 | // Post data are expected to be escaped for wp_insert_post/wp_update_post 67 | $data_for_post = wp_slash( $data_for_post ); 68 | 69 | // Merge or add ? 70 | if ( ! empty( $current_media_id ) && (int) $current_media_id > 0 ) { // Edit, update only main fields 71 | 72 | $data_for_post['ID'] = $current_media_id; 73 | $new_media_id = wp_update_post( $data_for_post ); 74 | if ( is_wp_error( $new_media_id ) || $new_media_id === 0 ) { 75 | return new WP_Error( 'invalid_datas', 'Error - An fatal error occurred during attachment insertion.' ); 76 | } 77 | 78 | do_action( 'bea_csf.client_attachment_after_update', $current_media_id, $data['attachment_dir'], $data['post_parent'], $data ); 79 | 80 | } else { // Insert with WP media public static function 81 | 82 | //$data_for_post['import_id'] = $data_for_post['ID']; 83 | unset( $data_for_post['ID'] ); 84 | $new_media_id = wp_insert_post( $data_for_post ); 85 | if ( is_wp_error( $new_media_id ) || $new_media_id === 0 ) { 86 | return new WP_Error( 'invalid_datas', 'Error - An fatal error occurred during attachment insertion.' ); 87 | } 88 | 89 | do_action( 'bea_csf.client_attachment_after_insert', $new_media_id, $data['attachment_dir'], $data['post_parent'], $data ); 90 | 91 | } 92 | 93 | // Append to relations table 94 | BEA_CSF_Relations::merge( 'attachment', $data['blogid'], $data['ID'], $GLOBALS['wpdb']->blogid, $new_media_id ); 95 | 96 | // Save all metas for new post 97 | if ( isset( $data['meta_data'] ) && is_array( $data['meta_data'] ) && ! empty( $data['meta_data'] ) ) { 98 | foreach ( $data['meta_data'] as $key => $values ) { 99 | if ( count( $values ) > 1 || ! isset( $values[0] ) ) { 100 | // TODO: Management exception, SO RARE in WP ! 101 | continue; 102 | } else { 103 | update_post_meta( $new_media_id, $key, maybe_unserialize( $values[0] ) ); 104 | } 105 | } 106 | } 107 | 108 | // Clean data for each taxonomy 109 | if ( isset( $data['taxonomies'] ) ) { 110 | wp_delete_object_term_relationships( $new_media_id, $data['taxonomies'] ); 111 | } 112 | 113 | // Association with terms 114 | if ( isset( $data['terms'] ) && is_array( $data['terms'] ) && ! empty( $data['terms'] ) ) { 115 | $term_ids = array(); 116 | 117 | foreach ( $data['terms'] as $term ) { 118 | // Sync settings, check if term is in an allowed taxonomy 119 | if ( ! empty( $sync_fields['taxonomies'] ) && ! in_array( $term['taxonomy'], (array) $sync_fields['taxonomies'] ) ) { 120 | continue; 121 | } 122 | 123 | $local_term_id = BEA_CSF_Relations::get_object_for_any( 'taxonomy', $data['blogid'], $sync_fields['_current_receiver_blog_id'], (int) $term['term_id'], (int) $term['term_id'] ); 124 | if ( (int) $local_term_id > 0 ) { 125 | if ( ! isset( $term_ids[ $term['taxonomy'] ] ) ) { 126 | $term_ids[ $term['taxonomy'] ] = array(); 127 | } 128 | 129 | $term_ids[ $term['taxonomy'] ][] = (int) $local_term_id; 130 | } 131 | } 132 | 133 | foreach ( $term_ids as $taxonomy => $local_term_ids ) { 134 | wp_set_object_terms( $new_media_id, $local_term_ids, $taxonomy, false ); 135 | } 136 | } 137 | 138 | $new_attachment = get_post( $new_media_id ); 139 | if ( ! empty( $new_attachment ) ) { 140 | $new_attachment->is_edition = ( ! empty( $current_media_id ) && (int) $current_media_id > 0 ) ? true : false; 141 | } 142 | 143 | // App new new media for 3rd party plugins 144 | $data['new_media_id'] = $new_media_id; 145 | 146 | return apply_filters( 'bea_csf.client.attachment.merge', $data, $sync_fields, $new_attachment ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /classes/client/p2p.php: -------------------------------------------------------------------------------- 1 | '7', 7 | 'p2p_from' => '104', 8 | 'p2p_to' => '29', 9 | 'p2p_type' => 'establishment_to_ambassador', 10 | 'blogid' => 1, 11 | ) 12 | */ 13 | 14 | /** 15 | * Add connection on DB 16 | */ 17 | public static function merge( array $data, array $sync_fields ) { 18 | if ( empty( $data ) || ! is_array( $data ) ) { 19 | return new WP_Error( 'invalid_datas', 'Error - Datas is invalid.' ); 20 | } 21 | 22 | if ( ! isset( $data['blogid'] ) ) { 23 | return new WP_Error( 'missing_blog_id', 'Error - Missing a blog ID for allow insertion.' ); 24 | } 25 | 26 | // P2P Type must be sync ? 27 | if ( ! in_array( $data['p2p_type'], $sync_fields['p2p_connections'] ) ) { 28 | return false; 29 | } 30 | 31 | // From (post/users) 32 | if ( $data['p2p_obj']->side['from']->get_object_type() != 'user' ) { 33 | // Posts exists ? 34 | $p2p_from_local = BEA_CSF_Relations::get_object_for_any( 'posttype', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['p2p_from'], $data['p2p_from'] ); 35 | } else { 36 | $p2p_from_local = $data['p2p_from']; 37 | 38 | // Prefered role by connection ? 39 | $role = isset( $data['p2p_obj']->side['from']->query_vars['role'] ) ? $data['p2p_obj']->side['from']->query_vars['role'] : 'subscriber'; 40 | 41 | // Try to user to blog (if need) and set right role for this connection 42 | self::maybe_add_user_to_current_blog( $p2p_from_local, $role ); 43 | } 44 | 45 | // To (post/users) 46 | if ( $data['p2p_obj']->side['to']->get_object_type() != 'user' ) { 47 | // Posts exists ? 48 | $p2p_to_local = BEA_CSF_Relations::get_object_for_any( 'posttype', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['p2p_to'], $data['p2p_to'] ); 49 | } else { 50 | $p2p_to_local = $data['p2p_to']; 51 | 52 | // Prefered role by connection ? 53 | $role = isset( $data['p2p_obj']->side['to']->query_vars['role'] ) ? $data['p2p_obj']->side['to']->query_vars['role'] : 'subscriber'; 54 | 55 | // Try to user to blog (if need) and set right role for this connection 56 | self::maybe_add_user_to_current_blog( $p2p_to_local, $role ); 57 | } 58 | 59 | // If from or empty not exists, stop process 60 | if ( empty( $p2p_from_local ) || empty( $p2p_to_local ) ) { 61 | return false; 62 | } 63 | 64 | // Create connection 65 | p2p_type( $data['p2p_type'] )->connect( 66 | $p2p_from_local, 67 | $p2p_to_local, 68 | array( 69 | 'date' => current_time( 'mysql' ), 70 | ) 71 | ); 72 | } 73 | 74 | /** 75 | * Delete a connection, take the master id, try to find the new ID and delete local connection 76 | * 77 | * @param array $term 78 | * 79 | * @return \WP_Error|boolean 80 | */ 81 | public static function delete( array $data, array $sync_fields ) { 82 | // P2P Type must be sync ? 83 | if ( ! in_array( $data['p2p_type'], $sync_fields['p2p_connections'] ) ) { 84 | return false; 85 | } 86 | 87 | // From (post/users) 88 | if ( $data['p2p_obj']->side['from']->get_object_type() != 'user' ) { 89 | $p2p_from_local = BEA_CSF_Relations::get_object_for_any( 'posttype', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['p2p_from'], $data['p2p_from'] ); 90 | } else { 91 | $p2p_from_local = $data['p2p_from']; 92 | } 93 | 94 | // To (post/users) 95 | if ( $data['p2p_obj']->side['to']->get_object_type() != 'user' ) { 96 | $p2p_to_local = BEA_CSF_Relations::get_object_for_any( 'posttype', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['p2p_to'], $data['p2p_to'] ); 97 | } else { 98 | $p2p_to_local = $data['p2p_to']; 99 | } 100 | 101 | if ( empty( $p2p_from_local ) || empty( $p2p_to_local ) ) { 102 | return false; 103 | } 104 | 105 | p2p_type( $data['p2p_type'] )->disconnect( $p2p_from_local, $p2p_to_local ); 106 | } 107 | 108 | /** 109 | * @param $user_id 110 | * @param string $prefered_role 111 | * 112 | */ 113 | public static function maybe_add_user_to_current_blog( $user_id, $prefered_role = 'subscriber' ) { 114 | global $wpdb; 115 | 116 | $blogs = get_blogs_of_user( $user_id, true ); 117 | 118 | // Add user to current blog if not exist 119 | if ( ! isset( $blogs[ $wpdb->blogid ] ) ) { 120 | add_user_to_blog( $wpdb->blogid, $user_id, $prefered_role ); 121 | } else { 122 | wp_update_user( 123 | array( 124 | 'ID' => $user_id, 125 | 'role' => $prefered_role, 126 | ) 127 | ); 128 | } 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /classes/client/post_type.php: -------------------------------------------------------------------------------- 1 | 0 ? $local_parent_id : 0; 28 | } 29 | 30 | $data = apply_filters( 'bea_csf/client/posttype/before_merge', $data, $sync_fields ); 31 | 32 | if ( false === $data || empty( $data ) ) { 33 | return new WP_Error( 'empty_data', 'Error - exclude post to sync' ); 34 | } 35 | 36 | // Clone datas for post insertion 37 | $data_for_post = $data; 38 | unset( $data_for_post['medias'], $data_for_post['terms'], $data_for_post['tags_input'], $data_for_post['post_category'] ); 39 | 40 | // Post data are expected to be escaped for wp_insert_post/wp_update_post 41 | $data_for_post = wp_slash( $data_for_post ); 42 | 43 | // Merge post 44 | if ( ! empty( $data['local_id'] ) && (int) $data['local_id'] > 0 ) { 45 | $current_value = (int) get_post_meta( $data['local_id'], '_exclude_from_futur_sync', true ); 46 | if ( $current_value == 1 ) { 47 | return new WP_Error( 'future_sync_exclusion', 'Error - This post is exclude from future sync.' ); 48 | } 49 | 50 | // Sync settings, if pending, never try to change the post status ? 51 | if ( 'pending' === $sync_fields['status'] || self::is_blog_post_pending_user_selection( $sync_fields, $data ) ) { 52 | unset( $data_for_post['post_status'] ); 53 | } 54 | 55 | $data_for_post['ID'] = $data['local_id']; 56 | $new_post_id = wp_update_post( $data_for_post, true ); 57 | 58 | } else { 59 | // Sync settings, allow change post status. Apply only for post creation 60 | if ( 'pending' === $sync_fields['status'] || self::is_blog_post_pending_user_selection( $sync_fields, $data ) ) { 61 | $data_for_post['post_status'] = 'pending'; 62 | } 63 | 64 | //$data_for_post['import_id'] = $data_for_post['ID']; 65 | unset( $data_for_post['ID'] ); 66 | $new_post_id = wp_insert_post( $data_for_post, true ); 67 | } 68 | 69 | // Post on DB ? 70 | if ( is_wp_error( $new_post_id ) ) { 71 | return new WP_Error( 'post_insertion', 'Error during the post insertion ' . $new_post_id->get_error_message() ); 72 | } 73 | 74 | BEA_CSF_Relations::merge( 'posttype', $data['blogid'], $data['ID'], $GLOBALS['wpdb']->blogid, $new_post_id ); 75 | 76 | // Save all metas for new post 77 | if ( isset( $data['meta_data'] ) && is_array( $data['meta_data'] ) && ! empty( $data['meta_data'] ) ) { 78 | foreach ( $data['meta_data'] as $key => $values ) { 79 | if ( count( $values ) > 1 || ! isset( $values[0] ) ) { 80 | // TODO: Management exception, SO RARE in WP ! 81 | continue; 82 | } else { 83 | update_post_meta( $new_post_id, $key, maybe_unserialize( $values[0] ) ); 84 | } 85 | } 86 | } 87 | 88 | // Clean data for each taxonomy 89 | if ( isset( $data['taxonomies'] ) ) { 90 | wp_delete_object_term_relationships( $new_post_id, $data['taxonomies'] ); 91 | } 92 | 93 | // Association with terms 94 | if ( isset( $data['terms'] ) && is_array( $data['terms'] ) && ! empty( $data['terms'] ) ) { 95 | $term_ids = array(); 96 | 97 | foreach ( $data['terms'] as $term ) { 98 | // Sync settings, check if term is in an allowed taxonomy 99 | if ( ! empty( $sync_fields['taxonomies'] ) && ! in_array( $term['taxonomy'], (array) $sync_fields['taxonomies'], true ) ) { 100 | continue; 101 | } 102 | 103 | $local_term_id = BEA_CSF_Relations::get_object_for_any( 'taxonomy', $data['blogid'], $sync_fields['_current_receiver_blog_id'], (int) $term['term_id'], (int) $term['term_id'] ); 104 | if ( (int) $local_term_id > 0 ) { 105 | if ( ! isset( $term_ids[ $term['taxonomy'] ] ) ) { 106 | $term_ids[ $term['taxonomy'] ] = array(); 107 | } 108 | 109 | $term_ids[ $term['taxonomy'] ][] = (int) $local_term_id; 110 | } 111 | } 112 | 113 | foreach ( $term_ids as $taxonomy => $local_term_ids ) { 114 | wp_set_object_terms( $new_post_id, $local_term_ids, $taxonomy, false ); 115 | } 116 | } 117 | 118 | // Medias exist ? 119 | if ( is_array( $data['medias'] ) && ! empty( $data['medias'] ) ) { 120 | // Loop for medias 121 | foreach ( $data['medias'] as $media ) { 122 | // Media exists ? 123 | $current_media_id = (int) BEA_CSF_Relations::get_object_for_any( 'attachment', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $media['ID'], $media['ID'] ); 124 | if ( empty( $current_media_id ) ) { 125 | continue; 126 | } 127 | 128 | wp_update_post( 129 | array( 130 | 'ID' => $current_media_id, 131 | 'post_parent' => $new_post_id, 132 | ) 133 | ); 134 | } 135 | } 136 | 137 | // Remove old thumb 138 | delete_post_meta( $new_post_id, '_thumbnail_id' ); 139 | 140 | // Restore post thumb 141 | $thumbnail_id = (int) BEA_CSF_Relations::get_object_for_any( 'attachment', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['_thumbnail_id'], $data['_thumbnail_id'] ); 142 | if ( empty( $thumbnail_id ) && (int) $thumbnail_id > 0 ) { 143 | update_post_meta( $new_post_id, '_thumbnail_id', $thumbnail_id->receiver_id ); 144 | } elseif ( ! empty( $data['_thumbnail'] ) ) { 145 | $data['_thumbnail']['blogid'] = $data['blogid']; 146 | $new_media = BEA_CSF_Client_Attachment::merge( $data['_thumbnail'], $sync_fields ); 147 | if ( ! is_wp_error( $new_media ) && isset( $new_media['new_media_id'] ) ) { 148 | update_post_meta( $new_post_id, '_thumbnail_id', $new_media['new_media_id'] ); 149 | } 150 | } 151 | 152 | // Set P2P connections 153 | if ( isset( $data['connections'] ) && ! empty( $data['connections'] ) ) { 154 | foreach ( (array) $data['connections'] as $connection ) { 155 | $connection['blogid'] = $data['blogid']; 156 | BEA_CSF_Client_P2P::merge( $connection, $sync_fields ); 157 | } 158 | } 159 | 160 | $new_post = get_post( $new_post_id ); 161 | if ( ! empty( $new_post ) ) { 162 | $new_post->is_edition = ( ! empty( $data['local_id'] ) && (int) $data['local_id'] > 0 ) ? true : false; 163 | } 164 | 165 | return apply_filters( 'bea_csf.client.posttype.merge', $data, $sync_fields, $new_post ); 166 | } 167 | 168 | /** 169 | * Check if the current blog have a user selection status 170 | * 171 | * @param $sync_fields 172 | * @param $data 173 | * 174 | * @return bool 175 | */ 176 | private static function is_user_selection_blog_post( $sync_fields, $data ) { 177 | return ( 'user_selection' === $sync_fields['status'] && isset( $data['meta_data'][ '_b' . $data['blogid'] . '_post_receivers_status' ] ) ); 178 | } 179 | 180 | /** 181 | * Get post status from post meta for the current blog 182 | * 183 | * @param $data 184 | * 185 | * @return string 186 | */ 187 | private static function get_user_selection_blog_post( $data ) { 188 | $_post_receivers_status = maybe_unserialize( $data['meta_data'][ '_b' . $data['blogid'] . '_post_receivers_status' ][0] ); 189 | $_current_blog_id = (int) $GLOBALS['wpdb']->blogid; 190 | if ( isset( $_post_receivers_status[ $_current_blog_id ] ) ) { 191 | return $_post_receivers_status[ $_current_blog_id ]; 192 | } 193 | 194 | return ''; 195 | } 196 | 197 | /** 198 | * Check if blog post have a pending value from user selection 199 | * 200 | * @param $sync_fields 201 | * @param $data 202 | * 203 | * @return bool 204 | */ 205 | private static function is_blog_post_pending_user_selection( $sync_fields, $data ) { 206 | if ( ! self::is_user_selection_blog_post( $sync_fields, $data ) ) { 207 | return false; 208 | } 209 | 210 | if ( ! in_array( self::get_user_selection_blog_post( $data ), [ 'pending', 'pending-draft' ], true ) ) { 211 | return false; 212 | } 213 | 214 | return true; 215 | } 216 | 217 | /** 218 | * Delete a post, take the master id, try to find the new ID and delete local post 219 | * 220 | * @param array $data 221 | * @param array $sync_fields 222 | * 223 | * @return bool|WP_Error 224 | * @internal param int $master_id 225 | */ 226 | public static function delete( array $data, array $sync_fields ) { 227 | if ( empty( $data ) || ! is_array( $data ) ) { 228 | return new WP_Error( 'invalid_datas', 'Error - Datas is invalid.' ); 229 | } 230 | 231 | $local_id = BEA_CSF_Relations::get_object_for_any( 'posttype', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['ID'], $data['ID'] ); 232 | if ( ! empty( $local_id ) && (int) $local_id > 0 ) { 233 | wp_delete_post( $local_id, true ); 234 | 235 | BEA_CSF_Relations::delete_by_receiver( 'posttype', (int) $GLOBALS['wpdb']->blogid, (int) $local_id ); 236 | 237 | // Delete additional if reciprocal synchro 238 | BEA_CSF_Relations::delete_by_emitter_and_receiver( 'posttype', (int) $GLOBALS['wpdb']->blogid, (int) $local_id, (int) $data['blogid'], (int) $data['ID'] ); 239 | } 240 | 241 | return apply_filters( 'bea_csf.client.posttype.delete', $data, $sync_fields ); 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /classes/client/taxonomy.php: -------------------------------------------------------------------------------- 1 | model->term_exists_by_slug( $data['slug'], $data['pll']['language'], $data['taxonomy'], BEA_CSF_Relations::get_object_for_any( 'taxonomy', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['parent'], $data['parent'] ) ); 40 | } 41 | 42 | $local_term_id = BEA_CSF_Relations::get_object_for_any( 'taxonomy', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['term_id'], $data['term_id'] ); 43 | if ( ! empty( $local_term_id ) && (int) $local_term_id > 0 ) { 44 | // Prevent PLL same slug for several languages on update 45 | if ( ! empty( $data['pll']['is_translated'] ) && ! empty( $exist_pll_term_id ) ) { 46 | $data['slug'] = $data['slug'] . '___' . $data['pll']['language']; // Prevent exist term 47 | } 48 | 49 | $new_term_id = wp_update_term( $local_term_id, $data['taxonomy'], array( 50 | 'name' => $data['name'], 51 | 'description' => $data['description'], 52 | 'slug' => $data['slug'], 53 | 'parent' => BEA_CSF_Relations::get_object_for_any( 'taxonomy', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['parent'], $data['parent'] ), 54 | ) ); 55 | } else { 56 | // Prevent PLL same slug for several languages on insert 57 | if ( ! empty( $data['pll']['is_translated'] ) && empty( $exist_pll_term_id ) ) { 58 | $data['slug'] = $data['slug'] . '___' . $data['pll']['language']; // Create new one 59 | } 60 | 61 | $new_term_id = wp_insert_term( 62 | $data['name'], 63 | $data['taxonomy'], 64 | array( 65 | 'description' => $data['description'], 66 | 'slug' => $data['slug'], 67 | 'parent' => BEA_CSF_Relations::get_object_for_any( 'taxonomy', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['parent'], $data['parent'] ), 68 | 69 | ) 70 | ); 71 | 72 | // try to manage error when term already exist with the same name ! 73 | if ( is_wp_error( $new_term_id ) && 'term_exists' === $new_term_id->get_error_code() ) { 74 | 75 | $term_exists_result = term_exists( $data['name'], $data['taxonomy'], $data['parent'] ); 76 | 77 | // Prevent PLL same slug for several languages on update 78 | if ( ! empty( $data['pll']['is_translated'] ) && ! empty( $exist_pll_term_id ) ) { 79 | $term_exists_result = [ 'term_id' => $exist_pll_term_id ]; 80 | $data['slug'] = $data['slug'] . '___' . $data['pll']['language']; // Prevent exist term 81 | } 82 | 83 | if ( false !== $term_exists_result && isset( $term_exists_result['term_id'] ) ) { 84 | $new_term_id = wp_update_term( 85 | $term_exists_result['term_id'], 86 | $data['taxonomy'], 87 | array( 88 | 'name' => $data['name'], 89 | 'description' => $data['description'], 90 | 'slug' => $data['slug'], 91 | 'parent' => BEA_CSF_Relations::get_object_for_any( 'taxonomy', $data['blogid'], $sync_fields['_current_receiver_blog_id'], $data['parent'], $data['parent'] ), 92 | ) 93 | ); 94 | update_term_meta( $term_exists_result['term_id'], 'already_exists', 1 ); 95 | } 96 | } 97 | } 98 | 99 | // Delete this variable for skip conflict with next item to sync 100 | unset( $_bea_origin_blog_id ); 101 | 102 | // Test merge/insertion 103 | if ( is_wp_error( $new_term_id ) ) { 104 | return new WP_Error( 'term_insertion', $new_term_id->get_error_message() ); 105 | } elseif ( is_array( $new_term_id ) && isset( $new_term_id['term_id'] ) ) { 106 | $new_term_id = (int) $new_term_id['term_id']; 107 | } elseif ( 0 !== (int) $new_term_id ) { 108 | $new_term_id = (int) $new_term_id; 109 | } 110 | 111 | // Always valid ? 112 | if ( 0 === $new_term_id ) { 113 | return new WP_Error( 'term_id_invalid', 'Error - Term ID is egal to 0' ); 114 | } 115 | 116 | // Get term object 117 | $new_term_obj = get_term( $new_term_id, $data['taxonomy'] ); 118 | if ( is_wp_error( $new_term_obj ) ) { 119 | return new WP_Error( 'term_valid', 'Error - Term seems invalid' ); 120 | } 121 | 122 | BEA_CSF_Relations::merge( 'taxonomy', $data['blogid'], $data['term_id'], $GLOBALS['wpdb']->blogid, $new_term_obj->term_id ); 123 | 124 | // Save all metas for new post 125 | if ( isset( $data['meta_data'] ) && is_array( $data['meta_data'] ) && ! empty( $data['meta_data'] ) ) { 126 | foreach ( $data['meta_data'] as $key => $values ) { 127 | if ( count( $values ) > 1 || ! isset( $values[0] ) ) { 128 | // TODO: Management exception, SO RARE in WP ! 129 | continue; 130 | } else { 131 | update_term_meta( $new_term_id, $key, $values[0] ); 132 | } 133 | } 134 | } 135 | 136 | return apply_filters( 'bea_csf.client.taxonomy.merge', $data, $sync_fields, $new_term_obj ); 137 | } 138 | 139 | /** 140 | * Delete a term, take the master id, try to find the new ID and delete local term 141 | * 142 | * @param array $term 143 | * @param array $sync_fields 144 | * 145 | * @return bool|WP_Error 146 | */ 147 | public static function delete( array $term, array $sync_fields ) { 148 | if ( empty( $term ) || ! is_array( $term ) ) { 149 | return new WP_Error( 'invalid_datas', __( 'Bad call, invalid datas.' ) ); 150 | } 151 | 152 | $local_term_id = BEA_CSF_Relations::get_object_for_any( 'taxonomy', $term['blogid'], $sync_fields['_current_receiver_blog_id'], $term['term_id'], $term['term_id'] ); 153 | if ( ! empty( $local_term_id ) && (int) $local_term_id > 0 ) { 154 | BEA_CSF_Relations::delete_by_receiver( 'taxonomy', (int) $GLOBALS['wpdb']->blogid, (int) $local_term_id ); 155 | 156 | // Delete additional if reciprocal synchro 157 | BEA_CSF_Relations::delete_by_emitter_and_receiver( 'taxonomy', (int) $GLOBALS['wpdb']->blogid, (int) $local_term_id, (int) $term['blogid'], (int) $term['term_id'] ); 158 | 159 | // Term already exist before sync, keep it ! 160 | $already_exists = get_term_meta( $local_term_id, 'already_exists', true ); 161 | if ( ! empty( $already_exists ) && 1 === absint( $already_exists ) ) { 162 | return false; 163 | } 164 | 165 | wp_delete_term( $local_term_id, $term['taxonomy'] ); 166 | } 167 | 168 | return apply_filters( 'bea_csf.client.taxonomy.delete', true, $sync_fields ); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /classes/media.php: -------------------------------------------------------------------------------- 1 | &$source ) { 55 | $sources[ $key ]['url'] = self::replace_base_upload_dir( $source['url'], get_current_blog_id(), $external->emitter_blog_id ); 56 | } 57 | 58 | return $sources; 59 | } 60 | 61 | /** 62 | * Edit attachment URL on fly for synchronized media 63 | * 64 | * @param string $url 65 | * @param integer $post_id 66 | * 67 | * @return string 68 | */ 69 | public static function wp_get_attachment_url( $url, $post_id ) { 70 | $external = BEA_CSF_Relations::current_object_is_synchronized( 'attachment', get_current_blog_id(), $post_id ); 71 | if ( empty( $external ) ) { 72 | return $url; 73 | } 74 | 75 | return self::replace_base_upload_dir( $url, get_current_blog_id(), $external->emitter_blog_id ); 76 | } 77 | 78 | /** 79 | * Filter the attachment file path 80 | * 81 | * @param string $file 82 | * @param integer $attachment_id 83 | * 84 | * @return string 85 | */ 86 | public static function get_attached_file( $file, $attachment_id ) { 87 | $external = BEA_CSF_Relations::current_object_is_synchronized( 'attachment', get_current_blog_id(), $attachment_id ); 88 | if ( empty( $external ) ) { 89 | return $file; 90 | } 91 | 92 | return self::replace_base_upload_dir( $file, get_current_blog_id(), $external->emitter_blog_id, 'basedir' ); 93 | } 94 | 95 | /** 96 | * An kind of str_replace function with helper wp_upload_dir replacement 97 | * 98 | * @param string $string 99 | * @param integer $receiver_blog_id 100 | * @param integer $emitter_blog_id 101 | * @param string $wp_upload_dir_key 102 | * 103 | * @return string 104 | */ 105 | public static function replace_base_upload_dir( &$string, $receiver_blog_id, $emitter_blog_id, $wp_upload_dir_key = 'baseurl' ) { 106 | $receiver_wp_upload_dir = self::get_blog_wp_upload_dir( $receiver_blog_id ); 107 | $emitter_wp_upload_dir = self::get_blog_wp_upload_dir( $emitter_blog_id ); 108 | 109 | return str_replace( 110 | [ 111 | $receiver_wp_upload_dir[ $wp_upload_dir_key ], 112 | '/sites/1/', 113 | ], 114 | [ $emitter_wp_upload_dir[ $wp_upload_dir_key ], '/' ], 115 | $string 116 | ); 117 | } 118 | 119 | /** 120 | * Get wp_upload_dir data with a blog_id param and a local cache with static variable 121 | * 122 | * @param int $blog_id 123 | * 124 | * @return bool|array 125 | */ 126 | public static function get_blog_wp_upload_dir( $blog_id = 0 ) { 127 | if ( 0 === $blog_id ) { 128 | $blog_id = get_current_blog_id(); 129 | } 130 | 131 | // Check on static variables for get cache data 132 | if ( isset( self::$blogs_wp_upload_dir[ $blog_id ] ) ) { 133 | return self::$blogs_wp_upload_dir[ $blog_id ]; 134 | } 135 | 136 | // Need to switch for get this value for another blog 137 | if ( get_current_blog_id() !== $blog_id ) { 138 | switch_to_blog( $blog_id ); 139 | self::$blogs_wp_upload_dir[ $blog_id ] = wp_upload_dir(); 140 | restore_current_blog(); 141 | } else { 142 | self::$blogs_wp_upload_dir[ $blog_id ] = wp_upload_dir(); 143 | } 144 | 145 | return self::$blogs_wp_upload_dir[ $blog_id ]; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /classes/multisite.php: -------------------------------------------------------------------------------- 1 | bea_csf_relations} ( 35 | `id` INT(20) NOT NULL AUTO_INCREMENT , 36 | `type` VARCHAR(255) NOT NULL , 37 | `emitter_blog_id` INT(20) NOT NULL , 38 | `emitter_id` INT(20) NOT NULL , 39 | `receiver_blog_id` INT(20) NOT NULL , 40 | `receiver_id` INT(20) NOT NULL , 41 | PRIMARY KEY (id), 42 | UNIQUE KEY `type_emitter_receiver` (`type`(191), `emitter_blog_id`, `emitter_id`, `receiver_blog_id`, `receiver_id`), 43 | KEY `emitters` ( `type`(191), `emitter_blog_id`, `emitter_id` ), 44 | KEY `receivers` ( `type`(191), `receiver_blog_id`, `receiver_id` ) 45 | );"; 46 | 47 | if ( ! function_exists( 'dbDelta' ) ) { 48 | require_once ABSPATH . 'wp-admin/includes/upgrade.php'; 49 | } 50 | 51 | // Ensure we always try to create this, regardless of whether we're on the 52 | // main site or not. dbDelta will skip creation of global tables on 53 | // non-main sites. 54 | $offset = array_search( 'bea_csf_relations', $wpdb->ms_global_tables, true ); 55 | if ( ! empty( $offset ) ) { 56 | unset( $wpdb->ms_global_tables[ $offset ] ); 57 | } 58 | $result = dbDelta( $schema ); 59 | $wpdb->ms_global_tables[] = 'bea_csf_relations'; 60 | 61 | if ( empty( $result ) ) { 62 | // No changes, database already exists and is up-to-date 63 | return 'exists'; 64 | } 65 | 66 | return 'created'; 67 | } 68 | 69 | public static function create_queue_table() { 70 | global $wpdb; 71 | 72 | $schema = "CREATE TABLE {$wpdb->bea_csf_queue} ( 73 | `id` BIGINT(20) NOT NULL auto_increment, 74 | `type` VARCHAR(255) NOT NULL, 75 | `object_name` VARCHAR(255) NOT NULL, 76 | `hook_data` TEXT NOT NULL, 77 | `current_filter` TEXT NOT NULL, 78 | `receiver_blog_id` BIGINT(20), 79 | `fields` TEXT NOT NULL, 80 | PRIMARY KEY (id), 81 | UNIQUE KEY `unicity_key` (`hook_data`(191),`current_filter`(191),`receiver_blog_id`) 82 | );"; 83 | 84 | /** 85 | * Indexes for table `wp_bea_csf_queue` 86 | * ALTER TABLE `wp_bea_csf_queue` ADD UNIQUE KEY `unicity_key` (`hook_data`(255),`current_filter`(255),`receiver_blog_id`,`fields`(255)); 87 | **/ 88 | if ( ! function_exists( 'dbDelta' ) ) { 89 | require_once ABSPATH . 'wp-admin/includes/upgrade.php'; 90 | } 91 | 92 | // Ensure we always try to create this, regardless of whether we're on the 93 | // main site or not. dbDelta will skip creation of global tables on 94 | // non-main sites. 95 | $offset = array_search( 'bea_csf_queue', $wpdb->ms_global_tables, true ); 96 | if ( ! empty( $offset ) ) { 97 | unset( $wpdb->ms_global_tables[ $offset ] ); 98 | } 99 | $result = dbDelta( $schema ); 100 | $wpdb->ms_global_tables[] = 'bea_csf_queue'; 101 | 102 | if ( empty( $result ) ) { 103 | // No changes, database already exists and is up-to-date 104 | return 'exists'; 105 | } 106 | 107 | return 'created'; 108 | } 109 | 110 | public static function create_queue_table_maintenance() { 111 | global $wpdb; 112 | 113 | $schema = "CREATE TABLE {$wpdb->bea_csf_queue_maintenance} ( 114 | `id` BIGINT(20) NOT NULL auto_increment, 115 | `type` VARCHAR(255) NOT NULL, 116 | `object_name` VARCHAR(255) NOT NULL, 117 | `hook_data` TEXT NOT NULL, 118 | `current_filter` TEXT NOT NULL, 119 | `receiver_blog_id` BIGINT(20), 120 | `fields` TEXT NOT NULL, 121 | PRIMARY KEY (id), 122 | UNIQUE KEY `unicity_key` (`hook_data`(191),`current_filter`(191),`receiver_blog_id`) 123 | );"; 124 | 125 | /** 126 | * Indexes for table `wp_bea_csf_queue_maintenance` 127 | * ALTER TABLE `wp_bea_csf_queue_maintenance` ADD UNIQUE KEY `unicity_key` (`hook_data`(255),`current_filter`(255),`receiver_blog_id`,`fields`(255)); 128 | **/ 129 | 130 | if ( ! function_exists( 'dbDelta' ) ) { 131 | require_once ABSPATH . 'wp-admin/includes/upgrade.php'; 132 | } 133 | 134 | // Ensure we always try to create this, regardless of whether we're on the 135 | // main site or not. dbDelta will skip creation of global tables on 136 | // non-main sites. 137 | $offset = array_search( 'bea_csf_queue_maintenance', $wpdb->ms_global_tables, true ); 138 | if ( ! empty( $offset ) ) { 139 | unset( $wpdb->ms_global_tables[ $offset ] ); 140 | } 141 | $result = dbDelta( $schema ); 142 | $wpdb->ms_global_tables[] = 'bea_csf_queue_maintenance'; 143 | 144 | if ( empty( $result ) ) { 145 | // No changes, database already exists and is up-to-date 146 | return 'exists'; 147 | } 148 | 149 | return 'created'; 150 | } 151 | 152 | /** 153 | * Do nothing :) 154 | */ 155 | public static function deactivate() { 156 | } 157 | 158 | /** 159 | * Get post ID from post meta with meta_key and meta_value 160 | * 161 | * @param string $key 162 | * @param string $value 163 | * 164 | * @return int 165 | */ 166 | public static function get_post_id_from_meta( $key, $value ) { 167 | global $wpdb; 168 | 169 | return (int) $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s", $key, $value ) ); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /classes/query.php: -------------------------------------------------------------------------------- 1 | get( 'bea_csf_filter' ) ) ) { 42 | return $join; 43 | } 44 | 45 | $join_type = $query->get( 'bea_csf_filter' ) === 'local-only' ? 'LEFT' : 'INNER'; 46 | 47 | // Get current blog ID safely 48 | $current_blog_id = (int) get_current_blog_id(); 49 | 50 | // Prepare the join SQL 51 | $join .= $wpdb->prepare( 52 | " $join_type JOIN {$wpdb->bea_csf_relations} AS bcr ON ({$wpdb->posts}.ID = bcr.receiver_id AND bcr.receiver_blog_id = %d) ", 53 | $current_blog_id 54 | ); 55 | 56 | return $join; 57 | } 58 | 59 | /** 60 | * Add join with relations tables for filter local 61 | * 62 | * @param $where 63 | * @param WP_Query $query 64 | * 65 | * @return string 66 | */ 67 | public static function posts_where( $where, WP_Query $query ) { 68 | if ( empty( $query->get( 'bea_csf_filter' ) ) || $query->get( 'bea_csf_filter' ) !== 'local-only' ) { 69 | return $where; 70 | } 71 | 72 | $where .= ' AND bcr.receiver_id IS NULL '; 73 | 74 | return $where; 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /classes/seo.php: -------------------------------------------------------------------------------- 1 | emitter_blog_id ); 60 | $canonical_url = get_permalink( $external->emitter_id ); 61 | restore_current_blog(); 62 | 63 | return $canonical_url; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /classes/server/attachment.php: -------------------------------------------------------------------------------- 1 | $attachment ), $sync_fields ); 25 | } 26 | 27 | /** 28 | * Generic method for get all data need for sync 29 | * 30 | * @param WP_Post|integer $attachment_id 31 | * 32 | * @return array|boolean 33 | */ 34 | public static function get_data( $attachment = false ) { 35 | $attachment = get_post( $attachment, ARRAY_A ); 36 | if ( empty( $attachment ) ) { 37 | return false; 38 | } 39 | 40 | $attachment['meta_data'] = get_post_custom( $attachment['ID'] ); 41 | $attachment['attachment_url'] = get_permalink( $attachment['ID'] ); 42 | $attachment['attachment_dir'] = get_attached_file( $attachment['ID'] ); 43 | //$attachment['metadata'] = wp_get_attachment_metadata( $attachment['ID'] ); 44 | 45 | // Get terms for this object 46 | $taxonomies = get_object_taxonomies( $attachment['post_type'] ); 47 | if ( false != $taxonomies ) { 48 | $attachment['terms'] = wp_get_object_terms( $attachment['ID'], $taxonomies ); 49 | $attachment['taxonomies'] = $taxonomies; 50 | 51 | foreach ( $attachment['terms'] as $key => $term ) { 52 | $attachment['terms'][ $key ] = BEA_CSF_Server_Taxonomy::get_data( $term ); 53 | } 54 | } 55 | 56 | return $attachment; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /classes/server/p2p.php: -------------------------------------------------------------------------------- 1 | $post, 19 | ); 20 | 21 | if ( empty( $post ) ) { 22 | return false; 23 | } 24 | 25 | return apply_filters( 'bea_csf.server.posttype.delete', (array) $post, $sync_fields ); 26 | } 27 | 28 | /** 29 | * @param WP_Post|boolean|integer $post 30 | * @param array $sync_fields 31 | * 32 | * @return mixed|null|void 33 | */ 34 | public static function merge( $post, array $sync_fields ) { 35 | return apply_filters( 'bea_csf.server.posttype.merge', self::get_data( $post, $sync_fields ), $sync_fields ); 36 | } 37 | 38 | /** 39 | * Generic method for get all data need for sync 40 | * 41 | * @param WP_Post|integer $post 42 | * @param array $sync_fields 43 | * 44 | * @return array|boolean 45 | */ 46 | public static function get_data( $post, array $sync_fields ) { 47 | global $wpdb; 48 | 49 | if ( empty( $post ) ) { 50 | return false; 51 | } 52 | 53 | // Get object from object or ID 54 | $post = get_post( $post ); 55 | if ( empty( $post ) ) { 56 | return false; 57 | } 58 | 59 | // Transform objet to array 60 | $post = (array) $post; 61 | 62 | // Get post metas 63 | $post['_thumbnail_id'] = (int) get_post_meta( $post['ID'], '_thumbnail_id', true ); 64 | if ( $post['_thumbnail_id'] > 0 ) { 65 | $post['_thumbnail'] = BEA_CSF_Server_Attachment::get_data( $post['_thumbnail_id'] ); 66 | } else { 67 | $post['_thumbnail'] = false; 68 | } 69 | 70 | // Get metas 71 | $post['meta_data'] = get_post_custom( $post['ID'] ); 72 | 73 | // Remove some internal meta 74 | if ( isset( $post['meta_data'][ '_b' . get_current_blog_id() . '_post_receivers' ] ) ) { 75 | unset( $post['meta_data'][ '_b' . get_current_blog_id() . '_post_receivers' ] ); 76 | } 77 | if ( isset( $post['meta_data']['_exclude_from_sync'] ) ) { 78 | unset( $post['meta_data']['_exclude_from_sync'] ); 79 | } 80 | 81 | // Get terms for this object 82 | $taxonomies = get_object_taxonomies( $post['post_type'] ); 83 | if ( false != $taxonomies ) { 84 | $post['terms'] = wp_get_object_terms( $post['ID'], $taxonomies ); 85 | $post['taxonomies'] = $taxonomies; 86 | 87 | foreach ( $post['terms'] as $key => $term ) { 88 | $post['terms'][ $key ] = BEA_CSF_Server_Taxonomy::get_data( $term ); 89 | } 90 | } 91 | 92 | // Init medias children 93 | $post['medias'] = array(); 94 | 95 | // Get medias attachment 96 | $attachments = get_children( 97 | array( 98 | 'post_parent' => $post['ID'], 99 | 'post_type' => 'attachment', 100 | ) 101 | ); 102 | 103 | foreach ( $attachments as $attachment ) { 104 | $post['medias'][] = BEA_CSF_Server_Attachment::get_data( $attachment ); 105 | } 106 | 107 | // Get P2P connections 108 | if ( defined( 'P2P_PLUGIN_VERSION' ) ) { 109 | $results = (array) $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->p2p WHERE p2p_from = %d OR p2p_to = %d", $post['ID'], $post['ID'] ) ); 110 | 111 | $post['connections'] = array(); 112 | foreach ( $results as $result ) { 113 | $post['connections'][] = BEA_CSF_Server_P2P::merge( $result, $sync_fields ); 114 | } 115 | } 116 | 117 | return $post; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /classes/server/taxonomy.php: -------------------------------------------------------------------------------- 1 | $term_values[1], 23 | 'taxonomy' => $term_values[0], 24 | ); 25 | 26 | if ( empty( $term ) ) { 27 | return false; 28 | } 29 | } else { // is_stdClass 30 | $term = (array) $term; 31 | } 32 | 33 | return apply_filters( 'bea_csf.server.taxonomy.delete', $term, $sync_fields ); 34 | } 35 | 36 | /** 37 | * Check for new term and send it to client 38 | * 39 | * @param stdClass|boolean|integer $post 40 | * @param array $sync_fields 41 | * 42 | * @return mixed|null|void 43 | */ 44 | public static function merge( $term, array $sync_fields ) { 45 | if ( empty( $term ) ) { 46 | return false; 47 | } 48 | 49 | if ( is_string( $term ) ) { // Internal format : taxonomy|||term_id 50 | $term_values = explode( '|||', $term ); 51 | 52 | $term = get_term( $term_values[1], $term_values[0] ); 53 | if ( empty( $term ) ) { 54 | return false; 55 | } 56 | } 57 | 58 | $term = self::get_data( $term ); 59 | 60 | return apply_filters( 'bea_csf.server.taxonomy.merge', $term, $sync_fields ); 61 | } 62 | 63 | /** 64 | * Generic method for get all data need for sync 65 | * 66 | * @param stdClass $term 67 | * 68 | * @return array 69 | */ 70 | public static function get_data( $term ) { 71 | // Get all meta for current term 72 | $term->meta_data = get_term_meta( $term->term_id ); 73 | 74 | // Remove some internal meta 75 | if ( isset( $term->meta_data['already_exists'] ) ) { 76 | unset( $term->meta_data['already_exists'] ); 77 | } 78 | 79 | // Remove unused fields 80 | unset( $term->term_group, $term->count ); 81 | 82 | return (array) $term; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /codeception.dist.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | tests: tests 3 | output: tests/_output 4 | data: tests/_data 5 | support: tests/_support 6 | envs: tests/_envs 7 | actor_suffix: Tester 8 | extensions: 9 | enabled: 10 | - Codeception\Extension\RunFailed 11 | commands: 12 | - Codeception\Command\GenerateWPUnit 13 | - Codeception\Command\GenerateWPRestApi 14 | - Codeception\Command\GenerateWPRestController 15 | - Codeception\Command\GenerateWPRestPostTypeController 16 | - Codeception\Command\GenerateWPAjax 17 | - Codeception\Command\GenerateWPCanonical 18 | - Codeception\Command\GenerateWPXMLRPC 19 | params: 20 | - .env.testing 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bea/bea-content-sync-fusion", 3 | "description": "Manage content synchronisation across a WordPress Multisite.", 4 | "license": "GPL-2.0-or-later", 5 | "authors": [ 6 | { 7 | "name": "Be API", 8 | "email": "technical@beapi.fr" 9 | } 10 | ], 11 | "type" : "wordpress-plugin", 12 | "config": { 13 | "optimize-autoloader": true, 14 | "preferred-install": "dist", 15 | "sort-packages": true, 16 | "allow-plugins": { 17 | "composer/installers": true, 18 | "phpro/grumphp-shim": true, 19 | "roots/wordpress-core-installer": true, 20 | "dealerdirect/phpcodesniffer-composer-installer": true 21 | } 22 | }, 23 | "require" : { 24 | "beapi/gutenberg-serializer": "^1.0", 25 | "composer/installers" : "~1.0" 26 | }, 27 | "require-dev": { 28 | "codeception/module-asserts": "^1.3", 29 | "codeception/module-cli": "^1.0", 30 | "codeception/module-db": "^1.0", 31 | "codeception/module-filesystem": "^1.0", 32 | "codeception/module-phpbrowser": "^1.0", 33 | "codeception/module-webdriver": "^1.1", 34 | "codeception/util-universalframework": "^1.0", 35 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", 36 | "lucatume/wp-browser": "^2.6", 37 | "php-parallel-lint/php-parallel-lint": "^1.2", 38 | "phpcompatibility/php-compatibility": "^9.3", 39 | "phpro/grumphp-shim": "^1.0", 40 | "roots/wordpress": "^5.5", 41 | "squizlabs/php_codesniffer": "^3.5", 42 | "vimeo/psalm": "^4.0", 43 | "wp-coding-standards/wpcs": "^2.3" 44 | }, 45 | "extra": { 46 | "installer-paths": { 47 | "wordpress": ["roots/wordpress"] 48 | } 49 | }, 50 | "scripts": { 51 | "cs": "./vendor/bin/phpcs", 52 | "cbf": "./vendor/bin/phpcbf", 53 | "psalm": "./vendor/bin/psalm", 54 | "test-unit": "./vendor/bin/codecept run unit --html", 55 | "test-wpunit": "./vendor/bin/codecept run wpunit --html", 56 | "test-functional": "./vendor/bin/codecept run functional --html", 57 | "test-acceptance": "./vendor/bin/codecept run acceptance --html" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cron/cronjob.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 2018 - BE API - Cronjob for "BEA Content Sync Fusion" plugin 3 | 4 | ## INSTALL 5 | # We recommend to exec this task each minute : */1 * * * * 6 | 7 | ## USAGE 8 | # 5 arguments max 9 | # 1st argument : REQUIRED - the network URL of WordPress, eg : mydomain.fr, https://mydomain.fr 10 | # 2nd argument : OPTIONAL - the WP-CLI binary command, eg: wp, "lando wp", "php wp-cli.phar" (Default value : wp) 11 | # 3nd argument : OPTIONAL - the path of WP installation, eg: /var/www/wp/ (not default value) 12 | # 4rth argument : OPTIONAL - the alternate queue, eg : true (Default value : false) 13 | # 5th argument : OPTIONAL - additional parameters to pass to WPCLI, eg : --skip-plugins=stream 14 | 15 | ## TODO 16 | # Allow to customize path for PID file 17 | 18 | # set -e # same AS set -o errexit 19 | set -o pipefail 20 | set -o nounset 21 | 22 | # Set magic variables for current file & dir 23 | # See: https://kvz.io/blog/2013/11/21/bash-best-practices/ 24 | __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 25 | __file="${__dir}/$(basename "${BASH_SOURCE[0]}")" 26 | __base="$(basename ${__file} .sh)" 27 | 28 | # The first argument is mandatory 29 | if [ -z "${1:-}" ] 30 | then 31 | echo "You must pass at least one argument, the WordPress network URL..." 32 | exit 1 33 | fi 34 | 35 | # Set variables from command arguments 36 | WP_NETWORK_URL=${1} 37 | WP_CLI_BIN=${2:-wp} 38 | ALT_QUEUE=${4:-false} 39 | ADDITIONAL_ARGS=${5:-''} 40 | 41 | # Wrap the 3rd argument if is filled 42 | if [ -n "${3:-}" ]; then 43 | WP_PATH="--path=${3}" 44 | else 45 | WP_PATH="" 46 | fi 47 | 48 | # Use the 4rth argument if is filled 49 | if [ false == ALT_QUEUE ]; then 50 | PIDFILE="$__dir/wp-bea-csf.pid" 51 | else 52 | PIDFILE="$__dir/wp-bea-csf-alt.pid" 53 | fi 54 | 55 | # Create and test for a LOCK PID FILE for Preventing duplicate cron job executions 56 | # See: https://bencane.com/2015/09/22/preventing-duplicate-cron-job-executions/ 57 | if [ -f $PIDFILE ] 58 | then 59 | PID=$(cat $PIDFILE) 60 | ps -p $PID > /dev/null 2>&1 61 | if [ $? -eq 0 ] 62 | then 63 | echo "Process already running" 64 | exit 1 65 | else 66 | ## Process not found assume not running 67 | echo $$ > $PIDFILE 68 | if [ $? -ne 0 ] 69 | then 70 | echo "Could not create PID file" 71 | exit 1 72 | fi 73 | fi 74 | else 75 | echo $$ > $PIDFILE 76 | if [ $? -ne 0 ] 77 | then 78 | echo "Could not create PID file" 79 | exit 1 80 | fi 81 | fi 82 | 83 | # Regular queue 84 | $WP_CLI_BIN content-sync-fusion queue get_sites --alternativeq=$ALT_QUEUE --url="$WP_NETWORK_URL" $ADDITIONAL_ARGS $WP_PATH | xargs -I {} $WP_CLI_BIN content-sync-fusion queue pull --alternativeq=$ALT_QUEUE --url={} $ADDITIONAL_ARGS $WP_PATH 85 | 86 | # Check for resync content (new site/blog) 87 | $WP_CLI_BIN content-sync-fusion resync new_sites --smart=true --attachments=true --post_type=true --taxonomies=true --url="$WP_NETWORK_URL" $ADDITIONAL_ARGS $WP_PATH 88 | 89 | # Remove lock PIDFILE 90 | rm $PIDFILE 91 | 92 | exit 0 -------------------------------------------------------------------------------- /functions/api.php: -------------------------------------------------------------------------------- 1 | get_var( $wpdb->prepare( "SELECT tm.term_id FROM $wpdb->termmeta tm INNER JOIN $wpdb->term_taxonomy tt ON tm.term_id = tt.term_id WHERE tm.meta_key = %s AND tm.meta_value = %s AND tt.taxonomy = %s", $meta_key, $meta_value, $taxonomy ) ); 37 | } else { 38 | $result = (int) $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->termmeta WHERE meta_key = %s AND meta_value = %s", $meta_key, $meta_value ) ); 39 | } 40 | 41 | //wp_cache_set( $key, $result, 'term_meta' ); 42 | //} 43 | 44 | return $result; 45 | } 46 | -------------------------------------------------------------------------------- /functions/template.php: -------------------------------------------------------------------------------- 1 | 2 | have_posts() ) : ?> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | have_posts() ) : 12 | $query_contents->the_post(); 13 | ?> 14 | 15 | 16 | 17 | 18 | 19 |
-
20 | 21 |

22 | 23 | 24 | -------------------------------------------------------------------------------- /views/admin/blog-widget-status.php: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 |

5 | 6 | 0 ) : ?> 7 |
8 |

9 | 10 | 11 |

12 |
13 | 14 |
15 | -------------------------------------------------------------------------------- /views/admin/client-metabox.php: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | /> 6 | 9 | 10 |

11 |

12 | 13 |

14 | 15 |

16 | %1$s, and from the original article: %2$s', 'bea-content-sync-fusion' ), $emitter_data['blog_name'], $emitter_data['post_title'] ); 18 | ?> 19 |

20 | -------------------------------------------------------------------------------- /views/admin/client-page-settings-notification.php: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 |
5 |

6 |
7 | get_field( 'id' ) ] ) ) { 10 | $current_values[ $sync_obj->get_field( 'id' ) ] = array(); 11 | } 12 | ?> 13 | 14 |

get_field( 'label' ); ?>

15 | 20 | 21 | 22 | 23 |

24 | 25 | 26 |

27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /views/admin/client-terms-form.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 |
16 | 17 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /views/admin/server-metabox-attachment.php: -------------------------------------------------------------------------------- 1 | ID); ?> 2 |

3 | 4 |

5 | 6 | /> 8 | 9 | 12 | 13 |

14 | %s', 'bea-content-sync-fusion' ), implode( ', ', $sync_names ) ); ?> 15 |

16 | -------------------------------------------------------------------------------- /views/admin/server-metabox-auto.php: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | /> 7 | 10 | 11 |

12 | 13 | 14 |

15 | 16 |

17 | %s', 'bea-content-sync-fusion' ), implode( ', ', $sync_names ) ); ?> 18 |

19 | -------------------------------------------------------------------------------- /views/admin/server-metabox-manual.php: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |
6 |
    7 | 13 |
  • 14 | 19 | 20 | 23 | 24 | 34 | 35 | 36 |
  • 37 | 38 |
39 |
40 | 41 |

42 | 43 | 45 |

46 | 47 |

48 | %s', 'bea-content-sync-fusion' ), implode( ', ', $sync_names ) ); ?> 49 |

50 | -------------------------------------------------------------------------------- /views/admin/server-page-add.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | 5 |

6 | 7 | 8 | 9 |
10 |

11 | 12 | 14 | 16 |

17 | 18 |

19 | 20 | 27 | 29 |

30 | 31 |

32 | 33 | 39 | 41 |

42 | 43 | 44 |

45 | 46 | 52 | 54 |

55 | 56 | 57 |

58 | 59 | 73 | 75 |

76 | 77 |

78 | 79 | 93 | 95 |

96 | 97 |

98 | 99 | 107 | 109 |

110 | 111 |

112 | 113 | 121 | 123 |

124 | 125 |

126 | 127 | 128 | 129 | 131 | 133 | 134 | 136 | 137 |

138 |
139 |
140 | -------------------------------------------------------------------------------- /views/admin/server-page-queue.php: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | ' . sprintf( __( 'Cron process %1$d items by %2$d items', 'bea-content-sync-fusion' ), BEA_CSF_CRON_QTY, BEA_CSF_CRON_QTY ) . '

'; 8 | 9 | echo __( 'Number of items in the queue : ', 'bea-content-sync-fusion' ) . $nbqueue; 10 | 11 | $lock_file = sys_get_temp_dir() . '/bea-content-sync-fusion.lock'; 12 | if ( file_exists( $lock_file ) ) { 13 | echo '

' . sprintf( __( 'The file %s has been modified : ', 'bea-content-sync-fusion' ), $lock_file ) . date( 'd F Y H:i:s.', filemtime( $lock_file ) ) . '

'; 14 | ?> 15 |
16 |

17 | 18 | 20 |

21 |
22 | ' . sprintf( __( 'The file %s has been deleted', 'bea-content-sync-fusion' ), $lock_file ) . '

'; 26 | } 27 | 28 | // Maintenance 29 | global $wpdb; 30 | $nb_queue_maintenance = (int) $wpdb->get_var( 'SELECT COUNT(id) as nbqueue FROM ' . $GLOBALS['wpdb']->bea_csf_queue_maintenance ); 31 | if ( $nb_queue_maintenance > 0 ) { 32 | echo '

' . __( 'Number of items in the queue of maintenance : ', 'bea-content-sync-fusion' ) . $nb_queue_maintenance . '

'; 33 | } 34 | ?> 35 | 36 |

37 |

38 |
39 | -------------------------------------------------------------------------------- /views/admin/server-page-settings.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 | '; 27 | else : 28 | $class = 'alternate'; 29 | $i = 0; 30 | foreach ( $registered_syncs as $sync ) : 31 | // Skip invalid data 32 | $label = $sync->get_field( 'label' ); 33 | if ( empty( $label ) ) { 34 | continue; 35 | } 36 | 37 | // Get post type label from cpt name 38 | $post_type_label = '-'; 39 | $post_type_object = get_post_type_object( $sync->get_field( 'post_type' ) ); 40 | if ( $post_type_object != false ) { 41 | $post_type_label = $post_type_object->labels->name; 42 | } 43 | 44 | // Get taxonomies labels from taxo name 45 | $taxonomies_label = array(); 46 | foreach ( $sync->get_field( 'taxonomies' ) as $taxonomy_name ) { 47 | $taxonomy_object = get_taxonomy( $taxonomy_name ); 48 | if ( $taxonomy_object != false ) { 49 | $taxonomies_label[] = $taxonomy_object->labels->name; 50 | } 51 | } 52 | $taxonomies_label = implode( ', ', $taxonomies_label ); 53 | 54 | // Get P2P labels from taxo name 55 | $p2p_label = implode( ', ', (array) $sync->get_field( 'p2p_connections' ) ); 56 | 57 | $i ++; 58 | $class = ( $class == 'alternate' ) ? '' : 'alternate'; 59 | ?> 60 | 61 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 115 | 116 |
' . sprintf( __( 'No synchronization exists. Want to create one?', 'bea-content-sync-fusion' ), network_admin_url( 'admin.php?page=' . 'bea-csf-add' ) ) . '
62 | get_field( 'label' ) ); ?> 63 | 64 |
65 | 66 | is_locked() ) : ?> 67 | 78 | | 79 | get_field( 'label' ) ) ); ?>');"> 94 | 95 | 96 | 97 | 98 |
99 |
get_field( 'active' ) ] ); ?>get_field( 'mode' ) ); ?>get_field( 'status' ) ); ?>', self::get_sites( $sync->get_field( 'emitters', true ), 'blogname' ) ); ?>', self::get_sites( $sync->get_field( 'receivers', true ), 'blogname' ) ); ?>
117 |
118 | 119 | 120 | 121 |

122 |
123 |
124 | 125 | 126 | 127 | 128 | 138 | 139 | 140 |
129 |
130 | 136 |
137 |
141 | 142 | 143 |

144 | 146 |

147 |
148 |
149 |
150 | -------------------------------------------------------------------------------- /views/admin/server-term-metabox-manual.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | 6 |

7 | 8 |
9 |
    10 | 11 |
  • 12 | 17 |
  • 18 | 19 |
20 |
21 | 22 | 23 |

24 | %s', 'bea-content-sync-fusion' ), implode( ', ', $sync_names ) ); ?> 25 |

26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /wp-cli.yml: -------------------------------------------------------------------------------- 1 | url: https://bea-content-sync-fusion.lndo.site 2 | path: /app/wordpress --------------------------------------------------------------------------------