Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions assets/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.wprc-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(160px,1fr)); gap:16px; }
.wprc-card { border:1px solid #e5e7eb; border-radius:12px; overflow:hidden; background:#fff; box-shadow:0 1px 2px rgba(0,0,0,.04); }
.wprc-card img { width:100%; height:auto; display:block; }
.wprc-meta { padding:10px; }
.wprc-sub { color:#6b7280; font-size:12px; margin-top:4px; }
.wprc-pre { white-space:pre-wrap; font-size:12px; background:#f8fafc; padding:10px; border-radius:8px; }
56 changes: 56 additions & 0 deletions includes/class-wprc-fetcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
namespace WPRC;

if (!defined('ABSPATH')) exit;

class Fetcher {
public static function fetch(array $overrides = []) {
$opts = get_option(Settings::OPTION, []);
$base_url = isset($opts['base_url']) ? rtrim($opts['base_url'], '/') : '';
$path = isset($opts['endpoint_path']) ? '/' . ltrim($opts['endpoint_path'], '/') : '';
$token = isset($opts['bearer_token']) ? $opts['bearer_token'] : '';
$defaults = isset($opts['default_query']) ? $opts['default_query'] : '';
parse_str($defaults, $default_qs);

$qs = [];
if (!empty($overrides['query'])) {
parse_str($overrides['query'], $qs);
}
$query = array_merge($default_qs ?: [], $qs);

$url = $base_url . $path;
if (!empty($query)) {
$url = add_query_arg(array_map('rawurlencode', $query), $url);
}

$cache_minutes = isset($opts['cache_minutes']) ? intval($opts['cache_minutes']) : 0;
$cache_key = 'wprc_' . md5($url);

if ($cache_minutes > 0) {
$cached = get_transient($cache_key);
if ($cached !== false) return $cached;
}

$headers = ['Accept' => 'application/json'];
if ($token) $headers['Authorization'] = 'Bearer ' . $token;

$resp = wp_remote_get($url, [
'headers' => $headers,
'timeout' => 20,
]);

if (is_wp_error($resp)) {
return ['error' => $resp->get_error_message(), 'status' => 0];
}

$code = wp_remote_retrieve_response_code($resp);
$body = wp_remote_retrieve_body($resp);
$data = json_decode($body, true);

$result = ['status' => $code, 'url' => $url, 'data' => $data];
if ($cache_minutes > 0 && $code >= 200 && $code < 300) {
set_transient($cache_key, $result, $cache_minutes * MINUTE_IN_SECONDS);
}
return $result;
}
}
27 changes: 27 additions & 0 deletions includes/class-wprc-rest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
namespace WPRC;

if (!defined('ABSPATH')) exit;

class Rest {
public static function init() {
add_action('rest_api_init', [__CLASS__, 'routes']);
}

public static function routes() {
register_rest_route('wprc/v1', '/fetch', [
'methods' => 'GET',
'callback' => [__CLASS__, 'handle_fetch'],
'permission_callback' => '__return_true', // read-only, but throttling may be added later
'args' => [
'query' => ['type' => 'string', 'required' => false],
],
]);
}

public static function handle_fetch(\WP_REST_Request $req) {
$query = $req->get_param('query') ?: '';
$res = Fetcher::fetch(['query' => $query]);
return rest_ensure_response($res);
}
}
78 changes: 78 additions & 0 deletions includes/class-wprc-settings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
namespace WPRC;

if (!defined('ABSPATH')) exit;

class Settings {
const OPTION = 'wprc_options';

public static function init() {
add_action('admin_menu', [__CLASS__, 'menu']);
add_action('admin_init', [__CLASS__, 'register']);
}

public static function menu() {
add_options_page(
'WP Remote Content',
'WP Remote Content',
'manage_options',
'wprc',
[__CLASS__, 'render_page']
);
}

public static function register() {
register_setting(self::OPTION, self::OPTION, ['sanitize_callback' => [__CLASS__, 'sanitize']]);

add_settings_section('wprc_main', 'API Settings', function () {
echo '<p>Set your remote API details. Data is fetched server-side.</p>';
}, 'wprc');

add_settings_field('base_url', 'Base URL', [__CLASS__, 'field_text'], 'wprc', 'wprc_main', ['key' => 'base_url', 'placeholder' => 'https://api.themoviedb.org/3']);
add_settings_field('endpoint_path', 'Endpoint Path', [__CLASS__, 'field_text'], 'wprc', 'wprc_main', ['key' => 'endpoint_path', 'placeholder' => '/discover/movie']);
add_settings_field('bearer_token', 'Bearer Token', [__CLASS__, 'field_password'], 'wprc', 'wprc_main', ['key' => 'bearer_token']);
add_settings_field('default_query', 'Default Query (key=value&key2=value2)', [__CLASS__, 'field_text'], 'wprc', 'wprc_main', ['key' => 'default_query', 'placeholder' => 'sort_by=popularity.desc']);
add_settings_field('cache_minutes', 'Cache Minutes', [__CLASS__, 'field_number'], 'wprc', 'wprc_main', ['key' => 'cache_minutes', 'placeholder' => '10']);
}

public static function sanitize($opts) {
$clean = [];
$clean['base_url'] = isset($opts['base_url']) ? esc_url_raw(trim($opts['base_url'])) : '';
$clean['endpoint_path'] = isset($opts['endpoint_path']) ? sanitize_text_field(trim($opts['endpoint_path'])) : '';
$clean['bearer_token'] = isset($opts['bearer_token']) ? trim($opts['bearer_token']) : '';
$clean['default_query'] = isset($opts['default_query']) ? sanitize_text_field(trim($opts['default_query'])) : '';
$clean['cache_minutes'] = isset($opts['cache_minutes']) ? max(0, intval($opts['cache_minutes'])) : 0;
return $clean;
}

public static function render_page() {
if (!current_user_can('manage_options')) return;
?>
<div class="wrap">
<h1>WP Remote Content</h1>
<form method="post" action="options.php">
<?php
settings_fields(self::OPTION);
do_settings_sections('wprc');
submit_button();
?>
</form>
<p><strong>Tip:</strong> The token is stored in the database (options table). Don’t commit it to Git.</p>
</div>
<?php
}

// Field renderers
public static function field_text($args) { self::field_input('text', $args); }
public static function field_password($args) { self::field_input('password', $args); }
public static function field_number($args) { self::field_input('number', $args); }
private static function field_input($type, $args) {
$opts = get_option(self::OPTION, []);
$key = esc_attr($args['key']);
$val = isset($opts[$key]) ? $opts[$key] : '';
printf(
'<input type="%s" name="%s[%s]" value="%s" placeholder="%s" class="regular-text" />',
esc_attr($type), esc_attr(self::OPTION), $key, esc_attr($val), isset($args['placeholder']) ? esc_attr($args['placeholder']) : ''
);
}
}
53 changes: 53 additions & 0 deletions includes/class-wprc-shortcode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
namespace WPRC;

if (!defined('ABSPATH')) exit;

class Shortcode {
public static function init() {
add_shortcode('remote_content', [__CLASS__, 'render']);
}

public static function render($atts) {
$atts = shortcode_atts([
'query' => '', // e.g. "sort_by=popularity.desc&page=1"
'type' => 'tmdb', // simple display template
'limit' => 12
], $atts, 'remote_content');

$res = Fetcher::fetch(['query' => $atts['query']]);
if (!empty($res['error'])) {
return '<p>⚠️ Error: ' . esc_html($res['error']) . '</p>';
}
if (empty($res['data'])) {
return '<p>No data.</p>';
}

wp_enqueue_style('wprc-styles');

// Very simple renderer for TMDB "results"
$html = '<div class="wprc-grid">';
$items = [];

if ($atts['type'] === 'tmdb' && isset($res['data']['results']) && is_array($res['data']['results'])) {
$items = array_slice($res['data']['results'], 0, intval($atts['limit']));
foreach ($items as $m) {
$title = isset($m['title']) ? $m['title'] : (isset($m['name']) ? $m['name'] : 'Untitled');
$date = isset($m['release_date']) ? $m['release_date'] : '';
$img = isset($m['poster_path']) ? 'https://image.tmdb.org/t/p/w200' . $m['poster_path'] : '';
$html .= '<div class="wprc-card">';
if ($img) $html .= '<img src="' . esc_url($img) . '" alt="' . esc_attr($title) . '"/>';
$html .= '<div class="wprc-meta"><strong>' . esc_html($title) . '</strong>';
if ($date) $html .= '<div class="wprc-sub">' . esc_html($date) . '</div>';
$html .= '</div></div>';
}
} else {
// Generic fallback: dump keys for first level arrays
$items = is_array($res['data']) ? $res['data'] : [];
$html .= '<pre class="wprc-pre">' . esc_html(print_r($items, true)) . '</pre>';
}

$html .= '</div>';
return $html;
}
}