', esc_attr($opt), Settings::canConfigureStores() ? '' : ' disabled'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- literal attribute foreach (StoreFactory::available() as $id => $label) { printf('', esc_attr($id), selected($saved, $id, false), esc_html($label)); } echo ''; self::moveNote($saved); $line = Settings::canConfigureStores() ? __('Where the vectors are kept. Vectorize uses your Cloudflare account; Qdrant and PostgreSQL are servers you run or rent. Save, then use Test connection.', 'aipcf-ai-provider-for-cloudflare') : __('Only a network administrator can change the vector store and its connection on this network.', 'aipcf-ai-provider-for-cloudflare'); echo FreeTierSettings::help($line); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- help() escapes }, self::PAGE, 'aipcf_embed_store', ['label_for' => 'aipcf-embed-store']); add_settings_field('vectorize_index', __('Vectorize index name', 'aipcf-ai-provider-for-cloudflare'), static function () use ($opt): void { $saved = (string) (get_plugin_settings()['vectorize_index'] ?? ''); printf( '', esc_attr($opt), esc_attr($saved), /* translators: %s: default name */ esc_attr(sprintf(__('Default: %s', 'aipcf-ai-provider-for-cloudflare'), VectorizeStore::sanitizeIndexName('aipcf-' . (string) wp_parse_url(home_url(), PHP_URL_HOST)))), self::disabled('vectorize') // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- literal attribute ); echo FreeTierSettings::help(__('Leave empty for one index per site. Lowercase letters, digits and dashes.', 'aipcf-ai-provider-for-cloudflare')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- help() escapes }, self::PAGE, 'aipcf_embed_store', self::rowArgs('vectorize', true, 'aipcf-vectorize-index')); // Qdrant self::text('qdrant_url', __('Qdrant URL', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_store', __('e.g. https://xyz.cloud.qdrant.io:6333', 'aipcf-ai-provider-for-cloudflare'), __('Qdrant Cloud or your own server, including the port. Content is sent here. Use https unless it runs on this machine.', 'aipcf-ai-provider-for-cloudflare'), 'qdrant'); self::secret('qdrant_api_key', __('Qdrant API key', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_store', 'AIPCF_QDRANT_API_KEY', 'qdrant'); self::text('qdrant_collection', __('Collection name', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_store', sprintf(/* translators: %s: default name */ __('Default: %s', 'aipcf-ai-provider-for-cloudflare'), QdrantStore::sanitizeName('aipcf-' . (string) wp_parse_url(home_url(), PHP_URL_HOST))), __('Leave empty for one collection per site.', 'aipcf-ai-provider-for-cloudflare'), 'qdrant', true); // PostgreSQL + pgvector add_settings_field('pg_requirements', __('Requirements', 'aipcf-ai-provider-for-cloudflare'), static function (): void { echo PgvectorStore::available() // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- literal HTML with escaped text ? '
' . esc_html__('This server\'s PHP can connect to PostgreSQL (pdo_pgsql is installed). The database needs the pgvector extension.', 'aipcf-ai-provider-for-cloudflare') . '
' : '' . esc_html__('This server\'s PHP cannot connect to PostgreSQL: the pdo_pgsql extension is not installed.', 'aipcf-ai-provider-for-cloudflare') . ' ' . esc_html__('Ask your host to enable it, or choose Vectorize or Qdrant.', 'aipcf-ai-provider-for-cloudflare') . '
'; }, self::PAGE, 'aipcf_embed_store', self::rowArgs('pgvector')); self::text('pg_host', __('Host', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_store', __('e.g. db.example.com', 'aipcf-ai-provider-for-cloudflare'), '', 'pgvector'); self::number('pg_port', __('Port', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_store', '', 1, 65535, 'pgvector'); self::text('pg_database', __('Database', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_store', __('e.g. wordpress_vectors', 'aipcf-ai-provider-for-cloudflare'), '', 'pgvector'); self::text('pg_user', __('User', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_store', __('e.g. wordpress', 'aipcf-ai-provider-for-cloudflare'), '', 'pgvector'); self::secret('pg_password', __('Password', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_store', 'AIPCF_PG_PASSWORD', 'pgvector'); add_settings_field('pg_sslmode', __('SSL', 'aipcf-ai-provider-for-cloudflare'), static function () use ($opt): void { $saved = (string) (get_plugin_settings()['pg_sslmode'] ?? 'prefer'); printf(''; echo FreeTierSettings::help(__('"require" or stricter for a database on another machine.', 'aipcf-ai-provider-for-cloudflare')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- help() escapes }, self::PAGE, 'aipcf_embed_store', self::rowArgs('pgvector', true, 'aipcf-pg-sslmode')); self::text('pg_table', __('Table name', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_store', sprintf(/* translators: %s: default name */ __('Default: %s', 'aipcf-ai-provider-for-cloudflare'), PgvectorStore::sanitizeName('aipcf_' . (string) wp_parse_url(home_url(), PHP_URL_HOST))), __('Leave empty for one table per site.', 'aipcf-ai-provider-for-cloudflare'), 'pgvector', true); add_settings_section('aipcf_embed_model', __('2. Embedding model', 'aipcf-ai-provider-for-cloudflare'), static function (): void { echo '' . esc_html__('The model that turns text into vectors. Changing it means every item is embedded again. If the new model\'s vectors have a different size, press Rebuild (under More actions) to start a new index; with a custom index name, enter a new name instead.', 'aipcf-ai-provider-for-cloudflare') . '
'; }, self::PAGE, SettingsPage::panel()); add_settings_field('embed_model', __('Model', 'aipcf-ai-provider-for-cloudflare'), static function () use ($opt): void { $saved = Models::current(); printf(''; echo FreeTierSettings::help(__('BGE-M3 is the default: multilingual, the cheapest, and reads up to 8,192 tokens. The English-only BGE models read at most 512.', 'aipcf-ai-provider-for-cloudflare')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- help() escapes }, self::PAGE, 'aipcf_embed_model', ['label_for' => 'aipcf-embed-model']); add_settings_section('aipcf_embed_select', __('3. What to embed', 'aipcf-ai-provider-for-cloudflare'), static function (): void { echo '' . esc_html__('Only what is selected here is sent to Workers AI and stored. Password-protected posts are never included, and any post can be left out from its editor.', 'aipcf-ai-provider-for-cloudflare') . '
'; }, self::PAGE, SettingsPage::panel()); add_settings_field('embed_post_types', __('Content types', 'aipcf-ai-provider-for-cloudflare'), static function () use ($opt): void { printf(''; }, self::PAGE, 'aipcf_embed_select'); add_settings_field('embed_statuses', __('Include', 'aipcf-ai-provider-for-cloudflare'), static function () use ($opt): void { printf(''; }, self::PAGE, 'aipcf_embed_select'); add_settings_field('embed_fields', __('Fields', 'aipcf-ai-provider-for-cloudflare'), static function () use ($opt): void { printf(''; }, self::PAGE, 'aipcf_embed_select'); add_settings_field('embed_meta_keys', __('Custom fields', 'aipcf-ai-provider-for-cloudflare'), static function () use ($opt): void { printf('', esc_attr($opt), esc_attr(implode(', ', Settings::metaKeys())), esc_attr__('e.g. price, location', 'aipcf-ai-provider-for-cloudflare')); echo FreeTierSettings::help(__('Meta keys to include when "Custom fields" is ticked, separated by commas. Text values only.', 'aipcf-ai-provider-for-cloudflare')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- help() escapes }, self::PAGE, 'aipcf_embed_select', FreeTierSettings::fieldArgs(true) + ['label_for' => 'aipcf-embed-meta-keys']); add_settings_field('embed_tax_mode', __('Categories and tags', 'aipcf-ai-provider-for-cloudflare'), static function () use ($opt): void { printf(''; }, self::PAGE, 'aipcf_embed_select', FreeTierSettings::fieldArgs(true)); add_settings_field('embed_after', __('Published after', 'aipcf-ai-provider-for-cloudflare'), static function () use ($opt): void { printf('', esc_attr($opt), esc_attr(Settings::after())); echo FreeTierSettings::help(__('Leave empty for everything. Useful to skip an old archive.', 'aipcf-ai-provider-for-cloudflare')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- help() escapes }, self::PAGE, 'aipcf_embed_select', FreeTierSettings::fieldArgs(true) + ['label_for' => 'aipcf-embed-after']); self::number('embed_min_words', __('Shortest post', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_select', __('Posts and pages with fewer words are left out of the content index. Very short ones (a link, a quick note) then cost nothing and do not show up as related posts or search matches. 0 embeds everything. Images are not counted.', 'aipcf-ai-provider-for-cloudflare'), 0, 5000, '', __('words', 'aipcf-ai-provider-for-cloudflare')); self::checkbox('embed_store_text', __('Store the text', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_select', __('Keep each passage\'s text next to its vector', 'aipcf-ai-provider-for-cloudflare'), true, __('Used for the snippets in Test search and in Search (when a site shows them) and for the quotes in the Link assistant.', 'aipcf-ai-provider-for-cloudflare')); add_settings_section('aipcf_embed_estimate', __('4. Estimate and switch on', 'aipcf-ai-provider-for-cloudflare'), [self::class, 'renderEstimate'], self::PAGE, SettingsPage::panel()); self::checkbox('embeddings_enabled', __('Embeddings', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_estimate', __('On: embed the content selected above and keep the vector store in sync', 'aipcf-ai-provider-for-cloudflare')); self::number('embed_daily_neurons', __('Daily limit', 'aipcf-ai-provider-for-cloudflare'), 'aipcf_embed_estimate', __('Pays for embedding new and changed content. 0 means no limit of its own. It also counts against the daily budget.', 'aipcf-ai-provider-for-cloudflare'), 0, 10000000, '', __('neurons a day', 'aipcf-ai-provider-for-cloudflare')); } public static function renderIntro(): void { echo '' . esc_html__('Beta.', 'aipcf-ai-provider-for-cloudflare') . ' ' . esc_html__('Turns the content you choose into embeddings with a Workers AI model and keeps them in a vector store: the content index. The Site assistant, Related posts, Search, the Link assistant, Search insights and AI agents use it.', 'aipcf-ai-provider-for-cloudflare') . '
'; if (!Settings::enabled()) { echo '' . esc_html__('Off: no content is sent, and the vector store is only contacted when you press Test connection or Create index. Go through steps 1 to 4 below, check the estimate, then switch it on at the end and save.', 'aipcf-ai-provider-for-cloudflare') . '
'; return; } printf( '%s %s
', esc_html(sprintf( /* translators: %s: estimated neurons */ __('Embedding neurons used today: about %s.', 'aipcf-ai-provider-for-cloudflare'), number_format_i18n(UsageTracker::todayEmbedNeurons(), 1) )), esc_html__('Workers AI reports no usage for embeddings, so this is estimated from text length.', 'aipcf-ai-provider-for-cloudflare') ); } public static function renderStorePanel(): void { echo '' . esc_html($store->get_error_message()) . '
'; return; } // While off, the store is only contacted on request (Test connection / Create index). $r = EmbeddingsStatus::storeStatus($store, Settings::enabled()); $dims = (int) (Models::get(Models::current())['dims'] ?? 0); // Every branch is one full sentence with the index name as a placeholder. The // sentence is escaped first; the name is escaped and wrapped in separately.
$name = '' . esc_html($store->indexName()) . '';
echo '';
if (isset($r['unchecked'])) {
/* translators: %s: index name */
printf(esc_html__('Index %s is not checked yet. Press Test connection.', 'aipcf-ai-provider-for-cloudflare'), $name); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above
} elseif (isset($r['error'])) {
/* translators: 1: index name, 2: error detail */
printf(esc_html__('Index %1$s cannot be read: %2$s', 'aipcf-ai-provider-for-cloudflare'), $name, esc_html((string) $r['error'])); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above
} elseif (empty($r['exists'])) {
/* translators: %s: index name */
printf(esc_html__('Index %s is not created yet.', 'aipcf-ai-provider-for-cloudflare'), $name); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above
} elseif ((int) $r['dims'] !== $dims) {
// Rebuild starts a new index only for the site's own index; a custom name is re-embedded in place.
printf(
$store->isSiteOwn()
/* translators: 1: index name, 2: numbers in each vector the index holds, 3: numbers in each vector the model makes */
? esc_html__('Index %1$s holds vectors of %2$s numbers, but the selected model makes vectors of %3$s. Press Rebuild (under More actions) to start a new index, or go back to a model with the same vector size.', 'aipcf-ai-provider-for-cloudflare')
/* translators: 1: index name, 2: numbers in each vector the index holds, 3: numbers in each vector the model makes */
: esc_html__('Index %1$s holds vectors of %2$s numbers, but the selected model makes vectors of %3$s. Enter a new index name, or go back to a model with the same vector size.', 'aipcf-ai-provider-for-cloudflare'),
$name, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above
esc_html(number_format_i18n((int) $r['dims'])),
esc_html(number_format_i18n($dims))
);
} elseif (empty($r['ready'])) {
/* translators: %s: index name */
printf(esc_html__('Index %s is being prepared by Cloudflare. This can take a few minutes.', 'aipcf-ai-provider-for-cloudflare'), $name); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above
} else {
printf(
/* translators: 1: index name, 2: vector count, 3: numbers in each vector (e.g. 1,024) */
esc_html(_n('Index %1$s is ready: %2$s vector of %3$s numbers.', 'Index %1$s is ready: %2$s vectors of %3$s numbers.', (int) ($r['vector_count'] ?? 0), 'aipcf-ai-provider-for-cloudflare')),
$name, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above
esc_html(number_format_i18n((int) ($r['vector_count'] ?? 0))),
esc_html(number_format_i18n((int) $r['dims']))
);
if (empty($r['settled'])) {
echo ' ' . esc_html__('Recent changes are still being applied (about a minute).', 'aipcf-ai-provider-for-cloudflare');
}
}
echo '
';
printf(
'',
esc_url(wp_nonce_url(admin_url('admin-post.php?action=aipcf_embed_test'), 'aipcf_embed_test')),
esc_attr__('Testing...', 'aipcf-ai-provider-for-cloudflare'),
esc_html__('Test connection', 'aipcf-ai-provider-for-cloudflare'),
esc_url(wp_nonce_url(admin_url('admin-post.php?action=aipcf_embed_prepare'), 'aipcf_embed_prepare')),
esc_attr__('Checking...', 'aipcf-ai-provider-for-cloudflare'),
empty($r['exists']) ? esc_html__('Create index', 'aipcf-ai-provider-for-cloudflare') : esc_html__('Create or check index', 'aipcf-ai-provider-for-cloudflare')
);
if ($store->id() === 'vectorize') {
$help = FreeTierSettings::help(
__('Vectorize uses your Cloudflare token, which needs the Account > Vectorize > Edit permission in addition to Workers AI.', 'aipcf-ai-provider-for-cloudflare'),
__('Adding the permission, and the Vectorize free plan', 'aipcf-ai-provider-for-cloudflare'),
esc_html__('On dash.cloudflare.com, open My Profile > API Tokens, click "..." > Edit on the token this site uses, add the permission Account > Vectorize > Edit, and save. The token value does not change, so nothing needs updating here. Press Test connection. (If Test connection says "Authentication error", the permission went onto a different token.)', 'aipcf-ai-provider-for-cloudflare')
. '
' .
esc_html__('The Vectorize free plan (part of Workers Free) stores 5 million dimensions: vectors times the numbers in each, about 4,900 passages at 1,024 numbers. It answers searches over 30 million dimensions a month. Workers Paid includes 10 million stored dimensions, then $0.05 per 100 million.', 'aipcf-ai-provider-for-cloudflare')
);
} elseif ($store->id() === 'qdrant') {
$help = FreeTierSettings::help(__('Qdrant applies writes at once. Qdrant Cloud has a free 1 GB cluster (about 200,000 passages with vectors of 1,024 numbers).', 'aipcf-ai-provider-for-cloudflare'));
} else {
$help = FreeTierSettings::help(__('PostgreSQL applies writes at once. The plugin creates one table with a search index.', 'aipcf-ai-provider-for-cloudflare'));
}
echo $help; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- help() escapes
foreach (Settings::transportWarnings() as $warning) {
echo '' . esc_html($warning) . '
';
}
}
public static function renderEstimate(): void
{
echo '';
$e = Estimate::get();
if ($e['objects'] === 0) {
echo '' . esc_html__('Nothing matches the selection yet.', 'aipcf-ai-provider-for-cloudflare') . '
';
} else {
echo '';
/* translators: %s: number of items */
echo self::count(_n('%s item', '%s items', $e['objects'], 'aipcf-ai-provider-for-cloudflare'), number_format_i18n($e['objects'])); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- count() escapes
/* translators: %s: estimated number of words */
echo self::count(_n('%s word', '%s words', (int) ($e['words'] ?? 0), 'aipcf-ai-provider-for-cloudflare'), '~' . number_format_i18n((int) ($e['words'] ?? 0))); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- count() escapes
if (!empty($e['short'])) {
/* translators: %s: estimated number of posts under the minimum length */
echo self::count(_n('%s too short, left out', '%s too short, left out', (int) $e['short'], 'aipcf-ai-provider-for-cloudflare'), '~' . number_format_i18n((int) $e['short'])); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- count() escapes
}
/* translators: %s: estimated number of passages */
echo self::count(_n('%s passage (parts of posts)', '%s passages (parts of posts)', $e['chunks'], 'aipcf-ai-provider-for-cloudflare'), '~' . number_format_i18n($e['chunks'])); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- count() escapes
/* translators: %s: estimated number of tokens */
echo self::count(_n('%s token', '%s tokens', $e['tokens'], 'aipcf-ai-provider-for-cloudflare'), '~' . number_format_i18n($e['tokens'])); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- count() escapes
/* translators: %s: estimated neurons */
echo self::count(_n('%s neuron to embed everything once', '%s neurons to embed everything once', (int) round($e['neurons']), 'aipcf-ai-provider-for-cloudflare'), '~' . number_format_i18n($e['neurons'], 0)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- count() escapes
echo '
';
$cap = Settings::dailyNeurons();
echo '';
if ($cap > 0) {
echo esc_html(sprintf(
/* translators: 1: days, 2: daily limit in neurons */
_n('About %1$d day at %2$s neurons a day.', 'About %1$d days at %2$s neurons a day.', $e['days'], 'aipcf-ai-provider-for-cloudflare'),
$e['days'],
number_format_i18n($cap)
)) . ' ';
}
$fits = [
'free' => __('Fits the Vectorize free plan (5 million stored dimensions).', 'aipcf-ai-provider-for-cloudflare'),
'paid' => __('Too big for the Vectorize free plan. It fits the 10 million included with Workers Paid.', 'aipcf-ai-provider-for-cloudflare'),
'paid_extra' => __('Needs Workers Paid. Storage beyond 10 million dimensions costs $0.05 per 100 million a month.', 'aipcf-ai-provider-for-cloudflare'),
];
if (isset($fits[$e['fits']])) {
echo esc_html(sprintf(
/* translators: %s: stored dimensions (vectors times the numbers in each) */
__('Storage: about %s dimensions.', 'aipcf-ai-provider-for-cloudflare'),
number_format_i18n($e['stored_dims'])
)) . ' ' . esc_html($fits[$e['fits']]);
}
echo '
';
}
printf(
'%s %s
',
esc_html(sprintf(
/* translators: 1: sampled count, 2: time ago */
_n('From %1$d sampled item, %2$s ago. Save to update it after changing the selection.', 'From %1$d sampled items, %2$s ago. Save to update it after changing the selection.', $e['sampled'], 'aipcf-ai-provider-for-cloudflare'),
$e['sampled'],
human_time_diff($e['computed_at'])
)),
esc_url(wp_nonce_url(admin_url('admin-post.php?action=aipcf_embed_estimate'), 'aipcf_embed_estimate')),
esc_attr__('Calculating...', 'aipcf-ai-provider-for-cloudflare'),
esc_html__('Recalculate', 'aipcf-ai-provider-for-cloudflare')
);
echo '';
}
/**
* One piece of a counts line: the translated format with the number in .
*
* @param string $format A translated format with one %s for the number.
*/
public static function count(string $format, string $number): string
{
return '' . sprintf(esc_html($format), '' . esc_html($number) . '') . '';
}
// ================================================================ field helpers
private static function checkbox(string $key, string $label, string $section, string $desc, bool $advanced = false, string $help = ''): void
{
$opt = AIPCF_SETTINGS_OPTION;
add_settings_field($key, $label, static function () use ($key, $opt, $desc, $help): void {
printf('', esc_attr(self::fieldId($key)), esc_attr($opt), esc_attr($key), checked(!empty(get_plugin_settings()[$key]), true, false), esc_html($desc));
if ($help !== '') {
echo FreeTierSettings::help($help); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- help() escapes
}
}, self::PAGE, $section, FreeTierSettings::fieldArgs($advanced) + ['label_for' => self::fieldId($key)]);
}
private static function number(string $key, string $label, string $section, string $desc, int $min, int $max, string $store = '', string $unit = ''): void
{
$opt = AIPCF_SETTINGS_OPTION;
add_settings_field($key, $label, static function () use ($key, $opt, $desc, $min, $max, $store, $unit): void {
printf('', esc_attr(self::fieldId($key)), esc_attr($opt), esc_attr($key), (int) (get_plugin_settings()[$key] ?? 0), (int) $min, (int) $max, $store !== '' ? self::disabled($store) : ''); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- literal attribute
if ($unit !== '') {
echo '' . esc_html($unit) . '';
}
if ($desc !== '') {
echo FreeTierSettings::help($desc); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- help() escapes
}
}, self::PAGE, $section, $store !== '' ? self::rowArgs($store, false, self::fieldId($key)) : ['label_for' => self::fieldId($key)]);
}
/**
* Row attributes for one store's field: hidden unless that store is the saved
* choice (JavaScript switches them live), locked when the user may not configure stores.
*
* @return array{class: string}
*/
private static function rowArgs(string $store, bool $advanced = false, string $labelFor = ''): array
{
$off = StoreFactory::currentId() !== $store;
$args = ['class' => trim(($advanced ? 'aipcf-advanced ' : '') . 'aipcf-store-row aipcf-store-' . $store . ($off ? ' aipcf-store-off' : '') . (Settings::canConfigureStores() ? '' : ' aipcf-store-locked'))];
if ($labelFor !== '') {
$args['label_for'] = $labelFor; // the row heading becomes the field's