Plugin Directory

Changeset 3482030


Ignore:
Timestamp:
03/13/2026 01:32:56 PM (3 weeks ago)
Author:
a1tools
Message:

Version 2.0.5 - Maintenance release to sync latest plugin updates

Location:
a1-tools/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • a1-tools/trunk/a1-tools.php

    r3479067 r3482030  
    44 * Plugin URI:        https://tools.a-1chimney.com
    55 * 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.4
     6 * Version:           2.0.5
    77 * Requires at least: 5.0
    88 * Requires PHP:      7.4
     
    2121
    2222// Plugin constants.
    23 define( 'A1TOOLS_VERSION', '2.0.4' );
     23define( 'A1TOOLS_VERSION', '2.0.5' );
    2424define( 'A1TOOLS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    2525define( 'A1TOOLS_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
     
    46154615    ) );
    46164616    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(
    46174627        'type'              => 'integer',
    46184628        'default'           => 0,
     
    48634873                        <p class="description">
    48644874                            <?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' ); ?>
    48654909                        </p>
    48664910                    </td>
     
    57515795        'fields'      => $fields,
    57525796        '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 ),
    57555801    );
    57565802
  • a1-tools/trunk/includes/class-a1-tools-media-management.php

    r3479067 r3482030  
    124124        }
    125125
    126         // Post content & postmeta references (Elementor, page builders, ACF, etc.).
     126        // Post content references (batch by filename).
    127127        foreach ( $attachment_ids as $aid ) {
    128128            $url = wp_get_attachment_url( $aid );
     
    131131            $name_part = pathinfo( $basename, PATHINFO_FILENAME );
    132132            if ( strlen( $name_part ) < 3 ) continue;
    133             $like = '%' . $wpdb->esc_like( $name_part ) . '%';
    134133            $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 ) . '%'
    146136            ) );
    147137            $usage[ $aid ] += $count;
     
    328318                $name_part = pathinfo( basename( $url ), PATHINFO_FILENAME );
    329319                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 ) . '%'
    335323                ) );
    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                }
    344327            }
    345328            set_transient( 'a1tools_unused_media_ids', $unused, 300 );
     
    368351                $path = $base . $r->filepath;
    369352                if ( file_exists( $path ) && filesize( $path ) < 512000 ) $score++;
    370                 // Check usage (post content + postmeta).
     353                // Check usage (simplified).
    371354                if ( in_array( (int) $r->ID, $featured, true ) ) {
    372355                    $score++;
     
    374357                    $name_part = pathinfo( $r->filepath, PATHINFO_FILENAME );
    375358                    if ( strlen( $name_part ) >= 3 ) {
    376                         $like = '%' . $wpdb->esc_like( $name_part ) . '%';
    377359                        $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                             $like
     360                            "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 ) . '%'
    380362                        ) );
    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                                 $like
    385                             ) );
    386                         }
    387363                        if ( $found > 0 ) $score++;
    388364                    }
     
    800776    }
    801777
    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 
    846778    private function do_rename( $attachment_id, $new_name, $update_refs = false ) {
    847779        $post = get_post( $attachment_id );
     
    956888                ) );
    957889            }
    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            ) );
    960894            foreach ( $old_sizes as $sk => $sd ) {
    961895                if ( ! isset( $renamed_sizes[ $sk ] ) ) continue;
    962896                $osu = trailingslashit( $upload_dir['baseurl'] ) . trailingslashit( $sub_dir ) . $sd['file'];
    963897                $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                ) );
    965902            }
    966903        }
     
    11981135function esc(s) { var d=document.createElement('div'); d.textContent=s; return d.innerHTML; }
    11991136function escA(s) { return s.replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/'/g,'&#39;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
    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 }
    12101137
    12111138// ---- Load Media ----
     
    13821309                $m.text(txt).attr('class','a1mm-item-msg success');
    13831310                if ($it.find('.a1mm-alt-input').val()) $it.find('.a1mm-badge-no-alt').remove();
    1384                 updateSeoScore($it);
    13851311            } else {
    13861312                $m.text(r.data.message||'Meta save failed.').attr('class','a1mm-item-msg error');
  • a1-tools/trunk/readme.txt

    r3479067 r3482030  
    44Requires at least: 5.0
    55Tested up to: 6.9
    6 Stable tag: 2.0.4
     6Stable tag: 2.0.5
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    152152== Changelog ==
    153153
    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
    158160
    159161= 2.0.1 =
Note: See TracChangeset for help on using the changeset viewer.