Compare commits

..

11 Commits

14 changed files with 183 additions and 59 deletions

6
.gitignore vendored
View File

@@ -47,3 +47,9 @@ wp-config.php
# Note: If you wish to whitelist themes, # Note: If you wish to whitelist themes,
# uncomment the next line # uncomment the next line
#/wp-content/themes #/wp-content/themes
# ---> Node.js
/node_modules/
/package-lock.json

View File

@@ -2,4 +2,23 @@
# wp-spaceapi-consumer # wp-spaceapi-consumer
Small WordPress plugin to consume an SpaceAPI endpoint and indicate on the WordPress website if the Space is Open or Closed Small WordPress plugin to consume an SpaceAPI endpoint and indicate on the WordPress website if the Space is Open or Closed
## Usage
### WordPress Editor Block (Gutenberg)
You can use the `Space Status` block in the WordPress editor to display the status of the SpaceAPI endpoint.
### Shortcode
You can use the shortcode `[space_status]` to display the status of the SpaceAPI endpoint on your WordPress site.
### Widget
You can also use the widget `SpaceAPI Status` to display the status of the SpaceAPI endpoint in your WordPress sidebar or footer.
## Changes
### 0.4.x
- Added WordPress Editor Block (Gutenberg) support

21
blocks-register.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
return;
}
function ssi_register_space_status_block() : void {
$asset_path = plugin_dir_path( __FILE__ ) . 'build/space-status';
register_block_type( $asset_path, [
'render_callback' => 'ssi_render_block',
] );
}
add_action( 'init', 'ssi_register_space_status_block' );
/**
* Serverside markup for the block.
*/
function ssi_render_block( array $attributes, string $content ) : string {
// Reuse existing helper & CSS class logic.
return ssi_shortcode();
}

View File

@@ -0,0 +1,15 @@
{
"apiVersion": 3,
"name": "wp-spaceapi-consumer/space-status",
"title": "Space Status",
"description": "Show whether the space is open or closed (SpaceAPI consumer).",
"category": "widgets",
"icon": "building",
"textdomain": "wp-spaceapi-consumer",
"style": "file:./style.css",
"editorStyle": "file:./editor.css",
"editorScript": "file:./index.js",
"supports": {
"html": false
}
}

View File

@@ -0,0 +1 @@
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-blocks', 'wp-i18n', 'wp-server-side-render'), 'version' => 'e81eff0b8644d0be4dac');

View File

