$status ) ); }
function dashe_seo_admin_menu() {
add_options_page( 'Dashe SEO', 'Dashe SEO', 'manage_options', 'dashe-seo', 'dashe_seo_admin' );
}
add_action( 'admin_menu', 'dashe_seo_admin_menu' );
function dashe_seo_admin() {
if ( ! current_user_can( 'manage_options' ) ) { return; }
$c = dashe_seo_config();
$security = dashe_seo_host_security();
?>
Dashe SEO
Connect this website to your Dashe workspace to manage approved SEO fields and publish articles. Chat settings remain independent.
Dashe receives PHP and WordPress versions, operating-system family, session-upload status and cURL Brotli support/version during authenticated connection and security checks. It does not receive phpinfo, environment secrets or the full server configuration.
Dashe receives public page content, metadata, selected author/category names and publication receipts. Content may be processed by your workspace's configured AI and search-data providers. Privacy · Service terms
SEO connection active. Changes remain limited to the permissions selected here and in Dashe.
403 ) ); }
check_admin_referer( 'dashe_seo_connect' );
$token = isset( $_POST['pairing_code'] ) ? sanitize_text_field( wp_unslash( $_POST['pairing_code'] ) ) : '';
if ( ! preg_match( '/^[a-f0-9]{64}$/D', $token ) ) { wp_die( 'Use a fresh connection code from Dashe.' ); }
$previous = dashe_seo_config();
$c = array( 'enabled' => true, 'secret' => bin2hex( random_bytes( 32 ) ), 'user_id' => get_current_user_id(), 'metadata' => ! empty( $_POST['metadata'] ), 'publish' => ! empty( $_POST['publish'] ) );
$c['content'] = ! empty( $_POST['content'] );
$c['approved_media'] = array_slice( array_filter( array_map( 'absint', explode( ',', sanitize_text_field( wp_unslash( $_POST['approved_media'] ?? '' ) ) ) ) ), 0, 20 );
update_option( 'dashe_seo_connection', $c, false );
$response = wp_remote_post( 'https://www.getdashe.com/api/seo/pair', array( 'timeout' => 30, 'redirection' => 0, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'url' => trailingslashit( home_url() ), 'endpoint' => rest_url( 'dashe-seo/v1/exchange' ), 'token' => $token, 'secret' => $c['secret'] ) ) ) );
$ok = ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) && ! empty( json_decode( wp_remote_retrieve_body( $response ), true )['ok'] );
if ( ! $ok ) { update_option( 'dashe_seo_connection', $previous, false ); }
wp_safe_redirect( admin_url( 'options-general.php?page=dashe-seo&dashe_seo_status=' . ( $ok ? 'connected' : 'failed' ) ) ); exit;
}
add_action( 'admin_post_dashe_seo_connect', 'dashe_seo_connect' );
function dashe_seo_disconnect() {
if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'Not authorized.', '', array( 'response' => 403 ) ); }
check_admin_referer( 'dashe_seo_disconnect' ); delete_option( 'dashe_seo_connection' );
wp_safe_redirect( admin_url( 'options-general.php?page=dashe-seo&dashe_seo_status=disconnected' ) ); exit;
}
add_action( 'admin_post_dashe_seo_disconnect', 'dashe_seo_disconnect' );
function dashe_seo_permission( $request ) {
$c = dashe_seo_config();
if ( empty( $c['enabled'] ) || empty( $c['secret'] ) || ! user_can( $c['user_id'], 'manage_options' ) ) { return dashe_seo_error( 'SEO access is disabled.', 403 ); }
$stamp = $request->get_header( 'x-dashe-timestamp' ); $nonce = $request->get_header( 'x-dashe-nonce' ); $signature = $request->get_header( 'x-dashe-signature' );
if ( ! ctype_digit( $stamp ) || abs( time() - (int) $stamp ) > 120 || ! preg_match( '/^[a-f0-9]{40}$/D', $nonce ) || strlen( $request->get_body() ) > 2000000 ) { return dashe_seo_error( 'Invalid signed request.', 403 ); }
$expected = hash_hmac( 'sha256', $stamp . "\n" . $nonce . "\n" . $request->get_body(), $c['secret'] );
if ( ! hash_equals( $expected, $signature ) ) { return dashe_seo_error( 'Invalid signature.', 403 ); }
$GLOBALS['dashe_seo_request_secret'] = $c['secret'];
// Unique option insertion is atomic on both MySQL and WordPress SQLite.
if ( ! add_option( 'dashe_seo_nonce_' . $nonce, time(), '', false ) ) { return dashe_seo_error( 'Request already used.', 409 ); }
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Expired nonce housekeeping; nonce entries are never read from cache.
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s AND CAST(option_value AS UNSIGNED) < %d", $wpdb->esc_like( 'dashe_seo_nonce_' ) . '%', time() - 300 ) );
return true;
}
add_action( 'rest_api_init', function () {
register_rest_route( 'dashe-seo/v1', '/exchange', array( 'methods' => 'POST', 'callback' => 'dashe_seo_exchange', 'permission_callback' => 'dashe_seo_permission' ) );
} );
function dashe_seo_adapter() {
if ( ( defined( 'WPSEO_VERSION' ) && defined( 'RANK_MATH_VERSION' ) ) || defined( 'SEOPRESS_VERSION' ) || defined( 'AIOSEO_VERSION' ) || defined( 'THE_SEO_FRAMEWORK_VERSION' ) ) { return 'unsupported'; }
if ( defined( 'RANK_MATH_VERSION' ) && class_exists( '\RankMath\Helper' ) && \RankMath\Helper::is_invalid_registration() ) { return 'setup_required'; }
return defined( 'WPSEO_VERSION' ) ? 'yoast' : ( defined( 'RANK_MATH_VERSION' ) ? 'rank_math' : 'dashe' );
}
function dashe_seo_keys() {
$a = dashe_seo_adapter();
return 'yoast' === $a ? array( '_yoast_wpseo_title', '_yoast_wpseo_metadesc' ) : ( 'rank_math' === $a ? array( 'rank_math_title', 'rank_math_description' ) : array( '_dashe_seo_title', '_dashe_seo_description' ) );
}
function dashe_seo_snapshot( $id ) {
$p = get_post( $id ); if ( ! $p ) { return null; }
$keys = dashe_seo_keys();
$builder = get_post_meta( $id, '_elementor_edit_mode', true ) || get_post_meta( $id, '_et_pb_use_builder', true );
$s = array( 'id' => (int) $id, 'url' => get_permalink( $id ), 'type' => $p->post_type, 'status' => $p->post_status, 'title' => $p->post_title, 'content' => $p->post_content, 'excerpt' => $p->post_excerpt, 'seo_title' => get_post_meta( $id, $keys[0], true ), 'description' => get_post_meta( $id, $keys[1], true ), 'editable' => ! $builder, 'adapter' => dashe_seo_adapter(), 'modified' => $p->post_modified_gmt, 'author' => (int) $p->post_author );
$s['featured_media'] = (int) get_post_thumbnail_id( $id );
$s['fragments'] = ! $builder && 'product' !== $p->post_type ? array_map( function ( $f ) { return array( 'index' => $f['index'], 'text' => $f['text'] ); }, dashe_seo_fragments( $p->post_content ) ) : array();
$s['hash'] = hash( 'sha256', wp_json_encode( $s ) ); return $s;
}
function dashe_seo_fragments( $content ) {
// Preserve all HTML, block delimiters, attributes, links and unknown storage.
foreach ( parse_blocks( $content ) as $block ) { if ( ! empty( $block['blockName'] ) && ! in_array( $block['blockName'], array( 'core/paragraph', 'core/heading', 'core/list', 'core/image' ), true ) ) { return array(); } }
if ( preg_match( '/\[[a-zA-Z]|<(script|style|textarea|pre|code|iframe)\b/i', $content ) ) { return array(); }
preg_match_all( '#' . esc_html( $block['text'] ) . '
' . "\n"; }
elseif ( 'link' === $type && isset( $block['url'], $block['text'] ) && wp_parse_url( $block['url'], PHP_URL_HOST ) === wp_parse_url( home_url(), PHP_URL_HOST ) && in_array( wp_parse_url( $block['url'], PHP_URL_SCHEME ), array( 'https', 'http' ), true ) ) { $html .= '