'', 'token' => '', 'nonce' => '', 'nonce_at' => 0, 'mode' => 'launcher', // launcher | off 'site_url' => '', 'installed' => '', 'last_state' => '', 'last_error' => '', 'words' => array(), ); $saved = get_option( ANSWERHALL_OPTION, array() ); return wp_parse_args( is_array( $saved ) ? $saved : array(), $defaults ); } function answerhall_save( $patch ) { $s = array_merge( answerhall_settings(), $patch ); update_option( ANSWERHALL_OPTION, $s, false ); return $s; } /* ------------------------------------------------------------------ ownership check * answerhall.com asks this site for /?answerhall_verify=1 and expects the nonce the * plugin sent it. Only a site running this plugin can answer, so nobody can claim * somebody else's site. */ add_action( 'init', function () { if ( isset( $_GET['answerhall_verify'] ) ) { // The code is shown only during the ten minutes after the owner presses "Read my site", and is cleared // as soon as answerhall.com has seen it, so it cannot be read later and replayed. $s = answerhall_settings(); $fresh = $s['nonce'] && ( time() - (int) $s['nonce_at'] ) < 600; nocache_headers(); header( 'Content-Type: text/plain; charset=utf-8' ); echo esc_html( $fresh ? $s['nonce'] : 'none' ); exit; } } ); /* ------------------------------------------------------------------ the button on every page */ function answerhall_enqueue() { $s = answerhall_settings(); if ( $s['slug'] ) { // The script is served by answerhall.com (the service this plugin connects to) and renders inside its own // shadow root; it changes nothing else on the page. wp_enqueue_script( 'answerhall', ANSWERHALL_API . '/' . $s['slug'] . '/embed.js', array(), null, array( 'strategy' => 'async', 'in_footer' => true ) ); } } add_action( 'wp_enqueue_scripts', function () { $s = answerhall_settings(); if ( 'launcher' === $s['mode'] ) { answerhall_enqueue(); } } ); /* [answerhall] or [answerhall mode="bar"]: the box or the bar inside a page or sidebar. */ add_shortcode( 'answerhall', function ( $atts ) { $s = answerhall_settings(); if ( ! $s['slug'] ) { return ''; } $atts = shortcode_atts( array( 'mode' => 'box', 'tagline' => '' ), $atts, 'answerhall' ); $mode = 'bar' === $atts['mode'] ? ' data-mode="bar"' : ''; $tag = $atts['tagline'] ? ' data-tagline="' . esc_attr( $atts['tagline'] ) . '"' : ''; answerhall_enqueue(); static $done = false; if ( $done ) { return ''; // one box per page } $done = true; return '
'; } ); /* ------------------------------------------------------------------ talking to answerhall.com */ function answerhall_api( $path, $body ) { $r = wp_remote_post( ANSWERHALL_API . $path, array( 'timeout' => 45, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $body ), ) ); if ( is_wp_error( $r ) ) { return array( 'error' => $r->get_error_message() ); } $data = json_decode( wp_remote_retrieve_body( $r ), true ); if ( ! is_array( $data ) ) { $data = array( 'error' => 'answerhall.com gave an unexpected reply (' . wp_remote_retrieve_response_code( $r ) . ')' ); } if ( wp_remote_retrieve_response_code( $r ) >= 400 && empty( $data['error'] ) ) { $data['error'] = 'answerhall.com answered ' . wp_remote_retrieve_response_code( $r ); } return $data; } /** Start reading this site. Idempotent. */ function answerhall_install( $rebuild = false ) { $s = answerhall_settings(); $nonce = wp_generate_password( 32, false ); answerhall_save( array( 'nonce' => $nonce, 'nonce_at' => time() ) ); $site = home_url( '/' ); $data = answerhall_api( '/api/wp/install', array( 'url' => $site, 'nonce' => $nonce, 'token' => $s['token'], 'name' => get_bloginfo( 'name' ), 'email' => get_option( 'admin_email' ), 'rebuild' => (bool) $rebuild, ) ); answerhall_save( array( 'nonce' => '', 'nonce_at' => 0 ) ); if ( empty( $data['error'] ) && ( empty( $data['slug'] ) || empty( $data['token'] ) ) ) { $data = array( 'error' => 'answerhall.com did not return the site details' ); } if ( ! empty( $data['error'] ) ) { answerhall_save( array( 'last_error' => $data['error'] ) ); return $data; } answerhall_save( array( 'slug' => sanitize_key( $data['slug'] ), 'token' => $data['token'], 'site_url' => $site, 'installed' => $s['installed'] ? $s['installed'] : gmdate( 'c' ), 'last_state' => isset( $data['state'] ) ? $data['state'] : '', 'last_error' => '', ) ); return $data; } function answerhall_status() { $s = answerhall_settings(); if ( ! $s['slug'] || ! $s['token'] ) { return array( 'error' => 'not installed' ); } $cached = get_transient( 'answerhall_status' ); if ( is_array( $cached ) ) { return $cached; } $data = answerhall_api( '/api/wp/status', array( 'slug' => $s['slug'], 'token' => $s['token'] ) ); if ( empty( $data['error'] ) ) { set_transient( 'answerhall_status', $data, 'built' === ( $data['state'] ?? '' ) ? 60 : 10 ); } return $data; } /* ------------------------------------------------------------------ admin page */ add_action( 'admin_menu', function () { add_options_page( 'Answerhall', 'Answerhall', 'manage_options', 'answerhall', 'answerhall_page' ); } ); add_action( 'admin_init', function () { if ( ! current_user_can( 'manage_options' ) || ! isset( $_POST['answerhall_action'] ) ) { return; } check_admin_referer( 'answerhall_admin' ); $action = sanitize_key( wp_unslash( $_POST['answerhall_action'] ) ); $s = answerhall_settings(); if ( 'install' === $action ) { $data = answerhall_install( false ); set_transient( 'answerhall_notice', empty( $data['error'] ) ? 'Reading your site. This takes a few minutes.' : 'Could not start: ' . $data['error'], 60 ); } elseif ( 'checkout' === $action ) { $data = answerhall_api( '/api/wp/checkout', array( 'slug' => $s['slug'], 'token' => $s['token'], 'email' => get_option( 'admin_email' ), 'return' => admin_url( 'options-general.php?page=answerhall' ), ) ); if ( empty( $data['error'] ) && ! empty( $data['url'] ) && 'checkout.stripe.com' === wp_parse_url( $data['url'], PHP_URL_HOST ) ) { add_filter( 'allowed_redirect_hosts', function ( $hosts ) { $hosts[] = 'checkout.stripe.com'; return $hosts; } ); wp_safe_redirect( esc_url_raw( $data['url'] ) ); // Stripe's checkout page, then back here exit; } set_transient( 'answerhall_notice', 'Could not open the payment page: ' . ( $data['error'] ?? 'no address returned' ), 60 ); } elseif ( 'refresh' === $action ) { $data = answerhall_api( '/api/wp/refresh', array( 'slug' => $s['slug'], 'token' => $s['token'] ) ); set_transient( 'answerhall_notice', empty( $data['error'] ) ? 'Reading your site again. A few minutes.' : 'Could not start: ' . $data['error'], 60 ); } elseif ( 'words' === $action ) { $patch = array( 'tagline_bar' => sanitize_text_field( wp_unslash( $_POST['tagline_bar'] ?? '' ) ), 'placeholder' => sanitize_text_field( wp_unslash( $_POST['placeholder'] ?? '' ) ), 'button' => sanitize_text_field( wp_unslash( $_POST['button'] ?? '' ) ), ); $mode = ( isset( $_POST['mode'] ) && 'off' === $_POST['mode'] ) ? 'off' : 'launcher'; answerhall_save( array( 'mode' => $mode, 'words' => $patch ) ); $data = answerhall_api( '/api/wp/settings', array_merge( array( 'slug' => $s['slug'], 'token' => $s['token'] ), $patch ) ); set_transient( 'answerhall_notice', empty( $data['error'] ) ? 'Saved. Your site shows the new words within about twenty minutes.' : 'Could not save: ' . $data['error'], 60 ); } delete_transient( 'answerhall_status' ); wp_safe_redirect( admin_url( 'options-general.php?page=answerhall' ) ); exit; } ); function answerhall_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $s = answerhall_settings(); $notice = get_transient( 'answerhall_notice' ); if ( $notice ) { delete_transient( 'answerhall_notice' ); } $st = $s['slug'] ? answerhall_status() : null; $state = $st && empty( $st['error'] ) ? ( $st['state'] ?? '' ) : ''; if ( isset( $_GET['answerhall_paid'] ) && ! $notice ) { $notice = 'Thank you. Your plan is on; the first 14 days are free and Stripe emails the receipt.'; } ?>

