'array',
'sanitize_callback' => 'rayachat_sync_sanitize',
));
}
// Deactivating the plugin unschedules its jobs. Turned back on, it would still
// send changes but never run the daily check again, and after three quiet days
// Rayachat stops quoting the store's prices. Looked at once an hour, which is
// nothing against three days, instead of on every admin page. The time of the
// last look is an autoloaded option, so the pages in between read nothing (a
// transient would cost the queries it saves); deactivating deletes it. AJAX
// (the editor's heartbeat among it) skips the lookup.
function rayachat_keep_daily_sync()
{
if (wp_doing_ajax() || (int) get_option('rayachat_daily_checked') > time() - HOUR_IN_SECONDS) {
return;
}
update_option('rayachat_daily_checked', time(), true);
if (rayachat_sync_active()) {
rayachat_schedule_daily_sync();
}
}
/**
* The form posts only `token` and `forget`, and nothing else it posts is read.
* This runs inside update_option, so it returns what to save and writes nothing
* itself.
*/
function rayachat_sync_sanitize($input)
{
if ($input !== null && $input === rayachat_sync_cleaned()) {
return $input;
}
return rayachat_sync_cleaned(rayachat_sync_clean(rayachat_sync_from_form($input)));
}
/**
* Saving the form checks the key with Rayachat before keeping it. An empty key
* field keeps the saved key, which the form never prints back.
*/
function rayachat_sync_from_form($input)
{
$current = rayachat_sync_settings();
if (!is_array($input)) {
return $current;
}
if (!empty($input['forget'])) {
return array_merge($current, rayachat_sync_forget(true));
}
$typed = isset($input['token']) && is_string($input['token']) ? trim($input['token']) : '';
$token = $typed !== '' ? $typed : $current['token'];
if ($token === '') {
return $current;
}
if (!rayachat_sync_key_valid($token)) {
add_settings_error(RAYACHAT_SYNC_OPTION, 'rayachat-sync-key', __('That is not a Rayachat sync key. Create one in the Rayachat dashboard: your chatbot’s Publish page → WordPress plugin → WooCommerce sync.', 'raya-ai'));
return $current;
}
if ($token === $current['token'] && $current['error'] === '') {
return $current;
}
$adopted = rayachat_sync_adopt($token);
if (is_wp_error($adopted)) {
add_settings_error(RAYACHAT_SYNC_OPTION, 'rayachat-sync-' . $adopted->get_error_code(), $adopted->get_error_message());
return $current;
}
if (empty($adopted['stats']['currencySupported'])) {
add_settings_error(
RAYACHAT_SYNC_OPTION,
'rayachat-sync-currency',
/* translators: %s: the store's currency code. */
sprintf(__('Your store’s currency (%s) can’t be converted to toman, so products are synced without prices.', 'raya-ai'), get_woocommerce_currency()),
'warning'
);
}
return array_merge($current, $adopted);
}
/**
* Checks a sync key with Rayachat and starts syncing with it: everything once
* now, in the background, then once a day. Returns the settings to save, or a
* WP_Error whose message says why not.
*/
function rayachat_sync_adopt($token)
{
$result = rayachat_sync_connect($token);
if (is_wp_error($result)) {
return new WP_Error('offline', sprintf(
/* translators: %s: the network error. */
__('Could not reach Rayachat, so the sync settings were not changed: %s', 'raya-ai'),
$result->get_error_message()
));
}
if ($result['code'] === 401) {
return new WP_Error('key', __('Rayachat did not accept this sync key. It may have been replaced by a newer one; copy the current key from the Rayachat dashboard.', 'raya-ai'));
}
if ($result['code'] !== 200 || !is_array($result['body'])) {
return new WP_Error('failed', __('Rayachat could not save the sync settings. Please try again in a minute.', 'raya-ai'));
}
if (function_exists('as_enqueue_async_action')) {
as_enqueue_async_action('rayachat_full_sync', array('products', 1), RAYACHAT_SYNC_GROUP);
}
rayachat_schedule_daily_sync();
return array(
'token' => $token,
'agent' => isset($result['body']['agentName']) ? (string) $result['body']['agentName'] : '',
'error' => '',
'held' => 0,
'stats' => rayachat_sync_stats($result['body']),
);
}
/**
* Stops syncing. With `$tell_rayachat`, the key is revoked there too, so the
* chatbot stops quoting this store's prices now rather than after three quiet
* days. Returns the settings to save.
*/
function rayachat_sync_forget($tell_rayachat)
{
$token = rayachat_sync_settings()['token'];
if ($tell_rayachat && $token !== '') {
// With the token passed in, a 401 here changes nothing locally.
rayachat_sync_request('disconnect', array(), $token);
}
rayachat_unschedule_sync();
$reset = array('token' => '', 'agent' => '', 'error' => '', 'held' => 0, 'stats' => array());
if (!doing_filter('sanitize_option_' . RAYACHAT_SYNC_OPTION)) {
rayachat_sync_update($reset);
}
return $reset;
}
function rayachat_enqueue_sync_script($hook)
{
if ($hook !== RAYACHAT_PAGE_HOOK || !rayachat_sync_active()) {
return;
}
wp_enqueue_script('rayachat-sync', plugins_url('assets/sync.js', RAYACHAT_FILE), array(), RAYACHAT_VERSION, true);
wp_localize_script('rayachat-sync', 'rayachatSync', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('rayachat_full_sync'),
/* translators: 1: products sent so far, 2: products in the store. */
'products' => __('Products: %1$s of %2$s sent…', 'raya-ai'),
'cleanup' => __('Removing products the store no longer shows…', 'raya-ai'),
/* translators: 1: new products, 2: updated products, 3: products already up to date, 4: removed products. */
'done' => __('Done: %1$s new, %2$s updated, %3$s already up to date, %4$s removed.', 'raya-ai'),
/* translators: %s: number of products. */
'limit' => __('%s products did not fit your Rayachat plan’s product limit.', 'raya-ai'),
/* translators: %s: number of products. */
'queued' => __('%s more are being sent in the background.', 'raya-ai'),
/* translators: %s: number of products. */
'retrying' => __('%s products did not get through; they will be sent again automatically.', 'raya-ai'),
/* translators: %s: number of products. */
'invalid' => __('Rayachat could not accept %s products; check their name and prices.', 'raya-ai'),
'held' => rayachat_held_message(),
'failed' => __('The sync stopped. Check your connection and try again.', 'raya-ai'),
));
}
function rayachat_ajax_full_sync()
{
check_ajax_referer('rayachat_full_sync');
if (!current_user_can('manage_options')) {
wp_send_json_error(null, 403);
}
if (!rayachat_sync_active()) {
wp_send_json_error(null, 409);
}
$phase = isset($_POST['phase']) ? sanitize_key(wp_unslash($_POST['phase'])) : 'products';
$page = isset($_POST['page']) ? absint(wp_unslash($_POST['page'])) : 1;
if (!in_array($phase, array('products', 'reconcile'), true)) {
$phase = 'products';
}
wp_send_json_success(rayachat_full_sync_step($phase, $page));
}
// One wording for the progress line and the notice; %s is the number of products.
function rayachat_held_message()
{
/* translators: %s: number of products. */
return __('%s products are no longer in your store, but that is half your synced products or more, so Rayachat kept them to be safe. If you really removed them, confirm in the Rayachat dashboard: your chatbot’s Publish page → WordPress plugin.', 'raya-ai');
}
function rayachat_sync_date($timestamp)
{
return rayachat_digits(wp_date(get_option('date_format') . ' ' . get_option('time_format'), (int) $timestamp));
}
function rayachat_number($number)
{
return rayachat_digits(number_format_i18n((int) $number));
}
function rayachat_render_sync_card()
{
$sync = rayachat_sync_settings();
$stats = is_array($sync['stats']) ? $sync['stats'] : array();
$published = (int) wp_count_posts('product')->publish;
if ($sync['token'] === '') {
$tone = 'neutral';
$label = __('Not set up', 'raya-ai');
$line = __('Not syncing yet. Turn it on with “Connect to Rayachat”, and the chatbot will know your products, prices and stock.', 'raya-ai');
} elseif ($sync['error'] !== '') {
$tone = 'error';
$label = __('Key rejected', 'raya-ai');
$line = __('Rayachat stopped accepting this site’s sync key, so nothing is being synced. Click “Connect to Rayachat” to set it up again.', 'raya-ai');
} else {
$tone = 'success';
$label = __('Syncing', 'raya-ai');
/* translators: %s: the chatbot's name. */
$line = sprintf(__('Products go to the chatbot “%s” and are kept up to date as they change.', 'raya-ai'), $sync['agent']);
}
$next = rayachat_sync_active() && function_exists('as_next_scheduled_action')
? as_next_scheduled_action('rayachat_daily_sync')
: false;
$counts = rayachat_sync_active() && $stats;
?>