Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
run: composer install --prefer-dist --no-progress

- name: Run PHPCS
run: php -d xdebug.mode=off vendor/bin/phpcs --standard=WordPress src/ owlstack.php
run: php -d xdebug.mode=off vendor/bin/phpcs

tests:
name: PHPUnit (PHP ${{ matrix.php }})
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ If you see `'owlstack-wp'` as a second argument in `__()`, `_e()`, `esc_html__()
## Tool Usage

- **Prefer grep and find** to locate code — never guess file locations or class names
- **Run PHPCS after every change**: `php -d xdebug.mode=off vendor/bin/phpcs --standard=WordPress src/ owlstack.php`
- **Run I18n check specifically**: `php -d xdebug.mode=off vendor/bin/phpcs --standard=WordPress --sniffs=WordPress.WP.I18n src/ owlstack.php`
- **Run PHPCS after every change**: `php -d xdebug.mode=off vendor/bin/phpcs` (uses `phpcs.xml.dist` — WordPress security/I18n/DB/PHP sniffs on the repo's PSR-style code; do NOT run the raw `--standard=WordPress` ruleset, the codebase intentionally doesn't follow its formatting rules)
- **Run I18n check specifically**: `php -d xdebug.mode=off vendor/bin/phpcs --sniffs=WordPress.WP.I18n`
- **Use `git diff`** to verify changes before committing
4 changes: 2 additions & 2 deletions owlstack.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Plugin Name: Owlstack
* Plugin URI: https://owlstack.dev
* Description: Publish content to Telegram, X (Twitter), Facebook, Instagram, LinkedIn, Discord, and more — directly from WordPress.
* Version: 1.0.1
* Version: 1.1.0
* Requires at least: 6.4
* Requires PHP: 8.1
* Author: Ali Hesari
Expand All @@ -23,7 +23,7 @@
}

// Plugin constants.
define('OWLSTACK_VERSION', '1.0.1');
define('OWLSTACK_VERSION', '1.1.0');
define('OWLSTACK_FILE', __FILE__);
define('OWLSTACK_DIR', plugin_dir_path(__FILE__));
define('OWLSTACK_URL', plugin_dir_url(__FILE__));
Expand Down
59 changes: 59 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0"?>
<ruleset name="Owlstack">
<description>Owlstack WP coding standard: WordPress security, I18n, DB, and PHP sniffs on a PSR-style codebase.</description>

<file>src</file>
<file>owlstack.php</file>
<file>uninstall.php</file>

<arg name="extensions" value="php"/>
<arg value="sp"/>

<!-- Security: escaping, nonces, input sanitization, safe redirects. -->
<rule ref="WordPress.Security"/>

<!-- WordPress API usage: I18n, deprecated/alternative/discouraged functions, enqueues, capabilities. -->
<rule ref="WordPress.WP"/>
<rule ref="WordPress.WP.Capabilities">
<properties>
<property name="custom_capabilities" type="array">
<element value="manage_owlstack"/>
<element value="owlstack_publish"/>
<element value="owlstack_view_logs"/>
</property>
</properties>
</rule>
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array">
<element value="owlstack"/>
</property>
</properties>
</rule>

<!-- Database access. -->
<rule ref="WordPress.DB"/>

<!-- PHP pitfalls (silenced errors, extract, dev functions, strict in_array, ...). -->
<rule ref="WordPress.PHP">
<!-- The codebase intentionally does not use Yoda conditions. -->
<exclude name="WordPress.PHP.YodaConditions"/>
<!-- Deprecated in WordPressCS 3.3, removed in 4.0. -->
<exclude name="WordPress.PHP.POSIXFunctions"/>
</rule>

<!-- Global namespace hygiene. -->
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
<properties>
<property name="prefixes" type="array">
<element value="owlstack"/>
<element value="Owlstack"/>
<element value="OWLSTACK"/>
</property>
</properties>
</rule>
<rule ref="WordPress.NamingConventions.ValidHookName"/>

<!-- Parseable PHP. -->
<rule ref="Generic.PHP.Syntax"/>
</ruleset>
12 changes: 11 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Donate link: https://owlstack.dev
Tags: social media, auto publish, telegram, twitter, facebook
Requires at least: 6.4
Tested up to: 6.9
Stable tag: 1.0.1
Stable tag: 1.1.0
Requires PHP: 8.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Expand Down Expand Up @@ -102,6 +102,10 @@ Yes. Configure proxy settings in **Owlstack > Settings** for servers that cannot

Delivery logs are stored in a custom database table (`wp_owlstack_delivery_logs`). You can view them under **Owlstack > Delivery Logs**.

= What is the OwlStack Cloud connection? =

If you use the OwlStack Cloud dashboard, you can connect it to your site without sharing a WordPress username or Application Password. Go to **Owlstack > Cloud**, generate a site token, and paste it into the OwlStack dashboard together with your site URL. The token is scoped to a minimal set of plugin endpoints: OwlStack Cloud can create posts (as the author you choose, as a draft or published based on your setting), upload images for featured media, and remove posts it created — nothing else. Only a hash of the token is stored, you can revoke it at any time, and the plugin never makes outbound calls to OwlStack Cloud. The feature is entirely optional and inactive until you generate a token.

== Third-Party Services ==

This plugin connects to external third-party services to publish your content. Data such as post title, excerpt, URL, and featured image may be sent to the platforms you configure. **No data is sent unless you explicitly configure and enable a platform.**
Expand Down Expand Up @@ -180,6 +184,12 @@ This plugin connects to external third-party services to publish your content. D

== Changelog ==

= 1.1.0 =
* New: OwlStack Cloud connection (**Owlstack > Cloud**). Generate a revocable site token so the OwlStack Cloud dashboard can publish posts to this site without a WordPress username or Application Password.
* New: Incoming content controls — always draft, always publish, or honor the requested status; configurable post author and post type.
* New: REST endpoints under `owlstack/v1/cloud` for site info, post creation, image upload, and removal of Cloud-created posts. Token-authenticated; only a SHA-256 hash of the token is stored.
* The plugin makes no outbound calls to OwlStack Cloud; the feature is inactive until a token is generated.

= 1.0.0 =
* Initial public release.
* Support for 11 platforms: Telegram, X (Twitter), Facebook, Instagram, LinkedIn, Discord, Pinterest, Reddit, Slack, Tumblr, and WhatsApp.
Expand Down
145 changes: 145 additions & 0 deletions src/Admin/CloudSettingsPage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

declare(strict_types=1);

namespace Owlstack\WordPress\Admin;

defined( 'ABSPATH' ) || exit;

use Owlstack\WordPress\Cloud\CloudSettings;
use Owlstack\WordPress\Cloud\CloudTokenService;

/**
* Admin page for connecting this site to OwlStack Cloud.
*
* Generates/revokes the site token and manages how incoming Cloud
* content is handled (status policy, author, post type).
*/
class CloudSettingsPage
{
private const PAGE_SLUG = 'owlstack-cloud';

private const REVEAL_TRANSIENT = 'owlstack_cloud_token_reveal_';

public function __construct(
private readonly CloudTokenService $tokens,
private readonly CloudSettings $settings,
) {
}

/**
* Register the submenu page.
*/
public function register(): void
{
add_submenu_page(
parent_slug: 'owlstack',
page_title: __('Cloud', 'owlstack'),
menu_title: __('Cloud', 'owlstack'),
capability: 'manage_options',
menu_slug: self::PAGE_SLUG,
callback: [$this, 'render'],
);
}

/**
* Register admin-post form handlers.
*/
public function registerActions(): void
{
add_action('admin_post_owlstack_cloud_generate', [$this, 'handleGenerate']);
add_action('admin_post_owlstack_cloud_revoke', [$this, 'handleRevoke']);
add_action('admin_post_owlstack_cloud_settings', [$this, 'handleSettings']);
}

/**
* Render the Cloud settings page.
*/
public function render(): void
{
if (! current_user_can('manage_options')) {
return;
}

$revealKey = self::REVEAL_TRANSIENT . get_current_user_id();
$revealedToken = get_transient($revealKey);
if (is_string($revealedToken) && $revealedToken !== '') {
delete_transient($revealKey);
} else {
$revealedToken = null;
}

$isPaired = $this->tokens->isPaired();
$tokenInfo = $this->tokens->info();
$settings = $this->settings;

require __DIR__ . '/views/cloud-settings-page.php';
}

/**
* Generate (or regenerate) the site token.
*/
public function handleGenerate(): void
{
$this->verifyRequest('owlstack_cloud_generate');

$token = $this->tokens->generate(get_current_user_id());

// One-time reveal for the current admin only.
set_transient(self::REVEAL_TRANSIENT . get_current_user_id(), $token, 5 * MINUTE_IN_SECONDS);

$this->redirectBack('token-generated');
}

/**
* Revoke the site token.
*/
public function handleRevoke(): void
{
$this->verifyRequest('owlstack_cloud_revoke');

$this->tokens->revoke();

$this->redirectBack('token-revoked');
}

/**
* Save content-handling settings.
*/
public function handleSettings(): void
{
$this->verifyRequest('owlstack_cloud_settings');

// phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in verifyRequest().
$policy = isset($_POST['post_status_policy']) ? sanitize_key(wp_unslash($_POST['post_status_policy'])) : CloudSettings::POLICY_HONOR;
$author = isset($_POST['default_author']) ? absint(wp_unslash($_POST['default_author'])) : 0;
$type = isset($_POST['post_type']) ? sanitize_key(wp_unslash($_POST['post_type'])) : 'post';
// phpcs:enable WordPress.Security.NonceVerification.Missing

$this->settings->update($policy, $author, $type);

$this->redirectBack('settings-saved');
}

// ── Helpers ──────────────────────────────────────────────────────────

private function verifyRequest(string $action): void
{
if (! current_user_can('manage_options')) {
wp_die(esc_html__('You are not allowed to manage Owlstack Cloud settings.', 'owlstack'));
}

check_admin_referer($action);
}

private function redirectBack(string $notice): void
{
wp_safe_redirect(
add_query_arg(
['page' => self::PAGE_SLUG, 'owlstack-notice' => $notice],
admin_url('admin.php'),
),
);
exit;
}
}
Loading
Loading