Answerhall

Late at night a visitor is on one of your pages, wondering about the one thing it does not quite say. Nobody is there to ask, so they leave. From now on they can ask, and the answer comes from your own pages, with a link to the page it came from. When your site does not say, it says so and points to you.

1. Every page, read

Your site address changed since it was read. Read it again so answers come from the new address.

Answerhall reads : pages, posts, any products, and the PDFs they link to. Nothing behind a login.

Pressing the button sends this site's address, its name and the admin email to answerhall.com, the service that reads the site and answers visitors. Privacy.

Ready. Read pages. Open the preview and ask what a visitor would ask. Could not read the site. Reading your site. This takes a few minutes; reload this page to see where it is.

2. The button on your site

The Ask button is at the bottom right of every page. Prefer a box or a bar inside a page? Put [answerhall] or [answerhall mode="bar"] where you want it.

Plan active. $79 a month. To cancel, write to pierre@bessondigital.com or use the link in Stripe's receipt; deleting the plugin does not cancel it.

Your free days have ended. The shared page still works; the button on your site needs the plan.

Your first days are free from install. After that it is $79 a month, cancelled any time by writing to us.

Payments open shortly; your free days run meanwhile.

3. Questions

asked · not on your site yet

Visitors asked these and your pages had no answer. A line on the right page fixes each one.

QuestionAnswer given
Not on your site. The visitor was pointed to you.' : esc_html( preg_replace( '/\[\d+\]/', '', $r['a'] ) ); ?>

A person answers: pierre@bessondigital.com. Privacy · Terms

Settings' ); return $links; } );