Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 121 |
|
0.00% |
0 / 6 |
CRAP | n/a |
0 / 0 |
|
| wpcomsh_apply_headstart_terms | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
56 | |||
| wpcomsh_headstart_log | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| wpcomsh_apply_headstart_custom_term_assignments | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
| wpcomsh_apply_headstart_custom_term_assignment | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| wpcomsh_apply_headstart_custom_terms | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
132 | |||
| wpcomsh_apply_headstart_custom_term_meta | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
90 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Functions to read WooCommerce terms and taxonomy information from |
| 4 | * a theme's headstart annotation, then apply them to the site. |
| 5 | * To be used after WooCommerce is installed. |
| 6 | * |
| 7 | * @package wpcomsh |
| 8 | */ |
| 9 | |
| 10 | /** |
| 11 | * Main function for applying all term and taxonomy information from |
| 12 | * the current theme's headstart annotation. |
| 13 | * |
| 14 | * @return array $results An associative array. Key 'missing_taxonomies' has an array value: if there were terms in the annotation that could not be added because a taxonomy was missing on the site, the list of missing taxonomies. (An array of strings). |
| 15 | */ |
| 16 | function wpcomsh_apply_headstart_terms() { |
| 17 | $theme_to_headstart = get_option( 'woocommerce_should_run_headstart_for_theme', false ); |
| 18 | $early_return_data = array( 'missing_taxonomies' => array() ); |
| 19 | |
| 20 | if ( false === $theme_to_headstart ) { |
| 21 | return $early_return_data; |
| 22 | } |
| 23 | |
| 24 | $theme = wp_get_theme(); |
| 25 | $theme_name = $theme->get_stylesheet(); |
| 26 | |
| 27 | if ( $theme_name !== $theme_to_headstart ) { |
| 28 | wpcomsh_headstart_log( "wpcomsh_apply_headstart_terms: Theme slugs don't match. Theme to Headstart: [$theme_to_headstart], Theme Slug: [$theme_name]." ); |
| 29 | |
| 30 | // Delete the option; the theme has changed. |
| 31 | delete_option( 'woocommerce_should_run_headstart_for_theme' ); |
| 32 | return $early_return_data; |
| 33 | } |
| 34 | |
| 35 | $locale = get_locale(); |
| 36 | $fallback_locale = 'en'; |
| 37 | |
| 38 | $annotation = wpcomsh_headstart_get_annotation( $theme_name, $locale, $fallback_locale ); |
| 39 | $missing_taxonomies = array(); |
| 40 | |
| 41 | if ( false === $annotation ) { |
| 42 | wpcomsh_headstart_log( "wpcomsh_apply_headstart_terms: Could not find the headstart annotation for theme [$theme_name]. locale=[$locale] fallback_locale=[$fallback_locale]" ); |
| 43 | |
| 44 | // Delete the option; we don't have a Headstart annotation for this theme. |
| 45 | delete_option( 'woocommerce_should_run_headstart_for_theme' ); |
| 46 | return $missing_taxonomies; |
| 47 | } |
| 48 | |
| 49 | if ( ! empty( $annotation['custom_terms_by_taxonomy'] ) ) { |
| 50 | wpcomsh_headstart_log( "wpcomsh_apply_headstart_terms: Found custom_terms_by_taxonomy for [$theme], applying." ); |
| 51 | $custom_terms_return = wpcomsh_apply_headstart_custom_terms( $annotation['custom_terms_by_taxonomy'] ); |
| 52 | $term_id_map = $custom_terms_return['term_id_map']; |
| 53 | $missing_taxonomies = $custom_terms_return['missing_taxonomies']; |
| 54 | wpcomsh_headstart_log( compact( 'term_id_map', 'missing_taxonomies' ) ); |
| 55 | if ( ! empty( $annotation['custom_term_meta'] ) ) { |
| 56 | wpcomsh_headstart_log( "wpcomsh_apply_headstart_terms: Found custom_terms_meta for [$theme], applying." ); |
| 57 | wpcomsh_apply_headstart_custom_term_meta( $annotation['custom_term_meta'], $term_id_map ); |
| 58 | } |
| 59 | if ( ! empty( $annotation['custom_term_assignments'] ) ) { |
| 60 | wpcomsh_headstart_log( "wpcomsh_apply_headstart_terms: Found custom_term_assignments for [$theme], applying." ); |
| 61 | wpcomsh_apply_headstart_custom_term_assignments( $annotation['custom_term_assignments'] ); |
| 62 | } |
| 63 | } else { |
| 64 | wpcomsh_headstart_log( "wpcomsh_apply_headstart_terms: Found an annotation for [$theme], but not taking action since it did not contain custom_terms_by_taxonomy." ); |
| 65 | } |
| 66 | |
| 67 | delete_option( 'woocommerce_should_run_headstart_for_theme' ); |
| 68 | return array( 'missing_taxonomies' => $missing_taxonomies ); |
| 69 | } |
| 70 | |
| 71 | /** |
| 72 | * Logging wrapper. |
| 73 | * |
| 74 | * @param mixed $input Anything you want to log. |
| 75 | */ |
| 76 | function wpcomsh_headstart_log( $input ) { |
| 77 | if ( is_wp_error( $input ) ) { |
| 78 | $input = $input->get_error_message(); |
| 79 | } elseif ( ! is_string( $input ) ) { |
| 80 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r |
| 81 | $input = print_r( $input, true ); |
| 82 | } |
| 83 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log |
| 84 | error_log( 'WPComSH: headstart: ' . $input ); |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Given a list of custom term assignments for several posts from a headstart annotation, |
| 89 | * try to locate posts on the live site matching the "old ids" in the assignment, then |
| 90 | * apply the terms listed. |
| 91 | * |
| 92 | * Old vs New: |
| 93 | * "old" post id: 1379 - This represents the "Red Shirt" post's ID in the annotation. |
| 94 | * "new" post id: 203482 - This represents the "Red Shirt" post's ID on the current site. |
| 95 | * |
| 96 | * @param array $custom_term_assignments An array where keys are "old" post IDs and the values |
| 97 | * are arrays. The inner arrays' keys are taxonomy names and values are arrays of term names. |
| 98 | * Example: |
| 99 | * "81": { |
| 100 | * "product_cat": [ "Men", "Women" ], |
| 101 | * "product_tag": [ "New Arrivals" ], |
| 102 | * }, |
| 103 | * "80": { |
| 104 | * "product_cat": [ "Men", "Women" ], |
| 105 | * "product_tag": [ "New Arrivals" ], |
| 106 | * }. |
| 107 | **/ |
| 108 | function wpcomsh_apply_headstart_custom_term_assignments( $custom_term_assignments ) { |
| 109 | $filter = array( |
| 110 | 'fields' => 'ids', |
| 111 | 'nopaging' => true, |
| 112 | 'post_status' => 'publish', |
| 113 | 'post_type' => get_post_types(), |
| 114 | ); |
| 115 | $post_ids = get_posts( $filter ); |
| 116 | |
| 117 | foreach ( $post_ids as $post_id ) { |
| 118 | $old_id = get_post_meta( $post_id, '_hs_old_id', true ); |
| 119 | if ( empty( $old_id ) || empty( $custom_term_assignments[ $old_id ] ) ) { |
| 120 | continue; |
| 121 | } |
| 122 | wpcomsh_apply_headstart_custom_term_assignment( $post_id, $custom_term_assignments[ $old_id ] ); |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * Given a single post id, and a list of custom term assignments for |
| 128 | * up to several taxonomies, add those terms assignments to the post. |
| 129 | * |
| 130 | * @param int $post_id A single post id. |
| 131 | * @param array $assignments An array where keys are taxonomy names and values are arrays of term names. |
| 132 | * Example: { |
| 133 | * "product_cat": [ "Men", "Women" ], |
| 134 | * "product_tag": [ "New Arrivals" ], |
| 135 | * }. |
| 136 | */ |
| 137 | function wpcomsh_apply_headstart_custom_term_assignment( $post_id, $assignments ) { |
| 138 | $append = true; // Don't ever delete terms |
| 139 | foreach ( $assignments as $taxonomy => $terms ) { |
| 140 | if ( empty( $terms ) ) { |
| 141 | continue; |
| 142 | } |
| 143 | $result = wp_set_object_terms( $post_id, $terms, $taxonomy, $append ); |
| 144 | if ( is_wp_error( $result ) ) { |
| 145 | $error_message = "Failed to set terms for post $post_id in taxonomy $taxonomy due to error: " . $result->get_error_message(); |
| 146 | wpcomsh_headstart_log( $error_message ); |
| 147 | } |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Given a list of custom terms for several taxonomies, |
| 153 | * check if each of those terms exists and if not, add it to |
| 154 | * the site. |
| 155 | * |
| 156 | * A term is one value of a taxonomy. For example, the "category" taxonomy might |
| 157 | * have the "Shirts" and "Shoes" terms. |
| 158 | * |
| 159 | * @param array $custom_terms_by_taxonomy An array where the keys are the taxonomy names |
| 160 | * and the values are arrays of arrays (the same as what's returned by get_terms( $taxonomy )). |
| 161 | * Example: { |
| 162 | * "product_cat": [ |
| 163 | * { "term_id": 1379, "name": "Men", "slug": "men", "term_group": 0, "term_taxonomy_id": 25, |
| 164 | * "taxonomy": "product_cat", "description": "", "parent": 0, "count": 4, "filter": "raw" }, |
| 165 | * { "term_id": 1380, "name": "Women", "slug": "women", "term_group": 0, "term_taxonomy_id": 26, |
| 166 | * "taxonomy": "product_cat", "description": "", "parent": 0, "count": 4, "filter": "raw" } |
| 167 | * ], |
| 168 | * "product_tag": [ |
| 169 | * { "term_id": 1381, "name": "New Arrivals", "slug": "new-arrivals", "term_group": 0, |
| 170 | * "term_taxonomy_id": 27, "taxonomy": "product_tag", "description": "", "parent": 0, |
| 171 | * "count": 4, "filter": "raw" } |
| 172 | * ] |
| 173 | * }. |
| 174 | * @return array $term_id_map A mapping of "old" term_ids to "new" term_ids for any terms that were inserted or found. |
| 175 | */ |
| 176 | function wpcomsh_apply_headstart_custom_terms( $custom_terms_by_taxonomy ) { |
| 177 | $term_id_map = array(); |
| 178 | $missing_taxonomies = array(); |
| 179 | |
| 180 | foreach ( $custom_terms_by_taxonomy as $taxonomy => $terms ) { |
| 181 | if ( ! taxonomy_exists( $taxonomy ) ) { |
| 182 | wpcomsh_headstart_log( "Skipping taxonomy [$taxonomy] because it does not exist." ); |
| 183 | $missing_taxonomies[] = $taxonomy; |
| 184 | continue; |
| 185 | } |
| 186 | |
| 187 | // First pass: Move terms into $terms_by_parent |
| 188 | $terms_by_parent = array(); |
| 189 | foreach ( $terms as $term ) { |
| 190 | $parent_id = $term['parent']; |
| 191 | if ( empty( $parent_id ) ) { |
| 192 | $parent_id = 0; |
| 193 | } |
| 194 | |
| 195 | if ( empty( $terms_by_parent[ $parent_id ] ) ) { |
| 196 | $terms_by_parent[ $parent_id ] = array( $term ); |
| 197 | } else { |
| 198 | $terms_by_parent[ $parent_id ][] = $term; |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | // Top level terms are 'addable': put those into a queue. |
| 203 | // As we insert terms, any terms that depend on those will be added to the queue. |
| 204 | $addable_terms = $terms_by_parent['0']; |
| 205 | while ( ! empty( $addable_terms ) ) { |
| 206 | $term = array_shift( $addable_terms ); |
| 207 | |
| 208 | $old_term_id = $term['term_id']; |
| 209 | $new_term_id = null; |
| 210 | |
| 211 | $term_info = term_exists( $term['slug'], $taxonomy ); |
| 212 | if ( $term_info ) { |
| 213 | // Already exists |
| 214 | $new_term_id = $term_info['term_id']; |
| 215 | } else { |
| 216 | // Doesn't exist, will add |
| 217 | $args = array( |
| 218 | 'slug' => $term['slug'], |
| 219 | 'description' => $term['description'], |
| 220 | 'parent' => $term_id_map[ $term['parent'] ] ?? 0, |
| 221 | ); |
| 222 | $result = wp_insert_term( $term['name'], $taxonomy, $args ); |
| 223 | if ( ! is_wp_error( $result ) ) { |
| 224 | $new_term_id = $result['term_id']; |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | if ( ! empty( $new_term_id ) ) { |
| 229 | // Record the ID mapping |
| 230 | $term_id_map[ $old_term_id ] = $new_term_id; |
| 231 | |
| 232 | // Mark any terms depending on me as "addable" |
| 233 | if ( ! empty( $terms_by_parent[ $old_term_id ] ) ) { |
| 234 | $addable_terms = array_merge( $addable_terms, $terms_by_parent[ $old_term_id ] ); |
| 235 | unset( $terms_by_parent[ $old_term_id ] ); |
| 236 | } |
| 237 | } |
| 238 | } |
| 239 | } |
| 240 | return array( |
| 241 | 'term_id_map' => $term_id_map, |
| 242 | 'missing_taxonomies' => $missing_taxonomies, |
| 243 | ); |
| 244 | } |
| 245 | |
| 246 | /** |
| 247 | * Given an annotation's list of term_meta assignments, and a |
| 248 | * map of "old" term ids to "new" term ids, write the term_meta values |
| 249 | * in the assignment to the "new" term ids. |
| 250 | * |
| 251 | * Example: |
| 252 | * "old" term id: 1379 - This represents the "New Arrivals" category's ID in the annotation. |
| 253 | * "new" term id: 203482 - This represents the "New Arrivals" category's ID on the current site. |
| 254 | * |
| 255 | * @param array $custom_term_meta Term_meta assignments by "old" id. |
| 256 | * Example: { "1379": { "order": [ 0 ], "another_meta_key": [ "another_meta_value" ] }, ... }. |
| 257 | * @param array $term_id_map Mapping of "old" term ids to "new" term ids. |
| 258 | * Example: { "1379": "203482", "1380": "203483", ... }. |
| 259 | */ |
| 260 | function wpcomsh_apply_headstart_custom_term_meta( $custom_term_meta, $term_id_map ) { |
| 261 | foreach ( $custom_term_meta as $old_term_id => $metas ) { |
| 262 | if ( empty( $term_id_map[ $old_term_id ] ) ) { |
| 263 | continue; |
| 264 | } |
| 265 | $new_term_id = $term_id_map[ $old_term_id ]; |
| 266 | |
| 267 | // Find which term meta keys are already set on this term. |
| 268 | $existing_meta_keys = array(); |
| 269 | $has_term_meta = has_term_meta( $new_term_id ); |
| 270 | if ( ! empty( $has_term_meta ) ) { |
| 271 | $existing_meta_keys = array_map( |
| 272 | function ( $row ) { |
| 273 | return $row['meta_key']; |
| 274 | }, |
| 275 | $has_term_meta |
| 276 | ); |
| 277 | } |
| 278 | |
| 279 | foreach ( $metas as $meta_key => $meta_value ) { |
| 280 | if ( is_array( $meta_value ) && count( $meta_value ) === 1 ) { |
| 281 | $meta_value = reset( $meta_value ); |
| 282 | } |
| 283 | |
| 284 | // Only write values if they do not already exist. |
| 285 | if ( in_array( $meta_key, $existing_meta_keys, true ) ) { |
| 286 | continue; |
| 287 | } |
| 288 | |
| 289 | $result = add_term_meta( $new_term_id, $meta_key, $meta_value ); |
| 290 | if ( is_wp_error( $result ) ) { |
| 291 | $error_message = "Failed to add term_meta for term $new_term_id (old term id: $old_term_id) and meta key $meta_key due to error: " . $result->get_error_message(); |
| 292 | wpcomsh_headstart_log( $error_message ); |
| 293 | } |
| 294 | } |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | add_action( 'woocommerce_installed', 'wpcomsh_apply_headstart_terms' ); |