dsifford/academic-bloggers-toolkit

View on GitHub
src/php/endpoints.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/**
 * AJAX Endpoints.
 *
 * @package ABT
 */

// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

namespace ABT\Endpoints;

defined( 'ABSPATH' ) || exit;

use function ABT\Utils\get_citation_styles;

/**
 * Fetch and return citation style JSON.
 */
function get_style_json() {
    check_ajax_referer( 'abt-ajax' );
    $styles        = get_citation_styles();
    $current_style = get_option( ABT_OPTIONS_KEY )['citation_style'];
    if ( 'custom' === $current_style['kind'] ) {
        $styles->styles[] = $current_style;
    }
    wp_send_json( $styles );
}
add_action( 'wp_ajax_get_style_json', __NAMESPACE__ . '\get_style_json' );

/**
 * AJAX Method for getting metadata from other websites for citations.
 */
function get_website_meta() {
    if ( ! extension_loaded( 'dom' ) || ! extension_loaded( 'libxml' ) ) {
        wp_send_json_error( [], 501 );
        exit;
    }
    check_ajax_referer( 'abt-ajax' );

    if ( ! isset( $_POST['url'] ) ) {
        wp_send_json_error( [], 400 );
    }

    $url  = esc_url_raw( wp_unslash( $_POST['url'] ) );
    $raw  = wp_remote_retrieve_body( wp_safe_remote_get( $url ) );
    $html = new \DOMDocument();
    libxml_use_internal_errors( true );
    $html->loadHTML( $raw );
    $xpath = new \DOMXPath( $html );

    $payload = [
        'article'  => [],
        'authors'  => [],
        'og'       => [],
        'sailthru' => [],
    ];

    /**
     * Stray Meta "name" Tags.
     */
    $authors = $xpath->query( '//meta[ @name="author" ]' );
    foreach ( $authors as $node ) {
        $a = explode( ' ', sanitize_text_field( $node->getAttribute( 'content' ) ), 2 );
        $a = [
            'given'  => $a[0],
            'family' => $a[1],
        ];
        if ( ! is_int( array_search( $a, $payload['authors'], true ) ) ) {
            $payload['authors'][] = $a;
        }
    }

    $issued = $xpath->query( '//meta[ @name="pubdate" ]' )->item( 0 );
    if ( $issued ) {
        $payload['issued'] = sanitize_text_field( $issued->getAttribute( 'content' ) );
    }

    /**
     * Open Graph Tags.
     */
    $opengraph = $xpath->query( '//meta[ starts-with( @property, "og:" ) ]' );
    foreach ( $opengraph as $node ) {
        $expl                  = explode( ':', $node->getAttribute( 'property' ), 2 );
        $key                   = sanitize_key( str_replace( ':', '_', $expl[1] ) );
        $value                 = sanitize_text_field( $node->getAttribute( 'content' ) );
        $payload['og'][ $key ] = $value;
    }

    /**
     * Article Tags.
     */
    $article = $xpath->query( '//meta[ starts-with( @property, "article:" ) ]' );
    foreach ( $article as $node ) {
        $expl                       = explode( ':', $node->getAttribute( 'property' ), 2 );
        $key                        = sanitize_key( $expl[1] );
        $value                      = sanitize_text_field( $node->getAttribute( 'content' ) );
        $payload['article'][ $key ] = $value;
    }

    /**
     * Sailthru Tags.
     */
    $sailthru = $xpath->query( '//meta[ starts-with( @name, "sailthru" ) ]' );
    foreach ( $sailthru as $node ) {
        $expl  = explode( '.', $node->getAttribute( 'name' ), 2 );
        $key   = sanitize_key( $expl[1] );
        $value = sanitize_text_field( $node->getAttribute( 'content' ) );

        if ( 'author' === $key ) {
            if ( strlen( $value ) > 50 ) {
                continue;
            }
            $a = explode( ' ', $value, 2 );
            $a = [
                'given'  => $a[0],
                'family' => $a[1],
            ];
            if ( ! is_int( array_search( $a, $payload['authors'], true ) ) ) {
                $payload['authors'][] = $a;
            }
            continue;
        }

        $payload['sailthru'][ $key ] = $value;
    }

    /**
     * Itemprop Tags.
     */
    $issued = $xpath->query( '//*[ @itemprop="datePublished" ]' )->item( 0 );
    if ( $issued ) {
        if ( $issued->hasAttribute( 'datetime' ) ) {
            $payload['issued'] = esc_attr( $issued->getAttribute( 'datetime' ) );
        } elseif ( $issued->hasAttribute( 'content' ) ) {
            $payload['issued'] = esc_attr( $issued->getAttribute( 'content' ) );
        }
    }

    $authors = $xpath->query( '//*[ @itemprop="author" ][ not( ancestor::*[ @itemtype="http://schema.org/Comment" ] ) ]' );
    foreach ( $authors as $author ) {
        if ( $author->nodeName === 'meta' ) {
            continue;
        }
        $a = explode( ' ', sanitize_text_field( $author->textContent ), 2 );
        $a = [
            'given'  => $a[0],
            'family' => $a[1],
        ];
        if ( ! is_int( array_search( $a, $payload['authors'], true ) ) ) {
            $payload['authors'][] = $a;
        }
    }

    $title = $xpath->query( '//*[ @itemprop="headline" ]' )->item( 0 );
    if ( $title ) {
        $payload['title'] = sanitize_text_field( $title->textContent );
    }

    /**
     * ABT Tags.
     */
    $abt = $xpath->query( '//meta[ starts-with( @property, "abt:" ) ]' );
    foreach ( $abt as $node ) {
        $expl  = explode( ':', $node->getAttribute( 'property' ), 2 );
        $key   = sanitize_key( $expl[1] );
        $value = sanitize_text_field( $node->getAttribute( 'content' ) );
        if ( 'author' === $key ) {
            $a = explode( '|', $value, 2 );
            $a = [
                'given'  => $a[0],
                'family' => $a[1],
            ];
            if ( ! is_int( array_search( $a, $payload['authors'], true ) ) ) {
                $payload['authors'][] = $a;
            }
        }
    }

    // Last ditch effort to get a title of the site.
    if ( ! $payload['title'] ) {
        $title = $xpath->query( '/html/head/title' )->item( 0 );
        if ( $title ) {
            $payload['title'] = sanitize_text_field( $title->textContent );
        }
    }

    wp_send_json( $payload );
}
add_action( 'wp_ajax_get_website_meta', __NAMESPACE__ . '\get_website_meta' );

/**
 * Custom API to update _abt_state meta since current WordPress APIs are broken.
 */
function update_abt_state() {
    check_ajax_referer( 'abt-ajax' );
    if ( ! isset( $_POST['state'], $_POST['post_id'] ) ) {
        wp_send_json_error( 'required fields not sent', 400 );
    }
    $post_id = intval( $_POST['post_id'] );
    $state   = json_decode( wp_unslash( $_POST['state'] ) ); // phpcs:ignore
    if ( is_null( $state ) ) {
        wp_send_json_error( 'state is null', 400 );
    }
    $updated = update_post_meta(
        $post_id,
        '_abt_state',
        wp_slash(
            wp_json_encode( $state )
        )
    );
    wp_send_json_success( $state );
}
add_action( 'wp_ajax_update_abt_state', __NAMESPACE__ . '\update_abt_state' );