one bar * POST /bars/ save { title, settings } → { bar } * DELETE /bars/ delete for good * POST /bars//duplicate copy (switched off) * POST /bars//active { active: bool } → { bar } * GET /titles?ids=1,2&terms=category:3 names of pages / posts / terms chosen in display rules * POST /preview { settings } → { html } (the same HTML as on the site) */ defined( 'ABSPATH' ) || exit; define( 'RUNLINE_REST_NS', 'runline/v1' ); add_action( 'rest_api_init', 'runline_rest_routes' ); function runline_rest_routes() { $admin = function () { return current_user_can( 'manage_options' ); }; $id = array( 'id' => array( 'type' => 'integer', 'required' => true, 'validate_callback' => function ( $value ) { return RUNLINE_POST_TYPE === get_post_type( (int) $value ); }, ), ); $body = array( 'title' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), 'settings' => array( 'type' => 'object' ), ); register_rest_route( RUNLINE_REST_NS, '/bars', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => 'runline_rest_list', 'permission_callback' => $admin, ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => 'runline_rest_create', 'permission_callback' => $admin, 'args' => $body, ), ) ); register_rest_route( RUNLINE_REST_NS, '/bars/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => 'runline_rest_get', 'permission_callback' => $admin, 'args' => $id, ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => 'runline_rest_update', 'permission_callback' => $admin, 'args' => array_merge( $id, $body ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => 'runline_rest_delete', 'permission_callback' => $admin, 'args' => $id, ), ) ); register_rest_route( RUNLINE_REST_NS, '/bars/(?P\d+)/duplicate', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => 'runline_rest_duplicate', 'permission_callback' => $admin, 'args' => $id, ) ); register_rest_route( RUNLINE_REST_NS, '/bars/(?P\d+)/active', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => 'runline_rest_active', 'permission_callback' => $admin, 'args' => array_merge( $id, array( 'active' => array( 'type' => 'boolean', 'required' => true, ), ) ), ) ); // "Do not show again" of a hint in the editor (per admin). register_rest_route( RUNLINE_REST_NS, '/hint', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { update_user_meta( get_current_user_id(), 'runline_hint_' . $request['hint'], 1 ); return rest_ensure_response( array( 'closed' => true ) ); }, 'permission_callback' => $admin, 'args' => array( 'hint' => array( 'type' => 'string', 'enum' => array( 'dayslot' ), 'required' => true, ), ), ) ); register_rest_route( RUNLINE_REST_NS, '/titles', array( 'methods' => WP_REST_Server::READABLE, 'callback' => 'runline_rest_titles_of', 'permission_callback' => $admin, 'args' => array( 'ids' => array( 'type' => 'string', 'default' => '', ), 'terms' => array( 'type' => 'string', 'default' => '', ), ), ) ); register_rest_route( RUNLINE_REST_NS, '/export', array( 'methods' => WP_REST_Server::READABLE, 'callback' => 'runline_rest_export', 'permission_callback' => $admin, ) ); register_rest_route( RUNLINE_REST_NS, '/import', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => 'runline_rest_import', 'permission_callback' => $admin, 'args' => array( 'bars' => array( 'type' => 'array' ) ), ) ); register_rest_route( RUNLINE_REST_NS, '/font', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $family = runline_google_family( 'g:' . (string) $request['family'] ); return rest_ensure_response( array( 'url' => $family ? runline_font_download( $family ) : '' ) ); }, 'permission_callback' => $admin, 'args' => array( 'family' => array( 'type' => 'string', 'required' => true, ), ), ) ); register_rest_route( RUNLINE_REST_NS, '/source-items', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $settings = runline_sanitize_settings( array( 'source' => (array) $request->get_param( 'source' ) ) ); return rest_ensure_response( runline_source_items( $settings['source'] ) ); }, 'permission_callback' => $admin, 'args' => array( 'source' => array( 'type' => 'object' ) ), ) ); register_rest_route( RUNLINE_REST_NS, '/sale-end', array( 'methods' => WP_REST_Server::READABLE, 'callback' => function ( WP_REST_Request $request ) { return rest_ensure_response( array( 'end' => runline_sale_end( (int) $request['product'] ) * 1000 ) ); }, 'permission_callback' => $admin, 'args' => array( 'product' => array( 'type' => 'integer', 'required' => true, ), ), ) ); register_rest_route( RUNLINE_REST_NS, '/preview', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => 'runline_rest_preview', 'permission_callback' => $admin, 'args' => array( 'settings' => array( 'type' => 'object' ) ), ) ); } /** * A bar as the admin app sees it. * * @param int $id Bar ID. * @return array */ function runline_rest_bar( $id ) { $settings = runline_get_settings( $id ); return array( 'id' => (int) $id, 'title' => get_post_field( 'post_title', $id, 'raw' ), 'active' => '1' === get_post_meta( $id, RUNLINE_ACTIVE_META, true ) && 'publish' === get_post_status( $id ), 'settings' => $settings, 'shortcode' => '[runline id="' . (int) $id . '"]', 'summary' => runline_rules_summary( $settings['rules'], $settings['schedule'] ), 'modified' => get_post_modified_time( 'c', true, $id ), ); } function runline_rest_list() { $ids = get_posts( array( 'post_type' => RUNLINE_POST_TYPE, 'post_status' => array( 'publish', 'draft', 'private' ), 'posts_per_page' => -1, 'fields' => 'ids', 'orderby' => 'date', 'order' => 'DESC', ) ); $totals = runline_stats_totals(); $bars = array_map( 'runline_rest_bar', $ids ); foreach ( $bars as &$bar ) { $bar['stats'] = isset( $totals[ $bar['id'] ] ) ? $totals[ $bar['id'] ] : array( 'views' => 0, 'clicks' => 0, ); } unset( $bar ); return rest_ensure_response( $bars ); } function runline_rest_get( WP_REST_Request $request ) { return rest_ensure_response( runline_rest_bar( (int) $request['id'] ) ); } /** * Saves title and settings (and with them, on / off). * * @param int $id Bar ID. * @param WP_REST_Request $request Request. */ function runline_rest_store( $id, WP_REST_Request $request ) { $settings = runline_sanitize_settings( (array) $request->get_param( 'settings' ) ); runline_save_settings( $id, $settings ); return rest_ensure_response( array( 'bar' => runline_rest_bar( $id ) ) ); } /** * Names for the chosen pages / posts / terms of display rules: { posts: { id: title }, terms: { "tax:id": name } }. */ function runline_rest_titles_of( WP_REST_Request $request ) { $posts = array(); foreach ( array_filter( array_map( 'absint', explode( ',', (string) $request['ids'] ) ) ) as $id ) { $post = get_post( $id ); if ( $post ) { $type = get_post_type_object( $post->post_type ); $posts[ $id ] = array( 'title' => get_the_title( $post ) ? wp_strip_all_tags( get_the_title( $post ) ) : '#' . $id, 'type' => $type ? $type->labels->singular_name : $post->post_type, ); } } $terms = array(); foreach ( array_filter( explode( ',', (string) $request['terms'] ) ) as $key ) { if ( ! preg_match( '/^([a-z0-9_-]+):(\d+)$/', $key, $m ) ) { continue; } $term = get_term( (int) $m[2], $m[1] ); if ( $term && ! is_wp_error( $term ) ) { $taxonomy = get_taxonomy( $m[1] ); $terms[ $key ] = array( 'title' => $term->name, 'type' => $taxonomy ? $taxonomy->labels->singular_name : $m[1], ); } } return rest_ensure_response( array( 'posts' => (object) $posts, 'terms' => (object) $terms, ) ); } function runline_rest_create( WP_REST_Request $request ) { $title = (string) $request->get_param( 'title' ); $id = wp_insert_post( array( 'post_type' => RUNLINE_POST_TYPE, 'post_status' => 'publish', 'post_title' => '' !== $title ? $title : __( 'New bar', 'runline' ), ), true ); if ( is_wp_error( $id ) ) { return $id; } return runline_rest_store( $id, $request ); } function runline_rest_update( WP_REST_Request $request ) { $id = (int) $request['id']; $title = $request->get_param( 'title' ); $post = array( 'ID' => $id, 'post_status' => 'publish', ); if ( null !== $title ) { $post['post_title'] = '' !== $title ? $title : __( 'New bar', 'runline' ); } $result = wp_update_post( $post, true ); if ( is_wp_error( $result ) ) { return $result; } return runline_rest_store( $id, $request ); } function runline_rest_delete( WP_REST_Request $request ) { $deleted = wp_delete_post( (int) $request['id'], true ); return rest_ensure_response( array( 'deleted' => (bool) $deleted ) ); } function runline_rest_duplicate( WP_REST_Request $request ) { $id = (int) $request['id']; $new_id = wp_insert_post( array( 'post_type' => RUNLINE_POST_TYPE, 'post_status' => 'publish', /* translators: %s: name of the copied bar. */ 'post_title' => sprintf( __( '%s (copy)', 'runline' ), get_post_field( 'post_title', $id, 'raw' ) ), ), true ); if ( is_wp_error( $new_id ) ) { return $new_id; } // The copy starts switched off, so the site does not change. $settings = runline_get_settings( $id ); $settings['enabled'] = false; runline_save_settings( $new_id, $settings ); return rest_ensure_response( runline_rest_bar( $new_id ) ); } function runline_rest_active( WP_REST_Request $request ) { $id = (int) $request['id']; if ( 'publish' !== get_post_status( $id ) ) { wp_update_post( array( 'ID' => $id, 'post_status' => 'publish', ) ); } runline_set_active( $id, (bool) $request['active'] ); return rest_ensure_response( array( 'bar' => runline_rest_bar( $id ) ) ); } function runline_rest_preview( WP_REST_Request $request ) { $settings = runline_sanitize_settings( (array) $request->get_param( 'settings' ) ); return rest_ensure_response( array( 'html' => runline_render_bar( 0, $settings, 'preview' ) ) ); } /** * All bars as a file for another site: { plugin, version, bars: [ { title, settings } ] }. */ function runline_rest_export() { $ids = get_posts( array( 'post_type' => RUNLINE_POST_TYPE, 'post_status' => array( 'publish', 'draft', 'private' ), 'posts_per_page' => -1, 'fields' => 'ids', 'orderby' => 'date', 'order' => 'ASC', ) ); $bars = array(); foreach ( $ids as $id ) { $bars[] = array( 'title' => get_post_field( 'post_title', $id, 'raw' ), 'settings' => runline_get_settings( $id ), ); } return rest_ensure_response( array( 'plugin' => 'runline', 'version' => RUNLINE_VERSION, 'bars' => $bars, ) ); } /** * Bars from an export file: each one is cleaned like a saved bar and added switched off. */ function runline_rest_import( WP_REST_Request $request ) { $added = 0; foreach ( array_slice( (array) $request->get_param( 'bars' ), 0, 200 ) as $bar ) { if ( ! is_array( $bar ) || ! isset( $bar['settings'] ) || ! is_array( $bar['settings'] ) ) { continue; } $id = wp_insert_post( array( 'post_type' => RUNLINE_POST_TYPE, 'post_status' => 'publish', 'post_title' => isset( $bar['title'] ) ? sanitize_text_field( (string) $bar['title'] ) : __( 'New bar', 'runline' ), ), true ); if ( is_wp_error( $id ) ) { continue; } $settings = runline_sanitize_settings( $bar['settings'] ); $settings['enabled'] = false; runline_save_settings( $id, $settings ); ++$added; } return rest_ensure_response( array( 'added' => $added ) ); }