Plugin Directory

Changeset 3485046


Ignore:
Timestamp:
03/17/2026 07:00:13 PM (11 days ago)
Author:
alttextai
Message:

Update to version 1.10.31 from GitHub

Location:
alttext-ai
Files:
18 edited
1 copied

Legend:

Unmodified
Added
Removed
  • alttext-ai/tags/1.10.31/README.txt

    r3476576 r3485046  
    66Requires at least: 4.7
    77Tested up to: 6.9
    8 Stable tag: 1.10.30
     8Stable tag: 1.10.31
    99WC requires at least: 3.3
    1010WC tested up to: 10.1
  • alttext-ai/tags/1.10.31/admin/class-atai-settings.php

    r3476576 r3485046  
    373373    register_setting(
    374374            'atai-settings',
     375      'atai_wpml_enabled_languages',
     376      array(
     377        'sanitize_callback' => array( $this, 'sanitize_wpml_enabled_languages' ),
     378      )
     379    );
     380
     381    register_setting(
     382            'atai-settings',
    375383      'atai_update_title',
    376384      array(
     
    593601  public function sanitize_yes_no_checkbox( $input ) {
    594602    return $input === 'yes' ? 'yes' : 'no';
     603  }
     604
     605  /**
     606   * Sanitize the WPML enabled languages array.
     607   *
     608   * @since 1.10.31
     609   * @access public
     610   *
     611   * @param mixed $input The submitted languages array.
     612   * @return array Sanitized array of language codes.
     613   */
     614  public function sanitize_wpml_enabled_languages( $input ) {
     615    if ( null === $input || '' === $input ) {
     616      return array();
     617    }
     618    if ( ! is_array( $input ) ) {
     619      return array();
     620    }
     621    $active_languages = apply_filters( 'wpml_active_languages', NULL );
     622    $valid_codes = is_array( $active_languages ) ? array_keys( $active_languages ) : array();
     623    $sanitized = array_map( 'sanitize_text_field', $input );
     624    $sanitized = array_map( 'trim', $sanitized );
     625    $sanitized = array_filter( $sanitized, 'strlen' );
     626    $sanitized = array_values( array_unique( $sanitized ) );
     627    if ( ! empty( $valid_codes ) ) {
     628      $sanitized = array_values( array_intersect( $sanitized, $valid_codes ) );
     629    }
     630    return $sanitized;
     631  }
     632
     633  /**
     634   * Determine whether the current request is saving the plugin settings form.
     635   *
     636   * @since 1.10.31
     637   * @access private
     638   *
     639   * @return bool
     640   */
     641  private function is_atai_settings_submission() {
     642    return isset( $_POST['option_page'], $_POST['action'] ) &&
     643      $_POST['option_page'] === 'atai-settings' &&
     644      $_POST['action'] === 'update';
     645  }
     646
     647  /**
     648   * Preserve WPML enabled languages when the checklist is not present in the form.
     649   *
     650   * When the WPML section is hidden (e.g. Polylang-only site), the checkbox array
     651   * won't be in $_POST and WordPress would overwrite the value. This hook preserves
     652   * the old value unless the sentinel field confirms the checklist was rendered.
     653   *
     654   * @since 1.10.31
     655   * @access public
     656   *
     657   * @param mixed $value     The new value.
     658   * @param mixed $old_value The old value.
     659   * @param string $option   The option name.
     660   * @return mixed
     661   */
     662  public function preserve_wpml_enabled_languages( $value, $old_value, $option ) {
     663    if ( ! $this->is_atai_settings_submission() ) {
     664      return $value;
     665    }
     666
     667    if ( empty( $_POST['atai_wpml_enabled_languages_present'] ) ) {
     668      return $old_value;
     669    }
     670    if ( ! isset( $_POST['atai_wpml_enabled_languages'] ) ) {
     671      return array();
     672    }
     673    return $value;
     674  }
     675
     676  /**
     677   * Determine whether this request is running on a subsite with network-wide settings locked.
     678   *
     679   * @since 1.10.31
     680   * @access private
     681   *
     682   * @return bool
     683   */
     684  private function is_network_settings_locked_subsite() {
     685    return is_multisite() && ! is_main_site() && get_site_option( 'atai_network_all_settings' ) === 'yes';
     686  }
     687
     688  /**
     689   * Determine whether the API key is locked on this subsite.
     690   *
     691   * @since 1.10.31
     692   * @access private
     693   *
     694   * @return bool
     695   */
     696  private function is_network_api_key_locked_subsite() {
     697    return is_multisite() && ! is_main_site() && (
     698      get_site_option( 'atai_network_all_settings' ) === 'yes' ||
     699      get_site_option( 'atai_network_api_key' ) === 'yes'
     700    );
     701  }
     702
     703  /**
     704   * Return the settings that should follow network-wide sharing.
     705   *
     706   * Site-local options are intentionally excluded so subsites can keep their own
     707   * runtime state where needed.
     708   *
     709   * @since 1.10.31
     710   * @access private
     711   *
     712   * @return array
     713   */
     714  private function get_network_controlled_option_defaults() {
     715    return array(
     716      'atai_api_key'                => '',
     717      'atai_lang'                   => 'en',
     718      'atai_model_name'             => '',
     719      'atai_force_lang'             => 'no',
     720      'atai_update_title'           => 'no',
     721      'atai_update_caption'         => 'no',
     722      'atai_update_description'     => 'no',
     723      'atai_enabled'                => 'yes',
     724      'atai_skip_filenotfound'      => 'no',
     725      'atai_keywords'               => 'yes',
     726      'atai_keywords_title'         => 'no',
     727      'atai_ecomm'                  => 'yes',
     728      'atai_ecomm_title'            => 'no',
     729      'atai_alt_prefix'             => '',
     730      'atai_alt_suffix'             => '',
     731      'atai_gpt_prompt'             => '',
     732      'atai_type_extensions'        => '',
     733      'atai_excluded_post_types'    => '',
     734      'atai_bulk_refresh_overwrite' => 'no',
     735      'atai_bulk_refresh_external'  => 'no',
     736      'atai_refresh_src_attr'       => 'src',
     737      'atai_wp_generate_metadata'   => 'no',
     738      'atai_timeout'                => '20',
     739      'atai_public'                 => 'no',
     740      'atai_no_credit_warning'      => 'no',
     741      'atai_admin_capability'       => 'manage_options',
     742    );
     743  }
     744
     745  /**
     746   * Prevent subsites from overwriting network-controlled settings.
     747   *
     748   * @since 1.10.31
     749   * @access public
     750   *
     751   * @param mixed  $value     The new value.
     752   * @param string $option    The option name.
     753   * @param mixed  $old_value The current value.
     754   * @return mixed
     755   */
     756  public function preserve_network_controlled_setting( $value, $option, $old_value ) {
     757    if ( strpos( $option, 'atai_' ) !== 0 ) {
     758      return $value;
     759    }
     760
     761    if ( ! $this->is_network_settings_locked_subsite() ) {
     762      return $value;
     763    }
     764
     765    if ( ! array_key_exists( $option, $this->get_network_controlled_option_defaults() ) ) {
     766      return $value;
     767    }
     768
     769    return $old_value;
    595770  }
    596771
     
    700875   */
    701876  public function save_api_key( $api_key, $old_api_key ) {
     877    if ( $this->is_network_api_key_locked_subsite() ) {
     878      return $old_api_key;
     879    }
     880
    702881    $delete = is_null( $api_key );
    703882
     
    770949    }
    771950
    772     // Settings with their defaults - prevents false from being stored for unset options
    773     $settings_with_defaults = array(
    774       'atai_api_key'              => '',
    775       'atai_lang'                 => 'en',
    776       'atai_model_name'           => '',
    777       'atai_force_lang'           => 'no',
    778       'atai_update_title'         => 'no',
    779       'atai_update_caption'       => 'no',
    780       'atai_update_description'   => 'no',
    781       'atai_enabled'              => 'yes',
    782       'atai_skip_filenotfound'    => 'no',
    783       'atai_keywords'             => 'yes',
    784       'atai_keywords_title'       => 'no',
    785       'atai_ecomm'                => 'yes',
    786       'atai_ecomm_title'          => 'no',
    787       'atai_alt_prefix'           => '',
    788       'atai_alt_suffix'           => '',
    789       'atai_gpt_prompt'           => '',
    790       'atai_type_extensions'      => '',
    791       'atai_excluded_post_types'  => '',
    792       'atai_bulk_refresh_overwrite' => 'no',
    793       'atai_bulk_refresh_external'  => 'no',
    794       'atai_refresh_src_attr'     => 'src',
    795       'atai_wp_generate_metadata' => 'no',
    796       'atai_timeout'              => '20',
    797       'atai_public'               => 'no',
    798       'atai_no_credit_warning'    => 'no',
    799       'atai_admin_capability'     => 'manage_options',
    800     );
     951    // Settings with their defaults - prevents false from being stored for unset options.
     952    // Site-local options are intentionally excluded here even when all settings are shared.
     953    $settings_with_defaults = $this->get_network_controlled_option_defaults();
    801954
    802955    // Create a network_settings array with values from the main site (with defaults)
  • alttext-ai/tags/1.10.31/admin/partials/settings.php

    r3476576 r3485046  
    3737  // API key is locked when either network API key OR all settings are shared
    3838  $api_key_locked = $is_multisite && ! $is_main_site && ( $network_controls_api_key || $network_controls_all_settings );
     39
     40  $show_force_lang_toggle = ATAI_Utility::has_multiple_languages();
     41  $show_wpml_language_selector = $show_force_lang_toggle && ATAI_Utility::has_wpml();
     42  $wpml_active_languages = $show_wpml_language_selector ? apply_filters( 'wpml_active_languages', null ) : array();
     43  $show_wpml_language_selector = $show_wpml_language_selector && is_array( $wpml_active_languages ) && count( $wpml_active_languages ) > 1;
     44  $enabled_languages = $show_wpml_language_selector ? ATAI_Utility::get_setting( 'atai_wpml_enabled_languages', null ) : null;
     45  $show_local_wpml_save = $settings_network_controlled && $show_wpml_language_selector;
    3946?>
    4047
     
    142149    <?php do_settings_sections( 'atai-settings' ); ?>
    143150
    144     <?php if ( ! $settings_network_controlled ) : ?>
    145       <input type="submit" name="submit" value="Save Changes" class="atai-button blue mt-4 cursor-pointer appearance-none no-underline shadow-sm">
     151    <?php if ( ! $settings_network_controlled || $show_local_wpml_save ) : ?>
     152      <input type="submit" name="submit" value="<?php echo esc_attr( $show_local_wpml_save ? __( 'Save Language Selection', 'alttext-ai' ) : __( 'Save Changes', 'alttext-ai' ) ); ?>" class="atai-button blue mt-4 cursor-pointer appearance-none no-underline shadow-sm <?php echo $show_local_wpml_save ? 'atai-local-setting-submit' : ''; ?>">
    146153    <?php endif; ?>
    147154    <div class="mt-4 space-y-4 border-b-0 border-t border-solid divide-x-0 divide-y divide-solid sm:space-y-6 border-x-0 border-gray-900/10 divide-gray-900/10">
     
    262269                ?>
    263270              </select>
    264               <?php if ( ATAI_Utility::has_polylang() || ATAI_Utility::has_wpml() ) : ?>
     271              <?php if ( $show_force_lang_toggle ) : ?>
    265272                <div class="ml-2 mt-4 flex relative gap-x-3">
    266273                  <div class="flex items-center h-6">
     
    277284                    <label for="atai_force_lang" class="text-gray-600"><?php esc_html_e( 'Always use this language, even if translations exist.', 'alttext-ai' ); ?></label>
    278285                  </div>
     286                </div>
     287              <?php elseif ( ATAI_Utility::has_polylang() || ATAI_Utility::has_wpml() ) : ?>
     288                <input type="hidden" name="atai_force_lang" value="<?php echo esc_attr( ATAI_Utility::get_setting( 'atai_force_lang', 'no' ) ); ?>">
     289              <?php endif; ?>
     290
     291              <?php if ( $show_wpml_language_selector ) : ?>
     292                <div class="ml-2 mt-4">
     293                  <input type="hidden" name="atai_wpml_enabled_languages_present" value="1">
     294                  <p class="text-sm font-medium text-gray-700 mb-2"><?php esc_html_e( 'Generate alt text for these WPML languages:', 'alttext-ai' ); ?></p>
     295                  <?php if ( $show_local_wpml_save ) : ?>
     296                    <p class="mb-2 text-xs text-gray-500"><?php esc_html_e( 'This language filter is saved per site even when other settings are managed network-wide.', 'alttext-ai' ); ?></p>
     297                  <?php endif; ?>
     298                  <?php foreach ( $wpml_active_languages as $code => $lang_info ) :
     299                    $is_checked = ( null === $enabled_languages ) || in_array( $code, (array) $enabled_languages, true );
     300                  ?>
     301                  <div class="flex items-center gap-x-2 mb-1">
     302                    <input
     303                      type="checkbox"
     304                      id="atai_wpml_lang_<?php echo esc_attr( $code ); ?>"
     305                      name="atai_wpml_enabled_languages[]"
     306                      value="<?php echo esc_attr( $code ); ?>"
     307                      class="w-4 h-4 rounded border-gray-300 text-primary-600 focus:ring-primary-600"
     308                      <?php checked( $is_checked ); ?>
     309                    >
     310                    <label for="atai_wpml_lang_<?php echo esc_attr( $code ); ?>" class="text-sm text-gray-600">
     311                      <?php
     312                        echo esc_html( $lang_info['native_name'] );
     313                        if ( ! empty( $lang_info['translated_name'] ) && $lang_info['translated_name'] !== $lang_info['native_name'] ) {
     314                          echo ' (' . esc_html( $lang_info['translated_name'] ) . ')';
     315                        }
     316                      ?>
     317                    </label>
     318                  </div>
     319                  <?php endforeach; ?>
     320                  <p class="mt-1 text-xs text-gray-500"><?php esc_html_e( 'Unchecked languages will be skipped when auto-generating translations.', 'alttext-ai' ); ?></p>
     321                  <?php if ( ATAI_Utility::get_setting( 'atai_force_lang' ) === 'yes' ) : ?>
     322                    <p class="mt-1 text-xs text-amber-600"><?php esc_html_e( 'Note: "Always use this language" is enabled, so all translations will use the selected language above.', 'alttext-ai' ); ?></p>
     323                  <?php endif; ?>
    279324                </div>
    280325              <?php endif; ?>
     
    832877
    833878    <div class="atai-settings-footer">
    834       <?php if ( ! $settings_network_controlled ) : ?>
    835         <input type="submit" name="submit" value="Save Changes" class="atai-button blue cursor-pointer appearance-none no-underline shadow-sm">
     879      <?php if ( ! $settings_network_controlled || $show_local_wpml_save ) : ?>
     880        <input type="submit" name="submit" value="<?php echo esc_attr( $show_local_wpml_save ? __( 'Save Language Selection', 'alttext-ai' ) : __( 'Save Changes', 'alttext-ai' ) ); ?>" class="atai-button blue cursor-pointer appearance-none no-underline shadow-sm <?php echo $show_local_wpml_save ? 'atai-local-setting-submit' : ''; ?>">
    836881      <?php endif; ?>
    837882      <span class="atai-version">v<?php echo esc_html( ATAI_VERSION ); ?></span>
     
    848893      const inputs = form.querySelectorAll('input:not([type="hidden"]), select, textarea');
    849894      inputs.forEach(function(input) {
     895        if (input.matches('input[name="atai_wpml_enabled_languages[]"], .atai-local-setting-submit')) {
     896          return;
     897        }
    850898        input.disabled = true;
    851899      });
  • alttext-ai/tags/1.10.31/atai.php

    r3476576 r3485046  
    1616 * Plugin URI:        https://alttext.ai/product
    1717 * Description:       Automatically generate image alt text with AltText.ai.
    18  * Version:           1.10.30
     18 * Version:           1.10.31
    1919 * Author:            AltText.ai
    2020 * Author URI:        https://alttext.ai
     
    3434 * Current plugin version.
    3535 */
    36 define( 'ATAI_VERSION', '1.10.30' );
     36define( 'ATAI_VERSION', '1.10.31' );
    3737
    3838/**
  • alttext-ai/tags/1.10.31/changelog.txt

    r3476576 r3485046  
    11*** AltText.ai Changelog ***
     2
     32026-03-16 - version 1.10.31
     4* NEW: WPML language filter — choose which languages auto-generate alt text
     5* Improved: Network-controlled settings guard prevents subsites from overwriting shared options
    26
    372026-03-06 - version 1.10.30
  • alttext-ai/tags/1.10.31/includes/class-atai-attachment.php

    r3466692 r3485046  
    11131113    }
    11141114
    1115     // Generate alt text for primary attachment
    1116     $this->generate_alt( $attachment_id );
    1117 
    1118     // Process WPML translations if applicable
     1115    // Generate alt text for primary attachment (skip if its language is excluded)
     1116    if ( $this->should_auto_process_wpml_attachment( $attachment_id ) ) {
     1117      $this->generate_alt( $attachment_id );
     1118    }
     1119
     1120    // Process WPML translations if applicable (has its own language filtering)
    11191121    $this->process_wpml_translations( $attachment_id );
    11201122
    11211123    // Process Polylang translations if applicable
    11221124    $this->process_polylang_translations( $attachment_id );
     1125  }
     1126
     1127  /**
     1128   * Get the saved WPML language selection for this request.
     1129   *
     1130   * @since 1.10.31
     1131   * @access private
     1132   *
     1133   * @return array|null
     1134   */
     1135  private function get_wpml_enabled_languages_selection() {
     1136    $enabled_languages = ATAI_Utility::get_setting( 'atai_wpml_enabled_languages', null );
     1137
     1138    return is_array( $enabled_languages ) ? $enabled_languages : null;
     1139  }
     1140
     1141  /**
     1142   * Filter active WPML languages against the saved per-site selection.
     1143   *
     1144   * Falls back to all active languages when the saved selection is stale
     1145   * (for example, WPML languages were reconfigured after the option was saved).
     1146   *
     1147   * @since 1.10.31
     1148   * @access private
     1149   *
     1150   * @param array $active_languages Active WPML languages keyed by language code.
     1151   * @param bool  $respect_enabled_languages Whether to apply the saved selection.
     1152   * @return array
     1153   */
     1154  private function filter_wpml_active_languages( $active_languages, $respect_enabled_languages = true ) {
     1155    if ( ! $respect_enabled_languages || ! is_array( $active_languages ) || count( $active_languages ) <= 1 ) {
     1156      return $active_languages;
     1157    }
     1158
     1159    $enabled_languages = $this->get_wpml_enabled_languages_selection();
     1160    if ( ! is_array( $enabled_languages ) ) {
     1161      return $active_languages;
     1162    }
     1163
     1164    if ( empty( $enabled_languages ) ) {
     1165      return array();
     1166    }
     1167
     1168    $filtered_languages = array_intersect_key( $active_languages, array_flip( $enabled_languages ) );
     1169    if ( empty( $filtered_languages ) ) {
     1170      return $active_languages;
     1171    }
     1172
     1173    return $filtered_languages;
     1174  }
     1175
     1176  /**
     1177   * Determine whether automatic processing should run for this attachment's WPML language.
     1178   *
     1179   * Manual single-image generation can still target any attachment directly; this
     1180   * guard is only for automatic and bulk flows where unchecked languages should
     1181   * skip the attachment entirely.
     1182   *
     1183   * @since 1.10.31
     1184   * @access private
     1185   *
     1186   * @param int $attachment_id Attachment ID.
     1187   * @return bool
     1188   */
     1189  private function should_auto_process_wpml_attachment( $attachment_id ) {
     1190    if ( ! ATAI_Utility::has_wpml() ) {
     1191      return true;
     1192    }
     1193
     1194    // If WPML no longer has multiple languages, ignore any stale filter
     1195    $active_languages = apply_filters( 'wpml_active_languages', null );
     1196    if ( ! is_array( $active_languages ) || count( $active_languages ) <= 1 ) {
     1197      return true;
     1198    }
     1199
     1200    $active_languages = $this->filter_wpml_active_languages( $active_languages );
     1201    if ( empty( $active_languages ) ) {
     1202      return false;
     1203    }
     1204
     1205    $language_details = apply_filters( 'wpml_post_language_details', null, $attachment_id );
     1206    if ( ! is_array( $language_details ) || empty( $language_details['language_code'] ) ) {
     1207      return true;
     1208    }
     1209
     1210    return array_key_exists( $language_details['language_code'], $active_languages );
    11231211  }
    11241212
     
    11351223      'processed_ids' => array(),
    11361224    );
     1225    $respect_enabled_languages = true;
     1226
     1227    if ( array_key_exists( 'respect_wpml_enabled_languages', $options ) ) {
     1228      $respect_enabled_languages = ! empty( $options['respect_wpml_enabled_languages'] );
     1229      unset( $options['respect_wpml_enabled_languages'] );
     1230    }
    11371231
    11381232    if ( ! ATAI_Utility::has_wpml() ) {
     
    11421236    $active_languages = apply_filters( 'wpml_active_languages', NULL );
    11431237    if ( empty( $active_languages ) || ! is_array( $active_languages ) ) {
     1238      return $results;
     1239    }
     1240
     1241    $active_languages = $this->filter_wpml_active_languages( $active_languages, $respect_enabled_languages );
     1242    if ( empty( $active_languages ) ) {
    11441243      return $results;
    11451244    }
     
    14451544          $reason_messages[] = sprintf(__('%d generation failures', 'alttext-ai'), $final_skip_reasons['generation_failed']);
    14461545        }
     1546        if ( isset( $final_skip_reasons['wpml_language_filtered'] ) && $final_skip_reasons['wpml_language_filtered'] > 0 ) {
     1547          $reason_messages[] = sprintf(__('%d skipped by WPML language filter', 'alttext-ai'), $final_skip_reasons['wpml_language_filtered']);
     1548        }
    14471549       
    14481550        if ( ! empty( $reason_messages ) ) {
     
    15211623        }
    15221624       
     1625        if ( ++$loop_count >= $query_limit ) {
     1626          break;
     1627        }
     1628        continue;
     1629      }
     1630
     1631      if ( ! $this->should_auto_process_wpml_attachment( $attachment_id ) ) {
     1632        $images_skipped++;
     1633        $last_post_id = $attachment_id;
     1634        $skip_reasons['wpml_language_filtered'] = ( $skip_reasons['wpml_language_filtered'] ?? 0 ) + 1;
     1635
     1636        if ( $mode === 'bulk-select' ) {
     1637          $processed_ids[] = $attachment_id;
     1638        }
     1639
    15231640        if ( ++$loop_count >= $query_limit ) {
    15241641          break;
     
    17041821    $this->generate_alt( $attachment_id );
    17051822
    1706     // Process WPML translations
    1707     $this->process_wpml_translations( $attachment_id );
     1823    // Process WPML translations without applying the automatic language filter.
     1824    $this->process_wpml_translations( $attachment_id, array(
     1825      'respect_wpml_enabled_languages' => false,
     1826    ) );
    17081827
    17091828    // Process Polylang translations
     
    17681887      // Process WPML translations for successfully generated primary image
    17691888      $wpml_results = $this->process_wpml_translations( $attachment_id, array(
    1770         'keywords' => $keywords,
     1889        'keywords'                       => $keywords,
     1890        'respect_wpml_enabled_languages' => false,
    17711891      ) );
    17721892
     
    25032623        }
    25042624
     2625        if ( ! $this->should_auto_process_wpml_attachment( $attachment_id ) ) {
     2626          $last_post_id = $attachment_id;
     2627          if ( ++$loop_count >= $query_limit ) {
     2628            break;
     2629          }
     2630          continue;
     2631        }
     2632
    25052633        $response = $this->generate_alt( $attachment_id );
    25062634
  • alttext-ai/tags/1.10.31/includes/class-atai-utility.php

    r3466692 r3485046  
    289289
    290290  /**
     291   * Determine if multiple languages are configured in WPML or Polylang.
     292   *
     293   * Only returns true when WPML/Polylang is active AND has 2+ languages set up.
     294   * Used to conditionally show the "force language" checkbox on the settings page.
     295   *
     296   * @since    1.10.31
     297   * @access public
     298   */
     299  public static function has_multiple_languages() {
     300    if ( self::has_wpml() ) {
     301      $active_languages = apply_filters( 'wpml_active_languages', NULL );
     302      return is_array( $active_languages ) && count( $active_languages ) > 1;
     303    }
     304
     305    if ( self::has_polylang() && function_exists( 'pll_languages_list' ) ) {
     306      $active_languages = pll_languages_list();
     307      return is_array( $active_languages ) && count( $active_languages ) > 1;
     308    }
     309
     310    return false;
     311  }
     312
     313  /**
    291314   * Determine if SmartCrawl is installed/active.
    292315   *
     
    392415   */
    393416  public static function get_setting( $option_name, $default = false ) {
     417    // WPML language selections are site-local even when network settings are shared,
     418    // because each site can have a different active language set.
     419    if ( $option_name === 'atai_wpml_enabled_languages' ) {
     420      return get_option( $option_name, $default );
     421    }
     422
    394423    // If not multisite, just get the regular option
    395424    if ( ! is_multisite() ) {
  • alttext-ai/tags/1.10.31/includes/class-atai.php

    r3466692 r3485046  
    214214
    215215    $this->loader->add_filter( 'pre_update_option_atai_api_key', $settings, 'save_api_key', 10, 2 );
     216    $this->loader->add_filter( 'pre_update_option_atai_wpml_enabled_languages', $settings, 'preserve_wpml_enabled_languages', 10, 3 );
     217    $this->loader->add_filter( 'pre_update_option', $settings, 'preserve_network_controlled_setting', 10, 3 );
    216218    $this->loader->add_filter( 'option_page_capability_atai-settings', $settings, 'filter_settings_capability' );
    217219
  • alttext-ai/tags/1.10.31/uninstall.php

    r3230517 r3485046  
    2626delete_option( 'atai_ecomm_title' );
    2727delete_option( 'atai_force_lang' );
     28delete_option( 'atai_wpml_enabled_languages' );
    2829delete_option( 'atai_update_title' );
    2930delete_option( 'atai_update_caption' );
  • alttext-ai/trunk/README.txt

    r3476576 r3485046  
    66Requires at least: 4.7
    77Tested up to: 6.9
    8 Stable tag: 1.10.30
     8Stable tag: 1.10.31
    99WC requires at least: 3.3
    1010WC tested up to: 10.1
  • alttext-ai/trunk/admin/class-atai-settings.php

    r3476576 r3485046  
    373373    register_setting(
    374374            'atai-settings',
     375      'atai_wpml_enabled_languages',
     376      array(
     377        'sanitize_callback' => array( $this, 'sanitize_wpml_enabled_languages' ),
     378      )
     379    );
     380
     381    register_setting(
     382            'atai-settings',
    375383      'atai_update_title',
    376384      array(
     
    593601  public function sanitize_yes_no_checkbox( $input ) {
    594602    return $input === 'yes' ? 'yes' : 'no';
     603  }
     604
     605  /**
     606   * Sanitize the WPML enabled languages array.
     607   *
     608   * @since 1.10.31
     609   * @access public
     610   *
     611   * @param mixed $input The submitted languages array.
     612   * @return array Sanitized array of language codes.
     613   */
     614  public function sanitize_wpml_enabled_languages( $input ) {
     615    if ( null === $input || '' === $input ) {
     616      return array();
     617    }
     618    if ( ! is_array( $input ) ) {
     619      return array();
     620    }
     621    $active_languages = apply_filters( 'wpml_active_languages', NULL );
     622    $valid_codes = is_array( $active_languages ) ? array_keys( $active_languages ) : array();
     623    $sanitized = array_map( 'sanitize_text_field', $input );
     624    $sanitized = array_map( 'trim', $sanitized );
     625    $sanitized = array_filter( $sanitized, 'strlen' );
     626    $sanitized = array_values( array_unique( $sanitized ) );
     627    if ( ! empty( $valid_codes ) ) {
     628      $sanitized = array_values( array_intersect( $sanitized, $valid_codes ) );
     629    }
     630    return $sanitized;
     631  }
     632
     633  /**
     634   * Determine whether the current request is saving the plugin settings form.
     635   *
     636   * @since 1.10.31
     637   * @access private
     638   *
     639   * @return bool
     640   */
     641  private function is_atai_settings_submission() {
     642    return isset( $_POST['option_page'], $_POST['action'] ) &&
     643      $_POST['option_page'] === 'atai-settings' &&
     644      $_POST['action'] === 'update';
     645  }
     646
     647  /**
     648   * Preserve WPML enabled languages when the checklist is not present in the form.
     649   *
     650   * When the WPML section is hidden (e.g. Polylang-only site), the checkbox array
     651   * won't be in $_POST and WordPress would overwrite the value. This hook preserves
     652   * the old value unless the sentinel field confirms the checklist was rendered.
     653   *
     654   * @since 1.10.31
     655   * @access public
     656   *
     657   * @param mixed $value     The new value.
     658   * @param mixed $old_value The old value.
     659   * @param string $option   The option name.
     660   * @return mixed
     661   */
     662  public function preserve_wpml_enabled_languages( $value, $old_value, $option ) {
     663    if ( ! $this->is_atai_settings_submission() ) {
     664      return $value;
     665    }
     666
     667    if ( empty( $_POST['atai_wpml_enabled_languages_present'] ) ) {
     668      return $old_value;
     669    }
     670    if ( ! isset( $_POST['atai_wpml_enabled_languages'] ) ) {
     671      return array();
     672    }
     673    return $value;
     674  }
     675
     676  /**
     677   * Determine whether this request is running on a subsite with network-wide settings locked.
     678   *
     679   * @since 1.10.31
     680   * @access private
     681   *
     682   * @return bool
     683   */
     684  private function is_network_settings_locked_subsite() {
     685    return is_multisite() && ! is_main_site() && get_site_option( 'atai_network_all_settings' ) === 'yes';
     686  }
     687
     688  /**
     689   * Determine whether the API key is locked on this subsite.
     690   *
     691   * @since 1.10.31
     692   * @access private
     693   *
     694   * @return bool
     695   */
     696  private function is_network_api_key_locked_subsite() {
     697    return is_multisite() && ! is_main_site() && (
     698      get_site_option( 'atai_network_all_settings' ) === 'yes' ||
     699      get_site_option( 'atai_network_api_key' ) === 'yes'
     700    );
     701  }
     702
     703  /**
     704   * Return the settings that should follow network-wide sharing.
     705   *
     706   * Site-local options are intentionally excluded so subsites can keep their own
     707   * runtime state where needed.
     708   *
     709   * @since 1.10.31
     710   * @access private
     711   *
     712   * @return array
     713   */
     714  private function get_network_controlled_option_defaults() {
     715    return array(
     716      'atai_api_key'                => '',
     717      'atai_lang'                   => 'en',
     718      'atai_model_name'             => '',
     719      'atai_force_lang'             => 'no',
     720      'atai_update_title'           => 'no',
     721      'atai_update_caption'         => 'no',
     722      'atai_update_description'     => 'no',
     723      'atai_enabled'                => 'yes',
     724      'atai_skip_filenotfound'      => 'no',
     725      'atai_keywords'               => 'yes',
     726      'atai_keywords_title'         => 'no',
     727      'atai_ecomm'                  => 'yes',
     728      'atai_ecomm_title'            => 'no',
     729      'atai_alt_prefix'             => '',
     730      'atai_alt_suffix'             => '',
     731      'atai_gpt_prompt'             => '',
     732      'atai_type_extensions'        => '',
     733      'atai_excluded_post_types'    => '',
     734      'atai_bulk_refresh_overwrite' => 'no',
     735      'atai_bulk_refresh_external'  => 'no',
     736      'atai_refresh_src_attr'       => 'src',
     737      'atai_wp_generate_metadata'   => 'no',
     738      'atai_timeout'                => '20',
     739      'atai_public'                 => 'no',
     740      'atai_no_credit_warning'      => 'no',
     741      'atai_admin_capability'       => 'manage_options',
     742    );
     743  }
     744
     745  /**
     746   * Prevent subsites from overwriting network-controlled settings.
     747   *
     748   * @since 1.10.31
     749   * @access public
     750   *
     751   * @param mixed  $value     The new value.
     752   * @param string $option    The option name.
     753   * @param mixed  $old_value The current value.
     754   * @return mixed
     755   */
     756  public function preserve_network_controlled_setting( $value, $option, $old_value ) {
     757    if ( strpos( $option, 'atai_' ) !== 0 ) {
     758      return $value;
     759    }
     760
     761    if ( ! $this->is_network_settings_locked_subsite() ) {
     762      return $value;
     763    }
     764
     765    if ( ! array_key_exists( $option, $this->get_network_controlled_option_defaults() ) ) {
     766      return $value;
     767    }
     768
     769    return $old_value;
    595770  }
    596771
     
    700875   */
    701876  public function save_api_key( $api_key, $old_api_key ) {
     877    if ( $this->is_network_api_key_locked_subsite() ) {
     878      return $old_api_key;
     879    }
     880
    702881    $delete = is_null( $api_key );
    703882
     
    770949    }
    771950
    772     // Settings with their defaults - prevents false from being stored for unset options
    773     $settings_with_defaults = array(
    774       'atai_api_key'              => '',
    775       'atai_lang'                 => 'en',
    776       'atai_model_name'           => '',
    777       'atai_force_lang'           => 'no',
    778       'atai_update_title'         => 'no',
    779       'atai_update_caption'       => 'no',
    780       'atai_update_description'   => 'no',
    781       'atai_enabled'              => 'yes',
    782       'atai_skip_filenotfound'    => 'no',
    783       'atai_keywords'             => 'yes',
    784       'atai_keywords_title'       => 'no',
    785       'atai_ecomm'                => 'yes',
    786       'atai_ecomm_title'          => 'no',
    787       'atai_alt_prefix'           => '',
    788       'atai_alt_suffix'           => '',
    789       'atai_gpt_prompt'           => '',
    790       'atai_type_extensions'      => '',
    791       'atai_excluded_post_types'  => '',
    792       'atai_bulk_refresh_overwrite' => 'no',
    793       'atai_bulk_refresh_external'  => 'no',
    794       'atai_refresh_src_attr'     => 'src',
    795       'atai_wp_generate_metadata' => 'no',
    796       'atai_timeout'              => '20',
    797       'atai_public'               => 'no',
    798       'atai_no_credit_warning'    => 'no',
    799       'atai_admin_capability'     => 'manage_options',
    800     );
     951    // Settings with their defaults - prevents false from being stored for unset options.
     952    // Site-local options are intentionally excluded here even when all settings are shared.
     953    $settings_with_defaults = $this->get_network_controlled_option_defaults();
    801954
    802955    // Create a network_settings array with values from the main site (with defaults)
  • alttext-ai/trunk/admin/partials/settings.php

    r3476576 r3485046  
    3737  // API key is locked when either network API key OR all settings are shared
    3838  $api_key_locked = $is_multisite && ! $is_main_site && ( $network_controls_api_key || $network_controls_all_settings );
     39
     40  $show_force_lang_toggle = ATAI_Utility::has_multiple_languages();
     41  $show_wpml_language_selector = $show_force_lang_toggle && ATAI_Utility::has_wpml();
     42  $wpml_active_languages = $show_wpml_language_selector ? apply_filters( 'wpml_active_languages', null ) : array();
     43  $show_wpml_language_selector = $show_wpml_language_selector && is_array( $wpml_active_languages ) && count( $wpml_active_languages ) > 1;
     44  $enabled_languages = $show_wpml_language_selector ? ATAI_Utility::get_setting( 'atai_wpml_enabled_languages', null ) : null;
     45  $show_local_wpml_save = $settings_network_controlled && $show_wpml_language_selector;
    3946?>
    4047
     
    142149    <?php do_settings_sections( 'atai-settings' ); ?>
    143150
    144     <?php if ( ! $settings_network_controlled ) : ?>
    145       <input type="submit" name="submit" value="Save Changes" class="atai-button blue mt-4 cursor-pointer appearance-none no-underline shadow-sm">
     151    <?php if ( ! $settings_network_controlled || $show_local_wpml_save ) : ?>
     152      <input type="submit" name="submit" value="<?php echo esc_attr( $show_local_wpml_save ? __( 'Save Language Selection', 'alttext-ai' ) : __( 'Save Changes', 'alttext-ai' ) ); ?>" class="atai-button blue mt-4 cursor-pointer appearance-none no-underline shadow-sm <?php echo $show_local_wpml_save ? 'atai-local-setting-submit' : ''; ?>">
    146153    <?php endif; ?>
    147154    <div class="mt-4 space-y-4 border-b-0 border-t border-solid divide-x-0 divide-y divide-solid sm:space-y-6 border-x-0 border-gray-900/10 divide-gray-900/10">
     
    262269                ?>
    263270              </select>
    264               <?php if ( ATAI_Utility::has_polylang() || ATAI_Utility::has_wpml() ) : ?>
     271              <?php if ( $show_force_lang_toggle ) : ?>
    265272                <div class="ml-2 mt-4 flex relative gap-x-3">
    266273                  <div class="flex items-center h-6">
     
    277284                    <label for="atai_force_lang" class="text-gray-600"><?php esc_html_e( 'Always use this language, even if translations exist.', 'alttext-ai' ); ?></label>
    278285                  </div>
     286                </div>
     287              <?php elseif ( ATAI_Utility::has_polylang() || ATAI_Utility::has_wpml() ) : ?>
     288                <input type="hidden" name="atai_force_lang" value="<?php echo esc_attr( ATAI_Utility::get_setting( 'atai_force_lang', 'no' ) ); ?>">
     289              <?php endif; ?>
     290
     291              <?php if ( $show_wpml_language_selector ) : ?>
     292                <div class="ml-2 mt-4">
     293                  <input type="hidden" name="atai_wpml_enabled_languages_present" value="1">
     294                  <p class="text-sm font-medium text-gray-700 mb-2"><?php esc_html_e( 'Generate alt text for these WPML languages:', 'alttext-ai' ); ?></p>
     295                  <?php if ( $show_local_wpml_save ) : ?>
     296                    <p class="mb-2 text-xs text-gray-500"><?php esc_html_e( 'This language filter is saved per site even when other settings are managed network-wide.', 'alttext-ai' ); ?></p>
     297                  <?php endif; ?>
     298                  <?php foreach ( $wpml_active_languages as $code => $lang_info ) :
     299                    $is_checked = ( null === $enabled_languages ) || in_array( $code, (array) $enabled_languages, true );
     300                  ?>
     301                  <div class="flex items-center gap-x-2 mb-1">
     302                    <input
     303                      type="checkbox"
     304                      id="atai_wpml_lang_<?php echo esc_attr( $code ); ?>"
     305                      name="atai_wpml_enabled_languages[]"
     306                      value="<?php echo esc_attr( $code ); ?>"
     307                      class="w-4 h-4 rounded border-gray-300 text-primary-600 focus:ring-primary-600"
     308                      <?php checked( $is_checked ); ?>
     309                    >
     310                    <label for="atai_wpml_lang_<?php echo esc_attr( $code ); ?>" class="text-sm text-gray-600">
     311                      <?php
     312                        echo esc_html( $lang_info['native_name'] );
     313                        if ( ! empty( $lang_info['translated_name'] ) && $lang_info['translated_name'] !== $lang_info['native_name'] ) {
     314                          echo ' (' . esc_html( $lang_info['translated_name'] ) . ')';
     315                        }
     316                      ?>
     317                    </label>
     318                  </div>
     319                  <?php endforeach; ?>
     320                  <p class="mt-1 text-xs text-gray-500"><?php esc_html_e( 'Unchecked languages will be skipped when auto-generating translations.', 'alttext-ai' ); ?></p>
     321                  <?php if ( ATAI_Utility::get_setting( 'atai_force_lang' ) === 'yes' ) : ?>
     322                    <p class="mt-1 text-xs text-amber-600"><?php esc_html_e( 'Note: "Always use this language" is enabled, so all translations will use the selected language above.', 'alttext-ai' ); ?></p>
     323                  <?php endif; ?>
    279324                </div>
    280325              <?php endif; ?>
     
    832877
    833878    <div class="atai-settings-footer">
    834       <?php if ( ! $settings_network_controlled ) : ?>
    835         <input type="submit" name="submit" value="Save Changes" class="atai-button blue cursor-pointer appearance-none no-underline shadow-sm">
     879      <?php if ( ! $settings_network_controlled || $show_local_wpml_save ) : ?>
     880        <input type="submit" name="submit" value="<?php echo esc_attr( $show_local_wpml_save ? __( 'Save Language Selection', 'alttext-ai' ) : __( 'Save Changes', 'alttext-ai' ) ); ?>" class="atai-button blue cursor-pointer appearance-none no-underline shadow-sm <?php echo $show_local_wpml_save ? 'atai-local-setting-submit' : ''; ?>">
    836881      <?php endif; ?>
    837882      <span class="atai-version">v<?php echo esc_html( ATAI_VERSION ); ?></span>
     
    848893      const inputs = form.querySelectorAll('input:not([type="hidden"]), select, textarea');
    849894      inputs.forEach(function(input) {
     895        if (input.matches('input[name="atai_wpml_enabled_languages[]"], .atai-local-setting-submit')) {
     896          return;
     897        }
    850898        input.disabled = true;
    851899      });
  • alttext-ai/trunk/atai.php

    r3476576 r3485046  
    1616 * Plugin URI:        https://alttext.ai/product
    1717 * Description:       Automatically generate image alt text with AltText.ai.
    18  * Version:           1.10.30
     18 * Version:           1.10.31
    1919 * Author:            AltText.ai
    2020 * Author URI:        https://alttext.ai
     
    3434 * Current plugin version.
    3535 */
    36 define( 'ATAI_VERSION', '1.10.30' );
     36define( 'ATAI_VERSION', '1.10.31' );
    3737
    3838/**
  • alttext-ai/trunk/changelog.txt

    r3476576 r3485046  
    11*** AltText.ai Changelog ***
     2
     32026-03-16 - version 1.10.31
     4* NEW: WPML language filter — choose which languages auto-generate alt text
     5* Improved: Network-controlled settings guard prevents subsites from overwriting shared options
    26
    372026-03-06 - version 1.10.30
  • alttext-ai/trunk/includes/class-atai-attachment.php

    r3466692 r3485046  
    11131113    }
    11141114
    1115     // Generate alt text for primary attachment
    1116     $this->generate_alt( $attachment_id );
    1117 
    1118     // Process WPML translations if applicable
     1115    // Generate alt text for primary attachment (skip if its language is excluded)
     1116    if ( $this->should_auto_process_wpml_attachment( $attachment_id ) ) {
     1117      $this->generate_alt( $attachment_id );
     1118    }
     1119
     1120    // Process WPML translations if applicable (has its own language filtering)
    11191121    $this->process_wpml_translations( $attachment_id );
    11201122
    11211123    // Process Polylang translations if applicable
    11221124    $this->process_polylang_translations( $attachment_id );
     1125  }
     1126
     1127  /**
     1128   * Get the saved WPML language selection for this request.
     1129   *
     1130   * @since 1.10.31
     1131   * @access private
     1132   *
     1133   * @return array|null
     1134   */
     1135  private function get_wpml_enabled_languages_selection() {
     1136    $enabled_languages = ATAI_Utility::get_setting( 'atai_wpml_enabled_languages', null );
     1137
     1138    return is_array( $enabled_languages ) ? $enabled_languages : null;
     1139  }
     1140
     1141  /**
     1142   * Filter active WPML languages against the saved per-site selection.
     1143   *
     1144   * Falls back to all active languages when the saved selection is stale
     1145   * (for example, WPML languages were reconfigured after the option was saved).
     1146   *
     1147   * @since 1.10.31
     1148   * @access private
     1149   *
     1150   * @param array $active_languages Active WPML languages keyed by language code.
     1151   * @param bool  $respect_enabled_languages Whether to apply the saved selection.
     1152   * @return array
     1153   */
     1154  private function filter_wpml_active_languages( $active_languages, $respect_enabled_languages = true ) {
     1155    if ( ! $respect_enabled_languages || ! is_array( $active_languages ) || count( $active_languages ) <= 1 ) {
     1156      return $active_languages;
     1157    }
     1158
     1159    $enabled_languages = $this->get_wpml_enabled_languages_selection();
     1160    if ( ! is_array( $enabled_languages ) ) {
     1161      return $active_languages;
     1162    }
     1163
     1164    if ( empty( $enabled_languages ) ) {
     1165      return array();
     1166    }
     1167
     1168    $filtered_languages = array_intersect_key( $active_languages, array_flip( $enabled_languages ) );
     1169    if ( empty( $filtered_languages ) ) {
     1170      return $active_languages;
     1171    }
     1172
     1173    return $filtered_languages;
     1174  }
     1175
     1176  /**
     1177   * Determine whether automatic processing should run for this attachment's WPML language.
     1178   *
     1179   * Manual single-image generation can still target any attachment directly; this
     1180   * guard is only for automatic and bulk flows where unchecked languages should
     1181   * skip the attachment entirely.
     1182   *
     1183   * @since 1.10.31
     1184   * @access private
     1185   *
     1186   * @param int $attachment_id Attachment ID.
     1187   * @return bool
     1188   */
     1189  private function should_auto_process_wpml_attachment( $attachment_id ) {
     1190    if ( ! ATAI_Utility::has_wpml() ) {
     1191      return true;
     1192    }
     1193
     1194    // If WPML no longer has multiple languages, ignore any stale filter
     1195    $active_languages = apply_filters( 'wpml_active_languages', null );
     1196    if ( ! is_array( $active_languages ) || count( $active_languages ) <= 1 ) {
     1197      return true;
     1198    }
     1199
     1200    $active_languages = $this->filter_wpml_active_languages( $active_languages );
     1201    if ( empty( $active_languages ) ) {
     1202      return false;
     1203    }
     1204
     1205    $language_details = apply_filters( 'wpml_post_language_details', null, $attachment_id );
     1206    if ( ! is_array( $language_details ) || empty( $language_details['language_code'] ) ) {
     1207      return true;
     1208    }
     1209
     1210    return array_key_exists( $language_details['language_code'], $active_languages );
    11231211  }
    11241212
     
    11351223      'processed_ids' => array(),
    11361224    );
     1225    $respect_enabled_languages = true;
     1226
     1227    if ( array_key_exists( 'respect_wpml_enabled_languages', $options ) ) {
     1228      $respect_enabled_languages = ! empty( $options['respect_wpml_enabled_languages'] );
     1229      unset( $options['respect_wpml_enabled_languages'] );
     1230    }
    11371231
    11381232    if ( ! ATAI_Utility::has_wpml() ) {
     
    11421236    $active_languages = apply_filters( 'wpml_active_languages', NULL );
    11431237    if ( empty( $active_languages ) || ! is_array( $active_languages ) ) {
     1238      return $results;
     1239    }
     1240
     1241    $active_languages = $this->filter_wpml_active_languages( $active_languages, $respect_enabled_languages );
     1242    if ( empty( $active_languages ) ) {
    11441243      return $results;
    11451244    }
     
    14451544          $reason_messages[] = sprintf(__('%d generation failures', 'alttext-ai'), $final_skip_reasons['generation_failed']);
    14461545        }
     1546        if ( isset( $final_skip_reasons['wpml_language_filtered'] ) && $final_skip_reasons['wpml_language_filtered'] > 0 ) {
     1547          $reason_messages[] = sprintf(__('%d skipped by WPML language filter', 'alttext-ai'), $final_skip_reasons['wpml_language_filtered']);
     1548        }
    14471549       
    14481550        if ( ! empty( $reason_messages ) ) {
     
    15211623        }
    15221624       
     1625        if ( ++$loop_count >= $query_limit ) {
     1626          break;
     1627        }
     1628        continue;
     1629      }
     1630
     1631      if ( ! $this->should_auto_process_wpml_attachment( $attachment_id ) ) {
     1632        $images_skipped++;
     1633        $last_post_id = $attachment_id;
     1634        $skip_reasons['wpml_language_filtered'] = ( $skip_reasons['wpml_language_filtered'] ?? 0 ) + 1;
     1635
     1636        if ( $mode === 'bulk-select' ) {
     1637          $processed_ids[] = $attachment_id;
     1638        }
     1639
    15231640        if ( ++$loop_count >= $query_limit ) {
    15241641          break;
     
    17041821    $this->generate_alt( $attachment_id );
    17051822
    1706     // Process WPML translations
    1707     $this->process_wpml_translations( $attachment_id );
     1823    // Process WPML translations without applying the automatic language filter.
     1824    $this->process_wpml_translations( $attachment_id, array(
     1825      'respect_wpml_enabled_languages' => false,
     1826    ) );
    17081827
    17091828    // Process Polylang translations
     
    17681887      // Process WPML translations for successfully generated primary image
    17691888      $wpml_results = $this->process_wpml_translations( $attachment_id, array(
    1770         'keywords' => $keywords,
     1889        'keywords'                       => $keywords,
     1890        'respect_wpml_enabled_languages' => false,
    17711891      ) );
    17721892
     
    25032623        }
    25042624
     2625        if ( ! $this->should_auto_process_wpml_attachment( $attachment_id ) ) {
     2626          $last_post_id = $attachment_id;
     2627          if ( ++$loop_count >= $query_limit ) {
     2628            break;
     2629          }
     2630          continue;
     2631        }
     2632
    25052633        $response = $this->generate_alt( $attachment_id );
    25062634
  • alttext-ai/trunk/includes/class-atai-utility.php

    r3466692 r3485046  
    289289
    290290  /**
     291   * Determine if multiple languages are configured in WPML or Polylang.
     292   *
     293   * Only returns true when WPML/Polylang is active AND has 2+ languages set up.
     294   * Used to conditionally show the "force language" checkbox on the settings page.
     295   *
     296   * @since    1.10.31
     297   * @access public
     298   */
     299  public static function has_multiple_languages() {
     300    if ( self::has_wpml() ) {
     301      $active_languages = apply_filters( 'wpml_active_languages', NULL );
     302      return is_array( $active_languages ) && count( $active_languages ) > 1;
     303    }
     304
     305    if ( self::has_polylang() && function_exists( 'pll_languages_list' ) ) {
     306      $active_languages = pll_languages_list();
     307      return is_array( $active_languages ) && count( $active_languages ) > 1;
     308    }
     309
     310    return false;
     311  }
     312
     313  /**
    291314   * Determine if SmartCrawl is installed/active.
    292315   *
     
    392415   */
    393416  public static function get_setting( $option_name, $default = false ) {
     417    // WPML language selections are site-local even when network settings are shared,
     418    // because each site can have a different active language set.
     419    if ( $option_name === 'atai_wpml_enabled_languages' ) {
     420      return get_option( $option_name, $default );
     421    }
     422
    394423    // If not multisite, just get the regular option
    395424    if ( ! is_multisite() ) {
  • alttext-ai/trunk/includes/class-atai.php

    r3466692 r3485046  
    214214
    215215    $this->loader->add_filter( 'pre_update_option_atai_api_key', $settings, 'save_api_key', 10, 2 );
     216    $this->loader->add_filter( 'pre_update_option_atai_wpml_enabled_languages', $settings, 'preserve_wpml_enabled_languages', 10, 3 );
     217    $this->loader->add_filter( 'pre_update_option', $settings, 'preserve_network_controlled_setting', 10, 3 );
    216218    $this->loader->add_filter( 'option_page_capability_atai-settings', $settings, 'filter_settings_capability' );
    217219
  • alttext-ai/trunk/uninstall.php

    r3230517 r3485046  
    2626delete_option( 'atai_ecomm_title' );
    2727delete_option( 'atai_force_lang' );
     28delete_option( 'atai_wpml_enabled_languages' );
    2829delete_option( 'atai_update_title' );
    2930delete_option( 'atai_update_caption' );
Note: See TracChangeset for help on using the changeset viewer.