@@ -0,0 +1 @@
(()=>{"use strict";var e={n:r=>{var s=r&&r.__esModule?()=>r.default:()=>r;return e.d(s,{a:s}),s},d:(r,s)=>{for(var t in s)e.o(s,t)&&!e.o(r,t)&&Object.defineProperty(r,t,{enumerable:!0,get:s[t]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r)};window.wp.i18n;const r=window.wp.blocks,s=window.wp.serverSideRender;var t=e.n(s);const o=window.ReactJSXRuntime;(0,r.registerBlockType)("wp-spaceapi-consumer/space-status",{edit:()=>(0,o.jsx)(t(),{block:"wp-spaceapi-consumer/space-status"}),save:()=>null})})();

15
package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "wp-spaceapi-consumer",
"version": "0.0.0-private",
"private": true,
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
},
"devDependencies": {
"@wordpress/scripts": "^30.18.0"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@@ -0,0 +1,13 @@
{
"apiVersion": 3,
"name": "wp-spaceapi-consumer/space-status",
"title": "Space Status",
"description": "Show whether the space is open or closed (SpaceAPI consumer).",
"category": "widgets",
"icon": "building",
"textdomain": "wp-spaceapi-consumer",
"style": "file:./style.css",
"editorStyle": "file:./editor.css",
"editorScript": "file:./index.js",
"supports": { "html": false }
}

View File

@@ -0,0 +1,7 @@
/* Editor-only: make the pill obvious & easy to select */
.wp-block-wp-spaceapi-consumer-space-status {
display: inline-block;
padding: 4px 8px;
border: 1px dashed var(--wp-admin-theme-color, #007cba);
border-radius: 9999px;
}

12
src/space-status/index.js Normal file
View File

@@ -0,0 +1,12 @@
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';
import ServerSideRender from '@wordpress/server-side-render';
registerBlockType( 'wp-spaceapi-consumer/space-status', {
edit: () => (
<ServerSideRender
block="wp-spaceapi-consumer/space-status"
/>
),
save: () => null, // dynamic block — markup comes from PHP
} );

View File

@@ -0,0 +1 @@
@import url('../../wp-spaceapi-consumer-style.css');

View File

@@ -1,5 +1,5 @@
<?php <?php
define( 'DSI_API_URL', 'https://nodered.jfig.net/api/v1/door-open' ); define( 'SSI_API_URL', 'https://lcdporto.org/api/spaceapi' );
define( 'DSI_CACHE_KEY', 'dsi_door_open_status' ); define( 'SSI_CACHE_KEY', 'ssi_space_api_status' );
define( 'DSI_CACHE_TTL', 10 ); define( 'SSI_CACHE_TTL', 10 );

View File

@@ -1,11 +1,11 @@
/* door-status.css styles the indicator as a pillshaped button */ /* space-status.css - styles the indicator as a pill-shaped button */
.door-status-indicator { .space-status-indicator {
display:inline-flex; display:inline-flex;
align-items:center; align-items:center;
gap:0.4em; gap:0.4em;
padding:0.25em 0.75em; padding:0.25em 0.75em;
border-radius:1em; /* fullyrounded corners */ border-radius:1em; /* fully-rounded corners */
font-size:0.95em; font-size:0.95em;
font-weight:600; font-weight:600;
line-height:1; line-height:1;
@@ -15,20 +15,20 @@
} }
/* Open = soft green background + dark green text */ /* Open = soft green background + dark green text */
.door-status-indicator.open { .space-status-indicator.open {
background:#e7f9ec; background:#e7f9ec;
color:#1e7a34; color:#1e7a34;
} }
/* Closed = soft red background + dark red text */ /* Closed = soft red background + dark red text */
.door-status-indicator.closed { .space-status-indicator.closed {
background:#fdeeee; background:#fdeeee;
color:#c03c3c; color:#c03c3c;
} }
/* (Optional) slight hover effect */ /* (Optional) slight hover effect */
.door-status-indicator:hover, .space-status-indicator:hover,
.door-status-indicator:focus { .space-status-indicator:focus {
filter:brightness(1.05); filter:brightness(1.05);
outline:none; outline:none;
} }

View File

@@ -1,14 +1,15 @@
<?php <?php
/** /**
* Plugin Name: Door Status Indicator * Plugin Name: WordPress SpaceAPI Consumer
* Plugin URI: https://example.com/ * Plugin Slug: wp-spaceapi-consumer
* Description: Lightweight, dependencyfree plugin that polls a JSON endpoint (default: https://nodered.jfig.net/api/v1/door-open), caches the value for a few seconds, and shows a realtime door open/closed indicator (🟢/🔴) via the [door_status] shortcode and the Admin Bar. * Plugin URI: https://gitea.alluna.pt/jfig/wp-spaceapi-consumer
* Version: 1.1.1 * Description: WordPress plugin to consume a SpaceAPI endpoint and indicate if the Space is Open or Closed
* Author: J (ChatGPT helper) * Version: 0.4.2
* Author URI: https://example.com/ * Author: Joao Figueiredo, LCD Porto Team, ChatGPT o3
* License: GPL v2 or later * Author URI: https://lcdporto.org
* License URI: https://www.gnu.org/licenses/gpl-2.0.html * License: MIT
* Text Domain: door-status-indicator * License URI: https://gitea.alluna.pt/jfig/wp-spaceapi-consumer/src/branch/dev/LICENSE
* Text Domain: wp-spaceapi-consumer
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
@@ -19,9 +20,9 @@ if ( ! defined( 'ABSPATH' ) ) {
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Configuration | Configuration
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Constants are expected in door-status-config.php located in the same | Constants are expected in wp-spaceapi-consumer-config.php located in the same
| directory. If that file is missing, sane defaults are used so the plugin | directory. If that file is missing, sane defaults are used so the plugin
| still works outofthebox. | still works out-of-the-box.
*/ */
$config_file = plugin_dir_path( __FILE__ ) . 'wp-spaceapi-consumer-config.php'; $config_file = plugin_dir_path( __FILE__ ) . 'wp-spaceapi-consumer-config.php';
@@ -30,39 +31,44 @@ if ( file_exists( $config_file ) ) {
} }
// Fallback defaults (only if not already defined by the config file). // Fallback defaults (only if not already defined by the config file).
! defined( 'DSI_API_URL' ) && define( 'DSI_API_URL', 'https://nodered.jfig.net/api/v1/door-open' ); ! defined( 'SSI_API_URL' ) && define( 'SSI_API_URL', 'https://lcdporto.org/api/spaceapi' );
! defined( 'DSI_CACHE_KEY' ) && define( 'DSI_CACHE_KEY', 'dsi_door_open_status' ); ! defined( 'SSI_CACHE_KEY' ) && define( 'SSI_CACHE_KEY', 'ssi_space_api_status' );
! defined( 'DSI_CACHE_TTL' ) && define( 'DSI_CACHE_TTL', 10 ); // seconds ! defined( 'SSI_CACHE_TTL' ) && define( 'SSI_CACHE_TTL', 10 ); // seconds
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Core fetch & cache | Core - fetch & cache
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
/** /**
* Retrieves dooropen status (boolean) with transient caching. * Retrieves space open status (boolean) with transient caching.
* *
* Supported JSON payloads (any case): * Supported JSON payloads (any case):
* true / false (bare boolean) * - true / false (bare boolean)
* {"open": true} * - {"state": {"open": true}} (SpaceAPI standard)
* {"doorOpen": true}
* *
* @return bool True if open, false if closed (or on error). * @return bool True if open, false if closed (or on error).
*/ */
function dsi_get_status() : bool { function ssi_get_status() : bool {
$cached = get_transient( DSI_CACHE_KEY ); $cached = get_transient( SSI_CACHE_KEY );
if ( false !== $cached ) { if ( false !== $cached ) {
return (bool) $cached; return (bool) $cached;
} }
$response = wp_remote_get( DSI_API_URL, [ // Get plugin data for dynamic user agent
$plugin_data = get_file_data( __FILE__, array(
'Version' => 'Version',
'TextDomain' => 'Text Domain'
), 'plugin' );
$response = wp_remote_get( SSI_API_URL, [
'timeout' => 3, 'timeout' => 3,
'user-agent' => 'DoorStatusIndicator/1.1.1 (+https://wordpress.org/)', 'user-agent' => $plugin_data['TextDomain'] . '/' . $plugin_data['Version'] . ' (+https://wordpress.org/)',
] ); ] );
if ( is_wp_error( $response ) ) { if ( is_wp_error( $response ) ) {
set_transient( DSI_CACHE_KEY, false, DSI_CACHE_TTL ); set_transient( SSI_CACHE_KEY, false, SSI_CACHE_TTL );
return false; return false;
} }
@@ -73,14 +79,13 @@ function dsi_get_status() : bool {
if ( is_bool( $decoded ) ) { if ( is_bool( $decoded ) ) {
$status = $decoded; $status = $decoded;
} elseif ( is_array( $decoded ) ) { } elseif ( is_array( $decoded ) ) {
if ( isset( $decoded['open'] ) ) { // SpaceAPI standard format - state.open
$status = (bool) $decoded['open']; if ( isset( $decoded['state']['open'] ) ) {
} elseif ( isset( $decoded['doorOpen'] ) ) { $status = (bool) $decoded['state']['open'];
$status = (bool) $decoded['doorOpen'];
} }
} }
set_transient( DSI_CACHE_KEY, $status, DSI_CACHE_TTL ); set_transient( SSI_CACHE_KEY, $status, SSI_CACHE_TTL );
return $status; return $status;
} }
@@ -90,17 +95,19 @@ function dsi_get_status() : bool {
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
function dsi_shortcode() : string { function ssi_shortcode() : string {
$open = dsi_get_status(); $open = ssi_get_status();
$emoji = $open ? '<div class="door-status-indicator open">🟢 Open</div>' : '<div class="door-status-indicator closed">🔴 Closed</div>'; $emoji = $open ? '<div class="space-status-indicator open">🟢 Open</div>' : '<div class="space-status-indicator closed">🔴 Closed</div>';
return sprintf( return sprintf(
'<span class="door-status-indicator" aria-label="Door is %s">%s</span>', '<span class="space-status-indicator" aria-label="Space is %s">%s</span>',
$open ? 'open' : 'closed', $open ? 'open' : 'closed',
$emoji $emoji
); );
} }
add_shortcode( 'door_status', 'dsi_shortcode' ); add_shortcode( 'space_status', 'ssi_shortcode' );
// Keep the old shortcode for backward compatibility
add_shortcode( 'door_status', 'ssi_shortcode' );
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -108,56 +115,62 @@ add_shortcode( 'door_status', 'dsi_shortcode' );
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
function dsi_admin_bar( WP_Admin_Bar $bar ) : void { function ssi_admin_bar( WP_Admin_Bar $bar ) : void {
if ( ! current_user_can( 'read' ) ) { if ( ! current_user_can( 'read' ) ) {
return; // Loggedin users only. return; // Logged-in users only.
} }
$open = dsi_get_status(); $open = ssi_get_status();
$emoji = $open ? '🟢' : '🔴'; $emoji = $open ? '🟢' : '🔴';
$text = $open ? __( 'Door Open', 'door-status-indicator' ) : __( 'Door Closed', 'door-status-indicator' ); $text = $open ? __( 'Space Open', 'wp-spaceapi-consumer' ) : __( 'Space Closed', 'wp-spaceapi-consumer' );
$bar->add_node( [ $bar->add_node( [
'id' => 'door-status-indicator', 'id' => 'space-status-indicator',
'title' => "$emoji $text", 'title' => "$emoji $text",
'href' => '#', 'href' => '#',
'meta' => [ 'title' => $text ], 'meta' => [ 'title' => $text ],
] ); ] );
} }
add_action( 'admin_bar_menu', 'dsi_admin_bar', 1000 ); add_action( 'admin_bar_menu', 'ssi_admin_bar', 1000 );
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Frontend inline CSS (kept minimal) | Front-end inline CSS (kept minimal)
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
/** /**
* Front-end + admin-bar styles for the indicator. * Front-end + admin-bar styles for the indicator.
*/ */
function dsi_enqueue_assets() : void { function ssi_enqueue_assets() : void {
// register & enqueue the standalone CSS file // register & enqueue the standalone CSS file
wp_enqueue_style( wp_enqueue_style(
'door-status-indicator', 'space-status-indicator',
plugins_url( 'wp-spaceapi-consumer-style.css', __FILE__ ), plugins_url( 'wp-spaceapi-consumer-style.css', __FILE__ ),
[], // no dependencies [], // no dependencies
'1.0.0' // file version '1.0.0' // file version
); );
} }
add_action( 'wp_enqueue_scripts', 'dsi_enqueue_assets' ); add_action( 'wp_enqueue_scripts', 'ssi_enqueue_assets' );
add_action( 'admin_enqueue_scripts', 'dsi_enqueue_assets' ); // so the Admin Bar icon also gets styled add_action( 'admin_enqueue_scripts', 'ssi_enqueue_assets' ); // so the Admin Bar icon also gets styled
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| WPCLI command (optional) | WP-CLI command (optional)
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
if ( defined( 'WP_CLI' ) && WP_CLI ) { if ( defined( 'WP_CLI' ) && WP_CLI ) {
WP_CLI::add_command( 'door-status', function() { WP_CLI::add_command( 'space-status', function() {
WP_CLI::success( dsi_get_status() ? 'Door is open 🟢' : 'Door is closed 🔴' ); WP_CLI::success( ssi_get_status() ? 'Space is open 🟢' : 'Space is closed 🔴' );
} ); } );
} }
// Gutenberg block support (dynamic Space Status block)
if ( function_exists( 'register_block_type' ) ) {
require_once plugin_dir_path( __FILE__ ) . 'blocks-register.php';
}
// End of file // End of file