Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Proof of concept showing how to implement virtual posts in WordPress but requires `make_post_instance` hook (or similar) to exist. See: https://core.trac.wordpress.org/ticket/12955#comment:24
<?php
/**
* Plugin Name: Virtual Posts
*
* @see https://github.com/usagov/Federal-Agency-Directory-API-Documentation
*/
class Virtual_Posts {
const POST_TYPE = 'vp_fed_mob_apps';
const TRANSIENT_KEY = 'vp_federal_mobile_app_ids';
const TIMESTAMP_KEY = 'vp_timestamp';
const DATE_FORMAT = 'Y-m-d h:i:s';
const API_URL = 'http://apps.usa.gov/apps-gallery/api/registrations.json';
/**
* @var array List of App IDs from the API.
*/
private static $_app_ids;
/**
*
*/
static function on_load() {
add_action( 'init', array( __CLASS__, '_init' ) );
add_action( 'pre_get_posts', array( __CLASS__, '_pre_get_posts' ) );
add_filter( 'make_post_instance', array( __CLASS__, '_make_post_instance' ) );
add_filter( 'wp_count_posts', array( __CLASS__, '_wp_count_posts' ) );
}
/**
* Refresh the post from the API if stale by 15 minutes or longer
*
* @param object $post
*
* @return array|null|WP_Post
*/
static function _make_post_instance( $post ) {
static $posts_processed = array();
if ( ! isset( $posts_processed[ $post->ID ] ) ) {
$posts_processed[ $post->ID ]= $post->ID;
$timestamp = get_post_meta( $post->ID, self::TIMESTAMP_KEY, true );
if ( ! $timestamp || $timestamp < date( self::DATE_FORMAT, strtotime( '-15 minutes' ) ) ) {
if ( 1 <= count( $apps = self::_call_api( "Id={$post->post_name}" ) ) ) {
self::_update_app( $post->ID, $apps[0] );
}
$post = get_post( $post->ID );
}
}
return $post;
}
/**
* @param $counts
*
* @return mixed
*/
static function _wp_count_posts( $counts ) {
$app_ids = self::_app_ids();
$counts->publish = is_array( $app_ids ) ? count( $app_ids ) : 0;
return $counts;
}
/**
* @param WP_Query $query
*/
static function _pre_get_posts( $query ) {
if ( $query->is_main_query() && self::POST_TYPE == $query->get( 'post_type' ) ) {
$post_ids = self::_post_ids( $query );
$needed = array_filter( array_keys( $post_ids ), function ( $post_id ) use ( $post_ids ) {
return 0 === $post_ids[ $post_id ];
});
if ( count( $needed ) ) {
$results = self::_call_api( 'Id=' . implode( '&Id=', $needed ) );
foreach( $results as $result ) {
self::_maybe_add_app( $result );
}
}
$query->set( 'orderby', 'ID' );
$query->set( 'order', 'ASC' );
}
}
/**
* @param int $post_id
* @return int[]
*/
private static function _update_app( $post_id, $app ) {
if ( ! is_numeric( $post_id ) ) {
$post = get_page_by_path( $post_id, OBJECT, self::POST_TYPE );
$post_id = $post ? $post->ID : false;
}
if ( $post_id ) {
wp_update_post( array(
'ID' => $post_id,
'post_name' => $app->Id,
'post_type' => self::POST_TYPE,
'post_title' => $app->Name,
'post_status' => 'publish',
'post_content' => $app->Long_Description,
'post_excerpt' => $app->Short_Description,
'post_date_gmt' => date( self::DATE_FORMAT, $app->created ),
'post_modified_gmt' => date( self::DATE_FORMAT, $app->updated ),
) );
$fields = array(
'Id',
'Name',
'Long_Description',
'Short_Description',
'created',
'updated'
);
foreach ( $fields as $field_name ) {
unset( $app->$field_name );
}
foreach ( (array) $app as $field_name => $value ) {
$value = is_array( $value ) ? print_r( $value, true ) : $value;
update_post_meta( $post_id, $field_name, $value );
}
update_post_meta( $post_id, self::TIMESTAMP_KEY, date( self::DATE_FORMAT, time() ) );
}
}
/**
* @param object $app
*/
private static function _maybe_add_app($app) {
$app_post = get_page_by_path( $app->Id, OBJECT, self::POST_TYPE );
if ( ! $app_post ) {
$post_id = wp_insert_post( array(
'post_name' => $app->Id,
'post_type' => self::POST_TYPE,
'post_title' => $app->Name,
));
if ( $post_id ) {
self::_update_app( $post_id, $app );
}
}
}
/**
* @param WP_Query $query
* @return int[]
*/
private static function _post_ids( $query ) {
global $wpdb;
$app_ids = self::_app_ids();
$record_count = $query->get( 'posts_per_page' );
$paged = $query->get('paged');
$offset = $paged ? ( $paged - 1 ) * $record_count + 1 : 1;
$app_ids = array_slice( $app_ids, $offset, $record_count ? $record_count : 20 );
$app_ids_sql = implode( "','", array_map( 'sanitize_key', $app_ids ) );
$post_ids = array();
$sql = "SELECT ID,post_name FROM {$wpdb->posts} WHERE post_name IN ('{$app_ids_sql}');";
if ( is_array( $results = $wpdb->get_results( $sql ) ) ) {
foreach( $results as $result ) {
$post_ids[ $result->post_name ] = $result->ID;
}
}
/**
* Now add in the missing ones
*/
foreach( $app_ids as $app_id ) {
if ( ! isset( $post_ids[ $app_id ] ) ) {
$post_ids[ $app_id ] = 0;
}
}
return $post_ids;
}
/**
* Load just the IDs into a transient
*/
static function _init() {
register_post_type( self::POST_TYPE, array(
'public' => true,
'label' => __( 'Federal Mobile Apps', 'virtpost' ),
'supports' => array(
'title',
'editor',
'custom-fields',
),
'rewrite' => array(
'with_front' => false,
'slug' => 'agencies'
),
));
}
/**
* Load just the IDs into a transient
*/
private static function _app_ids() {
if ( isset( self::$_app_ids ) ) {
return self::$_app_ids;
}
self::$_app_ids = get_transient( self::TRANSIENT_KEY );
if ( ! self::$_app_ids ) {
if ( $results = self::_call_api( 'select=Id' ) ) {
$app_ids = array();
foreach( $results as $index => $app ) {
$app_ids[] = $app->Id;
}
set_transient( self::TRANSIENT_KEY, $app_ids, 15*60 ); // Cached for 15 minutes
self::$_app_ids = $app_ids;
}
}
return self::$_app_ids;
}
/**
* @param $query_vars
*
* @return object[]|null
*/
static function _call_api( $query_vars ) {
$response = wp_remote_get( self::API_URL . "?{$query_vars}" );
if ( 200 == wp_remote_retrieve_response_code( $response ) ) {
$json = json_decode( wp_remote_retrieve_body( $response ) );
if ( is_array( $json->results ) ) {
$results = array();
foreach( $json->results as $index => $object ) {
$results[ $index ] = $object;
}
return $results;
}
}
return null;
}
}
Virtual_Posts::on_load();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment