Changeset 3482030
- Timestamp:
- 03/13/2026 01:32:56 PM (3 weeks ago)
- Location:
- a1-tools/trunk
- Files:
-
- 3 edited
-
a1-tools.php (modified) (5 diffs)
-
includes/class-a1-tools-media-management.php (modified) (9 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
a1-tools/trunk/a1-tools.php
r3479067 r3482030 4 4 * Plugin URI: https://tools.a-1chimney.com 5 5 * Description: Connects your WordPress site to the A1 Tools platform for centralized management of contact information, social media links, and business details. 6 * Version: 2.0. 46 * Version: 2.0.5 7 7 * Requires at least: 5.0 8 8 * Requires PHP: 7.4 … … 21 21 22 22 // Plugin constants. 23 define( 'A1TOOLS_VERSION', '2.0. 4' );23 define( 'A1TOOLS_VERSION', '2.0.5' ); 24 24 define( 'A1TOOLS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 25 25 define( 'A1TOOLS_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); … … 4615 4615 ) ); 4616 4616 register_setting( 'a1tools_settings', 'a1tools_form_group_id', array( 4617 'type' => 'integer', 4618 'default' => 0, 4619 'sanitize_callback' => 'absint', 4620 ) ); 4621 register_setting( 'a1tools_settings', 'a1tools_form_crm_company_id', array( 4622 'type' => 'integer', 4623 'default' => 0, 4624 'sanitize_callback' => 'absint', 4625 ) ); 4626 register_setting( 'a1tools_settings', 'a1tools_form_crm_franchise_id', array( 4617 4627 'type' => 'integer', 4618 4628 'default' => 0, … … 4863 4873 <p class="description"> 4864 4874 <?php esc_html_e( 'Sunday group ID (within the board) where new leads will be placed.', 'a1-tools' ); ?> 4875 </p> 4876 </td> 4877 </tr> 4878 <tr> 4879 <th scope="row" colspan="2"> 4880 <h3 style="margin: 0.5em 0 0;"><?php esc_html_e( 'CRM Lead Integration', 'a1-tools' ); ?></h3> 4881 <p class="description" style="font-weight: normal;"> 4882 <?php esc_html_e( 'Optionally create a CRM lead for each form submission. Set Company ID to 0 to disable.', 'a1-tools' ); ?> 4883 </p> 4884 </th> 4885 </tr> 4886 <tr> 4887 <th scope="row"> 4888 <label for="a1tools_form_crm_company_id"><?php esc_html_e( 'CRM Company ID', 'a1-tools' ); ?></label> 4889 </th> 4890 <td> 4891 <input type="number" id="a1tools_form_crm_company_id" name="a1tools_form_crm_company_id" 4892 value="<?php echo esc_attr( get_option( 'a1tools_form_crm_company_id', 0 ) ); ?>" 4893 class="small-text" min="0"> 4894 <p class="description"> 4895 <?php esc_html_e( 'CRM company ID where leads will be created. Find this in CRM Settings > Company & Franchises. Set to 0 to disable CRM lead creation.', 'a1-tools' ); ?> 4896 </p> 4897 </td> 4898 </tr> 4899 <tr> 4900 <th scope="row"> 4901 <label for="a1tools_form_crm_franchise_id"><?php esc_html_e( 'CRM Franchise ID', 'a1-tools' ); ?></label> 4902 </th> 4903 <td> 4904 <input type="number" id="a1tools_form_crm_franchise_id" name="a1tools_form_crm_franchise_id" 4905 value="<?php echo esc_attr( get_option( 'a1tools_form_crm_franchise_id', 0 ) ); ?>" 4906 class="small-text" min="0"> 4907 <p class="description"> 4908 <?php esc_html_e( 'CRM franchise ID for the lead. Find this in CRM Settings > Company & Franchises. Leave 0 for no franchise assignment.', 'a1-tools' ); ?> 4865 4909 </p> 4866 4910 </td> … … 5751 5795 'fields' => $fields, 5752 5796 'source_url' => $referrer, 5753 'source_ip' => a1tools_get_client_ip(), 5754 'form_secret' => $form_secret, 5797 'source_ip' => a1tools_get_client_ip(), 5798 'form_secret' => $form_secret, 5799 'crm_company_id' => (int) get_option( 'a1tools_form_crm_company_id', 0 ), 5800 'crm_franchise_id' => (int) get_option( 'a1tools_form_crm_franchise_id', 0 ), 5755 5801 ); 5756 5802 -
a1-tools/trunk/includes/class-a1-tools-media-management.php
r3479067 r3482030 124 124 } 125 125 126 // Post content & postmeta references (Elementor, page builders, ACF, etc.).126 // Post content references (batch by filename). 127 127 foreach ( $attachment_ids as $aid ) { 128 128 $url = wp_get_attachment_url( $aid ); … … 131 131 $name_part = pathinfo( $basename, PATHINFO_FILENAME ); 132 132 if ( strlen( $name_part ) < 3 ) continue; 133 $like = '%' . $wpdb->esc_like( $name_part ) . '%';134 133 $count = (int) $wpdb->get_var( $wpdb->prepare( 135 "SELECT COUNT(DISTINCT id) FROM ( 136 SELECT ID AS id FROM {$wpdb->posts} 137 WHERE post_content LIKE %s AND post_type NOT IN ('revision','attachment','nav_menu_item') 138 UNION 139 SELECT pm.post_id AS id FROM {$wpdb->postmeta} pm 140 INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID 141 WHERE pm.meta_value LIKE %s 142 AND p.post_type NOT IN ('revision','attachment','nav_menu_item') 143 AND pm.meta_key NOT IN ('_wp_attached_file','_wp_attachment_metadata','_wp_attachment_image_alt','_thumbnail_id') 144 ) AS combined", 145 $like, $like 134 "SELECT COUNT(DISTINCT ID) FROM {$wpdb->posts} WHERE post_content LIKE %s AND post_type NOT IN ('revision','attachment','nav_menu_item')", 135 '%' . $wpdb->esc_like( $name_part ) . '%' 146 136 ) ); 147 137 $usage[ $aid ] += $count; … … 328 318 $name_part = pathinfo( basename( $url ), PATHINFO_FILENAME ); 329 319 if ( strlen( $name_part ) < 3 ) { $unused[] = $aid; continue; } 330 $like = '%' . $wpdb->esc_like( $name_part ) . '%'; 331 // Check post content. 332 $in_content = (int) $wpdb->get_var( $wpdb->prepare( 333 "SELECT EXISTS(SELECT 1 FROM {$wpdb->posts} WHERE post_content LIKE %s AND post_type NOT IN ('revision','attachment','nav_menu_item') LIMIT 1)", 334 $like 320 $found = (int) $wpdb->get_var( $wpdb->prepare( 321 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_content LIKE %s AND post_type NOT IN ('revision','attachment','nav_menu_item') LIMIT 1", 322 '%' . $wpdb->esc_like( $name_part ) . '%' 335 323 ) ); 336 if ( $in_content ) continue; 337 // Check postmeta (Elementor backgrounds, page builders, ACF, etc.). 338 $in_meta = (int) $wpdb->get_var( $wpdb->prepare( 339 "SELECT EXISTS(SELECT 1 FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE pm.meta_value LIKE %s AND p.post_type NOT IN ('revision','attachment','nav_menu_item') AND pm.meta_key NOT IN ('_wp_attached_file','_wp_attachment_metadata','_wp_attachment_image_alt','_thumbnail_id') LIMIT 1)", 340 $like 341 ) ); 342 if ( $in_meta ) continue; 343 $unused[] = $aid; 324 if ( 0 === $found ) { 325 $unused[] = $aid; 326 } 344 327 } 345 328 set_transient( 'a1tools_unused_media_ids', $unused, 300 ); … … 368 351 $path = $base . $r->filepath; 369 352 if ( file_exists( $path ) && filesize( $path ) < 512000 ) $score++; 370 // Check usage ( post content + postmeta).353 // Check usage (simplified). 371 354 if ( in_array( (int) $r->ID, $featured, true ) ) { 372 355 $score++; … … 374 357 $name_part = pathinfo( $r->filepath, PATHINFO_FILENAME ); 375 358 if ( strlen( $name_part ) >= 3 ) { 376 $like = '%' . $wpdb->esc_like( $name_part ) . '%';377 359 $found = (int) $wpdb->get_var( $wpdb->prepare( 378 "SELECT EXISTS(SELECT 1 FROM {$wpdb->posts} WHERE post_content LIKE %s AND post_type NOT IN ('revision','attachment','nav_menu_item') LIMIT 1)",379 $like360 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_content LIKE %s AND post_type NOT IN ('revision','attachment','nav_menu_item') LIMIT 1", 361 '%' . $wpdb->esc_like( $name_part ) . '%' 380 362 ) ); 381 if ( ! $found ) {382 $found = (int) $wpdb->get_var( $wpdb->prepare(383 "SELECT EXISTS(SELECT 1 FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE pm.meta_value LIKE %s AND p.post_type NOT IN ('revision','attachment','nav_menu_item') AND pm.meta_key NOT IN ('_wp_attached_file','_wp_attachment_metadata','_wp_attachment_image_alt','_thumbnail_id') LIMIT 1)",384 $like385 ) );386 }387 363 if ( $found > 0 ) $score++; 388 364 } … … 800 776 } 801 777 802 private function safe_replace_in_postmeta( $old_str, $new_str ) {803 global $wpdb;804 $rows = $wpdb->get_results( $wpdb->prepare(805 "SELECT meta_id, meta_value FROM {$wpdb->postmeta} WHERE meta_value LIKE %s",806 '%' . $wpdb->esc_like( $old_str ) . '%'807 ) );808 $updated = 0;809 foreach ( $rows as $row ) {810 $new_value = $this->recursive_unserialize_replace( $old_str, $new_str, $row->meta_value );811 if ( $new_value !== $row->meta_value ) {812 $wpdb->update( $wpdb->postmeta, array( 'meta_value' => $new_value ), array( 'meta_id' => $row->meta_id ) );813 $updated++;814 }815 }816 return $updated;817 }818 819 private function recursive_unserialize_replace( $search, $replace, $data ) {820 if ( is_serialized( $data ) ) {821 $unserialized = @unserialize( $data );822 if ( false !== $unserialized || 'b:0;' === $data ) {823 $unserialized = $this->recursive_replace_value( $search, $replace, $unserialized );824 return serialize( $unserialized );825 }826 }827 return str_replace( $search, $replace, $data );828 }829 830 private function recursive_replace_value( $search, $replace, $data ) {831 if ( is_string( $data ) ) {832 return str_replace( $search, $replace, $data );833 }834 if ( is_array( $data ) ) {835 foreach ( $data as $key => $value ) {836 $data[ $key ] = $this->recursive_replace_value( $search, $replace, $value );837 }838 } elseif ( is_object( $data ) ) {839 foreach ( get_object_vars( $data ) as $key => $value ) {840 $data->$key = $this->recursive_replace_value( $search, $replace, $value );841 }842 }843 return $data;844 }845 846 778 private function do_rename( $attachment_id, $new_name, $update_refs = false ) { 847 779 $post = get_post( $attachment_id ); … … 956 888 ) ); 957 889 } 958 // Serialization-safe replacement for postmeta (handles PHP serialized data). 959 $this->safe_replace_in_postmeta( $old_url, $new_url ); 890 $wpdb->query( $wpdb->prepare( 891 "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_value LIKE %s", 892 $old_url, $new_url, '%' . $wpdb->esc_like( $old_url ) . '%' 893 ) ); 960 894 foreach ( $old_sizes as $sk => $sd ) { 961 895 if ( ! isset( $renamed_sizes[ $sk ] ) ) continue; 962 896 $osu = trailingslashit( $upload_dir['baseurl'] ) . trailingslashit( $sub_dir ) . $sd['file']; 963 897 $nsu = trailingslashit( $upload_dir['baseurl'] ) . trailingslashit( $sub_dir ) . $renamed_sizes[ $sk ]; 964 $this->safe_replace_in_postmeta( $osu, $nsu ); 898 $wpdb->query( $wpdb->prepare( 899 "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_value LIKE %s", 900 $osu, $nsu, '%' . $wpdb->esc_like( $osu ) . '%' 901 ) ); 965 902 } 966 903 } … … 1198 1135 function esc(s) { var d=document.createElement('div'); d.textContent=s; return d.innerHTML; } 1199 1136 function escA(s) { return s.replace(/&/g,'&').replace(/"/g,'"').replace(/'/g,''').replace(/</g,'<').replace(/>/g,'>'); } 1200 1201 function updateSeoScore($it) {1202 var score = 0;1203 if (!$it.find('.a1mm-badge-random').length) score++;1204 if ($it.find('.a1mm-alt-input').val().trim()) score++;1205 if (!$it.find('.a1mm-badge-large').length) score++;1206 if (!$it.find('.a1mm-badge-unused').length) score++;1207 var cls = score >= 4 ? 'a1mm-badge-seo-good' : (score >= 2 ? 'a1mm-badge-seo-warn' : 'a1mm-badge-seo-bad');1208 $it.find('.a1mm-badges .a1mm-badge').first().attr('class','a1mm-badge '+cls).text('SEO '+score+'/4');1209 }1210 1137 1211 1138 // ---- Load Media ---- … … 1382 1309 $m.text(txt).attr('class','a1mm-item-msg success'); 1383 1310 if ($it.find('.a1mm-alt-input').val()) $it.find('.a1mm-badge-no-alt').remove(); 1384 updateSeoScore($it);1385 1311 } else { 1386 1312 $m.text(r.data.message||'Meta save failed.').attr('class','a1mm-item-msg error'); -
a1-tools/trunk/readme.txt
r3479067 r3482030 4 4 Requires at least: 5.0 5 5 Tested up to: 6.9 6 Stable tag: 2.0. 46 Stable tag: 2.0.5 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 152 152 == Changelog == 153 153 154 = 2.0.4 = 155 * Fixed: Media usage detection now searches postmeta (Elementor backgrounds, ACF fields, page builder data) — images used in div backgrounds no longer show as "Unused" 156 * Fixed: Rename with "Update refs" now handles PHP serialized postmeta safely, preventing data corruption 157 * Improved: SEO score badge updates live after saving without requiring a page reload 154 = 2.0.5 = 155 * Maintenance: Version bump to sync latest plugin updates 156 157 = 2.0.2 = 158 * New: CRM Lead Integration — form submissions now optionally create CRM leads 159 * New: CRM Company ID and CRM Franchise ID settings for targeting leads to specific CRM locations 158 160 159 161 = 2.0.1 =
Note: See TracChangeset
for help on using the changeset viewer.